about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/services
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/services')
-rw-r--r--nixpkgs/nixos/modules/services/admin/oxidized.nix118
-rw-r--r--nixpkgs/nixos/modules/services/admin/salt/master.nix63
-rw-r--r--nixpkgs/nixos/modules/services/admin/salt/minion.nix67
-rw-r--r--nixpkgs/nixos/modules/services/amqp/activemq/ActiveMQBroker.java19
-rw-r--r--nixpkgs/nixos/modules/services/amqp/activemq/default.nix133
-rw-r--r--nixpkgs/nixos/modules/services/amqp/rabbitmq.nix211
-rw-r--r--nixpkgs/nixos/modules/services/audio/alsa.nix133
-rw-r--r--nixpkgs/nixos/modules/services/audio/icecast.nix130
-rw-r--r--nixpkgs/nixos/modules/services/audio/jack.nix291
-rw-r--r--nixpkgs/nixos/modules/services/audio/liquidsoap.nix69
-rw-r--r--nixpkgs/nixos/modules/services/audio/mopidy.nix108
-rw-r--r--nixpkgs/nixos/modules/services/audio/mpd.nix200
-rw-r--r--nixpkgs/nixos/modules/services/audio/roon-server.nix74
-rw-r--r--nixpkgs/nixos/modules/services/audio/slimserver.nix72
-rw-r--r--nixpkgs/nixos/modules/services/audio/snapserver.nix216
-rw-r--r--nixpkgs/nixos/modules/services/audio/spotifyd.nix42
-rw-r--r--nixpkgs/nixos/modules/services/audio/squeezelite.nix50
-rw-r--r--nixpkgs/nixos/modules/services/audio/ympd.nix57
-rw-r--r--nixpkgs/nixos/modules/services/backup/automysqlbackup.nix118
-rw-r--r--nixpkgs/nixos/modules/services/backup/bacula.nix556
-rw-r--r--nixpkgs/nixos/modules/services/backup/borgbackup.nix675
-rw-r--r--nixpkgs/nixos/modules/services/backup/borgbackup.xml227
-rw-r--r--nixpkgs/nixos/modules/services/backup/duplicati.nix67
-rw-r--r--nixpkgs/nixos/modules/services/backup/duplicity.nix141
-rw-r--r--nixpkgs/nixos/modules/services/backup/mysql-backup.nix125
-rw-r--r--nixpkgs/nixos/modules/services/backup/postgresql-backup.nix126
-rw-r--r--nixpkgs/nixos/modules/services/backup/postgresql-wal-receiver.nix204
-rw-r--r--nixpkgs/nixos/modules/services/backup/restic-rest-server.nix107
-rw-r--r--nixpkgs/nixos/modules/services/backup/restic.nix209
-rw-r--r--nixpkgs/nixos/modules/services/backup/rsnapshot.nix75
-rw-r--r--nixpkgs/nixos/modules/services/backup/sanoid.nix213
-rw-r--r--nixpkgs/nixos/modules/services/backup/syncoid.nix172
-rw-r--r--nixpkgs/nixos/modules/services/backup/tarsnap.nix406
-rw-r--r--nixpkgs/nixos/modules/services/backup/tsm.nix106
-rw-r--r--nixpkgs/nixos/modules/services/backup/zfs-replication.nix90
-rw-r--r--nixpkgs/nixos/modules/services/backup/znapzend.nix469
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix31
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/default.nix68
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix73
-rw-r--r--nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix74
-rw-r--r--nixpkgs/nixos/modules/services/cluster/k3s/default.nix101
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix167
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix332
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix334
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix469
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix167
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix287
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix134
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix350
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix400
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix94
-rw-r--r--nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix94
-rw-r--r--nixpkgs/nixos/modules/services/computing/boinc/client.nix131
-rw-r--r--nixpkgs/nixos/modules/services/computing/foldingathome/client.nix81
-rw-r--r--nixpkgs/nixos/modules/services/computing/slurm/slurm.nix413
-rw-r--r--nixpkgs/nixos/modules/services/computing/torque/mom.nix63
-rw-r--r--nixpkgs/nixos/modules/services/computing/torque/server.nix96
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix282
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix187
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/buildkite-agents.nix276
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix494
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix206
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix194
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hail.nix61
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix507
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix228
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix205
-rw-r--r--nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix68
-rw-r--r--nixpkgs/nixos/modules/services/databases/aerospike.nix156
-rw-r--r--nixpkgs/nixos/modules/services/databases/cassandra.nix479
-rw-r--r--nixpkgs/nixos/modules/services/databases/clickhouse.nix68
-rw-r--r--nixpkgs/nixos/modules/services/databases/cockroachdb.nix217
-rw-r--r--nixpkgs/nixos/modules/services/databases/couchdb.nix201
-rw-r--r--nixpkgs/nixos/modules/services/databases/firebird.nix161
-rw-r--r--nixpkgs/nixos/modules/services/databases/foundationdb.nix429
-rw-r--r--nixpkgs/nixos/modules/services/databases/foundationdb.xml443
-rw-r--r--nixpkgs/nixos/modules/services/databases/hbase.nix127
-rw-r--r--nixpkgs/nixos/modules/services/databases/influxdb.nix197
-rw-r--r--nixpkgs/nixos/modules/services/databases/memcached.nix111
-rw-r--r--nixpkgs/nixos/modules/services/databases/monetdb.nix100
-rw-r--r--nixpkgs/nixos/modules/services/databases/mongodb.nix188
-rw-r--r--nixpkgs/nixos/modules/services/databases/mysql.nix510
-rw-r--r--nixpkgs/nixos/modules/services/databases/neo4j.nix663
-rw-r--r--nixpkgs/nixos/modules/services/databases/openldap.nix288
-rw-r--r--nixpkgs/nixos/modules/services/databases/opentsdb.nix109
-rw-r--r--nixpkgs/nixos/modules/services/databases/pgmanage.nix206
-rw-r--r--nixpkgs/nixos/modules/services/databases/postgresql.nix379
-rw-r--r--nixpkgs/nixos/modules/services/databases/postgresql.xml192
-rw-r--r--nixpkgs/nixos/modules/services/databases/redis.nix248
-rw-r--r--nixpkgs/nixos/modules/services/databases/rethinkdb.nix108
-rw-r--r--nixpkgs/nixos/modules/services/databases/riak-cs.nix202
-rw-r--r--nixpkgs/nixos/modules/services/databases/riak.nix163
-rw-r--r--nixpkgs/nixos/modules/services/databases/stanchion.nix194
-rw-r--r--nixpkgs/nixos/modules/services/databases/victoriametrics.nix70
-rw-r--r--nixpkgs/nixos/modules/services/databases/virtuoso.nix94
-rw-r--r--nixpkgs/nixos/modules/services/desktops/accountsservice.nix58
-rw-r--r--nixpkgs/nixos/modules/services/desktops/bamf.nix27
-rw-r--r--nixpkgs/nixos/modules/services/desktops/blueman.nix25
-rw-r--r--nixpkgs/nixos/modules/services/desktops/deepin/deepin.nix123
-rw-r--r--nixpkgs/nixos/modules/services/desktops/dleyna-renderer.nix28
-rw-r--r--nixpkgs/nixos/modules/services/desktops/dleyna-server.nix28
-rw-r--r--nixpkgs/nixos/modules/services/desktops/flatpak.nix53
-rw-r--r--nixpkgs/nixos/modules/services/desktops/flatpak.xml56
-rw-r--r--nixpkgs/nixos/modules/services/desktops/geoclue2.nix268
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/at-spi2-core.nix49
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix33
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/evolution-data-server.nix45
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/glib-networking.nix37
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix90
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-keyring.nix53
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix43
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix43
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix24
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix78
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/gnome-user-share.nix40
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/rygel.nix36
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/sushi.nix42
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/tracker-miners.nix44
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gnome3/tracker.nix45
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gsignond.nix45
-rw-r--r--nixpkgs/nixos/modules/services/desktops/gvfs.nix63
-rw-r--r--nixpkgs/nixos/modules/services/desktops/malcontent.nix37
-rw-r--r--nixpkgs/nixos/modules/services/desktops/neard.nix23
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pantheon/files.nix13
-rw-r--r--nixpkgs/nixos/modules/services/desktops/pipewire.nix41
-rw-r--r--nixpkgs/nixos/modules/services/desktops/profile-sync-daemon.nix77
-rw-r--r--nixpkgs/nixos/modules/services/desktops/system-config-printer.nix41
-rw-r--r--nixpkgs/nixos/modules/services/desktops/telepathy.nix43
-rw-r--r--nixpkgs/nixos/modules/services/desktops/tumbler.nix52
-rw-r--r--nixpkgs/nixos/modules/services/desktops/zeitgeist.nix31
-rw-r--r--nixpkgs/nixos/modules/services/development/bloop.nix54
-rw-r--r--nixpkgs/nixos/modules/services/development/hoogle.nix76
-rw-r--r--nixpkgs/nixos/modules/services/development/jupyter/default.nix185
-rw-r--r--nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix60
-rw-r--r--nixpkgs/nixos/modules/services/development/lorri.nix47
-rw-r--r--nixpkgs/nixos/modules/services/editors/emacs.nix102
-rw-r--r--nixpkgs/nixos/modules/services/editors/emacs.xml580
-rw-r--r--nixpkgs/nixos/modules/services/editors/infinoted.nix160
-rw-r--r--nixpkgs/nixos/modules/services/games/factorio.nix242
-rw-r--r--nixpkgs/nixos/modules/services/games/minecraft-server.nix234
-rw-r--r--nixpkgs/nixos/modules/services/games/minetest-server.nix107
-rw-r--r--nixpkgs/nixos/modules/services/games/openarena.nix56
-rw-r--r--nixpkgs/nixos/modules/services/games/teeworlds.nix119
-rw-r--r--nixpkgs/nixos/modules/services/games/terraria.nix149
-rw-r--r--nixpkgs/nixos/modules/services/hardware/acpid.nix156
-rw-r--r--nixpkgs/nixos/modules/services/hardware/actkbd.nix133
-rw-r--r--nixpkgs/nixos/modules/services/hardware/bluetooth.nix99
-rw-r--r--nixpkgs/nixos/modules/services/hardware/bolt.nix34
-rw-r--r--nixpkgs/nixos/modules/services/hardware/brltty.nix50
-rw-r--r--nixpkgs/nixos/modules/services/hardware/evscript.nix50
-rw-r--r--nixpkgs/nixos/modules/services/hardware/fancontrol.nix45
-rw-r--r--nixpkgs/nixos/modules/services/hardware/freefall.nix64
-rw-r--r--nixpkgs/nixos/modules/services/hardware/fwupd.nix129
-rw-r--r--nixpkgs/nixos/modules/services/hardware/illum.nix35
-rw-r--r--nixpkgs/nixos/modules/services/hardware/interception-tools.nix61
-rw-r--r--nixpkgs/nixos/modules/services/hardware/irqbalance.nix24
-rw-r--r--nixpkgs/nixos/modules/services/hardware/lcd.nix172
-rw-r--r--nixpkgs/nixos/modules/services/hardware/lirc.nix100
-rw-r--r--nixpkgs/nixos/modules/services/hardware/nvidia-optimus.nix43
-rw-r--r--nixpkgs/nixos/modules/services/hardware/pcscd.nix69
-rw-r--r--nixpkgs/nixos/modules/services/hardware/pommed.nix50
-rw-r--r--nixpkgs/nixos/modules/services/hardware/ratbagd.nix27
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane.nix162
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix112
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix71
-rw-r--r--nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix26
-rw-r--r--nixpkgs/nixos/modules/services/hardware/tcsd.nix151
-rw-r--r--nixpkgs/nixos/modules/services/hardware/thermald.nix47
-rw-r--r--nixpkgs/nixos/modules/services/hardware/thinkfan.nix152
-rw-r--r--nixpkgs/nixos/modules/services/hardware/throttled.nix30
-rw-r--r--nixpkgs/nixos/modules/services/hardware/tlp.nix95
-rw-r--r--nixpkgs/nixos/modules/services/hardware/trezord.nix70
-rw-r--r--nixpkgs/nixos/modules/services/hardware/trezord.xml26
-rw-r--r--nixpkgs/nixos/modules/services/hardware/triggerhappy.nix122
-rw-r--r--nixpkgs/nixos/modules/services/hardware/udev.nix319
-rw-r--r--nixpkgs/nixos/modules/services/hardware/udisks2.nix44
-rw-r--r--nixpkgs/nixos/modules/services/hardware/undervolt.nix134
-rw-r--r--nixpkgs/nixos/modules/services/hardware/upower.nix240
-rw-r--r--nixpkgs/nixos/modules/services/hardware/usbmuxd.nix76
-rw-r--r--nixpkgs/nixos/modules/services/hardware/vdr.nix82
-rw-r--r--nixpkgs/nixos/modules/services/hardware/xow.nix17
-rw-r--r--nixpkgs/nixos/modules/services/logging/SystemdJournal2Gelf.nix59
-rw-r--r--nixpkgs/nixos/modules/services/logging/awstats.nix257
-rw-r--r--nixpkgs/nixos/modules/services/logging/fluentd.nix58
-rw-r--r--nixpkgs/nixos/modules/services/logging/graylog.nix170
-rw-r--r--nixpkgs/nixos/modules/services/logging/heartbeat.nix74
-rw-r--r--nixpkgs/nixos/modules/services/logging/journalbeat.nix107
-rw-r--r--nixpkgs/nixos/modules/services/logging/journaldriver.nix112
-rw-r--r--nixpkgs/nixos/modules/services/logging/journalwatch.nix264
-rw-r--r--nixpkgs/nixos/modules/services/logging/klogd.nix37
-rw-r--r--nixpkgs/nixos/modules/services/logging/logcheck.nix238
-rw-r--r--nixpkgs/nixos/modules/services/logging/logrotate.nix105
-rw-r--r--nixpkgs/nixos/modules/services/logging/logstash.nix185
-rw-r--r--nixpkgs/nixos/modules/services/logging/rsyslogd.nix105
-rw-r--r--nixpkgs/nixos/modules/services/logging/syslog-ng.nix101
-rw-r--r--nixpkgs/nixos/modules/services/logging/syslogd.nix130
-rw-r--r--nixpkgs/nixos/modules/services/mail/clamsmtp.nix181
-rw-r--r--nixpkgs/nixos/modules/services/mail/davmail.nix99
-rw-r--r--nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix120
-rw-r--r--nixpkgs/nixos/modules/services/mail/dovecot.nix496
-rw-r--r--nixpkgs/nixos/modules/services/mail/dspam.nix150
-rw-r--r--nixpkgs/nixos/modules/services/mail/exim.nix120
-rw-r--r--nixpkgs/nixos/modules/services/mail/freepops.nix89
-rw-r--r--nixpkgs/nixos/modules/services/mail/mail.nix33
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailcatcher.nix68
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailhog.nix44
-rw-r--r--nixpkgs/nixos/modules/services/mail/mailman.nix400
-rw-r--r--nixpkgs/nixos/modules/services/mail/mlmmj.nix154
-rw-r--r--nixpkgs/nixos/modules/services/mail/nullmailer.nix243
-rw-r--r--nixpkgs/nixos/modules/services/mail/offlineimap.nix72
-rw-r--r--nixpkgs/nixos/modules/services/mail/opendkim.nix136
-rw-r--r--nixpkgs/nixos/modules/services/mail/opensmtpd.nix132
-rw-r--r--nixpkgs/nixos/modules/services/mail/pfix-srsd.nix56
-rw-r--r--nixpkgs/nixos/modules/services/mail/postfix.nix903
-rw-r--r--nixpkgs/nixos/modules/services/mail/postgrey.nix205
-rw-r--r--nixpkgs/nixos/modules/services/mail/postsrsd.nix135
-rw-r--r--nixpkgs/nixos/modules/services/mail/public-inbox.nix455
-rw-r--r--nixpkgs/nixos/modules/services/mail/roundcube.nix231
-rw-r--r--nixpkgs/nixos/modules/services/mail/rspamd.nix416
-rw-r--r--nixpkgs/nixos/modules/services/mail/rss2email.nix138
-rw-r--r--nixpkgs/nixos/modules/services/mail/spamassassin.nix184
-rw-r--r--nixpkgs/nixos/modules/services/mail/sympa.nix594
-rw-r--r--nixpkgs/nixos/modules/services/misc/airsonic.nix155
-rw-r--r--nixpkgs/nixos/modules/services/misc/ankisyncd.nix79
-rw-r--r--nixpkgs/nixos/modules/services/misc/apache-kafka.nix154
-rw-r--r--nixpkgs/nixos/modules/services/misc/autofs.nix99
-rw-r--r--nixpkgs/nixos/modules/services/misc/autorandr.nix52
-rw-r--r--nixpkgs/nixos/modules/services/misc/bazarr.nix76
-rw-r--r--nixpkgs/nixos/modules/services/misc/beanstalkd.nix53
-rw-r--r--nixpkgs/nixos/modules/services/misc/bees.nix123
-rw-r--r--nixpkgs/nixos/modules/services/misc/bepasty.nix179
-rw-r--r--nixpkgs/nixos/modules/services/misc/calibre-server.nix63
-rw-r--r--nixpkgs/nixos/modules/services/misc/canto-daemon.nix37
-rw-r--r--nixpkgs/nixos/modules/services/misc/cfdyndns.nix70
-rw-r--r--nixpkgs/nixos/modules/services/misc/cgminer.nix140
-rw-r--r--nixpkgs/nixos/modules/services/misc/clipmenu.nix31
-rwxr-xr-xnixpkgs/nixos/modules/services/misc/confd.nix90
-rw-r--r--nixpkgs/nixos/modules/services/misc/couchpotato.nix42
-rw-r--r--nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix66
-rw-r--r--nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb69
-rw-r--r--nixpkgs/nixos/modules/services/misc/devmon.nix25
-rw-r--r--nixpkgs/nixos/modules/services/misc/dictd.nix69
-rw-r--r--nixpkgs/nixos/modules/services/misc/disnix.nix88
-rw-r--r--nixpkgs/nixos/modules/services/misc/docker-registry.nix157
-rw-r--r--nixpkgs/nixos/modules/services/misc/dwm-status.nix73
-rw-r--r--nixpkgs/nixos/modules/services/misc/dysnomia.nix217
-rw-r--r--nixpkgs/nixos/modules/services/misc/errbot.nix104
-rw-r--r--nixpkgs/nixos/modules/services/misc/etcd.nix195
-rw-r--r--nixpkgs/nixos/modules/services/misc/ethminer.nix117
-rw-r--r--nixpkgs/nixos/modules/services/misc/exhibitor.nix419
-rw-r--r--nixpkgs/nixos/modules/services/misc/felix.nix102
-rw-r--r--nixpkgs/nixos/modules/services/misc/freeswitch.nix105
-rw-r--r--nixpkgs/nixos/modules/services/misc/fstrim.nix46
-rw-r--r--nixpkgs/nixos/modules/services/misc/gammu-smsd.nix253
-rw-r--r--nixpkgs/nixos/modules/services/misc/geoip-updater.nix306
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitea.nix519
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitit.nix724
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitlab.nix881
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitlab.xml126
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitolite.nix231
-rw-r--r--nixpkgs/nixos/modules/services/misc/gitweb.nix59
-rw-r--r--nixpkgs/nixos/modules/services/misc/gogs.nix279
-rw-r--r--nixpkgs/nixos/modules/services/misc/gollum.nix111
-rw-r--r--nixpkgs/nixos/modules/services/misc/gpsd.nix115
-rw-r--r--nixpkgs/nixos/modules/services/misc/greenclip.nix31
-rw-r--r--nixpkgs/nixos/modules/services/misc/headphones.nix87
-rw-r--r--nixpkgs/nixos/modules/services/misc/home-assistant.nix275
-rw-r--r--nixpkgs/nixos/modules/services/misc/ihaskell.nix63
-rw-r--r--nixpkgs/nixos/modules/services/misc/irkerd.nix67
-rw-r--r--nixpkgs/nixos/modules/services/misc/jackett.nix82
-rw-r--r--nixpkgs/nixos/modules/services/misc/jellyfin.nix57
-rw-r--r--nixpkgs/nixos/modules/services/misc/leaps.nix62
-rw-r--r--nixpkgs/nixos/modules/services/misc/lidarr.nix89
-rw-r--r--nixpkgs/nixos/modules/services/misc/logkeys.nix30
-rw-r--r--nixpkgs/nixos/modules/services/misc/mame.nix67
-rw-r--r--nixpkgs/nixos/modules/services/misc/mathics.nix54
-rw-r--r--nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix162
-rw-r--r--nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml25
-rw-r--r--nixpkgs/nixos/modules/services/misc/matrix-synapse.nix732
-rw-r--r--nixpkgs/nixos/modules/services/misc/matrix-synapse.xml225
-rw-r--r--nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix163
-rw-r--r--nixpkgs/nixos/modules/services/misc/mbpfan.nix109
-rw-r--r--nixpkgs/nixos/modules/services/misc/mediatomb.nix288
-rw-r--r--nixpkgs/nixos/modules/services/misc/mesos-master.nix125
-rw-r--r--nixpkgs/nixos/modules/services/misc/mesos-slave.nix220
-rw-r--r--nixpkgs/nixos/modules/services/misc/metabase.nix103
-rw-r--r--nixpkgs/nixos/modules/services/misc/mwlib.nix258
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-daemon.nix544
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-gc.nix61
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-optimise.nix51
-rw-r--r--nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix61
-rw-r--r--nixpkgs/nixos/modules/services/misc/novacomd.nix31
-rw-r--r--nixpkgs/nixos/modules/services/misc/nzbget.nix99
-rw-r--r--nixpkgs/nixos/modules/services/misc/octoprint.nix129
-rw-r--r--nixpkgs/nixos/modules/services/misc/osrm.nix86
-rw-r--r--nixpkgs/nixos/modules/services/misc/packagekit.nix65
-rw-r--r--nixpkgs/nixos/modules/services/misc/paperless.nix183
-rw-r--r--nixpkgs/nixos/modules/services/misc/parsoid.nix129
-rw-r--r--nixpkgs/nixos/modules/services/misc/plex.nix151
-rw-r--r--nixpkgs/nixos/modules/services/misc/pykms.nix91
-rw-r--r--nixpkgs/nixos/modules/services/misc/radarr.nix75
-rw-r--r--nixpkgs/nixos/modules/services/misc/redmine.nix395
-rw-r--r--nixpkgs/nixos/modules/services/misc/ripple-data-api.nix193
-rw-r--r--nixpkgs/nixos/modules/services/misc/rippled.nix431
-rw-r--r--nixpkgs/nixos/modules/services/misc/safeeyes.nix47
-rw-r--r--nixpkgs/nixos/modules/services/misc/serviio.nix87
-rw-r--r--nixpkgs/nixos/modules/services/misc/sickbeard.nix92
-rw-r--r--nixpkgs/nixos/modules/services/misc/siproxd.nix179
-rw-r--r--nixpkgs/nixos/modules/services/misc/snapper.nix152
-rw-r--r--nixpkgs/nixos/modules/services/misc/sonarr.nix76
-rw-r--r--nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix30
-rw-r--r--nixpkgs/nixos/modules/services/misc/ssm-agent.nix45
-rw-r--r--nixpkgs/nixos/modules/services/misc/sssd.nix95
-rw-r--r--nixpkgs/nixos/modules/services/misc/subsonic.nix165
-rw-r--r--nixpkgs/nixos/modules/services/misc/sundtek.nix33
-rw-r--r--nixpkgs/nixos/modules/services/misc/svnserve.nix45
-rw-r--r--nixpkgs/nixos/modules/services/misc/synergy.nix124
-rw-r--r--nixpkgs/nixos/modules/services/misc/sysprof.nix19
-rw-r--r--nixpkgs/nixos/modules/services/misc/taskserver/default.nix568
-rw-r--r--nixpkgs/nixos/modules/services/misc/taskserver/doc.xml135
-rw-r--r--nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py688
-rw-r--r--nixpkgs/nixos/modules/services/misc/tautulli.nix81
-rw-r--r--nixpkgs/nixos/modules/services/misc/tiddlywiki.nix52
-rw-r--r--nixpkgs/nixos/modules/services/misc/tzupdate.nix45
-rw-r--r--nixpkgs/nixos/modules/services/misc/uhub.nix180
-rw-r--r--nixpkgs/nixos/modules/services/misc/weechat.nix58
-rw-r--r--nixpkgs/nixos/modules/services/misc/weechat.xml66
-rw-r--r--nixpkgs/nixos/modules/services/misc/xmr-stak.nix93
-rw-r--r--nixpkgs/nixos/modules/services/misc/zoneminder.nix370
-rw-r--r--nixpkgs/nixos/modules/services/misc/zookeeper.nix155
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/alerta.nix115
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/apcupsd.nix191
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/arbtt.nix63
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/bosun.nix166
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/cadvisor.nix140
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/collectd.nix138
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix34
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix272
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix8
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix236
-rwxr-xr-xnixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults9
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/do-agent.nix34
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix63
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix66
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/grafana.nix559
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/graphite.nix570
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/hdaps.nix23
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/heapster.nix57
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/incron.nix98
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/kapacitor.nix191
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/loki.nix112
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/longview.nix160
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/monit.nix46
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/munin.nix402
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/nagios.nix212
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/netdata.nix219
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix187
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix642
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix249
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml227
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix38
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bind.nix54
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix70
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix77
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix38
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix73
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix38
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/json.nix35
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/keylight.nix19
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix46
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix158
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix66
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/minio.nix64
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix58
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix65
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix40
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix82
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix47
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix92
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix70
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix31
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix44
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix66
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix88
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix66
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix166
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix47
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/riemann-dash.nix81
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix70
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/riemann.nix105
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/scollector.nix135
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/smartd.nix242
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/statsd.nix149
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/sysstat.nix76
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/teamviewer.nix46
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/telegraf.nix71
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/thanos.nix835
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/tuptime.nix84
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/ups.nix263
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/uptime.nix96
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/vnstat.nix58
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix159
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix299
-rw-r--r--nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix299
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/cachefilesd.nix59
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/ceph.nix418
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/davfs2.nix75
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/diod.nix159
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/drbd.nix65
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix211
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix264
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/kbfs.nix118
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix149
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/nfsd.nix175
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/openafs/client.nix253
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix33
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix269
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/orangefs/client.nix97
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/orangefs/server.nix225
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix122
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/samba.nix255
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix368
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/u9fs.nix78
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix480
-rw-r--r--nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix114
-rw-r--r--nixpkgs/nixos/modules/services/networking/3proxy.nix426
-rw-r--r--nixpkgs/nixos/modules/services/networking/amuled.nix77
-rw-r--r--nixpkgs/nixos/modules/services/networking/aria2.nix131
-rw-r--r--nixpkgs/nixos/modules/services/networking/asterisk.nix264
-rw-r--r--nixpkgs/nixos/modules/services/networking/atftpd.nix65
-rw-r--r--nixpkgs/nixos/modules/services/networking/autossh.nix113
-rw-r--r--nixpkgs/nixos/modules/services/networking/avahi-daemon.nix285
-rw-r--r--nixpkgs/nixos/modules/services/networking/babeld.nix95
-rw-r--r--nixpkgs/nixos/modules/services/networking/bind.nix205
-rw-r--r--nixpkgs/nixos/modules/services/networking/bird.nix78
-rw-r--r--nixpkgs/nixos/modules/services/networking/bitcoind.nix193
-rw-r--r--nixpkgs/nixos/modules/services/networking/bitlbee.nix193
-rw-r--r--nixpkgs/nixos/modules/services/networking/charybdis.nix107
-rw-r--r--nixpkgs/nixos/modules/services/networking/cjdns.nix294
-rw-r--r--nixpkgs/nixos/modules/services/networking/cntlm.nix121
-rw-r--r--nixpkgs/nixos/modules/services/networking/connman.nix161
-rw-r--r--nixpkgs/nixos/modules/services/networking/consul.nix254
-rw-r--r--nixpkgs/nixos/modules/services/networking/coredns.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/corerad.nix85
-rw-r--r--nixpkgs/nixos/modules/services/networking/coturn.nix334
-rw-r--r--nixpkgs/nixos/modules/services/networking/dante.nix62
-rw-r--r--nixpkgs/nixos/modules/services/networking/ddclient.nix209
-rw-r--r--nixpkgs/nixos/modules/services/networking/dhcpcd.nix225
-rw-r--r--nixpkgs/nixos/modules/services/networking/dhcpd.nix214
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscache.nix108
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix62
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix281
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnsdist.nix60
-rw-r--r--nixpkgs/nixos/modules/services/networking/dnsmasq.nix128
-rw-r--r--nixpkgs/nixos/modules/services/networking/ejabberd.nix157
-rw-r--r--nixpkgs/nixos/modules/services/networking/epmd.nix56
-rw-r--r--nixpkgs/nixos/modules/services/networking/ergo.nix141
-rw-r--r--nixpkgs/nixos/modules/services/networking/eternal-terminal.nix95
-rw-r--r--nixpkgs/nixos/modules/services/networking/fakeroute.nix65
-rw-r--r--nixpkgs/nixos/modules/services/networking/ferm.nix63
-rw-r--r--nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix183
-rw-r--r--nixpkgs/nixos/modules/services/networking/fireqos.nix52
-rw-r--r--nixpkgs/nixos/modules/services/networking/firewall.nix585
-rw-r--r--nixpkgs/nixos/modules/services/networking/flannel.nix191
-rw-r--r--nixpkgs/nixos/modules/services/networking/flashpolicyd.nix85
-rw-r--r--nixpkgs/nixos/modules/services/networking/freenet.nix64
-rw-r--r--nixpkgs/nixos/modules/services/networking/freeradius.nix84
-rw-r--r--nixpkgs/nixos/modules/services/networking/gale.nix181
-rw-r--r--nixpkgs/nixos/modules/services/networking/gateone.nix59
-rw-r--r--nixpkgs/nixos/modules/services/networking/gdomap.nix29
-rw-r--r--nixpkgs/nixos/modules/services/networking/git-daemon.nix130
-rw-r--r--nixpkgs/nixos/modules/services/networking/gnunet.nix166
-rw-r--r--nixpkgs/nixos/modules/services/networking/go-neb.nix53
-rw-r--r--nixpkgs/nixos/modules/services/networking/go-shadowsocks2.nix30
-rw-r--r--nixpkgs/nixos/modules/services/networking/gogoclient.nix85
-rw-r--r--nixpkgs/nixos/modules/services/networking/gvpe.nix124
-rw-r--r--nixpkgs/nixos/modules/services/networking/hans.nix145
-rw-r--r--nixpkgs/nixos/modules/services/networking/haproxy.nix112
-rw-r--r--nixpkgs/nixos/modules/services/networking/helpers.nix11
-rw-r--r--nixpkgs/nixos/modules/services/networking/heyefi.nix82
-rw-r--r--nixpkgs/nixos/modules/services/networking/hostapd.nix218
-rw-r--r--nixpkgs/nixos/modules/services/networking/htpdate.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/default.nix31
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/faxq-default.nix12
-rwxr-xr-xnixpkgs/nixos/modules/services/networking/hylafax/faxq-wait.sh29
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/hfaxd-default.nix10
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/modem-default.nix22
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/options.nix375
-rwxr-xr-xnixpkgs/nixos/modules/services/networking/hylafax/spool.sh111
-rw-r--r--nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix249
-rw-r--r--nixpkgs/nixos/modules/services/networking/i2p.nix34
-rw-r--r--nixpkgs/nixos/modules/services/networking/i2pd.nix684
-rw-r--r--nixpkgs/nixos/modules/services/networking/iodine.nix197
-rw-r--r--nixpkgs/nixos/modules/services/networking/iperf3.nix97
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh31
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/control.in26
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix125
-rw-r--r--nixpkgs/nixos/modules/services/networking/ircd-hybrid/ircd.conf1051
-rw-r--r--nixpkgs/nixos/modules/services/networking/iwd.nix29
-rw-r--r--nixpkgs/nixos/modules/services/networking/keepalived/default.nix303
-rw-r--r--nixpkgs/nixos/modules/services/networking/keepalived/virtual-ip-options.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix135
-rw-r--r--nixpkgs/nixos/modules/services/networking/keepalived/vrrp-script-options.nix64
-rw-r--r--nixpkgs/nixos/modules/services/networking/keybase.nix47
-rw-r--r--nixpkgs/nixos/modules/services/networking/kippo.nix117
-rw-r--r--nixpkgs/nixos/modules/services/networking/knot.nix117
-rw-r--r--nixpkgs/nixos/modules/services/networking/kresd.nix145
-rw-r--r--nixpkgs/nixos/modules/services/networking/lambdabot.nix82
-rw-r--r--nixpkgs/nixos/modules/services/networking/libreswan.nix129
-rw-r--r--nixpkgs/nixos/modules/services/networking/lldpd.nix39
-rw-r--r--nixpkgs/nixos/modules/services/networking/logmein-hamachi.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix28
-rw-r--r--nixpkgs/nixos/modules/services/networking/mailpile.nix72
-rw-r--r--nixpkgs/nixos/modules/services/networking/matterbridge.nix120
-rw-r--r--nixpkgs/nixos/modules/services/networking/minidlna.nix193
-rw-r--r--nixpkgs/nixos/modules/services/networking/miniupnpd.nix79
-rw-r--r--nixpkgs/nixos/modules/services/networking/miredo.nix92
-rw-r--r--nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/monero.nix238
-rw-r--r--nixpkgs/nixos/modules/services/networking/morty.nix97
-rw-r--r--nixpkgs/nixos/modules/services/networking/mosquitto.nix231
-rw-r--r--nixpkgs/nixos/modules/services/networking/mstpd.nix33
-rw-r--r--nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix110
-rw-r--r--nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix43
-rw-r--r--nixpkgs/nixos/modules/services/networking/murmur.nix270
-rw-r--r--nixpkgs/nixos/modules/services/networking/mxisd.nix127
-rw-r--r--nixpkgs/nixos/modules/services/networking/namecoind.nix199
-rw-r--r--nixpkgs/nixos/modules/services/networking/nat.nix290
-rw-r--r--nixpkgs/nixos/modules/services/networking/ndppd.nix189
-rw-r--r--nixpkgs/nixos/modules/services/networking/networkmanager.nix491
-rw-r--r--nixpkgs/nixos/modules/services/networking/nftables.nix136
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix131
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/backend-submodule.nix50
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/default.nix118
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix64
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/frontend-submodule.nix36
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix142
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/server-options.nix18
-rw-r--r--nixpkgs/nixos/modules/services/networking/nghttpx/tls-submodule.nix21
-rw-r--r--nixpkgs/nixos/modules/services/networking/ngircd.nix59
-rw-r--r--nixpkgs/nixos/modules/services/networking/nix-serve.nix81
-rw-r--r--nixpkgs/nixos/modules/services/networking/nix-store-gcs-proxy.nix75
-rw-r--r--nixpkgs/nixos/modules/services/networking/nixops-dns.nix79
-rw-r--r--nixpkgs/nixos/modules/services/networking/nntp-proxy.nix234
-rw-r--r--nixpkgs/nixos/modules/services/networking/nsd.nix980
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntopng.nix116
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/chrony.nix125
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix148
-rw-r--r--nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix82
-rw-r--r--nixpkgs/nixos/modules/services/networking/nullidentdmod.nix34
-rw-r--r--nixpkgs/nixos/modules/services/networking/nylon.nix166
-rw-r--r--nixpkgs/nixos/modules/services/networking/ocserv.nix99
-rw-r--r--nixpkgs/nixos/modules/services/networking/ofono.nix44
-rw-r--r--nixpkgs/nixos/modules/services/networking/oidentd.nix44
-rw-r--r--nixpkgs/nixos/modules/services/networking/openfire.nix56
-rw-r--r--nixpkgs/nixos/modules/services/networking/openvpn.nix219
-rw-r--r--nixpkgs/nixos/modules/services/networking/ostinato.nix104
-rw-r--r--nixpkgs/nixos/modules/services/networking/owamp.nix45
-rw-r--r--nixpkgs/nixos/modules/services/networking/pdns-recursor.nix224
-rw-r--r--nixpkgs/nixos/modules/services/networking/pdnsd.nix91
-rw-r--r--nixpkgs/nixos/modules/services/networking/pixiecore.nix134
-rw-r--r--nixpkgs/nixos/modules/services/networking/polipo.nix112
-rw-r--r--nixpkgs/nixos/modules/services/networking/powerdns.nix49
-rw-r--r--nixpkgs/nixos/modules/services/networking/pppd.nix136
-rw-r--r--nixpkgs/nixos/modules/services/networking/pptpd.nix124
-rw-r--r--nixpkgs/nixos/modules/services/networking/prayer.nix89
-rw-r--r--nixpkgs/nixos/modules/services/networking/privoxy.nix114
-rw-r--r--nixpkgs/nixos/modules/services/networking/prosody.nix882
-rw-r--r--nixpkgs/nixos/modules/services/networking/prosody.xml88
-rw-r--r--nixpkgs/nixos/modules/services/networking/quagga.nix185
-rw-r--r--nixpkgs/nixos/modules/services/networking/quassel.nix132
-rw-r--r--nixpkgs/nixos/modules/services/networking/quicktun.nix118
-rw-r--r--nixpkgs/nixos/modules/services/networking/quorum.nix229
-rw-r--r--nixpkgs/nixos/modules/services/networking/racoon.nix45
-rw-r--r--nixpkgs/nixos/modules/services/networking/radicale.nix89
-rw-r--r--nixpkgs/nixos/modules/services/networking/radvd.nix73
-rw-r--r--nixpkgs/nixos/modules/services/networking/rdnssd.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/redsocks.nix272
-rw-r--r--nixpkgs/nixos/modules/services/networking/resilio.nix263
-rw-r--r--nixpkgs/nixos/modules/services/networking/rpcbind.nix46
-rw-r--r--nixpkgs/nixos/modules/services/networking/rxe.nix52
-rw-r--r--nixpkgs/nixos/modules/services/networking/sabnzbd.nix67
-rw-r--r--nixpkgs/nixos/modules/services/networking/searx.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/seeks.nix75
-rw-r--r--nixpkgs/nixos/modules/services/networking/shadowsocks.nix112
-rw-r--r--nixpkgs/nixos/modules/services/networking/shairport-sync.nix83
-rw-r--r--nixpkgs/nixos/modules/services/networking/shorewall.nix70
-rw-r--r--nixpkgs/nixos/modules/services/networking/shorewall6.nix70
-rw-r--r--nixpkgs/nixos/modules/services/networking/shout.nix113
-rw-r--r--nixpkgs/nixos/modules/services/networking/skydns.nix92
-rw-r--r--nixpkgs/nixos/modules/services/networking/smartdns.nix61
-rw-r--r--nixpkgs/nixos/modules/services/networking/smokeping.nix320
-rw-r--r--nixpkgs/nixos/modules/services/networking/sniproxy.nix99
-rw-r--r--nixpkgs/nixos/modules/services/networking/softether.nix163
-rw-r--r--nixpkgs/nixos/modules/services/networking/spacecookie.nix83
-rw-r--r--nixpkgs/nixos/modules/services/networking/spiped.nix220
-rw-r--r--nixpkgs/nixos/modules/services/networking/squid.nix168
-rw-r--r--nixpkgs/nixos/modules/services/networking/ssh/lshd.nix183
-rw-r--r--nixpkgs/nixos/modules/services/networking/ssh/sshd.nix557
-rw-r--r--nixpkgs/nixos/modules/services/networking/sslh.nix160
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix84
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix162
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix82
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix1300
-rw-r--r--nixpkgs/nixos/modules/services/networking/strongswan.nix170
-rw-r--r--nixpkgs/nixos/modules/services/networking/stubby.nix217
-rw-r--r--nixpkgs/nixos/modules/services/networking/stunnel.nix234
-rw-r--r--nixpkgs/nixos/modules/services/networking/supplicant.nix249
-rw-r--r--nixpkgs/nixos/modules/services/networking/supybot.nix161
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncplay.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncthing-relay.nix121
-rw-r--r--nixpkgs/nixos/modules/services/networking/syncthing.nix528
-rw-r--r--nixpkgs/nixos/modules/services/networking/tailscale.nix49
-rw-r--r--nixpkgs/nixos/modules/services/networking/tcpcrypt.nix80
-rw-r--r--nixpkgs/nixos/modules/services/networking/teamspeak3.nix142
-rw-r--r--nixpkgs/nixos/modules/services/networking/tedicross.nix100
-rw-r--r--nixpkgs/nixos/modules/services/networking/tftpd.nix46
-rw-r--r--nixpkgs/nixos/modules/services/networking/thelounge.nix75
-rw-r--r--nixpkgs/nixos/modules/services/networking/tinc.nix212
-rw-r--r--nixpkgs/nixos/modules/services/networking/tinydns.nix55
-rw-r--r--nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix79
-rw-r--r--nixpkgs/nixos/modules/services/networking/tox-node.nix95
-rw-r--r--nixpkgs/nixos/modules/services/networking/toxvpn.nix68
-rw-r--r--nixpkgs/nixos/modules/services/networking/trickster.nix112
-rw-r--r--nixpkgs/nixos/modules/services/networking/tvheadend.nix61
-rw-r--r--nixpkgs/nixos/modules/services/networking/unbound.nix148
-rw-r--r--nixpkgs/nixos/modules/services/networking/unifi.nix180
-rw-r--r--nixpkgs/nixos/modules/services/networking/v2ray.nix87
-rw-r--r--nixpkgs/nixos/modules/services/networking/vsftpd.nix326
-rw-r--r--nixpkgs/nixos/modules/services/networking/wakeonlan.nix56
-rw-r--r--nixpkgs/nixos/modules/services/networking/websockify.nix54
-rw-r--r--nixpkgs/nixos/modules/services/networking/wg-quick.nix312
-rw-r--r--nixpkgs/nixos/modules/services/networking/wicd.nix40
-rw-r--r--nixpkgs/nixos/modules/services/networking/wireguard.nix452
-rw-r--r--nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix266
-rw-r--r--nixpkgs/nixos/modules/services/networking/xandikos.nix148
-rw-r--r--nixpkgs/nixos/modules/services/networking/xinetd.nix147
-rw-r--r--nixpkgs/nixos/modules/services/networking/xl2tpd.nix143
-rw-r--r--nixpkgs/nixos/modules/services/networking/xrdp.nix171
-rw-r--r--nixpkgs/nixos/modules/services/networking/yggdrasil.nix199
-rw-r--r--nixpkgs/nixos/modules/services/networking/zerobin.nix102
-rw-r--r--nixpkgs/nixos/modules/services/networking/zeronet.nix96
-rw-r--r--nixpkgs/nixos/modules/services/networking/zerotierone.nix82
-rw-r--r--nixpkgs/nixos/modules/services/networking/znc/default.nix308
-rw-r--r--nixpkgs/nixos/modules/services/networking/znc/options.nix270
-rw-r--r--nixpkgs/nixos/modules/services/printing/cupsd.nix460
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/atd.nix109
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/chronos.nix54
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/cron.nix133
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/fcron.nix167
-rw-r--r--nixpkgs/nixos/modules/services/scheduling/marathon.nix98
-rw-r--r--nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix94
-rw-r--r--nixpkgs/nixos/modules/services/search/elasticsearch.nix208
-rw-r--r--nixpkgs/nixos/modules/services/search/hound.nix125
-rw-r--r--nixpkgs/nixos/modules/services/search/kibana.nix208
-rw-r--r--nixpkgs/nixos/modules/services/search/solr.nix110
-rw-r--r--nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh17
-rw-r--r--nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix145
-rw-r--r--nixpkgs/nixos/modules/services/security/certmgr.nix201
-rw-r--r--nixpkgs/nixos/modules/services/security/cfssl.nix209
-rw-r--r--nixpkgs/nixos/modules/services/security/clamav.nix147
-rw-r--r--nixpkgs/nixos/modules/services/security/fail2ban.nix306
-rw-r--r--nixpkgs/nixos/modules/services/security/fprintd.nix54
-rw-r--r--nixpkgs/nixos/modules/services/security/fprot.nix79
-rw-r--r--nixpkgs/nixos/modules/services/security/haka.nix156
-rw-r--r--nixpkgs/nixos/modules/services/security/haveged.nix67
-rw-r--r--nixpkgs/nixos/modules/services/security/hologram-agent.nix58
-rw-r--r--nixpkgs/nixos/modules/services/security/hologram-server.nix130
-rw-r--r--nixpkgs/nixos/modules/services/security/munge.nix68
-rw-r--r--nixpkgs/nixos/modules/services/security/nginx-sso.nix58
-rw-r--r--nixpkgs/nixos/modules/services/security/oauth2_proxy.nix586
-rw-r--r--nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix64
-rw-r--r--nixpkgs/nixos/modules/services/security/physlock.nix129
-rw-r--r--nixpkgs/nixos/modules/services/security/privacyidea.nix279
-rw-r--r--nixpkgs/nixos/modules/services/security/shibboleth-sp.nix75
-rw-r--r--nixpkgs/nixos/modules/services/security/sks.nix146
-rw-r--r--nixpkgs/nixos/modules/services/security/sshguard.nix155
-rw-r--r--nixpkgs/nixos/modules/services/security/tor.nix789
-rw-r--r--nixpkgs/nixos/modules/services/security/torify.nix80
-rw-r--r--nixpkgs/nixos/modules/services/security/torsocks.nix120
-rw-r--r--nixpkgs/nixos/modules/services/security/usbguard.nix236
-rw-r--r--nixpkgs/nixos/modules/services/security/vault.nix156
-rw-r--r--nixpkgs/nixos/modules/services/system/cloud-init.nix180
-rw-r--r--nixpkgs/nixos/modules/services/system/dbus.nix116
-rw-r--r--nixpkgs/nixos/modules/services/system/earlyoom.nix124
-rw-r--r--nixpkgs/nixos/modules/services/system/kerberos/default.nix75
-rw-r--r--nixpkgs/nixos/modules/services/system/kerberos/heimdal.nix68
-rw-r--r--nixpkgs/nixos/modules/services/system/kerberos/mit.nix68
-rw-r--r--nixpkgs/nixos/modules/services/system/localtime.nix48
-rw-r--r--nixpkgs/nixos/modules/services/system/nscd.conf34
-rw-r--r--nixpkgs/nixos/modules/services/system/nscd.nix85
-rw-r--r--nixpkgs/nixos/modules/services/system/saslauthd.nix62
-rw-r--r--nixpkgs/nixos/modules/services/system/uptimed.nix56
-rw-r--r--nixpkgs/nixos/modules/services/torrent/deluge.nix271
-rw-r--r--nixpkgs/nixos/modules/services/torrent/flexget.nix100
-rw-r--r--nixpkgs/nixos/modules/services/torrent/magnetico.nix218
-rw-r--r--nixpkgs/nixos/modules/services/torrent/opentracker.nix45
-rw-r--r--nixpkgs/nixos/modules/services/torrent/peerflix.nix65
-rw-r--r--nixpkgs/nixos/modules/services/torrent/rtorrent.nix209
-rw-r--r--nixpkgs/nixos/modules/services/torrent/transmission.nix202
-rw-r--r--nixpkgs/nixos/modules/services/ttys/agetty.nix139
-rw-r--r--nixpkgs/nixos/modules/services/ttys/gpm.nix57
-rw-r--r--nixpkgs/nixos/modules/services/ttys/kmscon.nix100
-rw-r--r--nixpkgs/nixos/modules/services/wayland/cage.nix99
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix197
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix164
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix204
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/codimd.nix916
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/cryptpad.nix54
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/documize.nix149
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix388
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/engelsystem.nix186
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/frab.nix222
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/gerrit.nix218
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/gotify-server.nix49
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/grocy.nix172
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/grocy.xml77
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix244
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix157
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix141
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/jirafeau.nix169
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/limesurvey.nix280
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml107
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/matomo.nix306
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mattermost.nix236
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/mediawiki.nix473
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/miniflux.nix97
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/moinmoin.nix303
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/moodle.nix311
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud.nix615
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nextcloud.xml165
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/nexus.nix134
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix75
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/restya-board.nix383
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/selfoss.nix165
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/shiori.nix50
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/sogo.nix272
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/trac.nix79
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/trilium.nix137
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/tt-rss.nix679
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/virtlyst.nix73
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/wordpress.nix366
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/youtrack.nix180
-rw-r--r--nixpkgs/nixos/modules/services/web-apps/zabbix.nix217
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix745
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix54
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix275
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/caddy.nix111
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix77
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix72
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/hitch/default.nix111
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/hydron.nix165
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh72
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/jboss/default.nix82
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix91
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix58
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix256
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix52
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/meguca.nix174
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix132
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/minio.nix108
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/default.nix794
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix94
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix94
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix229
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix284
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/shellinabox.nix122
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/tomcat.nix421
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/traefik.nix170
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/ttyd.nix196
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/unit/default.nix150
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/uwsgi.nix183
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/varnish/default.nix113
-rw-r--r--nixpkgs/nixos/modules/services/web-servers/zope2.nix258
-rw-r--r--nixpkgs/nixos/modules/services/x11/clight.nix115
-rw-r--r--nixpkgs/nixos/modules/services/x11/colord.nix41
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/cde.nix72
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix98
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/enlightenment.nix103
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix397
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix31
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/lumina.nix42
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/lxqt.nix67
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix111
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix7
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix297
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.xml114
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix364
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix127
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix172
-rw-r--r--nixpkgs/nixos/modules/services/x11/desktop-managers/xterm.nix38
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/account-service-util.nix44
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/default.nix444
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix358
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix140
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix173
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix100
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix49
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix92
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix343
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix297
-rwxr-xr-xnixpkgs/nixos/modules/services/x11/display-managers/set-session.py86
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/slim.nix16
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/startx.nix45
-rw-r--r--nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix252
-rw-r--r--nixpkgs/nixos/modules/services/x11/extra-layouts.nix174
-rw-r--r--nixpkgs/nixos/modules/services/x11/fractalart.nix36
-rw-r--r--nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix45
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/cmt.nix59
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/digimend.nix38
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/libinput.nix252
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix213
-rw-r--r--nixpkgs/nixos/modules/services/x11/hardware/wacom.nix48
-rw-r--r--nixpkgs/nixos/modules/services/x11/imwheel.nix68
-rw-r--r--nixpkgs/nixos/modules/services/x11/picom.nix316
-rw-r--r--nixpkgs/nixos/modules/services/x11/redshift.nix129
-rw-r--r--nixpkgs/nixos/modules/services/x11/terminal-server.nix56
-rw-r--r--nixpkgs/nixos/modules/services/x11/unclutter-xfixes.nix58
-rw-r--r--nixpkgs/nixos/modules/services/x11/unclutter.nix82
-rw-r--r--nixpkgs/nixos/modules/services/x11/urxvtd.nix50
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix37
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix66
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/berry.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix77
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix24
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/cwm.nix23
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/default.nix84
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix37
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix54
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/fvwm.nix41
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix38
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/i3.nix78
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix27
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/lwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix30
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/none.nix12
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/notion.nix26
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix24
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/smallwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix27
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix24
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/tinywm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/twm.nix37
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix22
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix39
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix97
-rw-r--r--nixpkgs/nixos/modules/services/x11/window-managers/yeahwm.nix25
-rw-r--r--nixpkgs/nixos/modules/services/x11/xautolock.nix140
-rw-r--r--nixpkgs/nixos/modules/services/x11/xbanish.nix31
-rw-r--r--nixpkgs/nixos/modules/services/x11/xfs.conf15
-rw-r--r--nixpkgs/nixos/modules/services/x11/xfs.nix46
-rw-r--r--nixpkgs/nixos/modules/services/x11/xserver.nix815
864 files changed, 131439 insertions, 0 deletions
diff --git a/nixpkgs/nixos/modules/services/admin/oxidized.nix b/nixpkgs/nixos/modules/services/admin/oxidized.nix
new file mode 100644
index 000000000000..94b44630ba6c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/admin/oxidized.nix
@@ -0,0 +1,118 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.oxidized;
+in
+{
+  options.services.oxidized = {
+    enable = mkEnableOption "the oxidized configuration backup service";
+
+    user = mkOption {
+      type = types.str;
+      default = "oxidized";
+      description = ''
+        User under which the oxidized service runs.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "oxidized";
+      description = ''
+        Group under which the oxidized service runs.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/oxidized";
+      description = "State directory for the oxidized service.";
+    };
+
+    configFile = mkOption {
+      type = types.path;
+      example = literalExample ''
+        pkgs.writeText "oxidized-config.yml" '''
+          ---
+          debug: true
+          use_syslog: true
+          input:
+            default: ssh
+            ssh:
+              secure: true
+          interval: 3600
+          model_map:
+            dell: powerconnect
+            hp: procurve
+          source:
+            default: csv
+            csv:
+              delimiter: !ruby/regexp /:/
+              file: "/var/lib/oxidized/.config/oxidized/router.db"
+              map:
+                name: 0
+                model: 1
+                username: 2
+                password: 3
+          pid: "/var/lib/oxidized/.config/oxidized/pid"
+          rest: 127.0.0.1:8888
+          retries: 3
+          # ... additional config
+        ''';
+      '';
+      description = ''
+        Path to the oxidized configuration file.
+      '';
+    };
+
+    routerDB = mkOption {
+      type = types.path;
+      example = literalExample ''
+        pkgs.writeText "oxidized-router.db" '''
+          hostname-sw1:powerconnect:username1:password2
+          hostname-sw2:procurve:username2:password2
+          # ... additional hosts
+        '''
+      '';
+      description = ''
+        Path to the file/database which contains the targets for oxidized.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups.${cfg.group} = { };
+    users.users.${cfg.user} = {
+      description = "Oxidized service user";
+      group = cfg.group;
+      home = cfg.dataDir;
+      createHome = true;
+      isSystemUser = true;
+    };
+
+    systemd.services.oxidized = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        mkdir -p ${cfg.dataDir}/.config/oxidized
+        ln -f -s ${cfg.routerDB} ${cfg.dataDir}/.config/oxidized/router.db
+        ln -f -s ${cfg.configFile} ${cfg.dataDir}/.config/oxidized/config
+      '';
+
+      serviceConfig = {
+        ExecStart = "${pkgs.oxidized}/bin/oxidized";
+        User = cfg.user;
+        Group = cfg.group;
+        UMask = "0077";
+        NoNewPrivileges = true;
+        Restart  = "always";
+        WorkingDirectory = cfg.dataDir;
+        KillSignal = "SIGKILL";
+        PIDFile = "${cfg.dataDir}/.config/oxidized/pid";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/admin/salt/master.nix b/nixpkgs/nixos/modules/services/admin/salt/master.nix
new file mode 100644
index 000000000000..c6b1b0cc0bd8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/admin/salt/master.nix
@@ -0,0 +1,63 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg  = config.services.salt.master;
+
+  fullConfig = lib.recursiveUpdate {
+    # Provide defaults for some directories to allow an immutable config dir
+
+    # Default is equivalent to /etc/salt/master.d/*.conf
+    default_include = "/var/lib/salt/master.d/*.conf";
+    # Default is in /etc/salt/pki/master
+    pki_dir = "/var/lib/salt/pki/master";
+  } cfg.configuration;
+
+in
+
+{
+  options = {
+    services.salt.master = {
+      enable = mkEnableOption "Salt master service";
+      configuration = mkOption {
+        type = types.attrs;
+        default = {};
+        description = "Salt master configuration as Nix attribute set.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment = {
+      # Set this up in /etc/salt/master so `salt`, `salt-key`, etc. work.
+      # The alternatives are
+      # - passing --config-dir to all salt commands, not just the master unit,
+      # - setting a global environment variable,
+      etc."salt/master".source = pkgs.writeText "master" (
+        builtins.toJSON fullConfig
+      );
+      systemPackages = with pkgs; [ salt ];
+    };
+    systemd.services.salt-master = {
+      description = "Salt Master";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = with pkgs; [
+        utillinux  # for dmesg
+      ];
+      serviceConfig = {
+        ExecStart = "${pkgs.salt}/bin/salt-master";
+        LimitNOFILE = 16384;
+        Type = "notify";
+        NotifyAccess = "all";
+      };
+      restartTriggers = [
+        config.environment.etc."salt/master".source
+      ];
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ aneeshusa ];
+}
diff --git a/nixpkgs/nixos/modules/services/admin/salt/minion.nix b/nixpkgs/nixos/modules/services/admin/salt/minion.nix
new file mode 100644
index 000000000000..c8fa9461a209
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/admin/salt/minion.nix
@@ -0,0 +1,67 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg  = config.services.salt.minion;
+
+  fullConfig = lib.recursiveUpdate {
+    # Provide defaults for some directories to allow an immutable config dir
+    # NOTE: the config dir being immutable prevents `minion_id` caching
+
+    # Default is equivalent to /etc/salt/minion.d/*.conf
+    default_include = "/var/lib/salt/minion.d/*.conf";
+    # Default is in /etc/salt/pki/minion
+    pki_dir = "/var/lib/salt/pki/minion";
+  } cfg.configuration;
+
+in
+
+{
+  options = {
+    services.salt.minion = {
+      enable = mkEnableOption "Salt minion service";
+      configuration = mkOption {
+        type = types.attrs;
+        default = {};
+        description = ''
+          Salt minion configuration as Nix attribute set.
+          See <link xlink:href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html"/>
+          for details.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment = {
+      # Set this up in /etc/salt/minion so `salt-call`, etc. work.
+      # The alternatives are
+      # - passing --config-dir to all salt commands, not just the minion unit,
+      # - setting aglobal environment variable.
+      etc."salt/minion".source = pkgs.writeText "minion" (
+        builtins.toJSON fullConfig
+      );
+      systemPackages = with pkgs; [ salt ];
+    };
+    systemd.services.salt-minion = {
+      description = "Salt Minion";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = with pkgs; [
+        utillinux
+      ];
+      serviceConfig = {
+        ExecStart = "${pkgs.salt}/bin/salt-minion";
+        LimitNOFILE = 8192;
+        Type = "notify";
+        NotifyAccess = "all";
+      };
+      restartTriggers = [
+        config.environment.etc."salt/minion".source
+      ];
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/amqp/activemq/ActiveMQBroker.java b/nixpkgs/nixos/modules/services/amqp/activemq/ActiveMQBroker.java
new file mode 100644
index 000000000000..c0f5d16ea11a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/amqp/activemq/ActiveMQBroker.java
@@ -0,0 +1,19 @@
+import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.broker.BrokerFactory;
+import java.net.URI;
+
+public class ActiveMQBroker {
+
+  public static void main(String[] args) throws Throwable {
+    URI uri = new URI((args.length > 0) ? args[0] : "xbean:activemq.xml");
+    BrokerService broker = BrokerFactory.createBroker(uri);
+    broker.start();
+    if (broker.waitUntilStarted()) {
+      broker.waitUntilStopped();
+    } else {
+      System.out.println("Failed starting broker");
+      System.exit(-1);
+    };
+  }
+
+}
diff --git a/nixpkgs/nixos/modules/services/amqp/activemq/default.nix b/nixpkgs/nixos/modules/services/amqp/activemq/default.nix
new file mode 100644
index 000000000000..160dbddcd487
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/amqp/activemq/default.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+  cfg = config.services.activemq;
+
+  activemqBroker = stdenv.mkDerivation {
+    name = "activemq-broker";
+    phases = [ "installPhase" ];
+    buildInputs = [ jdk ];
+    installPhase = ''
+      mkdir -p $out/lib
+      source ${activemq}/lib/classpath.env
+      export CLASSPATH
+      ln -s "${./ActiveMQBroker.java}" ActiveMQBroker.java
+      javac -d $out/lib ActiveMQBroker.java
+    '';
+  };
+
+in {
+
+  options = {
+    services.activemq = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the Apache ActiveMQ message broker service.
+        '';
+      };
+      configurationDir = mkOption {
+        default = "${activemq}/conf";
+        description = ''
+          The base directory for ActiveMQ's configuration.
+          By default, this directory is searched for a file named activemq.xml,
+          which should contain the configuration for the broker service.
+        '';
+      };
+      configurationURI = mkOption {
+        type = types.str;
+        default = "xbean:activemq.xml";
+        description = ''
+          The URI that is passed along to the BrokerFactory to
+          set up the configuration of the ActiveMQ broker service.
+          You should not need to change this. For custom configuration,
+          set the <literal>configurationDir</literal> instead, and create
+          an activemq.xml configuration file in it.
+        '';
+      };
+      baseDir = mkOption {
+        type = types.str;
+        default = "/var/activemq";
+        description = ''
+          The base directory where ActiveMQ stores its persistent data and logs.
+          This will be overridden if you set "activemq.base" and "activemq.data"
+          in the <literal>javaProperties</literal> option. You can also override
+          this in activemq.xml.
+        '';
+      };
+      javaProperties = mkOption {
+        type = types.attrs;
+        default = { };
+        example = literalExample ''
+          {
+            "java.net.preferIPv4Stack" = "true";
+          }
+        '';
+        apply = attrs: {
+          "activemq.base" = "${cfg.baseDir}";
+          "activemq.data" = "${cfg.baseDir}/data";
+          "activemq.conf" = "${cfg.configurationDir}";
+          "activemq.home" = "${activemq}";
+        } // attrs;
+        description = ''
+          Specifies Java properties that are sent to the ActiveMQ
+          broker service with the "-D" option. You can set properties
+          here to change the behaviour and configuration of the broker.
+          All essential properties that are not set here are automatically
+          given reasonable defaults.
+        '';
+      };
+      extraJavaOptions = mkOption {
+        type = types.separatedString " ";
+        default = "";
+        example = "-Xmx2G -Xms2G -XX:MaxPermSize=512M";
+        description = ''
+          Add extra options here that you want to be sent to the
+          Java runtime when the broker service is started.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.activemq = {
+      description = "ActiveMQ server user";
+      group = "activemq";
+      uid = config.ids.uids.activemq;
+    };
+
+    users.groups.activemq.gid = config.ids.gids.activemq;
+
+    systemd.services.activemq_init = {
+      wantedBy = [ "activemq.service" ];
+      partOf = [ "activemq.service" ];
+      before = [ "activemq.service" ];
+      serviceConfig.Type = "oneshot";
+      script = ''
+        mkdir -p "${cfg.javaProperties."activemq.data"}"
+        chown -R activemq "${cfg.javaProperties."activemq.data"}"
+      '';
+    };
+
+    systemd.services.activemq = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ jre ];
+      serviceConfig.User = "activemq";
+      script = ''
+        source ${activemq}/lib/classpath.env
+        export CLASSPATH=${activemqBroker}/lib:${cfg.configurationDir}:$CLASSPATH
+        exec java \
+          ${concatStringsSep " \\\n" (mapAttrsToList (name: value: "-D${name}=${value}") cfg.javaProperties)} \
+          ${cfg.extraJavaOptions} ActiveMQBroker "${cfg.configurationURI}"
+      '';
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix b/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix
new file mode 100644
index 000000000000..646708e01c48
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/amqp/rabbitmq.nix
@@ -0,0 +1,211 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.rabbitmq;
+
+  inherit (builtins) concatStringsSep;
+
+  config_file_content = lib.generators.toKeyValue {} cfg.configItems;
+  config_file = pkgs.writeText "rabbitmq.conf" config_file_content;
+
+  advanced_config_file = pkgs.writeText "advanced.config" cfg.config;
+
+in {
+  ###### interface
+  options = {
+    services.rabbitmq = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the RabbitMQ server, an Advanced Message
+          Queuing Protocol (AMQP) broker.
+        '';
+      };
+
+      package = mkOption {
+        default = pkgs.rabbitmq-server;
+        type = types.package;
+        defaultText = "pkgs.rabbitmq-server";
+        description = ''
+          Which rabbitmq package to use.
+        '';
+      };
+
+      listenAddress = mkOption {
+        default = "127.0.0.1";
+        example = "";
+        description = ''
+          IP address on which RabbitMQ will listen for AMQP
+          connections.  Set to the empty string to listen on all
+          interfaces.  Note that RabbitMQ creates a user named
+          <literal>guest</literal> with password
+          <literal>guest</literal> by default, so you should delete
+          this user if you intend to allow external access.
+
+          Together with 'port' setting it's mostly an alias for
+          configItems."listeners.tcp.1" and it's left for backwards
+          compatibility with previous version of this module.
+        '';
+        type = types.str;
+      };
+
+      port = mkOption {
+        default = 5672;
+        description = ''
+          Port on which RabbitMQ will listen for AMQP connections.
+        '';
+        type = types.int;
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/rabbitmq";
+        description = ''
+          Data directory for rabbitmq.
+        '';
+      };
+
+      cookie = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Erlang cookie is a string of arbitrary length which must
+          be the same for several nodes to be allowed to communicate.
+          Leave empty to generate automatically.
+        '';
+      };
+
+      configItems = mkOption {
+        default = {};
+        type = types.attrsOf types.str;
+        example = literalExample ''
+          {
+            "auth_backends.1.authn" = "rabbit_auth_backend_ldap";
+            "auth_backends.1.authz" = "rabbit_auth_backend_internal";
+          }
+        '';
+        description = ''
+          Configuration options in RabbitMQ's new config file format,
+          which is a simple key-value format that can not express nested
+          data structures. This is known as the <literal>rabbitmq.conf</literal> file,
+          although outside NixOS that filename may have Erlang syntax, particularly
+          prior to RabbitMQ 3.7.0.
+
+          If you do need to express nested data structures, you can use
+          <literal>config</literal> option. Configuration from <literal>config</literal>
+          will be merged into these options by RabbitMQ at runtime to
+          form the final configuration.
+
+          See https://www.rabbitmq.com/configure.html#config-items
+          For the distinct formats, see https://www.rabbitmq.com/configure.html#config-file-formats
+        '';
+      };
+
+      config = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Verbatim advanced configuration file contents using the Erlang syntax.
+          This is also known as the <literal>advanced.config</literal> file or the old config format.
+
+          <literal>configItems</literal> is preferred whenever possible. However, nested
+          data structures can only be expressed properly using the <literal>config</literal> option.
+
+          The contents of this option will be merged into the <literal>configItems</literal>
+          by RabbitMQ at runtime to form the final configuration.
+
+          See the second table on https://www.rabbitmq.com/configure.html#config-items
+          For the distinct formats, see https://www.rabbitmq.com/configure.html#config-file-formats
+        '';
+      };
+
+      plugins = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        description = "The names of plugins to enable";
+      };
+
+      pluginDirs = mkOption {
+        default = [];
+        type = types.listOf types.path;
+        description = "The list of directories containing external plugins";
+      };
+    };
+  };
+
+
+  ###### implementation
+  config = mkIf cfg.enable {
+
+    # This is needed so we will have 'rabbitmqctl' in our PATH
+    environment.systemPackages = [ cfg.package ];
+
+    services.epmd.enable = true;
+
+    users.users.rabbitmq = {
+      description = "RabbitMQ server user";
+      home = "${cfg.dataDir}";
+      createHome = true;
+      group = "rabbitmq";
+      uid = config.ids.uids.rabbitmq;
+    };
+
+    users.groups.rabbitmq.gid = config.ids.gids.rabbitmq;
+
+    services.rabbitmq.configItems = {
+      "listeners.tcp.1" = mkDefault "${cfg.listenAddress}:${toString cfg.port}";
+    };
+
+    systemd.services.rabbitmq = {
+      description = "RabbitMQ Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "epmd.socket" ];
+      wants = [ "network.target" "epmd.socket" ];
+
+      path = [
+        cfg.package
+        pkgs.coreutils # mkdir/chown/chmod for preStart
+      ];
+
+      environment = {
+        RABBITMQ_MNESIA_BASE = "${cfg.dataDir}/mnesia";
+        RABBITMQ_LOGS = "-";
+        SYS_PREFIX = "";
+        RABBITMQ_CONFIG_FILE = config_file;
+        RABBITMQ_PLUGINS_DIR = concatStringsSep ":" cfg.pluginDirs;
+        RABBITMQ_ENABLED_PLUGINS_FILE = pkgs.writeText "enabled_plugins" ''
+          [ ${concatStringsSep "," cfg.plugins} ].
+        '';
+      } //  optionalAttrs (cfg.config != "") { RABBITMQ_ADVANCED_CONFIG_FILE = advanced_config_file; };
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/sbin/rabbitmq-server";
+        ExecStop = "${cfg.package}/sbin/rabbitmqctl shutdown";
+        User = "rabbitmq";
+        Group = "rabbitmq";
+        LogsDirectory = "rabbitmq";
+        WorkingDirectory = cfg.dataDir;
+        Type = "notify";
+        NotifyAccess = "all";
+        UMask = "0027";
+        LimitNOFILE = "100000";
+        Restart = "on-failure";
+        RestartSec = "10";
+        TimeoutStartSec = "3600";
+      };
+
+      preStart = ''
+        ${optionalString (cfg.cookie != "") ''
+            echo -n ${cfg.cookie} > ${cfg.dataDir}/.erlang.cookie
+            chmod 600 ${cfg.dataDir}/.erlang.cookie
+        ''}
+      '';
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/alsa.nix b/nixpkgs/nixos/modules/services/audio/alsa.nix
new file mode 100644
index 000000000000..3fe76a165401
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/alsa.nix
@@ -0,0 +1,133 @@
+# ALSA sound support.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) alsaUtils;
+
+  pulseaudioEnabled = config.hardware.pulseaudio.enable;
+
+in
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "sound" "enableMediaKeys" ] [ "sound" "mediaKeys" "enable" ])
+  ];
+
+  ###### interface
+
+  options = {
+
+    sound = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable ALSA sound.
+        '';
+      };
+
+      enableOSSEmulation = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable ALSA OSS emulation (with certain cards sound mixing may not work!).
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          defaults.pcm.!card 3
+        '';
+        description = ''
+          Set addition configuration for system-wide alsa.
+        '';
+      };
+
+      mediaKeys = {
+
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to enable volume and capture control with keyboard media keys.
+
+            You want to leave this disabled if you run a desktop environment
+            like KDE, Gnome, Xfce, etc, as those handle such things themselves.
+            You might want to enable this if you run a minimalistic desktop
+            environment or work from bare linux ttys/framebuffers.
+
+            Enabling this will turn on <option>services.actkbd</option>.
+          '';
+        };
+
+        volumeStep = mkOption {
+          type = types.str;
+          default = "1";
+          example = "1%";
+          description = ''
+            The value by which to increment/decrement volume on media keys.
+
+            See amixer(1) for allowed values.
+          '';
+        };
+
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.sound.enable {
+
+    environment.systemPackages = [ alsaUtils ];
+
+    environment.etc = mkIf (!pulseaudioEnabled && config.sound.extraConfig != "")
+      { "asound.conf".text = config.sound.extraConfig; };
+
+    # ALSA provides a udev rule for restoring volume settings.
+    services.udev.packages = [ alsaUtils ];
+
+    boot.kernelModules = optional config.sound.enableOSSEmulation "snd_pcm_oss";
+
+    systemd.services.alsa-store =
+      { description = "Store Sound Card State";
+        wantedBy = [ "multi-user.target" ];
+        unitConfig.RequiresMountsFor = "/var/lib/alsa";
+        unitConfig.ConditionVirtualization = "!systemd-nspawn";
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+          ExecStart = "${pkgs.coreutils}/bin/mkdir -p /var/lib/alsa";
+          ExecStop = "${alsaUtils}/sbin/alsactl store --ignore";
+        };
+      };
+
+    services.actkbd = mkIf config.sound.mediaKeys.enable {
+      enable = true;
+      bindings = [
+        # "Mute" media key
+        { keys = [ 113 ]; events = [ "key" ];       command = "${alsaUtils}/bin/amixer -q set Master toggle"; }
+
+        # "Lower Volume" media key
+        { keys = [ 114 ]; events = [ "key" "rep" ]; command = "${alsaUtils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}- unmute"; }
+
+        # "Raise Volume" media key
+        { keys = [ 115 ]; events = [ "key" "rep" ]; command = "${alsaUtils}/bin/amixer -q set Master ${config.sound.mediaKeys.volumeStep}+ unmute"; }
+
+        # "Mic Mute" media key
+        { keys = [ 190 ]; events = [ "key" ];       command = "${alsaUtils}/bin/amixer -q set Capture toggle"; }
+      ];
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/icecast.nix b/nixpkgs/nixos/modules/services/audio/icecast.nix
new file mode 100644
index 000000000000..6a8a0f9975b3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/icecast.nix
@@ -0,0 +1,130 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.icecast;
+  configFile = pkgs.writeText "icecast.xml" ''
+    <icecast>
+      <hostname>${cfg.hostname}</hostname>
+
+      <authentication>
+        <admin-user>${cfg.admin.user}</admin-user>
+        <admin-password>${cfg.admin.password}</admin-password>
+      </authentication>
+
+      <paths>
+        <logdir>${cfg.logDir}</logdir>
+        <adminroot>${pkgs.icecast}/share/icecast/admin</adminroot>
+        <webroot>${pkgs.icecast}/share/icecast/web</webroot>
+        <alias source="/" dest="/status.xsl"/>
+      </paths>
+
+      <listen-socket>
+        <port>${toString cfg.listen.port}</port>
+        <bind-address>${cfg.listen.address}</bind-address>
+      </listen-socket>   
+
+      <security>
+        <chroot>0</chroot>
+        <changeowner>
+            <user>${cfg.user}</user>
+            <group>${cfg.group}</group>
+        </changeowner>
+      </security>
+
+      ${cfg.extraConf}
+    </icecast>
+  '';
+in {
+
+  ###### interface
+
+  options = {
+
+    services.icecast = {
+
+      enable = mkEnableOption "Icecast server";
+
+      hostname = mkOption {
+        type = types.str;
+        description = "DNS name or IP address that will be used for the stream directory lookups or possibily the playlist generation if a Host header is not provided.";
+        default = config.networking.domain;
+      };
+
+      admin = {
+        user = mkOption {
+          type = types.str;
+          description = "Username used for all administration functions.";
+          default = "admin";
+        };
+
+        password = mkOption {
+          type = types.str;
+          description = "Password used for all administration functions.";
+        };
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        description = "Base directory used for logging.";
+        default = "/var/log/icecast";
+      };
+      
+      listen = {
+        port = mkOption {
+          type = types.int;
+          description = "TCP port that will be used to accept client connections.";
+          default = 8000;
+        };
+
+        address = mkOption {
+          type = types.str;
+          description = "Address Icecast will listen on.";
+          default = "::";
+        };
+      };
+
+      user = mkOption {
+        type = types.str;
+        description = "User privileges for the server.";
+        default = "nobody";
+      };
+
+      group = mkOption {
+        type = types.str;
+        description = "Group privileges for the server.";
+        default = "nogroup";
+      };
+
+      extraConf = mkOption {
+        type = types.lines;
+        description = "icecast.xml content.";
+        default = "";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.icecast = {
+      after = [ "network.target" ];
+      description = "Icecast Network Audio Streaming Server";
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = "mkdir -p ${cfg.logDir} && chown ${cfg.user}:${cfg.group} ${cfg.logDir}";
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.icecast}/bin/icecast -c ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/jack.nix b/nixpkgs/nixos/modules/services/audio/jack.nix
new file mode 100644
index 000000000000..ceff366d0bbb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/jack.nix
@@ -0,0 +1,291 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jack;
+
+  pcmPlugin = cfg.jackd.enable && cfg.alsa.enable;
+  loopback = cfg.jackd.enable && cfg.loopback.enable;
+
+  enable32BitAlsaPlugins = cfg.alsa.support32Bit && pkgs.stdenv.isx86_64 && pkgs.pkgsi686Linux.alsaLib != null;
+
+  umaskNeeded = versionOlder cfg.jackd.package.version "1.9.12";
+  bridgeNeeded = versionAtLeast cfg.jackd.package.version "1.9.12";
+in {
+  options = {
+    services.jack = {
+      jackd = {
+        enable = mkEnableOption ''
+          JACK Audio Connection Kit. You need to add yourself to the "jackaudio" group
+        '';
+
+        package = mkOption {
+          # until jack1 promiscuous mode is fixed
+          internal = true;
+          type = types.package;
+          default = pkgs.jack2;
+          defaultText = "pkgs.jack2";
+          example = literalExample "pkgs.jack1";
+          description = ''
+            The JACK package to use.
+          '';
+        };
+
+        extraOptions = mkOption {
+          type = types.listOf types.str;
+          default = [
+            "-dalsa"
+          ];
+          example = literalExample ''
+            [ "-dalsa" "--device" "hw:1" ];
+          '';
+          description = ''
+            Specifies startup command line arguments to pass to JACK server.
+          '';
+        };
+
+        session = mkOption {
+          type = types.lines;
+          description = ''
+            Commands to run after JACK is started.
+          '';
+        };
+
+      };
+
+      alsa = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Route audio to/from generic ALSA-using applications using ALSA JACK PCM plugin.
+          '';
+        };
+
+        support32Bit = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to support sound for 32-bit ALSA applications on 64-bit system.
+          '';
+        };
+      };
+
+      loopback = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Create ALSA loopback device, instead of using PCM plugin. Has broader
+            application support (things like Steam will work), but may need fine-tuning
+            for concrete hardware.
+          '';
+        };
+
+        index = mkOption {
+          type = types.int;
+          default = 10;
+          description = ''
+            Index of an ALSA loopback device.
+          '';
+        };
+
+        config = mkOption {
+          type = types.lines;
+          description = ''
+            ALSA config for loopback device.
+          '';
+        };
+
+        dmixConfig = mkOption {
+          type = types.lines;
+          default = "";
+          example = ''
+            period_size 2048
+            periods 2
+          '';
+          description = ''
+            For music production software that still doesn't support JACK natively you
+            would like to put buffer/period adjustments here
+            to decrease dmix device latency.
+          '';
+        };
+
+        session = mkOption {
+          type = types.lines;
+          description = ''
+            Additional commands to run to setup loopback device.
+          '';
+        };
+      };
+
+    };
+
+  };
+
+  config = mkMerge [
+
+    (mkIf pcmPlugin {
+      sound.extraConfig = ''
+        pcm_type.jack {
+          libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;
+          ${lib.optionalString enable32BitAlsaPlugins
+          "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_jack.so ;"}
+        }
+        pcm.!default {
+          @func getenv
+          vars [ PCM ]
+          default "plug:jack"
+        }
+      '';
+    })
+
+    (mkIf loopback {
+      boot.kernelModules = [ "snd-aloop" ];
+      boot.kernelParams = [ "snd-aloop.index=${toString cfg.loopback.index}" ];
+      sound.extraConfig = cfg.loopback.config;
+    })
+
+    (mkIf cfg.jackd.enable {
+      services.jack.jackd.session = ''
+        ${lib.optionalString bridgeNeeded "${pkgs.a2jmidid}/bin/a2jmidid -e &"}
+      '';
+      # https://alsa.opensrc.org/Jack_and_Loopback_device_as_Alsa-to-Jack_bridge#id06
+      services.jack.loopback.config = ''
+        pcm.loophw00 {
+          type hw
+          card ${toString cfg.loopback.index}
+          device 0
+          subdevice 0
+        }
+        pcm.amix {
+          type dmix
+          ipc_key 219345
+          slave {
+            pcm loophw00
+            ${cfg.loopback.dmixConfig}
+          }
+        }
+        pcm.asoftvol {
+          type softvol
+          slave.pcm "amix"
+          control { name Master }
+        }
+        pcm.cloop {
+          type hw
+          card ${toString cfg.loopback.index}
+          device 1
+          subdevice 0
+          format S32_LE
+        }
+        pcm.loophw01 {
+          type hw
+          card ${toString cfg.loopback.index}
+          device 0
+          subdevice 1
+        }
+        pcm.ploop {
+          type hw
+          card ${toString cfg.loopback.index}
+          device 1
+          subdevice 1
+          format S32_LE
+        }
+        pcm.aduplex {
+          type asym
+          playback.pcm "asoftvol"
+          capture.pcm "loophw01"
+        }
+        pcm.!default {
+          type plug
+          slave.pcm aduplex
+        }
+      '';
+      services.jack.loopback.session = ''
+        alsa_in -j cloop -dcloop &
+        alsa_out -j ploop -dploop &
+        while [ "$(jack_lsp cloop)" == "" ] || [ "$(jack_lsp ploop)" == "" ]; do sleep 1; done
+        jack_connect cloop:capture_1 system:playback_1
+        jack_connect cloop:capture_2 system:playback_2
+        jack_connect system:capture_1 ploop:playback_1
+        jack_connect system:capture_2 ploop:playback_2
+      '';
+
+      assertions = [
+        {
+          assertion = !(cfg.alsa.enable && cfg.loopback.enable);
+          message = "For JACK both alsa and loopback options shouldn't be used at the same time.";
+        }
+      ];
+
+      users.users.jackaudio = {
+        group = "jackaudio";
+        extraGroups = [ "audio" ];
+        description = "JACK Audio system service user";
+        isSystemUser = true;
+      };
+      # http://jackaudio.org/faq/linux_rt_config.html
+      security.pam.loginLimits = [
+        { domain = "@jackaudio"; type = "-"; item = "rtprio"; value = "99"; }
+        { domain = "@jackaudio"; type = "-"; item = "memlock"; value = "unlimited"; }
+      ];
+      users.groups.jackaudio = {};
+
+      environment = {
+        systemPackages = [ cfg.jackd.package ];
+        etc."alsa/conf.d/50-jack.conf".source = "${pkgs.alsaPlugins}/etc/alsa/conf.d/50-jack.conf";
+        variables.JACK_PROMISCUOUS_SERVER = "jackaudio";
+      };
+
+      services.udev.extraRules = ''
+        ACTION=="add", SUBSYSTEM=="sound", ATTRS{id}!="Loopback", TAG+="systemd", ENV{SYSTEMD_WANTS}="jack.service"
+      '';
+
+      systemd.services.jack = {
+        description = "JACK Audio Connection Kit";
+        serviceConfig = {
+          User = "jackaudio";
+          ExecStart = "${cfg.jackd.package}/bin/jackd ${lib.escapeShellArgs cfg.jackd.extraOptions}";
+          LimitRTPRIO = 99;
+          LimitMEMLOCK = "infinity";
+        } // optionalAttrs umaskNeeded {
+          UMask = "007";
+        };
+        path = [ cfg.jackd.package ];
+        environment = {
+          JACK_PROMISCUOUS_SERVER = "jackaudio";
+          JACK_NO_AUDIO_RESERVATION = "1";
+        };
+        restartIfChanged = false;
+      };
+      systemd.services.jack-session = {
+        description = "JACK session";
+        script = ''
+          jack_wait -w
+          ${cfg.jackd.session}
+          ${lib.optionalString cfg.loopback.enable cfg.loopback.session}
+        '';
+        serviceConfig = {
+          RemainAfterExit = true;
+          User = "jackaudio";
+          StateDirectory = "jack";
+          LimitRTPRIO = 99;
+          LimitMEMLOCK = "infinity";
+        };
+        path = [ cfg.jackd.package ];
+        environment = {
+          JACK_PROMISCUOUS_SERVER = "jackaudio";
+          HOME = "/var/lib/jack";
+        };
+        wantedBy = [ "jack.service" ];
+        partOf = [ "jack.service" ];
+        after = [ "jack.service" ];
+        restartIfChanged = false;
+      };
+    })
+
+  ];
+
+  meta.maintainers = [ maintainers.gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/audio/liquidsoap.nix b/nixpkgs/nixos/modules/services/audio/liquidsoap.nix
new file mode 100644
index 000000000000..3a047d10a631
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/liquidsoap.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  streams = builtins.attrNames config.services.liquidsoap.streams;
+
+  streamService =
+    name:
+    let stream = builtins.getAttr name config.services.liquidsoap.streams; in
+    { inherit name;
+      value = {
+        after = [ "network-online.target" "sound.target" ];
+        description = "${name} liquidsoap stream";
+        wantedBy = [ "multi-user.target" ];
+        path = [ pkgs.wget ];
+        serviceConfig = {
+          ExecStart = "${pkgs.liquidsoap}/bin/liquidsoap ${stream}";
+          User = "liquidsoap";
+          LogsDirectory = "liquidsoap";
+        };
+      };
+    };
+in
+{
+
+  ##### interface
+
+  options = {
+
+    services.liquidsoap.streams = mkOption {
+
+      description =
+        ''
+          Set of Liquidsoap streams to start,
+          one systemd service per stream.
+        '';
+
+      default = {};
+
+      example = {
+        myStream1 = literalExample "\"/etc/liquidsoap/myStream1.liq\"";
+        myStream2 = literalExample "./myStream2.liq";
+        myStream3 = literalExample "\"out(playlist(\\\"/srv/music/\\\"))\"";
+      };
+
+      type = types.attrsOf (types.either types.path types.str);
+    };
+
+  };
+  ##### implementation
+
+  config = mkIf (builtins.length streams != 0) {
+
+    users.users.liquidsoap = {
+      uid = config.ids.uids.liquidsoap;
+      group = "liquidsoap";
+      extraGroups = [ "audio" ];
+      description = "Liquidsoap streaming user";
+      home = "/var/lib/liquidsoap";
+      createHome = true;
+    };
+
+    users.groups.liquidsoap.gid = config.ids.gids.liquidsoap;
+
+    systemd.services = builtins.listToAttrs ( map streamService streams );
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/mopidy.nix b/nixpkgs/nixos/modules/services/audio/mopidy.nix
new file mode 100644
index 000000000000..6fd7eae5b892
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/mopidy.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with pkgs;
+with lib;
+
+let
+  uid = config.ids.uids.mopidy;
+  gid = config.ids.gids.mopidy;
+  cfg = config.services.mopidy;
+
+  mopidyConf = writeText "mopidy.conf" cfg.configuration;
+
+  mopidyEnv = buildEnv {
+    name = "mopidy-with-extensions-${mopidy.version}";
+    paths = closePropagation cfg.extensionPackages;
+    pathsToLink = [ "/${mopidyPackages.python.sitePackages}" ];
+    buildInputs = [ makeWrapper ];
+    postBuild = ''
+      makeWrapper ${mopidy}/bin/mopidy $out/bin/mopidy \
+        --prefix PYTHONPATH : $out/${mopidyPackages.python.sitePackages}
+    '';
+  };
+in {
+
+  options = {
+
+    services.mopidy = {
+
+      enable = mkEnableOption "Mopidy, a music player daemon";
+
+      dataDir = mkOption {
+        default = "/var/lib/mopidy";
+        type = types.str;
+        description = ''
+          The directory where Mopidy stores its state.
+        '';
+      };
+
+      extensionPackages = mkOption {
+        default = [];
+        type = types.listOf types.package;
+        example = literalExample "[ pkgs.mopidy-spotify ]";
+        description = ''
+          Mopidy extensions that should be loaded by the service.
+        '';
+      };
+
+      configuration = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          The configuration that Mopidy should use.
+        '';
+      };
+
+      extraConfigFiles = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        description = ''
+          Extra config file read by Mopidy when the service starts.
+          Later files in the list overrides earlier configuration.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - mopidy mopidy - -"
+    ];
+
+    systemd.services.mopidy = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "sound.target" ];
+      description = "mopidy music player daemon";
+      serviceConfig = {
+        ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)}";
+        User = "mopidy";
+      };
+    };
+
+    systemd.services.mopidy-scan = {
+      description = "mopidy local files scanner";
+      serviceConfig = {
+        ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)} local scan";
+        User = "mopidy";
+        Type = "oneshot";
+      };
+    };
+
+    users.users.mopidy = {
+      inherit uid;
+      group = "mopidy";
+      extraGroups = [ "audio" ];
+      description = "Mopidy daemon user";
+      home = cfg.dataDir;
+    };
+
+    users.groups.mopidy.gid = gid;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/mpd.nix b/nixpkgs/nixos/modules/services/audio/mpd.nix
new file mode 100644
index 000000000000..f4eb4a265a46
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/mpd.nix
@@ -0,0 +1,200 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  name = "mpd";
+
+  uid = config.ids.uids.mpd;
+  gid = config.ids.gids.mpd;
+  cfg = config.services.mpd;
+
+  mpdConf = pkgs.writeText "mpd.conf" ''
+    music_directory     "${cfg.musicDirectory}"
+    playlist_directory  "${cfg.playlistDirectory}"
+    ${lib.optionalString (cfg.dbFile != null) ''
+      db_file             "${cfg.dbFile}"
+    ''}
+    state_file          "${cfg.dataDir}/state"
+    sticker_file        "${cfg.dataDir}/sticker.sql"
+
+    ${optionalString (cfg.network.listenAddress != "any") ''bind_to_address "${cfg.network.listenAddress}"''}
+    ${optionalString (cfg.network.port != 6600)  ''port "${toString cfg.network.port}"''}
+
+    ${cfg.extraConfig}
+  '';
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.mpd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable MPD, the music player daemon.
+        '';
+      };
+
+      startWhenNeeded = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If set, <command>mpd</command> is socket-activated; that
+          is, instead of having it permanently running as a daemon,
+          systemd will start it on the first incoming connection.
+        '';
+      };
+
+      musicDirectory = mkOption {
+        type = with types; either path (strMatching "(http|https|nfs|smb)://.+");
+        default = "${cfg.dataDir}/music";
+        defaultText = ''''${dataDir}/music'';
+        description = ''
+          The directory or NFS/SMB network share where mpd reads music from.
+        '';
+      };
+
+      playlistDirectory = mkOption {
+        type = types.path;
+        default = "${cfg.dataDir}/playlists";
+        defaultText = ''''${dataDir}/playlists'';
+        description = ''
+          The directory where mpd stores playlists.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra directives added to to the end of MPD's configuration file,
+          mpd.conf. Basic configuration like file location and uid/gid
+          is added automatically to the beginning of the file. For available
+          options see <literal>man 5 mpd.conf</literal>'.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/${name}";
+        description = ''
+          The directory where MPD stores its state, tag cache,
+          playlists etc.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = name;
+        description = "User account under which MPD runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = name;
+        description = "Group account under which MPD runs.";
+      };
+
+      network = {
+
+        listenAddress = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          example = "any";
+          description = ''
+            The address for the daemon to listen on.
+            Use <literal>any</literal> to listen on all addresses.
+          '';
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 6600;
+          description = ''
+            This setting is the TCP port that is desired for the daemon to get assigned
+            to.
+          '';
+        };
+
+      };
+
+      dbFile = mkOption {
+        type = types.nullOr types.str;
+        default = "${cfg.dataDir}/tag_cache";
+        defaultText = ''''${dataDir}/tag_cache'';
+        description = ''
+          The path to MPD's database. If set to <literal>null</literal> the
+          parameter is omitted from the configuration.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.sockets.mpd = mkIf cfg.startWhenNeeded {
+      description = "Music Player Daemon Socket";
+      wantedBy = [ "sockets.target" ];
+      listenStreams = [
+        "${optionalString (cfg.network.listenAddress != "any") "${cfg.network.listenAddress}:"}${toString cfg.network.port}"
+      ];
+      socketConfig = {
+        Backlog = 5;
+        KeepAlive = true;
+        PassCredentials = true;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.playlistDirectory}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.mpd = {
+      after = [ "network.target" "sound.target" ];
+      description = "Music Player Daemon";
+      wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
+
+      serviceConfig = {
+        User = "${cfg.user}";
+        ExecStart = "${pkgs.mpd}/bin/mpd --no-daemon ${mpdConf}";
+        Type = "notify";
+        LimitRTPRIO = 50;
+        LimitRTTIME = "infinity";
+        ProtectSystem = true;
+        NoNewPrivileges = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectKernelModules = true;
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
+        RestrictNamespaces = true;
+        Restart = "always";
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == name) {
+      ${name} = {
+        inherit uid;
+        group = cfg.group;
+        extraGroups = [ "audio" ];
+        description = "Music Player Daemon user";
+        home = "${cfg.dataDir}";
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == name) {
+      ${name}.gid = gid;
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/roon-server.nix b/nixpkgs/nixos/modules/services/audio/roon-server.nix
new file mode 100644
index 000000000000..6aed485638cc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/roon-server.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  name = "roon-server";
+  cfg = config.services.roon-server;
+in {
+  options = {
+    services.roon-server = {
+      enable = mkEnableOption "Roon Server";
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the server.
+
+          UDP: 9003
+          TCP: 9100 - 9200
+        '';
+      };
+      user = mkOption {
+        type = types.str;
+        default = "roon-server";
+        description = ''
+          User to run the Roon Server as.
+        '';
+      };
+      group = mkOption {
+        type = types.str;
+        default = "roon-server";
+        description = ''
+          Group to run the Roon Server as.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.roon-server = {
+      after = [ "network.target" ];
+      description = "Roon Server";
+      wantedBy = [ "multi-user.target" ];
+
+      environment.ROON_DATAROOT = "/var/lib/${name}";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.roon-server}/opt/start.sh";
+        LimitNOFILE = 8192;
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectory = name;
+      };
+    };
+    
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPortRanges = [
+        { from = 9100; to = 9200; }
+      ];
+      allowedUDPPorts = [ 9003 ];
+    };
+
+    
+    users.groups.${cfg.group} = {};
+    users.users.${cfg.user} =
+      if cfg.user == "roon-server" then {
+        isSystemUser = true;
+        description = "Roon Server user";
+        group = cfg.group;
+        extraGroups = [ "audio" ];
+      }
+      else {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/audio/slimserver.nix b/nixpkgs/nixos/modules/services/audio/slimserver.nix
new file mode 100644
index 000000000000..8f94a2b49404
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/slimserver.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.slimserver;
+
+in {
+  options = {
+
+    services.slimserver = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable slimserver.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.slimserver;
+        defaultText = "pkgs.slimserver";
+        description = "Slimserver package to use.";
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/slimserver";
+        description = ''
+          The directory where slimserver stores its state, tag cache,
+          playlists etc.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - slimserver slimserver - -"
+    ];
+
+    systemd.services.slimserver = {
+      after = [ "network.target" ];
+      description = "Slim Server for Logitech Squeezebox Players";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User = "slimserver";
+        # Issue 40589: Disable broken image/video support (audio still works!)
+        ExecStart = "${cfg.package}/slimserver.pl --logdir ${cfg.dataDir}/logs --prefsdir ${cfg.dataDir}/prefs --cachedir ${cfg.dataDir}/cache --noimage --novideo";
+      };
+    };
+
+    users = {
+      users.slimserver = {
+        description = "Slimserver daemon user";
+        home = cfg.dataDir;
+        group = "slimserver";
+      };
+      groups.slimserver = {};
+    };
+  };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/audio/snapserver.nix b/nixpkgs/nixos/modules/services/audio/snapserver.nix
new file mode 100644
index 000000000000..b0b9264e8166
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/snapserver.nix
@@ -0,0 +1,216 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  name = "snapserver";
+
+  cfg = config.services.snapserver;
+
+  # Using types.nullOr to inherit upstream defaults.
+  sampleFormat = mkOption {
+    type = with types; nullOr str;
+    default = null;
+    description = ''
+      Default sample format.
+    '';
+    example = "48000:16:2";
+  };
+
+  codec = mkOption {
+    type = with types; nullOr str;
+    default = null;
+    description = ''
+      Default audio compression method.
+    '';
+    example = "flac";
+  };
+
+  streamToOption = name: opt:
+    let
+      os = val:
+        optionalString (val != null) "${val}";
+      os' = prefixx: val:
+        optionalString (val != null) (prefixx + "${val}");
+      flatten = key: value:
+        "&${key}=${value}";
+    in
+      "-s ${opt.type}://" + os opt.location + "?" + os' "name=" name
+        + concatStrings (mapAttrsToList flatten opt.query);
+
+  optionalNull = val: ret:
+    optional (val != null) ret;
+
+  optionString = concatStringsSep " " (mapAttrsToList streamToOption cfg.streams
+             ++ ["-p ${toString cfg.port}"]
+             ++ ["--controlPort ${toString cfg.controlPort}"]
+             ++ optionalNull cfg.sampleFormat "--sampleFormat ${cfg.sampleFormat}"
+             ++ optionalNull cfg.codec "-c ${cfg.codec}"
+             ++ optionalNull cfg.streamBuffer "--streamBuffer ${cfg.streamBuffer}"
+             ++ optionalNull cfg.buffer "-b ${cfg.buffer}"
+             ++ optional cfg.sendToMuted "--sendToMuted");
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.snapserver = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable snapserver.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 1704;
+        description = ''
+          The port that snapclients can connect to.
+        '';
+      };
+
+      controlPort = mkOption {
+        type = types.port;
+        default = 1705;
+        description = ''
+          The port for control connections (JSON-RPC).
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to automatically open the specified ports in the firewall.
+        '';
+      };
+
+      inherit sampleFormat;
+      inherit codec;
+
+      streams = mkOption {
+        type = with types; attrsOf (submodule {
+          options = {
+            location = mkOption {
+              type = types.path;
+              description = ''
+                The location of the pipe.
+              '';
+            };
+            type = mkOption {
+              type = types.enum [ "pipe" "file" "process" "spotify" "airplay" ];
+              default = "pipe";
+              description = ''
+                The type of input stream.
+              '';
+            };
+            query = mkOption {
+              type = attrsOf str;
+              default = {};
+              description = ''
+                Key-value pairs that convey additional parameters about a stream.
+              '';
+              example = literalExample ''
+                # for type == "pipe":
+                {
+                  mode = "listen";
+                };
+                # for type == "process":
+                {
+                  params = "--param1 --param2";
+                  logStderr = "true";
+                };
+              '';
+            };
+            inherit sampleFormat;
+            inherit codec;
+          };
+        });
+        default = { default = {}; };
+        description = ''
+          The definition for an input source.
+        '';
+        example = literalExample ''
+          {
+            mpd = {
+              type = "pipe";
+              location = "/run/snapserver/mpd";
+              sampleFormat = "48000:16:2";
+              codec = "pcm";
+            };
+          };
+        '';
+      };
+
+      streamBuffer = mkOption {
+        type = with types; nullOr int;
+        default = null;
+        description = ''
+          Stream read (input) buffer in ms.
+        '';
+        example = 20;
+      };
+
+      buffer = mkOption {
+        type = with types; nullOr int;
+        default = null;
+        description = ''
+          Network buffer in ms.
+        '';
+        example = 1000;
+      };
+
+      sendToMuted = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Send audio to muted clients.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.snapserver = {
+      after = [ "network.target" ];
+      description = "Snapserver";
+      wantedBy = [ "multi-user.target" ];
+      before = [ "mpd.service" "mopidy.service" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.snapcast}/bin/snapserver --daemon ${optionString}";
+        Type = "forking";
+        LimitRTPRIO = 50;
+        LimitRTTIME = "infinity";
+        NoNewPrivileges = true;
+        PIDFile = "/run/${name}/pid";
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectKernelModules = true;
+        RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
+        RestrictNamespaces = true;
+        RuntimeDirectory = name;
+        StateDirectory = name;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = optionals cfg.openFirewall [ cfg.port cfg.controlPort ];
+  };
+
+  meta = {
+    maintainers = with maintainers; [ tobim ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/spotifyd.nix b/nixpkgs/nixos/modules/services/audio/spotifyd.nix
new file mode 100644
index 000000000000..4b74e7532795
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/spotifyd.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.spotifyd;
+  spotifydConf = pkgs.writeText "spotifyd.conf" cfg.config;
+in
+{
+  options = {
+    services.spotifyd = {
+      enable = mkEnableOption "spotifyd, a Spotify playing daemon";
+
+      config = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Configuration for Spotifyd. For syntax and directives, see
+          https://github.com/Spotifyd/spotifyd#Configuration.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.spotifyd = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" "sound.target" ];
+      description = "spotifyd, a Spotify playing daemon";
+      serviceConfig = {
+        ExecStart = "${pkgs.spotifyd}/bin/spotifyd --no-daemon --cache-path /var/cache/spotifyd --config-path ${spotifydConf}";
+        Restart = "always";
+        RestartSec = 12;
+        DynamicUser = true;
+        CacheDirectory = "spotifyd";
+        SupplementaryGroups = ["audio"];
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.anderslundstedt ];
+}
diff --git a/nixpkgs/nixos/modules/services/audio/squeezelite.nix b/nixpkgs/nixos/modules/services/audio/squeezelite.nix
new file mode 100644
index 000000000000..05506f5bcc7a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/squeezelite.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  dataDir = "/var/lib/squeezelite";
+  cfg = config.services.squeezelite;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.squeezelite= {
+
+      enable = mkEnableOption "Squeezelite, a software Squeezebox emulator";
+
+      extraArguments = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Additional command line arguments to pass to Squeezelite.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.squeezelite= {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "sound.target" ];
+      description = "Software Squeezebox emulator";
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.squeezelite}/bin/squeezelite -N ${dataDir}/player-name ${cfg.extraArguments}";
+        StateDirectory = builtins.baseNameOf dataDir;
+        SupplementaryGroups = "audio";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/audio/ympd.nix b/nixpkgs/nixos/modules/services/audio/ympd.nix
new file mode 100644
index 000000000000..551bd941fe68
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/audio/ympd.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ympd;
+in {
+
+  ###### interface
+
+  options = {
+
+    services.ympd = {
+
+      enable = mkEnableOption "ympd, the MPD Web GUI";
+
+      webPort = mkOption {
+        type = types.either types.str types.port; # string for backwards compat
+        default = "8080";
+        description = "The port where ympd's web interface will be available.";
+        example = "ssl://8080:/path/to/ssl-private-key.pem";
+      };
+
+      mpd = {
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = "The host where MPD is listening.";
+          example = "localhost";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = config.services.mpd.network.port;
+          description = "The port where MPD is listening.";
+          example = 6600;
+        };
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.ympd = {
+      description = "Standalone MPD Web GUI written in C";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${pkgs.ympd}/bin/ympd --host ${cfg.mpd.host} --port ${toString cfg.mpd.port} --webport ${toString cfg.webPort} --user nobody";
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix b/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix
new file mode 100644
index 000000000000..e3a8d1f79934
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/automysqlbackup.nix
@@ -0,0 +1,118 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib) concatMapStringsSep concatStringsSep isInt isList literalExample;
+  inherit (lib) mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkOption optional types;
+
+  cfg = config.services.automysqlbackup;
+  pkg = pkgs.automysqlbackup;
+  user = "automysqlbackup";
+  group = "automysqlbackup";
+
+  toStr = val:
+    if isList val then "( ${concatMapStringsSep " " (val: "'${val}'") val} )"
+    else if isInt val then toString val
+    else if true == val then "'yes'"
+    else if false == val then "'no'"
+    else "'${toString val}'";
+
+  configFile = pkgs.writeText "automysqlbackup.conf" ''
+    #version=${pkg.version}
+    # DONT'T REMOVE THE PREVIOUS VERSION LINE!
+    #
+    ${concatStringsSep "\n" (mapAttrsToList (name: value: "CONFIG_${name}=${toStr value}") cfg.config)}
+  '';
+
+in
+{
+  # interface
+  options = {
+    services.automysqlbackup = {
+
+      enable = mkEnableOption "AutoMySQLBackup";
+
+      calendar = mkOption {
+        type = types.str;
+        default = "01:15:00";
+        description = ''
+          Configured when to run the backup service systemd unit (DayOfWeek Year-Month-Day Hour:Minute:Second).
+        '';
+      };
+
+      config = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
+        default = {};
+        description = ''
+          automysqlbackup configuration. Refer to
+          <filename>''${pkgs.automysqlbackup}/etc/automysqlbackup.conf</filename>
+          for details on supported values.
+        '';
+        example = literalExample ''
+          {
+            db_names = [ "nextcloud" "matomo" ];
+            table_exclude = [ "nextcloud.oc_users" "nextcloud.oc_whats_new" ];
+            mailcontent = "log";
+            mail_address = "admin@example.org";
+          }
+        '';
+      };
+
+    };
+  };
+
+  # implementation
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = !config.services.mysqlBackup.enable;
+        message = "Please choose one of services.mysqlBackup or services.automysqlbackup.";
+      }
+    ];
+
+    services.automysqlbackup.config = mapAttrs (name: mkDefault) {
+      mysql_dump_username = user;
+      mysql_dump_host = "localhost";
+      backup_dir = "/var/backup/mysql";
+      db_exclude = [ "information_schema" "performance_schema" ];
+      mailcontent = "stdout";
+      mysql_dump_single_transaction = true;
+    };
+
+    systemd.timers.automysqlbackup = {
+      description = "automysqlbackup timer";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = cfg.calendar;
+        AccuracySec = "5m";
+      };
+    };
+
+    systemd.services.automysqlbackup = {
+      description = "automysqlbackup service";
+      serviceConfig = {
+        User = user;
+        Group = group;
+        ExecStart = "${pkg}/bin/automysqlbackup ${configFile}";
+      };
+    };
+
+    environment.systemPackages = [ pkg ];
+
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
+    users.groups.${group} = { };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.config.backup_dir}' 0750 ${user} ${group} - -"
+    ];
+
+    services.mysql.ensureUsers = optional (config.services.mysql.enable && cfg.config.mysql_dump_host == "localhost") {
+      name = user;
+      ensurePermissions = { "*.*" = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES"; };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/bacula.nix b/nixpkgs/nixos/modules/services/backup/bacula.nix
new file mode 100644
index 000000000000..cef304734aee
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/bacula.nix
@@ -0,0 +1,556 @@
+{ config, lib, pkgs, ... }:
+
+# TODO: test configuration when building nixexpr (use -t parameter)
+# TODO: support sqlite3 (it's deprecate?) and mysql
+
+with lib;
+
+let
+  libDir = "/var/lib/bacula";
+
+  fd_cfg = config.services.bacula-fd;
+  fd_conf = pkgs.writeText "bacula-fd.conf"
+    ''
+      Client {
+        Name = "${fd_cfg.name}";
+        FDPort = ${toString fd_cfg.port};
+        WorkingDirectory = "${libDir}";
+        Pid Directory = "/run";
+        ${fd_cfg.extraClientConfig}
+      }
+     
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+      Director {
+        Name = "${name}";
+        Password = "${value.password}";
+        Monitor = "${value.monitor}";
+      }
+      '') fd_cfg.director)}
+     
+      Messages {
+        Name = Standard;
+        syslog = all, !skipped, !restored
+        ${fd_cfg.extraMessagesConfig}
+      }
+    '';
+
+  sd_cfg = config.services.bacula-sd;
+  sd_conf = pkgs.writeText "bacula-sd.conf" 
+    ''
+      Storage {
+        Name = "${sd_cfg.name}";
+        SDPort = ${toString sd_cfg.port};
+        WorkingDirectory = "${libDir}";
+        Pid Directory = "/run";
+        ${sd_cfg.extraStorageConfig}
+      }
+
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+      Autochanger {
+        Name = "${name}";
+        Device = ${concatStringsSep ", " (map (a: "\"${a}\"") value.devices)};
+        Changer Device =  "${value.changerDevice}";
+        Changer Command = "${value.changerCommand}";
+        ${value.extraAutochangerConfig}
+      }
+      '') sd_cfg.autochanger)}
+
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+      Device {
+        Name = "${name}";
+        Archive Device = "${value.archiveDevice}";
+        Media Type = "${value.mediaType}";
+        ${value.extraDeviceConfig}
+      }
+      '') sd_cfg.device)}
+
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
+      Director {
+        Name = "${name}";
+        Password = "${value.password}";
+        Monitor = "${value.monitor}";
+      }
+      '') sd_cfg.director)}
+
+      Messages {
+        Name = Standard;
+        syslog = all, !skipped, !restored
+        ${sd_cfg.extraMessagesConfig}
+      }
+    '';
+
+  dir_cfg = config.services.bacula-dir;
+  dir_conf = pkgs.writeText "bacula-dir.conf" 
+    ''
+    Director {
+      Name = "${dir_cfg.name}";
+      Password = "${dir_cfg.password}";
+      DirPort = ${toString dir_cfg.port};
+      Working Directory = "${libDir}";
+      Pid Directory = "/run/";
+      QueryFile = "${pkgs.bacula}/etc/query.sql";
+      ${dir_cfg.extraDirectorConfig}
+    }
+
+    Catalog {
+      Name = "PostgreSQL";
+      dbname = "bacula";
+      user = "bacula";
+    }
+
+    Messages {
+      Name = Standard;
+      syslog = all, !skipped, !restored
+      ${dir_cfg.extraMessagesConfig}
+    }
+
+    ${dir_cfg.extraConfig}
+    '';
+
+  directorOptions = {...}:
+  {
+    options = {
+      password = mkOption {
+        # TODO: required?
+        description = ''
+          Specifies the password that must be supplied for the default Bacula
+          Console to be authorized. The same password must appear in the
+          Director resource of the Console configuration file. For added
+          security, the password is never passed across the network but instead
+          a challenge response hash code created with the password. This
+          directive is required. If you have either /dev/random or bc on your
+          machine, Bacula will generate a random password during the
+          configuration process, otherwise it will be left blank and you must
+          manually supply it.
+
+          The password is plain text. It is not generated through any special
+          process but as noted above, it is better to use random text for
+          security reasons. 
+        '';
+      };
+      
+      monitor = mkOption {
+        default = "no";
+        example = "yes";
+        description = ''
+          If Monitor is set to <literal>no</literal>, this director will have
+          full access to this Storage daemon. If Monitor is set to
+          <literal>yes</literal>, this director will only be able to fetch the
+          current status of this Storage daemon.
+
+          Please note that if this director is being used by a Monitor, we
+          highly recommend to set this directive to yes to avoid serious
+          security problems. 
+        '';
+      };
+    };
+  };
+
+  autochangerOptions = {...}:
+  {
+    options = {
+      changerDevice = mkOption {
+        description = ''
+          The specified name-string must be the generic SCSI device name of the
+          autochanger that corresponds to the normal read/write Archive Device
+          specified in the Device resource. This generic SCSI device name
+          should be specified if you have an autochanger or if you have a
+          standard tape drive and want to use the Alert Command (see below).
+          For example, on Linux systems, for an Archive Device name of
+          <literal>/dev/nst0</literal>, you would specify
+          <literal>/dev/sg0</literal> for the Changer Device name.  Depending
+          on your exact configuration, and the number of autochangers or the
+          type of autochanger, what you specify here can vary. This directive
+          is optional. See the Using AutochangersAutochangersChapter chapter of
+          this manual for more details of using this and the following
+          autochanger directives.         
+          '';
+      };
+
+      changerCommand = mkOption {
+        description = ''
+          The name-string specifies an external program to be called that will
+          automatically change volumes as required by Bacula. Normally, this
+          directive will be specified only in the AutoChanger resource, which
+          is then used for all devices. However, you may also specify the
+          different Changer Command in each Device resource. Most frequently,
+          you will specify the Bacula supplied mtx-changer script as follows:
+
+          <literal>"/path/mtx-changer %c %o %S %a %d"</literal>
+
+          and you will install the mtx on your system (found in the depkgs
+          release). An example of this command is in the default bacula-sd.conf
+          file. For more details on the substitution characters that may be
+          specified to configure your autochanger please see the
+          AutochangersAutochangersChapter chapter of this manual. For FreeBSD
+          users, you might want to see one of the several chio scripts in
+          examples/autochangers.
+          '';
+        default = "/etc/bacula/mtx-changer %c %o %S %a %d";
+      };
+
+      devices = mkOption {
+        description = ''
+        '';
+      };
+
+      extraAutochangerConfig = mkOption {
+        default = "";
+        description = ''
+          Extra configuration to be passed in Autochanger directive.
+        '';
+        example = ''
+   
+        '';
+      };
+    };
+  };
+
+
+  deviceOptions = {...}:
+  {
+    options = {
+      archiveDevice = mkOption {
+        # TODO: required?
+        description = ''
+          The specified name-string gives the system file name of the storage
+          device managed by this storage daemon. This will usually be the
+          device file name of a removable storage device (tape drive), for
+          example <literal>/dev/nst0</literal> or
+          <literal>/dev/rmt/0mbn</literal>. For a DVD-writer, it will be for
+          example <literal>/dev/hdc</literal>. It may also be a directory name
+          if you are archiving to disk storage. In this case, you must supply
+          the full absolute path to the directory. When specifying a tape
+          device, it is preferable that the "non-rewind" variant of the device
+          file name be given. 
+        '';
+      };
+
+      mediaType = mkOption {
+        # TODO: required?
+        description = ''
+          The specified name-string names the type of media supported by this
+          device, for example, <literal>DLT7000</literal>. Media type names are
+          arbitrary in that you set them to anything you want, but they must be
+          known to the volume database to keep track of which storage daemons
+          can read which volumes. In general, each different storage type
+          should have a unique Media Type associated with it. The same
+          name-string must appear in the appropriate Storage resource
+          definition in the Director's configuration file.
+
+          Even though the names you assign are arbitrary (i.e. you choose the
+          name you want), you should take care in specifying them because the
+          Media Type is used to determine which storage device Bacula will
+          select during restore. Thus you should probably use the same Media
+          Type specification for all drives where the Media can be freely
+          interchanged. This is not generally an issue if you have a single
+          Storage daemon, but it is with multiple Storage daemons, especially
+          if they have incompatible media.
+
+          For example, if you specify a Media Type of <literal>DDS-4</literal>
+          then during the restore, Bacula will be able to choose any Storage
+          Daemon that handles <literal>DDS-4</literal>. If you have an
+          autochanger, you might want to name the Media Type in a way that is
+          unique to the autochanger, unless you wish to possibly use the
+          Volumes in other drives. You should also ensure to have unique Media
+          Type names if the Media is not compatible between drives. This
+          specification is required for all devices.
+
+          In addition, if you are using disk storage, each Device resource will
+          generally have a different mount point or directory. In order for
+          Bacula to select the correct Device resource, each one must have a
+          unique Media Type.
+        '';
+      };
+
+      extraDeviceConfig = mkOption {
+        default = "";
+        description = ''
+          Extra configuration to be passed in Device directive.
+        '';
+        example = ''
+          LabelMedia = yes
+          Random Access = no
+          AutomaticMount = no
+          RemovableMedia = no
+          MaximumOpenWait = 60
+          AlwaysOpen = no
+        '';
+      };
+    };
+  };
+
+in {
+  options = {
+    services.bacula-fd = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Bacula File Daemon.
+        '';
+      };
+ 
+      name = mkOption {
+        default = "${config.networking.hostName}-fd";
+        description = ''
+          The client name that must be used by the Director when connecting.
+          Generally, it is a good idea to use a name related to the machine so
+          that error messages can be easily identified if you have multiple
+          Clients. This directive is required.
+        '';
+      };
+ 
+      port = mkOption {
+        default = 9102;
+        type = types.int;
+        description = ''
+          This specifies the port number on which the Client listens for
+          Director connections. It must agree with the FDPort specified in
+          the Client resource of the Director's configuration file.
+        '';
+      };
+ 
+      director = mkOption {
+        default = {};
+        description = ''
+          This option defines director resources in Bacula File Daemon.
+        '';
+        type = with types; attrsOf (submodule directorOptions);
+      };
+
+      extraClientConfig = mkOption {
+        default = "";
+        description = ''
+          Extra configuration to be passed in Client directive.
+        '';
+        example = ''
+          Maximum Concurrent Jobs = 20;
+          Heartbeat Interval = 30;
+        '';
+      };
+
+      extraMessagesConfig = mkOption {
+        default = "";
+        description = ''
+          Extra configuration to be passed in Messages directive.
+        '';
+        example = ''
+          console = all
+        '';
+      };
+    };
+
+    services.bacula-sd = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Bacula Storage Daemon.
+        '';
+      };
+ 
+      name = mkOption {
+        default = "${config.networking.hostName}-sd";
+        description = ''
+          Specifies the Name of the Storage daemon.
+        '';
+      };
+ 
+      port = mkOption {
+        default = 9103;
+        type = types.int;
+        description = ''
+          Specifies port number on which the Storage daemon listens for
+          Director connections.
+        '';
+      };
+
+      director = mkOption {
+        default = {};
+        description = ''
+          This option defines Director resources in Bacula Storage Daemon.
+        '';
+        type = with types; attrsOf (submodule directorOptions);
+      };
+
+      device = mkOption {
+        default = {};
+        description = ''
+          This option defines Device resources in Bacula Storage Daemon.
+        '';
+        type = with types; attrsOf (submodule deviceOptions);
+      };
+
+      autochanger = mkOption {
+        default = {};
+        description = ''
+          This option defines Autochanger resources in Bacula Storage Daemon.
+        '';
+        type = with types; attrsOf (submodule autochangerOptions);
+      };
+
+      extraStorageConfig = mkOption {
+        default = "";
+        description = ''
+          Extra configuration to be passed in Storage directive.
+        '';
+        example = ''
+          Maximum Concurrent Jobs = 20;
+          Heartbeat Interval = 30;
+        '';
+      };
+
+      extraMessagesConfig = mkOption {
+        default = "";
+        description = ''
+          Extra configuration to be passed in Messages directive.
+        '';
+        example = ''
+          console = all
+        '';
+      };
+ 
+    };
+
+    services.bacula-dir = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Bacula Director Daemon.
+        '';
+      };
+
+      name = mkOption {
+        default = "${config.networking.hostName}-dir";
+        description = ''
+          The director name used by the system administrator. This directive is
+          required.
+        '';
+      };
+ 
+      port = mkOption {
+        default = 9101;
+        type = types.int;
+        description = ''
+          Specify the port (a positive integer) on which the Director daemon
+          will listen for Bacula Console connections. This same port number
+          must be specified in the Director resource of the Console
+          configuration file. The default is 9101, so normally this directive
+          need not be specified. This directive should not be used if you
+          specify DirAddresses (N.B plural) directive.
+        '';
+      };
+ 
+      password = mkOption {
+        # TODO: required?
+        description = ''
+           Specifies the password that must be supplied for a Director.
+        '';
+      };
+
+      extraMessagesConfig = mkOption {
+        default = "";
+        description = ''
+          Extra configuration to be passed in Messages directive.
+        '';
+        example = ''
+          console = all
+        '';
+      };
+
+      extraDirectorConfig = mkOption {
+        default = "";
+        description = ''
+          Extra configuration to be passed in Director directive.
+        '';
+        example = ''
+          Maximum Concurrent Jobs = 20;
+          Heartbeat Interval = 30;
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration for Bacula Director Daemon.
+        '';
+        example = ''
+          TODO
+        '';
+      };
+    };
+  };
+
+  config = mkIf (fd_cfg.enable || sd_cfg.enable || dir_cfg.enable) {
+    systemd.services.bacula-fd = mkIf fd_cfg.enable {
+      after = [ "network.target" ];
+      description = "Bacula File Daemon";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.bacula ];
+      serviceConfig = {
+        ExecStart = "${pkgs.bacula}/sbin/bacula-fd -f -u root -g bacula -c ${fd_conf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LogsDirectory = "bacula";
+        StateDirectory = "bacula";
+      };
+    };
+
+    systemd.services.bacula-sd = mkIf sd_cfg.enable {
+      after = [ "network.target" ];
+      description = "Bacula Storage Daemon";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.bacula ];
+      serviceConfig = {
+        ExecStart = "${pkgs.bacula}/sbin/bacula-sd -f -u bacula -g bacula -c ${sd_conf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LogsDirectory = "bacula";
+        StateDirectory = "bacula";
+      };
+    };
+
+    services.postgresql.enable = dir_cfg.enable == true;
+
+    systemd.services.bacula-dir = mkIf dir_cfg.enable {
+      after = [ "network.target" "postgresql.service" ];
+      description = "Bacula Director Daemon";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.bacula ];
+      serviceConfig = {
+        ExecStart = "${pkgs.bacula}/sbin/bacula-dir -f -u bacula -g bacula -c ${dir_conf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LogsDirectory = "bacula";
+        StateDirectory = "bacula";
+      };
+      preStart = ''
+        if ! test -e "${libDir}/db-created"; then
+            ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole bacula
+            #${pkgs.postgresql}/bin/createdb --owner bacula bacula
+
+            # populate DB
+            ${pkgs.bacula}/etc/create_bacula_database postgresql
+            ${pkgs.bacula}/etc/make_bacula_tables postgresql
+            ${pkgs.bacula}/etc/grant_bacula_privileges postgresql
+            touch "${libDir}/db-created"
+        else
+            ${pkgs.bacula}/etc/update_bacula_tables postgresql || true
+        fi
+      '';
+    };
+
+    environment.systemPackages = [ pkgs.bacula ];
+
+    users.users.bacula = {
+      group = "bacula";
+      uid = config.ids.uids.bacula;
+      home = "${libDir}";
+      createHome = true;
+      description = "Bacula Daemons user";
+      shell = "${pkgs.bash}/bin/bash";
+    };
+
+    users.groups.bacula.gid = config.ids.gids.bacula;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/borgbackup.nix b/nixpkgs/nixos/modules/services/backup/borgbackup.nix
new file mode 100644
index 000000000000..be661b201f0d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/borgbackup.nix
@@ -0,0 +1,675 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  isLocalPath = x:
+    builtins.substring 0 1 x == "/"      # absolute path
+    || builtins.substring 0 1 x == "."   # relative path
+    || builtins.match "[.*:.*]" == null; # not machine:path
+
+  mkExcludeFile = cfg:
+    # Write each exclude pattern to a new line
+    pkgs.writeText "excludefile" (concatStringsSep "\n" cfg.exclude);
+
+  mkKeepArgs = cfg:
+    # If cfg.prune.keep e.g. has a yearly attribute,
+    # its content is passed on as --keep-yearly
+    concatStringsSep " "
+      (mapAttrsToList (x: y: "--keep-${x}=${toString y}") cfg.prune.keep);
+
+  mkBackupScript = cfg: ''
+    on_exit()
+    {
+      exitStatus=$?
+      # Reset the EXIT handler, or else we're called again on 'exit' below
+      trap - EXIT
+      ${cfg.postHook}
+      exit $exitStatus
+    }
+    trap 'on_exit' INT TERM QUIT EXIT
+
+    archiveName="${cfg.archiveBaseName}-$(date ${cfg.dateFormat})"
+    archiveSuffix="${optionalString cfg.appendFailedSuffix ".failed"}"
+    ${cfg.preHook}
+  '' + optionalString cfg.doInit ''
+    # Run borg init if the repo doesn't exist yet
+    if ! borg list $extraArgs > /dev/null; then
+      borg init $extraArgs \
+        --encryption ${cfg.encryption.mode} \
+        $extraInitArgs
+      ${cfg.postInit}
+    fi
+  '' + ''
+    borg create $extraArgs \
+      --compression ${cfg.compression} \
+      --exclude-from ${mkExcludeFile cfg} \
+      $extraCreateArgs \
+      "::$archiveName$archiveSuffix" \
+      ${escapeShellArgs cfg.paths}
+  '' + optionalString cfg.appendFailedSuffix ''
+    borg rename $extraArgs \
+      "::$archiveName$archiveSuffix" "$archiveName"
+  '' + ''
+    ${cfg.postCreate}
+  '' + optionalString (cfg.prune.keep != { }) ''
+    borg prune $extraArgs \
+      ${mkKeepArgs cfg} \
+      --prefix ${escapeShellArg cfg.prune.prefix} \
+      $extraPruneArgs
+    ${cfg.postPrune}
+  '';
+
+  mkPassEnv = cfg: with cfg.encryption;
+    if passCommand != null then
+      { BORG_PASSCOMMAND = passCommand; }
+    else if passphrase != null then
+      { BORG_PASSPHRASE = passphrase; }
+    else { };
+
+  mkBackupService = name: cfg:
+    let
+      userHome = config.users.users.${cfg.user}.home;
+    in nameValuePair "borgbackup-job-${name}" {
+      description = "BorgBackup job ${name}";
+      path = with pkgs; [
+        borgbackup openssh
+      ];
+      script = mkBackupScript cfg;
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        # Only run when no other process is using CPU or disk
+        CPUSchedulingPolicy = "idle";
+        IOSchedulingClass = "idle";
+        ProtectSystem = "strict";
+        ReadWritePaths =
+          [ "${userHome}/.config/borg" "${userHome}/.cache/borg" ]
+          ++ cfg.readWritePaths
+          # Borg needs write access to repo if it is not remote
+          ++ optional (isLocalPath cfg.repo) cfg.repo;
+        PrivateTmp = cfg.privateTmp;
+      };
+      environment = {
+        BORG_REPO = cfg.repo;
+        inherit (cfg) extraArgs extraInitArgs extraCreateArgs extraPruneArgs;
+      } // (mkPassEnv cfg) // cfg.environment;
+      inherit (cfg) startAt;
+    };
+
+  # utility function around makeWrapper
+  mkWrapperDrv = {
+      original, name, set ? {}
+    }:
+    pkgs.runCommandNoCC "${name}-wrapper" {
+      buildInputs = [ pkgs.makeWrapper ];
+    } (with lib; ''
+      makeWrapper "${original}" "$out/bin/${name}" \
+        ${concatStringsSep " \\\n " (mapAttrsToList (name: value: ''--set ${name} "${value}"'') set)}
+    '');
+
+  mkBorgWrapper = name: cfg: mkWrapperDrv {
+    original = "${pkgs.borgbackup}/bin/borg";
+    name = "borg-job-${name}";
+    set = { BORG_REPO = cfg.repo; } // (mkPassEnv cfg) // cfg.environment;
+  };
+
+  # Paths listed in ReadWritePaths must exist before service is started
+  mkActivationScript = name: cfg:
+    let
+      install = "install -o ${cfg.user} -g ${cfg.group}";
+    in
+      nameValuePair "borgbackup-job-${name}" (stringAfter [ "users" ] (''
+        # Ensure that the home directory already exists
+        # We can't assert createHome == true because that's not the case for root
+        cd "${config.users.users.${cfg.user}.home}"
+        ${install} -d .config/borg
+        ${install} -d .cache/borg
+      '' + optionalString (isLocalPath cfg.repo && !cfg.removableDevice) ''
+        ${install} -d ${escapeShellArg cfg.repo}
+      ''));
+
+  mkPassAssertion = name: cfg: {
+    assertion = with cfg.encryption;
+      mode != "none" -> passCommand != null || passphrase != null;
+    message =
+      "passCommand or passphrase has to be specified because"
+      + '' borgbackup.jobs.${name}.encryption != "none"'';
+  };
+
+  mkRepoService = name: cfg:
+    nameValuePair "borgbackup-repo-${name}" {
+      description = "Create BorgBackup repository ${name} directory";
+      script = ''
+        mkdir -p ${escapeShellArg cfg.path}
+        chown ${cfg.user}:${cfg.group} ${escapeShellArg cfg.path}
+      '';
+      serviceConfig = {
+        # The service's only task is to ensure that the specified path exists
+        Type = "oneshot";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+  mkAuthorizedKey = cfg: appendOnly: key:
+    let
+      # Because of the following line, clients do not need to specify an absolute repo path
+      cdCommand = "cd ${escapeShellArg cfg.path}";
+      restrictedArg = "--restrict-to-${if cfg.allowSubRepos then "path" else "repository"} .";
+      appendOnlyArg = optionalString appendOnly "--append-only";
+      quotaArg = optionalString (cfg.quota != null) "--storage-quota ${cfg.quota}";
+      serveCommand = "borg serve ${restrictedArg} ${appendOnlyArg} ${quotaArg}";
+    in
+      ''command="${cdCommand} && ${serveCommand}",restrict ${key}'';
+
+  mkUsersConfig = name: cfg: {
+    users.${cfg.user} = {
+      openssh.authorizedKeys.keys =
+        (map (mkAuthorizedKey cfg false) cfg.authorizedKeys
+        ++ map (mkAuthorizedKey cfg true) cfg.authorizedKeysAppendOnly);
+      useDefaultShell = true;
+    };
+    groups.${cfg.group} = { };
+  };
+
+  mkKeysAssertion = name: cfg: {
+    assertion = cfg.authorizedKeys != [ ] || cfg.authorizedKeysAppendOnly != [ ];
+    message =
+      "borgbackup.repos.${name} does not make sense"
+      + " without at least one public key";
+  };
+
+  mkRemovableDeviceAssertions = name: cfg: {
+    assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice;
+    message = ''
+      borgbackup.repos.${name}: repo isn't a local path, thus it can't be a removable device!
+    '';
+  };
+
+in {
+  meta.maintainers = with maintainers; [ dotlambda ];
+  meta.doc = ./borgbackup.xml;
+
+  ###### interface
+
+  options.services.borgbackup.jobs = mkOption {
+    description = ''
+      Deduplicating backups using BorgBackup.
+      Adding a job will cause a borg-job-NAME wrapper to be added
+      to your system path, so that you can perform maintenance easily.
+      See also the chapter about BorgBackup in the NixOS manual.
+    '';
+    default = { };
+    example = literalExample ''
+      { # for a local backup
+        rootBackup = {
+          paths = "/";
+          exclude = [ "/nix" ];
+          repo = "/path/to/local/repo";
+          encryption = {
+            mode = "repokey";
+            passphrase = "secret";
+          };
+          compression = "auto,lzma";
+          startAt = "weekly";
+        };
+      }
+      { # Root backing each day up to a remote backup server. We assume that you have
+        #   * created a password less key: ssh-keygen -N "" -t ed25519 -f /path/to/ssh_key
+        #     best practices are: use -t ed25519, /path/to = /run/keys
+        #   * the passphrase is in the file /run/keys/borgbackup_passphrase
+        #   * you have initialized the repository manually
+        paths = [ "/etc" "/home" ];
+        exclude = [ "/nix" "'**/.cache'" ];
+        doInit = false;
+        repo =  "user3@arep.repo.borgbase.com:repo";
+        encryption = {
+          mode = "repokey-blake2";
+          passCommand = "cat /path/to/passphrase";
+        };
+        environment = { BORG_RSH = "ssh -i /path/to/ssh_key"; };
+        compression = "auto,lzma";
+        startAt = "daily";
+    };
+    '';
+    type = types.attrsOf (types.submodule (let globalConfig = config; in
+      { name, config, ... }: {
+        options = {
+
+          paths = mkOption {
+            type = with types; coercedTo str lib.singleton (listOf str);
+            description = "Path(s) to back up.";
+            example = "/home/user";
+          };
+
+          repo = mkOption {
+            type = types.str;
+            description = "Remote or local repository to back up to.";
+            example = "user@machine:/path/to/repo";
+          };
+
+          removableDevice = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether the repo (which must be local) is a removable device.";
+          };
+
+          archiveBaseName = mkOption {
+            type = types.strMatching "[^/{}]+";
+            default = "${globalConfig.networking.hostName}-${name}";
+            defaultText = "\${config.networking.hostName}-<name>";
+            description = ''
+              How to name the created archives. A timestamp, whose format is
+              determined by <option>dateFormat</option>, will be appended. The full
+              name can be modified at runtime (<literal>$archiveName</literal>).
+              Placeholders like <literal>{hostname}</literal> must not be used.
+            '';
+          };
+
+          dateFormat = mkOption {
+            type = types.str;
+            description = ''
+              Arguments passed to <command>date</command>
+              to create a timestamp suffix for the archive name.
+            '';
+            default = "+%Y-%m-%dT%H:%M:%S";
+            example = "-u +%s";
+          };
+
+          startAt = mkOption {
+            type = with types; either str (listOf str);
+            default = "daily";
+            description = ''
+              When or how often the backup should run.
+              Must be in the format described in
+              <citerefentry><refentrytitle>systemd.time</refentrytitle>
+              <manvolnum>7</manvolnum></citerefentry>.
+              If you do not want the backup to start
+              automatically, use <literal>[ ]</literal>.
+              It will generate a systemd service borgbackup-job-NAME.
+              You may trigger it manually via systemctl restart borgbackup-job-NAME.
+            '';
+          };
+
+          user = mkOption {
+            type = types.str;
+            description = ''
+              The user <command>borg</command> is run as.
+              User or group need read permission
+              for the specified <option>paths</option>.
+            '';
+            default = "root";
+          };
+
+          group = mkOption {
+            type = types.str;
+            description = ''
+              The group borg is run as. User or group needs read permission
+              for the specified <option>paths</option>.
+            '';
+            default = "root";
+          };
+
+          encryption.mode = mkOption {
+            type = types.enum [
+              "repokey" "keyfile"
+              "repokey-blake2" "keyfile-blake2"
+              "authenticated" "authenticated-blake2"
+              "none"
+            ];
+            description = ''
+              Encryption mode to use. Setting a mode
+              other than <literal>"none"</literal> requires
+              you to specify a <option>passCommand</option>
+              or a <option>passphrase</option>.
+            '';
+            example = ''
+              encryption.mode = "repokey-blake2" ;
+              encryption.passphrase = "mySecretPassphrase" ;
+            '';
+          };
+
+          encryption.passCommand = mkOption {
+            type = with types; nullOr str;
+            description = ''
+              A command which prints the passphrase to stdout.
+              Mutually exclusive with <option>passphrase</option>.
+            '';
+            default = null;
+            example = "cat /path/to/passphrase_file";
+          };
+
+          encryption.passphrase = mkOption {
+            type = with types; nullOr str;
+            description = ''
+              The passphrase the backups are encrypted with.
+              Mutually exclusive with <option>passCommand</option>.
+              If you do not want the passphrase to be stored in the
+              world-readable Nix store, use <option>passCommand</option>.
+            '';
+            default = null;
+          };
+
+          compression = mkOption {
+            # "auto" is optional,
+            # compression mode must be given,
+            # compression level is optional
+            type = types.strMatching "none|(auto,)?(lz4|zstd|zlib|lzma)(,[[:digit:]]{1,2})?";
+            description = ''
+              Compression method to use. Refer to
+              <command>borg help compression</command>
+              for all available options.
+            '';
+            default = "lz4";
+            example = "auto,lzma";
+          };
+
+          exclude = mkOption {
+            type = with types; listOf str;
+            description = ''
+              Exclude paths matching any of the given patterns. See
+              <command>borg help patterns</command> for pattern syntax.
+            '';
+            default = [ ];
+            example = [
+              "/home/*/.cache"
+              "/nix"
+            ];
+          };
+
+          readWritePaths = mkOption {
+            type = with types; listOf path;
+            description = ''
+              By default, borg cannot write anywhere on the system but
+              <literal>$HOME/.config/borg</literal> and <literal>$HOME/.cache/borg</literal>.
+              If, for example, your preHook script needs to dump files
+              somewhere, put those directories here.
+            '';
+            default = [ ];
+            example = [
+              "/var/backup/mysqldump"
+            ];
+          };
+
+          privateTmp = mkOption {
+            type = types.bool;
+            description = ''
+              Set the <literal>PrivateTmp</literal> option for
+              the systemd-service. Set to false if you need sockets
+              or other files from global /tmp.
+            '';
+            default = true;
+          };
+
+          doInit = mkOption {
+            type = types.bool;
+            description = ''
+              Run <command>borg init</command> if the
+              specified <option>repo</option> does not exist.
+              You should set this to <literal>false</literal>
+              if the repository is located on an external drive
+              that might not always be mounted.
+            '';
+            default = true;
+          };
+
+          appendFailedSuffix = mkOption {
+            type = types.bool;
+            description = ''
+              Append a <literal>.failed</literal> suffix
+              to the archive name, which is only removed if
+              <command>borg create</command> has a zero exit status.
+            '';
+            default = true;
+          };
+
+          prune.keep = mkOption {
+            # Specifying e.g. `prune.keep.yearly = -1`
+            # means there is no limit of yearly archives to keep
+            # The regex is for use with e.g. --keep-within 1y
+            type = with types; attrsOf (either int (strMatching "[[:digit:]]+[Hdwmy]"));
+            description = ''
+              Prune a repository by deleting all archives not matching any of the
+              specified retention options. See <command>borg help prune</command>
+              for the available options.
+            '';
+            default = { };
+            example = literalExample ''
+              {
+                within = "1d"; # Keep all archives from the last day
+                daily = 7;
+                weekly = 4;
+                monthly = -1;  # Keep at least one archive for each month
+              }
+            '';
+          };
+
+          prune.prefix = mkOption {
+            type = types.str;
+            description = ''
+              Only consider archive names starting with this prefix for pruning.
+              By default, only archives created by this job are considered.
+              Use <literal>""</literal> to consider all archives.
+            '';
+            default = config.archiveBaseName;
+            defaultText = "\${archiveBaseName}";
+          };
+
+          environment = mkOption {
+            type = with types; attrsOf str;
+            description = ''
+              Environment variables passed to the backup script.
+              You can for example specify which SSH key to use.
+            '';
+            default = { };
+            example = { BORG_RSH = "ssh -i /path/to/key"; };
+          };
+
+          preHook = mkOption {
+            type = types.lines;
+            description = ''
+              Shell commands to run before the backup.
+              This can for example be used to mount file systems.
+            '';
+            default = "";
+            example = ''
+              # To add excluded paths at runtime
+              extraCreateArgs="$extraCreateArgs --exclude /some/path"
+            '';
+          };
+
+          postInit = mkOption {
+            type = types.lines;
+            description = ''
+              Shell commands to run after <command>borg init</command>.
+            '';
+            default = "";
+          };
+
+          postCreate = mkOption {
+            type = types.lines;
+            description = ''
+              Shell commands to run after <command>borg create</command>. The name
+              of the created archive is stored in <literal>$archiveName</literal>.
+            '';
+            default = "";
+          };
+
+          postPrune = mkOption {
+            type = types.lines;
+            description = ''
+              Shell commands to run after <command>borg prune</command>.
+            '';
+            default = "";
+          };
+
+          postHook = mkOption {
+            type = types.lines;
+            description = ''
+              Shell commands to run just before exit. They are executed
+              even if a previous command exits with a non-zero exit code.
+              The latter is available as <literal>$exitStatus</literal>.
+            '';
+            default = "";
+          };
+
+          extraArgs = mkOption {
+            type = types.str;
+            description = ''
+              Additional arguments for all <command>borg</command> calls the
+              service has. Handle with care.
+            '';
+            default = "";
+            example = "--remote-path=/path/to/borg";
+          };
+
+          extraInitArgs = mkOption {
+            type = types.str;
+            description = ''
+              Additional arguments for <command>borg init</command>.
+              Can also be set at runtime using <literal>$extraInitArgs</literal>.
+            '';
+            default = "";
+            example = "--append-only";
+          };
+
+          extraCreateArgs = mkOption {
+            type = types.str;
+            description = ''
+              Additional arguments for <command>borg create</command>.
+              Can also be set at runtime using <literal>$extraCreateArgs</literal>.
+            '';
+            default = "";
+            example = "--stats --checkpoint-interval 600";
+          };
+
+          extraPruneArgs = mkOption {
+            type = types.str;
+            description = ''
+              Additional arguments for <command>borg prune</command>.
+              Can also be set at runtime using <literal>$extraPruneArgs</literal>.
+            '';
+            default = "";
+            example = "--save-space";
+          };
+
+        };
+      }
+    ));
+  };
+
+  options.services.borgbackup.repos = mkOption {
+    description = ''
+      Serve BorgBackup repositories to given public SSH keys,
+      restricting their access to the repository only.
+      See also the chapter about BorgBackup in the NixOS manual.
+      Also, clients do not need to specify the absolute path when accessing the repository,
+      i.e. <literal>user@machine:.</literal> is enough. (Note colon and dot.)
+    '';
+    default = { };
+    type = types.attrsOf (types.submodule (
+      { ... }: {
+        options = {
+          path = mkOption {
+            type = types.path;
+            description = ''
+              Where to store the backups. Note that the directory
+              is created automatically, with correct permissions.
+            '';
+            default = "/var/lib/borgbackup";
+          };
+
+          user = mkOption {
+            type = types.str;
+            description = ''
+              The user <command>borg serve</command> is run as.
+              User or group needs write permission
+              for the specified <option>path</option>.
+            '';
+            default = "borg";
+          };
+
+          group = mkOption {
+            type = types.str;
+            description = ''
+              The group <command>borg serve</command> is run as.
+              User or group needs write permission
+              for the specified <option>path</option>.
+            '';
+            default = "borg";
+          };
+
+          authorizedKeys = mkOption {
+            type = with types; listOf str;
+            description = ''
+              Public SSH keys that are given full write access to this repository.
+              You should use a different SSH key for each repository you write to, because
+              the specified keys are restricted to running <command>borg serve</command>
+              and can only access this single repository.
+            '';
+            default = [ ];
+          };
+
+          authorizedKeysAppendOnly = mkOption {
+            type = with types; listOf str;
+            description = ''
+              Public SSH keys that can only be used to append new data (archives) to the repository.
+              Note that archives can still be marked as deleted and are subsequently removed from disk
+              upon accessing the repo with full write access, e.g. when pruning.
+            '';
+            default = [ ];
+          };
+
+          allowSubRepos = mkOption {
+            type = types.bool;
+            description = ''
+              Allow clients to create repositories in subdirectories of the
+              specified <option>path</option>. These can be accessed using
+              <literal>user@machine:path/to/subrepo</literal>. Note that a
+              <option>quota</option> applies to repositories independently.
+              Therefore, if this is enabled, clients can create multiple
+              repositories and upload an arbitrary amount of data.
+            '';
+            default = false;
+          };
+
+          quota = mkOption {
+            # See the definition of parse_file_size() in src/borg/helpers/parseformat.py
+            type = with types; nullOr (strMatching "[[:digit:].]+[KMGTP]?");
+            description = ''
+              Storage quota for the repository. This quota is ensured for all
+              sub-repositories if <option>allowSubRepos</option> is enabled
+              but not for the overall storage space used.
+            '';
+            default = null;
+            example = "100G";
+          };
+
+        };
+      }
+    ));
+  };
+
+  ###### implementation
+
+  config = mkIf (with config.services.borgbackup; jobs != { } || repos != { })
+    (with config.services.borgbackup; {
+      assertions =
+        mapAttrsToList mkPassAssertion jobs
+        ++ mapAttrsToList mkKeysAssertion repos
+        ++ mapAttrsToList mkRemovableDeviceAssertions jobs;
+
+      system.activationScripts = mapAttrs' mkActivationScript jobs;
+
+      systemd.services =
+        # A job named "foo" is mapped to systemd.services.borgbackup-job-foo
+        mapAttrs' mkBackupService jobs
+        # A repo named "foo" is mapped to systemd.services.borgbackup-repo-foo
+        // mapAttrs' mkRepoService repos;
+
+      users = mkMerge (mapAttrsToList mkUsersConfig repos);
+
+      environment.systemPackages = with pkgs; [ borgbackup ] ++ (mapAttrsToList mkBorgWrapper jobs);
+    });
+}
diff --git a/nixpkgs/nixos/modules/services/backup/borgbackup.xml b/nixpkgs/nixos/modules/services/backup/borgbackup.xml
new file mode 100644
index 000000000000..bef7db608f82
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/borgbackup.xml
@@ -0,0 +1,227 @@
+<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-borgbase">
+ <title>BorgBackup</title>
+  <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/services/backup/borgbackup.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://borgbackup.readthedocs.io/"/>
+ </para>
+ <para>
+  <link xlink:href="https://www.borgbackup.org/">BorgBackup</link> (short: Borg)
+  is a deduplicating backup program. Optionally, it supports compression and
+  authenticated encryption.
+  </para>
+  <para>
+  The main goal of Borg is to provide an efficient and secure way to backup
+  data. The data deduplication technique used makes Borg suitable for daily
+  backups since only changes are stored. The authenticated encryption technique
+  makes it suitable for backups to not fully trusted targets.
+ </para>
+  <section xml:id="module-services-backup-borgbackup-configuring">
+  <title>Configuring</title>
+  <para>
+   A complete list of options for the Borgbase module may be found
+   <link linkend="opt-services.borgbackup.jobs">here</link>.
+  </para>
+</section>
+ <section xml:id="opt-services-backup-borgbackup-local-directory">
+  <title>Basic usage for a local backup</title>
+
+  <para>
+   A very basic configuration for backing up to a locally accessible directory
+   is:
+<programlisting>
+{
+    opt.services.borgbackup.jobs = {
+      { rootBackup = {
+          paths = "/";
+          exclude = [ "/nix" "/path/to/local/repo" ];
+          repo = "/path/to/local/repo";
+          doInit = true;
+          encryption = {
+            mode = "repokey";
+            passphrase = "secret";
+          };
+          compression = "auto,lzma";
+          startAt = "weekly";
+        };
+      }
+    };
+}</programlisting>
+  </para>
+  <warning>
+    <para>
+        If you do not want the passphrase to be stored in the world-readable
+        Nix store, use passCommand. You find an example below.
+    </para>
+  </warning>
+ </section>
+<section xml:id="opt-services-backup-create-server">
+  <title>Create a borg backup server</title>
+  <para>You should use a different SSH key for each repository you write to,
+    because the specified keys are restricted to running borg serve and can only
+    access this single repository. You need the output of the generate pub file.
+  </para>
+    <para>
+        <programlisting>
+# sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_my_borg_repo
+# cat /run/keys/id_ed25519_my_borg_repo
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos</programlisting>
+    </para>
+    <para>
+      Add the following snippet to your NixOS configuration:
+      <programlisting>
+{
+  services.borgbackup.repos = {
+    my_borg_repo = {
+      authorizedKeys = [
+        "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID78zmOyA+5uPG4Ot0hfAy+sLDPU1L4AiIoRYEIVbbQ/ root@nixos"
+      ] ;
+      path = "/var/lib/my_borg_repo" ;
+    };
+  };
+}</programlisting>
+    </para>
+</section>
+
+ <section xml:id="opt-services-backup-borgbackup-remote-server">
+  <title>Backup to the borg repository server</title>
+  <para>The following NixOS snippet creates an hourly backup to the service
+    (on the host nixos) as created in the section above. We assume
+    that you have stored a secret passphrasse in the file
+    <code>/run/keys/borgbackup_passphrase</code>, which should be only
+    accessible by root
+  </para>
+  <para>
+      <programlisting>
+{
+  services.borgbackup.jobs = {
+    backupToLocalServer = {
+      paths = [ "/etc/nixos" ];
+      doInit = true;
+      repo =  "borg@nixos:." ;
+      encryption = {
+        mode = "repokey-blake2";
+        passCommand = "cat /run/keys/borgbackup_passphrase";
+      };
+      environment = { BORG_RSH = "ssh -i /run/keys/id_ed25519_my_borg_repo"; };
+      compression = "auto,lzma";
+      startAt = "hourly";
+    };
+  };
+};</programlisting>
+  </para>
+  <para>The following few commands (run as root) let you test your backup.
+      <programlisting>
+> nixos-rebuild switch
+...restarting the following units: polkit.service
+> systemctl restart borgbackup-job-backupToLocalServer
+> sleep 10
+> systemctl restart borgbackup-job-backupToLocalServer
+> export BORG_PASSPHRASE=topSecrect
+> borg list --rsh='ssh -i /run/keys/id_ed25519_my_borg_repo' borg@nixos:.
+nixos-backupToLocalServer-2020-03-30T21:46:17 Mon, 2020-03-30 21:46:19 [84feb97710954931ca384182f5f3cb90665f35cef214760abd7350fb064786ac]
+nixos-backupToLocalServer-2020-03-30T21:46:30 Mon, 2020-03-30 21:46:32 [e77321694ecd160ca2228611747c6ad1be177d6e0d894538898de7a2621b6e68]</programlisting>
+    </para>
+</section>
+
+ <section xml:id="opt-services-backup-borgbackup-borgbase">
+  <title>Backup to a hosting service</title>
+
+  <para>
+    Several companies offer <link
+      xlink:href="https://www.borgbackup.org/support/commercial.html">(paid)
+      hosting services</link> for Borg repositories.
+  </para>
+  <para>
+    To backup your home directory to borgbase you have to:
+  </para>
+  <itemizedlist>
+  <listitem>
+    <para>
+      Generate a SSH key without a password, to access the remote server. E.g.
+    </para>
+    <para>
+        <programlisting>sudo ssh-keygen -N '' -t ed25519 -f /run/keys/id_ed25519_borgbase</programlisting>
+    </para>
+  </listitem>
+  <listitem>
+    <para>
+      Create the repository on the server by following the instructions for your
+      hosting server.
+    </para>
+  </listitem>
+  <listitem>
+    <para>
+      Initialize the repository on the server. Eg.
+      <programlisting>
+sudo borg init --encryption=repokey-blake2  \
+    -rsh "ssh -i /run/keys/id_ed25519_borgbase" \
+    zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo</programlisting>
+  </para>
+  </listitem>
+  <listitem>
+<para>Add it to your NixOS configuration, e.g.
+<programlisting>
+{
+    services.borgbackup.jobs = {
+    my_Remote_Backup = {
+        paths = [ "/" ];
+        exclude = [ "/nix" "'**/.cache'" ];
+        repo =  "zzz2aaaaa@zzz2aaaaa.repo.borgbase.com:repo";
+          encryption = {
+          mode = "repokey-blake2";
+          passCommand = "cat /run/keys/borgbackup_passphrase";
+        };
+        BORG_RSH = "ssh -i /run/keys/id_ed25519_borgbase";
+        compression = "auto,lzma";
+        startAt = "daily";
+    };
+  };
+}}</programlisting>
+  </para>
+  </listitem>
+</itemizedlist>
+ </section>
+  <section xml:id="opt-services-backup-borgbackup-vorta">
+  <title>Vorta backup client for the desktop</title>
+  <para>
+    Vorta is a backup client for macOS and Linux desktops. It integrates the
+    mighty BorgBackup with your desktop environment to protect your data from
+    disk failure, ransomware and theft.
+  </para>
+  <para>
+    It is available as a flatpak package. To enable it you must set the
+    following two configuration items.
+  </para>
+  <para>
+    <programlisting>
+services.flatpak.enable = true ;
+# next line is needed to avoid the Error
+# Error deploying: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown:
+services.accounts-daemon.enable = true;
+    </programlisting>
+  </para>
+  <para>As a normal user you must first install, then run vorta using the
+    following commands:
+    <programlisting>
+flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
+flatpak install flathub com.borgbase.Vorta
+flatpak run --branch=stable --arch=x86_64 --command=vorta com.borgbase.Vorta
+</programlisting>
+    After running <code>flatpak install</code> you can start Vorta also via
+        the KDE application menu.
+  </para>
+  <para>
+    Details about using Vorta can be found under <link
+      xlink:href="https://vorta.borgbase.com/usage">https://vorta.borgbase.com
+      </link>.
+  </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/backup/duplicati.nix b/nixpkgs/nixos/modules/services/backup/duplicati.nix
new file mode 100644
index 000000000000..0ff720c5897d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/duplicati.nix
@@ -0,0 +1,67 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.duplicati;
+in
+{
+  options = {
+    services.duplicati = {
+      enable = mkEnableOption "Duplicati";
+
+      port = mkOption {
+        default = 8200;
+        type = types.int;
+        description = ''
+          Port serving the web interface
+        '';
+      };
+
+      interface = mkOption {
+        default = "127.0.0.1";
+        type = types.str;
+        description = ''
+          Listening interface for the web UI
+          Set it to "any" to listen on all available interfaces
+        '';
+      };
+
+      user = mkOption {
+        default = "duplicati";
+        type = types.str;
+        description = ''
+          Duplicati runs as it's own user. It will only be able to backup world-readable files.
+          Run as root with special care.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.duplicati ];
+
+    systemd.services.duplicati = {
+      description = "Duplicati backup";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = "duplicati";
+        StateDirectory = "duplicati";
+        ExecStart = "${pkgs.duplicati}/bin/duplicati-server --webservice-interface=${cfg.interface} --webservice-port=${toString cfg.port} --server-datafolder=/var/lib/duplicati";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users.duplicati = lib.optionalAttrs (cfg.user == "duplicati") {
+      uid = config.ids.uids.duplicati;
+      home = "/var/lib/duplicati";
+      createHome = true;
+      group = "duplicati";
+    };
+    users.groups.duplicati.gid = config.ids.gids.duplicati;
+
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/backup/duplicity.nix b/nixpkgs/nixos/modules/services/backup/duplicity.nix
new file mode 100644
index 000000000000..a8d564248623
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/duplicity.nix
@@ -0,0 +1,141 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfg = config.services.duplicity;
+
+  stateDirectory = "/var/lib/duplicity";
+
+  localTarget = if hasPrefix "file://" cfg.targetUrl
+    then removePrefix "file://" cfg.targetUrl else null;
+
+in {
+  options.services.duplicity = {
+    enable = mkEnableOption "backups with duplicity";
+
+    root = mkOption {
+      type = types.path;
+      default = "/";
+      description = ''
+        Root directory to backup.
+      '';
+    };
+
+    include = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "/home" ];
+      description = ''
+        List of paths to include into the backups. See the FILE SELECTION
+        section in <citerefentry><refentrytitle>duplicity</refentrytitle>
+        <manvolnum>1</manvolnum></citerefentry> for details on the syntax.
+      '';
+    };
+
+    exclude = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        List of paths to exclude from backups. See the FILE SELECTION section in
+        <citerefentry><refentrytitle>duplicity</refentrytitle>
+        <manvolnum>1</manvolnum></citerefentry> for details on the syntax.
+      '';
+    };
+
+    targetUrl = mkOption {
+      type = types.str;
+      example = "s3://host:port/prefix";
+      description = ''
+        Target url to backup to. See the URL FORMAT section in
+        <citerefentry><refentrytitle>duplicity</refentrytitle>
+        <manvolnum>1</manvolnum></citerefentry> for supported urls.
+      '';
+    };
+
+    secretFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Path of a file containing secrets (gpg passphrase, access key...) in
+        the format of EnvironmentFile as described by
+        <citerefentry><refentrytitle>systemd.exec</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry>. For example:
+        <programlisting>
+        PASSPHRASE=<replaceable>...</replaceable>
+        AWS_ACCESS_KEY_ID=<replaceable>...</replaceable>
+        AWS_SECRET_ACCESS_KEY=<replaceable>...</replaceable>
+        </programlisting>
+      '';
+    };
+
+    frequency = mkOption {
+      type = types.nullOr types.str;
+      default = "daily";
+      description = ''
+        Run duplicity with the given frequency (see
+        <citerefentry><refentrytitle>systemd.time</refentrytitle>
+        <manvolnum>7</manvolnum></citerefentry> for the format).
+        If null, do not run automatically.
+      '';
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "--full-if-older-than" "1M" ];
+      description = ''
+        Extra command-line flags passed to duplicity. See
+        <citerefentry><refentrytitle>duplicity</refentrytitle>
+        <manvolnum>1</manvolnum></citerefentry>.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd = {
+      services.duplicity = {
+        description = "backup files with duplicity";
+
+        environment.HOME = stateDirectory;
+
+        serviceConfig = {
+          ExecStart = ''
+            ${pkgs.duplicity}/bin/duplicity ${escapeShellArgs (
+              [
+                cfg.root
+                cfg.targetUrl
+                "--archive-dir" stateDirectory
+              ]
+              ++ concatMap (p: [ "--include" p ]) cfg.include
+              ++ concatMap (p: [ "--exclude" p ]) cfg.exclude
+              ++ cfg.extraFlags)}
+          '';
+          PrivateTmp = true;
+          ProtectSystem = "strict";
+          ProtectHome = "read-only";
+          StateDirectory = baseNameOf stateDirectory;
+        } // optionalAttrs (localTarget != null) {
+          ReadWritePaths = localTarget;
+        } // optionalAttrs (cfg.secretFile != null) {
+          EnvironmentFile = cfg.secretFile;
+        };
+      } // optionalAttrs (cfg.frequency != null) {
+        startAt = cfg.frequency;
+      };
+
+      tmpfiles.rules = optional (localTarget != null) "d ${localTarget} 0700 root root -";
+    };
+
+    assertions = singleton {
+      # Duplicity will fail if the last file selection option is an include. It
+      # is not always possible to detect but this simple case can be caught.
+      assertion = cfg.include != [] -> cfg.exclude != [] || cfg.extraFlags != [];
+      message = ''
+        Duplicity will fail if you only specify included paths ("Because the
+        default is to include all files, the expression is redundant. Exiting
+        because this probably isn't what you meant.")
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/mysql-backup.nix b/nixpkgs/nixos/modules/services/backup/mysql-backup.nix
new file mode 100644
index 000000000000..31d606b141a8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/mysql-backup.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) mysql gzip;
+
+  cfg = config.services.mysqlBackup;
+  defaultUser = "mysqlbackup";
+
+  backupScript = ''
+    set -o pipefail
+    failed=""
+    ${concatMapStringsSep "\n" backupDatabaseScript cfg.databases}
+    if [ -n "$failed" ]; then
+      echo "Backup of database(s) failed:$failed"
+      exit 1
+    fi
+  '';
+  backupDatabaseScript = db: ''
+    dest="${cfg.location}/${db}.gz"
+    if ${mysql}/bin/mysqldump ${if cfg.singleTransaction then "--single-transaction" else ""} ${db} | ${gzip}/bin/gzip -c > $dest.tmp; then
+      mv $dest.tmp $dest
+      echo "Backed up to $dest"
+    else
+      echo "Failed to back up to $dest"
+      rm -f $dest.tmp
+      failed="$failed ${db}"
+    fi
+  '';
+
+in
+
+{
+  options = {
+
+    services.mysqlBackup = {
+
+      enable = mkEnableOption "MySQL backups";
+
+      calendar = mkOption {
+        type = types.str;
+        default = "01:15:00";
+        description = ''
+          Configured when to run the backup service systemd unit (DayOfWeek Year-Month-Day Hour:Minute:Second).
+        '';
+      };
+
+      user = mkOption {
+        default = defaultUser;
+        description = ''
+          User to be used to perform backup.
+        '';
+      };
+
+      databases = mkOption {
+        default = [];
+        description = ''
+          List of database names to dump.
+        '';
+      };
+
+      location = mkOption {
+        default = "/var/backup/mysql";
+        description = ''
+          Location to put the gzipped MySQL database dumps.
+        '';
+      };
+
+      singleTransaction = mkOption {
+        default = false;
+        description = ''
+          Whether to create database dump in a single transaction
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    users.users = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} = {
+        isSystemUser = true;
+        createHome = false;
+        home = cfg.location;
+        group = "nogroup";
+      };
+    };
+
+    services.mysql.ensureUsers = [{
+      name = cfg.user;
+      ensurePermissions = with lib;
+        let
+          privs = "SELECT, SHOW VIEW, TRIGGER, LOCK TABLES";
+          grant = db: nameValuePair "${db}.*" privs;
+        in
+          listToAttrs (map grant cfg.databases);
+    }];
+
+    systemd = {
+      timers.mysql-backup = {
+        description = "Mysql backup timer";
+        wantedBy = [ "timers.target" ];
+        timerConfig = {
+          OnCalendar = cfg.calendar;
+          AccuracySec = "5m";
+          Unit = "mysql-backup.service";
+        };
+      };
+      services.mysql-backup = {
+        description = "Mysql backup service";
+        enable = true;
+        serviceConfig = {
+          User = cfg.user;
+        };
+        script = backupScript;
+      };
+      tmpfiles.rules = [
+        "d ${cfg.location} 0700 ${cfg.user} - - -"
+      ];
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix b/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix
new file mode 100644
index 000000000000..428861a7598a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/postgresql-backup.nix
@@ -0,0 +1,126 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.postgresqlBackup;
+
+  postgresqlBackupService = db: dumpCmd:
+    {
+      enable = true;
+
+      description = "Backup of ${db} database(s)";
+
+      requires = [ "postgresql.service" ];
+
+      script = ''
+        umask 0077 # ensure backup is only readable by postgres user
+
+        if [ -e ${cfg.location}/${db}.sql.gz ]; then
+          ${pkgs.coreutils}/bin/mv ${cfg.location}/${db}.sql.gz ${cfg.location}/${db}.prev.sql.gz
+        fi
+
+        ${dumpCmd} | \
+          ${pkgs.gzip}/bin/gzip -c > ${cfg.location}/${db}.sql.gz
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = "postgres";
+      };
+
+      startAt = cfg.startAt;
+    };
+
+in {
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "postgresqlBackup" "period" ] ''
+       A systemd timer is now used instead of cron.
+       The starting time can be configured via <literal>services.postgresqlBackup.startAt</literal>.
+    '')
+  ];
+
+  options = {
+    services.postgresqlBackup = {
+      enable = mkEnableOption "PostgreSQL dumps";
+
+      startAt = mkOption {
+        default = "*-*-* 01:15:00";
+        description = ''
+          This option defines (see <literal>systemd.time</literal> for format) when the
+          databases should be dumped.
+          The default is to update at 01:15 (at night) every day.
+        '';
+      };
+
+      backupAll = mkOption {
+        default = cfg.databases == [];
+        defaultText = "services.postgresqlBackup.databases == []";
+        type = lib.types.bool;
+        description = ''
+          Backup all databases using pg_dumpall.
+          This option is mutual exclusive to
+          <literal>services.postgresqlBackup.databases</literal>.
+          The resulting backup dump will have the name all.sql.gz.
+          This option is the default if no databases are specified.
+        '';
+      };
+
+      databases = mkOption {
+        default = [];
+        description = ''
+          List of database names to dump.
+        '';
+      };
+
+      location = mkOption {
+        default = "/var/backup/postgresql";
+        description = ''
+          Location to put the gzipped PostgreSQL database dumps.
+        '';
+      };
+
+      pgdumpOptions = mkOption {
+        type = types.separatedString " ";
+        default = "-C";
+        description = ''
+          Command line options for pg_dump. This options is not used
+          if <literal>config.services.postgresqlBackup.backupAll</literal> is enabled.
+          Note that config.services.postgresqlBackup.backupAll is also active,
+          when no databases where specified.
+        '';
+      };
+    };
+
+  };
+
+  config = mkMerge [
+    {
+      assertions = [{
+        assertion = cfg.backupAll -> cfg.databases == [];
+        message = "config.services.postgresqlBackup.backupAll cannot be used together with config.services.postgresqlBackup.databases";
+      }];
+    }
+    (mkIf cfg.enable {
+      systemd.tmpfiles.rules = [
+        "d '${cfg.location}' 0700 postgres - - -"
+      ];
+    })
+    (mkIf (cfg.enable && cfg.backupAll) {
+      systemd.services.postgresqlBackup =
+        postgresqlBackupService "all" "${config.services.postgresql.package}/bin/pg_dumpall";
+    })
+    (mkIf (cfg.enable && !cfg.backupAll) {
+      systemd.services = listToAttrs (map (db:
+        let
+          cmd = "${config.services.postgresql.package}/bin/pg_dump ${cfg.pgdumpOptions} ${db}";
+        in {
+          name = "postgresqlBackup-${db}";
+          value = postgresqlBackupService db cmd;
+        }) cfg.databases);
+    })
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/backup/postgresql-wal-receiver.nix b/nixpkgs/nixos/modules/services/backup/postgresql-wal-receiver.nix
new file mode 100644
index 000000000000..3d9869d53431
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/postgresql-wal-receiver.nix
@@ -0,0 +1,204 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  receiverSubmodule = {
+    options = {
+      postgresqlPackage = mkOption {
+        type = types.package;
+        example = literalExample "pkgs.postgresql_11";
+        description = ''
+          PostgreSQL package to use.
+        '';
+      };
+
+      directory = mkOption {
+        type = types.path;
+        example = literalExample "/mnt/pg_wal/main/";
+        description = ''
+          Directory to write the output to.
+        '';
+      };
+
+      statusInterval = mkOption {
+        type = types.int;
+        default = 10;
+        description = ''
+          Specifies the number of seconds between status packets sent back to the server.
+          This allows for easier monitoring of the progress from server.
+          A value of zero disables the periodic status updates completely,
+          although an update will still be sent when requested by the server, to avoid timeout disconnect.
+        '';
+      };
+
+      slot = mkOption {
+        type = types.str;
+        default = "";
+        example = "some_slot_name";
+        description = ''
+          Require <command>pg_receivewal</command> to use an existing replication slot (see
+          <link xlink:href="https://www.postgresql.org/docs/current/warm-standby.html#STREAMING-REPLICATION-SLOTS">Section 26.2.6 of the PostgreSQL manual</link>).
+          When this option is used, <command>pg_receivewal</command> will report a flush position to the server,
+          indicating when each segment has been synchronized to disk so that the server can remove that segment if it is not otherwise needed.
+
+          When the replication client of <command>pg_receivewal</command> is configured on the server as a synchronous standby,
+          then using a replication slot will report the flush position to the server, but only when a WAL file is closed.
+          Therefore, that configuration will cause transactions on the primary to wait for a long time and effectively not work satisfactorily.
+          The option <option>synchronous</option> must be specified in addition to make this work correctly.
+        '';
+      };
+
+      synchronous = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Flush the WAL data to disk immediately after it has been received.
+          Also send a status packet back to the server immediately after flushing, regardless of <option>statusInterval</option>.
+
+          This option should be specified if the replication client of <command>pg_receivewal</command> is configured on the server as a synchronous standby,
+          to ensure that timely feedback is sent to the server.
+        '';
+      };
+
+      compress = mkOption {
+        type = types.ints.between 0 9;
+        default = 0;
+        description = ''
+          Enables gzip compression of write-ahead logs, and specifies the compression level
+          (<literal>0</literal> through <literal>9</literal>, <literal>0</literal> being no compression and <literal>9</literal> being best compression).
+          The suffix <literal>.gz</literal> will automatically be added to all filenames.
+
+          This option requires PostgreSQL >= 10.
+        '';
+      };
+
+      connection = mkOption {
+        type = types.str;
+        example = "postgresql://user@somehost";
+        description = ''
+          Specifies parameters used to connect to the server, as a connection string.
+          See <link xlink:href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING">Section 34.1.1 of the PostgreSQL manual</link> for more information.
+
+          Because <command>pg_receivewal</command> doesn't connect to any particular database in the cluster,
+          database name in the connection string will be ignored.
+        '';
+      };
+
+      extraArgs = mkOption {
+        type = with types; listOf str;
+        default = [ ];
+        example = literalExample ''
+          [
+            "--no-sync"
+          ]
+        '';
+        description = ''
+          A list of extra arguments to pass to the <command>pg_receivewal</command> command.
+        '';
+      };
+
+      environment = mkOption {
+        type = with types; attrsOf str;
+        default = { };
+        example = literalExample ''
+          {
+            PGPASSFILE = "/private/passfile";
+            PGSSLMODE = "require";
+          }
+        '';
+        description = ''
+          Environment variables passed to the service.
+          Usable parameters are listed in <link xlink:href="https://www.postgresql.org/docs/current/libpq-envars.html">Section 34.14 of the PostgreSQL manual</link>.
+        '';
+      };
+    };
+  };
+
+in {
+  options = {
+    services.postgresqlWalReceiver = {
+      receivers = mkOption {
+        type = with types; attrsOf (submodule receiverSubmodule);
+        default = { };
+        example = literalExample ''
+          {
+            main = {
+              postgresqlPackage = pkgs.postgresql_11;
+              directory = /mnt/pg_wal/main/;
+              slot = "main_wal_receiver";
+              connection = "postgresql://user@somehost";
+            };
+          }
+        '';
+        description = ''
+          PostgreSQL WAL receivers.
+          Stream write-ahead logs from a PostgreSQL server using <command>pg_receivewal</command> (formerly <command>pg_receivexlog</command>).
+          See <link xlink:href="https://www.postgresql.org/docs/current/app-pgreceivewal.html">the man page</link> for more information.
+        '';
+      };
+    };
+  };
+
+  config = let
+    receivers = config.services.postgresqlWalReceiver.receivers;
+  in mkIf (receivers != { }) {
+    users = {
+      users.postgres = {
+        uid = config.ids.uids.postgres;
+        group = "postgres";
+        description = "PostgreSQL server user";
+      };
+
+      groups.postgres = {
+        gid = config.ids.gids.postgres;
+      };
+    };
+
+    assertions = concatLists (attrsets.mapAttrsToList (name: config: [
+      {
+        assertion = config.compress > 0 -> versionAtLeast config.postgresqlPackage.version "10";
+        message = "Invalid configuration for WAL receiver \"${name}\": compress requires PostgreSQL version >= 10.";
+      }
+    ]) receivers);
+
+    systemd.tmpfiles.rules = mapAttrsToList (name: config: ''
+      d ${escapeShellArg config.directory} 0750 postgres postgres - -
+    '') receivers;
+
+    systemd.services = with attrsets; mapAttrs' (name: config: nameValuePair "postgresql-wal-receiver-${name}" {
+      description = "PostgreSQL WAL receiver (${name})";
+      wantedBy = [ "multi-user.target" ];
+      startLimitIntervalSec = 0; # retry forever, useful in case of network disruption
+
+      serviceConfig = {
+        User = "postgres";
+        Group = "postgres";
+        KillSignal = "SIGINT";
+        Restart = "always";
+        RestartSec = 60;
+      };
+
+      inherit (config) environment;
+
+      script = let
+        receiverCommand = postgresqlPackage:
+         if (versionAtLeast postgresqlPackage.version "10")
+           then "${postgresqlPackage}/bin/pg_receivewal"
+           else "${postgresqlPackage}/bin/pg_receivexlog";
+      in ''
+        ${receiverCommand config.postgresqlPackage} \
+          --no-password \
+          --directory=${escapeShellArg config.directory} \
+          --status-interval=${toString config.statusInterval} \
+          --dbname=${escapeShellArg config.connection} \
+          ${optionalString (config.compress > 0) "--compress=${toString config.compress}"} \
+          ${optionalString (config.slot != "") "--slot=${escapeShellArg config.slot}"} \
+          ${optionalString config.synchronous "--synchronous"} \
+          ${concatStringsSep " " config.extraArgs}
+      '';
+    }) receivers;
+  };
+
+  meta.maintainers = with maintainers; [ pacien ];
+}
diff --git a/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix b/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
new file mode 100644
index 000000000000..d1b775f150dc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/restic-rest-server.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.restic.server;
+in
+{
+  meta.maintainers = [ maintainers.bachp ];
+
+  options.services.restic.server = {
+    enable = mkEnableOption "Restic REST Server";
+
+    listenAddress = mkOption {
+      default = ":8000";
+      example = "127.0.0.1:8080";
+      type = types.str;
+      description = "Listen on a specific IP address and port.";
+    };
+
+    dataDir = mkOption {
+      default = "/var/lib/restic";
+      type = types.path;
+      description = "The directory for storing the restic repository.";
+    };
+
+    appendOnly = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enable append only mode.
+        This mode allows creation of new backups but prevents deletion and modification of existing backups.
+        This can be useful when backing up systems that have a potential of being hacked.
+      '';
+    };
+
+    privateRepos = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enable private repos.
+        Grants access only when a subdirectory with the same name as the user is specified in the repository URL.
+      '';
+    };
+
+    prometheus = mkOption {
+      default = false;
+      type = types.bool;
+      description = "Enable Prometheus metrics at /metrics.";
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra commandline options to pass to Restic REST server.
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.restic-rest-server;
+      defaultText = "pkgs.restic-rest-server";
+      type = types.package;
+      description = "Restic REST server package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.restic-rest-server = {
+      description = "Restic REST Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/rest-server \
+          --listen ${cfg.listenAddress} \
+          --path ${cfg.dataDir} \
+          ${optionalString cfg.appendOnly "--append-only"} \
+          ${optionalString cfg.privateRepos "--private-repos"} \
+          ${optionalString cfg.prometheus "--prometheus"} \
+          ${escapeShellArgs cfg.extraFlags} \
+        '';
+        Type = "simple";
+        User = "restic";
+        Group = "restic";
+
+        # Security hardening
+        ReadWritePaths = [ cfg.dataDir ];
+        PrivateTmp = true;
+        ProtectSystem = "strict";
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        PrivateDevices = true;
+      };
+    };
+
+    users.users.restic = {
+      group = "restic";
+      home = cfg.dataDir;
+      createHome = true;
+      uid = config.ids.uids.restic;
+    };
+
+    users.groups.restic.gid = config.ids.uids.restic;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/restic.nix b/nixpkgs/nixos/modules/services/backup/restic.nix
new file mode 100644
index 000000000000..2388f1d6ca13
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/restic.nix
@@ -0,0 +1,209 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  # Type for a valid systemd unit option. Needed for correctly passing "timerConfig" to "systemd.timers"
+  unitOption = (import ../../system/boot/systemd-unit-options.nix { inherit config lib; }).unitOption;
+in
+{
+  options.services.restic.backups = mkOption {
+    description = ''
+      Periodic backups to create with Restic.
+    '';
+    type = types.attrsOf (types.submodule ({ name, ... }: {
+      options = {
+        passwordFile = mkOption {
+          type = types.str;
+          description = ''
+            Read the repository password from a file.
+          '';
+          example = "/etc/nixos/restic-password";
+        };
+
+        s3CredentialsFile = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = ''
+            file containing the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
+            for an S3-hosted repository, in the format of an EnvironmentFile
+            as described by systemd.exec(5)
+          '';
+        };
+
+        repository = mkOption {
+          type = types.str;
+          description = ''
+            repository to backup to.
+          '';
+          example = "sftp:backup@192.168.1.100:/backups/${name}";
+        };
+
+        paths = mkOption {
+          type = types.listOf types.str;
+          default = [];
+          description = ''
+            Which paths to backup.
+          '';
+          example = [
+            "/var/lib/postgresql"
+            "/home/user/backup"
+          ];
+        };
+
+        timerConfig = mkOption {
+          type = types.attrsOf unitOption;
+          default = {
+            OnCalendar = "daily";
+          };
+          description = ''
+            When to run the backup. See man systemd.timer for details.
+          '';
+          example = {
+            OnCalendar = "00:05";
+            RandomizedDelaySec = "5h";
+          };
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "root";
+          description = ''
+            As which user the backup should run.
+          '';
+          example = "postgresql";
+        };
+
+        extraBackupArgs = mkOption {
+          type = types.listOf types.str;
+          default = [];
+          description = ''
+            Extra arguments passed to restic backup.
+          '';
+          example = [
+            "--exclude-file=/etc/nixos/restic-ignore"
+          ];
+        };
+
+        extraOptions = mkOption {
+          type = types.listOf types.str;
+          default = [];
+          description = ''
+            Extra extended options to be passed to the restic --option flag.
+          '';
+          example = [
+            "sftp.command='ssh backup@192.168.1.100 -i /home/user/.ssh/id_rsa -s sftp'"
+          ];
+        };
+
+        initialize = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Create the repository if it doesn't exist.
+          '';
+        };
+
+        pruneOpts = mkOption {
+          type = types.listOf types.str;
+          default = [];
+          description = ''
+            A list of options (--keep-* et al.) for 'restic forget
+            --prune', to automatically prune old snapshots.  The
+            'forget' command is run *after* the 'backup' command, so
+            keep that in mind when constructing the --keep-* options.
+          '';
+          example = [
+            "--keep-daily 7"
+            "--keep-weekly 5"
+            "--keep-monthly 12"
+            "--keep-yearly 75"
+          ];
+        };
+
+        dynamicFilesFrom = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = ''
+            A script that produces a list of files to back up.  The
+            results of this command are given to the '--files-from'
+            option.
+          '';
+          example = "find /home/matt/git -type d -name .git";
+        };
+      };
+    }));
+    default = {};
+    example = {
+      localbackup = {
+        paths = [ "/home" ];
+        repository = "/mnt/backup-hdd";
+        passwordFile = "/etc/nixos/secrets/restic-password";
+        initialize = true;
+      };
+      remotebackup = {
+        paths = [ "/home" ];
+        repository = "sftp:backup@host:/backups/home";
+        passwordFile = "/etc/nixos/secrets/restic-password";
+        extraOptions = [
+          "sftp.command='ssh backup@host -i /etc/nixos/secrets/backup-private-key -s sftp'"
+        ];
+        timerConfig = {
+          OnCalendar = "00:05";
+          RandomizedDelaySec = "5h";
+        };
+      };
+    };
+  };
+
+  config = {
+    systemd.services =
+      mapAttrs' (name: backup:
+        let
+          extraOptions = concatMapStrings (arg: " -o ${arg}") backup.extraOptions;
+          resticCmd = "${pkgs.restic}/bin/restic${extraOptions}";
+          filesFromTmpFile = "/run/restic-backups-${name}/includes";
+          backupPaths = if (backup.dynamicFilesFrom == null)
+                        then concatStringsSep " " backup.paths
+                        else "--files-from ${filesFromTmpFile}";
+          pruneCmd = optionals (builtins.length backup.pruneOpts > 0) [
+            ( resticCmd + " forget --prune " + (concatStringsSep " " backup.pruneOpts) )
+            ( resticCmd + " check" )
+          ];
+        in nameValuePair "restic-backups-${name}" ({
+          environment = {
+            RESTIC_PASSWORD_FILE = backup.passwordFile;
+            RESTIC_REPOSITORY = backup.repository;
+          };
+          path = [ pkgs.openssh ];
+          restartIfChanged = false;
+          serviceConfig = {
+            Type = "oneshot";
+            ExecStart = [ "${resticCmd} backup ${concatStringsSep " " backup.extraBackupArgs} ${backupPaths}" ] ++ pruneCmd;
+            User = backup.user;
+            RuntimeDirectory = "restic-backups-${name}";
+          } // optionalAttrs (backup.s3CredentialsFile != null) {
+            EnvironmentFile = backup.s3CredentialsFile;
+          };
+        } // optionalAttrs (backup.initialize || backup.dynamicFilesFrom != null) {
+          preStart = ''
+            ${optionalString (backup.initialize) ''
+              ${resticCmd} snapshots || ${resticCmd} init
+            ''}
+            ${optionalString (backup.dynamicFilesFrom != null) ''
+              ${pkgs.writeScript "dynamicFilesFromScript" backup.dynamicFilesFrom} > ${filesFromTmpFile}
+            ''}
+          '';
+        } // optionalAttrs (backup.dynamicFilesFrom != null) {
+          postStart = ''
+            rm ${filesFromTmpFile}
+          '';
+        })
+      ) config.services.restic.backups;
+    systemd.timers =
+      mapAttrs' (name: backup: nameValuePair "restic-backups-${name}" {
+        wantedBy = [ "timers.target" ];
+        timerConfig = backup.timerConfig;
+      }) config.services.restic.backups;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/rsnapshot.nix b/nixpkgs/nixos/modules/services/backup/rsnapshot.nix
new file mode 100644
index 000000000000..6635a51ec2c6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/rsnapshot.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.rsnapshot;
+  cfgfile = pkgs.writeText "rsnapshot.conf" ''
+    config_version	1.2
+    cmd_cp	${pkgs.coreutils}/bin/cp
+    cmd_rm	${pkgs.coreutils}/bin/rm
+    cmd_rsync	${pkgs.rsync}/bin/rsync
+    cmd_ssh	${pkgs.openssh}/bin/ssh
+    cmd_logger	${pkgs.inetutils}/bin/logger
+    cmd_du	${pkgs.coreutils}/bin/du
+    cmd_rsnapshot_diff	${pkgs.rsnapshot}/bin/rsnapshot-diff
+    lockfile	/run/rsnapshot.pid
+    link_dest	1
+
+    ${cfg.extraConfig}
+  '';
+in
+{
+  options = {
+    services.rsnapshot = {
+      enable = mkEnableOption "rsnapshot backups";
+      enableManualRsnapshot = mkOption {
+        description = "Whether to enable manual usage of the rsnapshot command with this module.";
+        default = true;
+        type = types.bool;
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        example = ''
+          retains	hourly	24
+          retain	daily	365
+          backup	/home/	localhost/
+        '';
+        type = types.lines;
+        description = ''
+          rsnapshot configuration option in addition to the defaults from
+          rsnapshot and this module.
+
+          Note that tabs are required to separate option arguments, and
+          directory names require trailing slashes.
+
+          The "extra" in the option name might be a little misleading right
+          now, as it is required to get a functional configuration.
+        '';
+      };
+
+      cronIntervals = mkOption {
+        default = {};
+        example = { hourly = "0 * * * *"; daily = "50 21 * * *"; };
+        type = types.attrsOf types.str;
+        description = ''
+          Periodicity at which intervals should be run by cron.
+          Note that the intervals also have to exist in configuration
+          as retain options.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      services.cron.systemCronJobs =
+        mapAttrsToList (interval: time: "${time} root ${pkgs.rsnapshot}/bin/rsnapshot -c ${cfgfile} ${interval}") cfg.cronIntervals;
+    }
+    (mkIf cfg.enableManualRsnapshot {
+      environment.systemPackages = [ pkgs.rsnapshot ];
+      environment.etc."rsnapshot.conf".source = cfgfile;
+    })
+  ]);
+}
diff --git a/nixpkgs/nixos/modules/services/backup/sanoid.nix b/nixpkgs/nixos/modules/services/backup/sanoid.nix
new file mode 100644
index 000000000000..0472fb4ba1e7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/sanoid.nix
@@ -0,0 +1,213 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sanoid;
+
+  datasetSettingsType = with types;
+    (attrsOf (nullOr (oneOf [ str int bool (listOf str) ]))) // {
+      description = "dataset/template options";
+    };
+
+  # Default values from https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf
+
+  commonOptions = {
+    hourly = mkOption {
+      description = "Number of hourly snapshots.";
+      type = types.ints.unsigned;
+      default = 48;
+    };
+
+    daily = mkOption {
+      description = "Number of daily snapshots.";
+      type = types.ints.unsigned;
+      default = 90;
+    };
+
+    monthly = mkOption {
+      description = "Number of monthly snapshots.";
+      type = types.ints.unsigned;
+      default = 6;
+    };
+
+    yearly = mkOption {
+      description = "Number of yearly snapshots.";
+      type = types.ints.unsigned;
+      default = 0;
+    };
+
+    autoprune = mkOption {
+      description = "Whether to automatically prune old snapshots.";
+      type = types.bool;
+      default = true;
+    };
+
+    autosnap = mkOption {
+      description = "Whether to automatically take snapshots.";
+      type = types.bool;
+      default = true;
+    };
+
+    settings = mkOption {
+      description = ''
+        Free-form settings for this template/dataset. See
+        <link xlink:href="https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf"/>
+        for allowed values.
+      '';
+      type = datasetSettingsType;
+    };
+  };
+
+  commonConfig = config: {
+    settings = {
+      hourly = mkDefault config.hourly;
+      daily = mkDefault config.daily;
+      monthly = mkDefault config.monthly;
+      yearly = mkDefault config.yearly;
+      autoprune = mkDefault config.autoprune;
+      autosnap = mkDefault config.autosnap;
+    };
+  };
+
+  datasetOptions = {
+    useTemplate = mkOption {
+      description = "Names of the templates to use for this dataset.";
+      type = (types.listOf (types.enum (attrNames cfg.templates))) // {
+        description = "list of template names";
+      };
+      default = [];
+    };
+
+    recursive = mkOption {
+      description = "Whether to recursively snapshot dataset children.";
+      type = types.bool;
+      default = false;
+    };
+
+    processChildrenOnly = mkOption {
+      description = "Whether to only snapshot child datasets if recursing.";
+      type = types.bool;
+      default = false;
+    };
+  };
+
+  datasetConfig = config: {
+    settings = {
+      use_template = mkDefault config.useTemplate;
+      recursive = mkDefault config.recursive;
+      process_children_only = mkDefault config.processChildrenOnly;
+    };
+  };
+
+  # Extract pool names from configured datasets
+  pools = unique (map (d: head (builtins.match "([^/]+).*" d)) (attrNames cfg.datasets));
+
+  configFile = let
+    mkValueString = v:
+      if builtins.isList v then concatStringsSep "," v
+      else generators.mkValueStringDefault {} v;
+
+    mkKeyValue = k: v: if v == null then ""
+      else generators.mkKeyValueDefault { inherit mkValueString; } "=" k v;
+  in generators.toINI { inherit mkKeyValue; } cfg.settings;
+
+  configDir = pkgs.writeTextDir "sanoid.conf" configFile;
+
+in {
+
+    # Interface
+
+    options.services.sanoid = {
+      enable = mkEnableOption "Sanoid ZFS snapshotting service";
+
+      interval = mkOption {
+        type = types.str;
+        default = "hourly";
+        example = "daily";
+        description = ''
+          Run sanoid at this interval. The default is to run hourly.
+
+          The format is described in
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>.
+        '';
+      };
+
+      datasets = mkOption {
+        type = types.attrsOf (types.submodule ({ config, ... }: {
+          options = commonOptions // datasetOptions;
+          config = mkMerge [ (commonConfig config) (datasetConfig config) ];
+        }));
+        default = {};
+        description = "Datasets to snapshot.";
+      };
+
+      templates = mkOption {
+        type = types.attrsOf (types.submodule ({ config, ... }: {
+          options = commonOptions;
+          config = commonConfig config;
+        }));
+        default = {};
+        description = "Templates for datasets.";
+      };
+
+      settings = mkOption {
+        type = types.attrsOf datasetSettingsType;
+        description = ''
+          Free-form settings written directly to the config file. See
+          <link xlink:href="https://github.com/jimsalterjrs/sanoid/blob/master/sanoid.defaults.conf"/>
+          for allowed values.
+        '';
+      };
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--verbose" "--readonly" "--debug" ];
+        description = ''
+          Extra arguments to pass to sanoid. See
+          <link xlink:href="https://github.com/jimsalterjrs/sanoid/#sanoid-command-line-options"/>
+          for allowed options.
+        '';
+      };
+    };
+
+    # Implementation
+
+    config = mkIf cfg.enable {
+      services.sanoid.settings = mkMerge [
+        (mapAttrs' (d: v: nameValuePair ("template_" + d) v.settings) cfg.templates)
+        (mapAttrs (d: v: v.settings) cfg.datasets)
+      ];
+
+      systemd.services.sanoid = {
+        description = "Sanoid snapshot service";
+        serviceConfig = {
+          ExecStartPre = map (pool: lib.escapeShellArgs [
+            "+/run/booted-system/sw/bin/zfs" "allow"
+            "sanoid" "snapshot,mount,destroy" pool
+          ]) pools;
+          ExecStart = lib.escapeShellArgs ([
+            "${pkgs.sanoid}/bin/sanoid"
+            "--cron"
+            "--configdir" configDir
+          ] ++ cfg.extraArgs);
+          ExecStopPost = map (pool: lib.escapeShellArgs [
+            "+/run/booted-system/sw/bin/zfs" "unallow" "sanoid" pool
+          ]) pools;
+          User = "sanoid";
+          Group = "sanoid";
+          DynamicUser = true;
+          RuntimeDirectory = "sanoid";
+          CacheDirectory = "sanoid";
+        };
+        # Prevents missing snapshots during DST changes
+        environment.TZ = "UTC";
+        after = [ "zfs.target" ];
+        startAt = cfg.interval;
+      };
+    };
+
+    meta.maintainers = with maintainers; [ lopsided98 ];
+  }
diff --git a/nixpkgs/nixos/modules/services/backup/syncoid.nix b/nixpkgs/nixos/modules/services/backup/syncoid.nix
new file mode 100644
index 000000000000..fff119c2cf00
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/syncoid.nix
@@ -0,0 +1,172 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.syncoid;
+in {
+
+    # Interface
+
+    options.services.syncoid = {
+      enable = mkEnableOption "Syncoid ZFS synchronization service";
+
+      interval = mkOption {
+        type = types.str;
+        default = "hourly";
+        example = "*-*-* *:15:00";
+        description = ''
+          Run syncoid at this interval. The default is to run hourly.
+
+          The format is described in
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "root";
+        example = "backup";
+        description = ''
+          The user for the service. Sudo or ZFS privilege delegation must be
+          configured to use a user other than root.
+        '';
+      };
+
+      sshKey = mkOption {
+        type = types.nullOr types.path;
+        # Prevent key from being copied to store
+        apply = mapNullable toString;
+        default = null;
+        description = ''
+          SSH private key file to use to login to the remote system. Can be
+          overridden in individual commands.
+        '';
+      };
+
+      commonArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--no-sync-snap" ];
+        description = ''
+          Arguments to add to every syncoid command, unless disabled for that
+          command. See
+          <link xlink:href="https://github.com/jimsalterjrs/sanoid/#syncoid-command-line-options"/>
+          for available options.
+        '';
+      };
+
+      commands = mkOption {
+        type = types.attrsOf (types.submodule ({ name, ... }: {
+          options = {
+            source = mkOption {
+              type = types.str;
+              example = "pool/dataset";
+              description = ''
+                Source ZFS dataset. Can be either local or remote. Defaults to
+                the attribute name.
+              '';
+            };
+
+            target = mkOption {
+              type = types.str;
+              example = "user@server:pool/dataset";
+              description = ''
+                Target ZFS dataset. Can be either local
+                (<replaceable>pool/dataset</replaceable>) or remote
+                (<replaceable>user@server:pool/dataset</replaceable>).
+              '';
+            };
+
+            recursive = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Whether to also transfer child datasets.
+              '';
+            };
+
+            sshKey = mkOption {
+              type = types.nullOr types.path;
+              # Prevent key from being copied to store
+              apply = mapNullable toString;
+              description = ''
+                SSH private key file to use to login to the remote system.
+                Defaults to <option>services.syncoid.sshKey</option> option.
+              '';
+            };
+
+            sendOptions = mkOption {
+              type = types.separatedString " ";
+              default = "";
+              example = "Lc e";
+              description = ''
+                Advanced options to pass to zfs send. Options are specified
+                without their leading dashes and separated by spaces.
+              '';
+            };
+
+            recvOptions = mkOption {
+              type = types.separatedString " ";
+              default = "";
+              example = "ux recordsize o compression=lz4";
+              description = ''
+                Advanced options to pass to zfs recv. Options are specified
+                without their leading dashes and separated by spaces.
+              '';
+            };
+
+            useCommonArgs = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether to add the configured common arguments to this command.
+              '';
+            };
+
+            extraArgs = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "--sshport 2222" ];
+              description = "Extra syncoid arguments for this command.";
+            };
+          };
+          config = {
+            source = mkDefault name;
+            sshKey = mkDefault cfg.sshKey;
+          };
+        }));
+        default = {};
+        example = literalExample ''
+          {
+            "pool/test".target = "root@target:pool/test";
+          }
+        '';
+        description = "Syncoid commands to run.";
+      };
+    };
+
+    # Implementation
+
+    config = mkIf cfg.enable {
+      systemd.services.syncoid = {
+        description = "Syncoid ZFS synchronization service";
+        script = concatMapStringsSep "\n" (c: lib.escapeShellArgs
+          ([ "${pkgs.sanoid}/bin/syncoid" ]
+            ++ (optionals c.useCommonArgs cfg.commonArgs)
+            ++ (optional c.recursive "-r")
+            ++ (optionals (c.sshKey != null) [ "--sshkey" c.sshKey ])
+            ++ c.extraArgs
+            ++ [ "--sendoptions" c.sendOptions
+                 "--recvoptions" c.recvOptions
+                 c.source c.target
+               ])) (attrValues cfg.commands);
+        after = [ "zfs.target" ];
+        serviceConfig.User = cfg.user;
+        startAt = cfg.interval;
+      };
+    };
+
+    meta.maintainers = with maintainers; [ lopsided98 ];
+  }
diff --git a/nixpkgs/nixos/modules/services/backup/tarsnap.nix b/nixpkgs/nixos/modules/services/backup/tarsnap.nix
new file mode 100644
index 000000000000..6d99a1efb613
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/tarsnap.nix
@@ -0,0 +1,406 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  gcfg = config.services.tarsnap;
+
+  configFile = name: cfg: ''
+    keyfile ${cfg.keyfile}
+    ${optionalString (cfg.cachedir != null) "cachedir ${cfg.cachedir}"}
+    ${optionalString cfg.nodump "nodump"}
+    ${optionalString cfg.printStats "print-stats"}
+    ${optionalString cfg.printStats "humanize-numbers"}
+    ${optionalString (cfg.checkpointBytes != null) ("checkpoint-bytes "+cfg.checkpointBytes)}
+    ${optionalString cfg.aggressiveNetworking "aggressive-networking"}
+    ${concatStringsSep "\n" (map (v: "exclude ${v}") cfg.excludes)}
+    ${concatStringsSep "\n" (map (v: "include ${v}") cfg.includes)}
+    ${optionalString cfg.lowmem "lowmem"}
+    ${optionalString cfg.verylowmem "verylowmem"}
+    ${optionalString (cfg.maxbw != null) "maxbw ${toString cfg.maxbw}"}
+    ${optionalString (cfg.maxbwRateUp != null) "maxbw-rate-up ${toString cfg.maxbwRateUp}"}
+    ${optionalString (cfg.maxbwRateDown != null) "maxbw-rate-down ${toString cfg.maxbwRateDown}"}
+  '';
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "tarsnap" "cachedir" ] "Use services.tarsnap.archives.<name>.cachedir")
+  ];
+
+  options = {
+    services.tarsnap = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable periodic tarsnap backups.
+        '';
+      };
+
+      keyfile = mkOption {
+        type = types.str;
+        default = "/root/tarsnap.key";
+        description = ''
+          The keyfile which associates this machine with your tarsnap
+          account.
+          Create the keyfile with <command>tarsnap-keygen</command>.
+
+          Note that each individual archive (specified below) may also have its
+          own individual keyfile specified. Tarsnap does not allow multiple
+          concurrent backups with the same cache directory and key (starting a
+          new backup will cause another one to fail). If you have multiple
+          archives specified, you should either spread out your backups to be
+          far apart, or specify a separate key for each archive. By default
+          every archive defaults to using
+          <literal>"/root/tarsnap.key"</literal>.
+
+          It's recommended for backups that you generate a key for every archive
+          using <literal>tarsnap-keygen(1)</literal>, and then generate a
+          write-only tarsnap key using <literal>tarsnap-keymgmt(1)</literal>,
+          and keep your master key(s) for a particular machine off-site.
+
+          The keyfile name should be given as a string and not a path, to
+          avoid the key being copied into the Nix store.
+        '';
+      };
+
+      archives = mkOption {
+        type = types.attrsOf (types.submodule ({ config, ... }:
+          {
+            options = {
+              keyfile = mkOption {
+                type = types.str;
+                default = gcfg.keyfile;
+                description = ''
+                  Set a specific keyfile for this archive. This defaults to
+                  <literal>"/root/tarsnap.key"</literal> if left unspecified.
+
+                  Use this option if you want to run multiple backups
+                  concurrently - each archive must have a unique key. You can
+                  generate a write-only key derived from your master key (which
+                  is recommended) using <literal>tarsnap-keymgmt(1)</literal>.
+
+                  Note: every archive must have an individual master key. You
+                  must generate multiple keys with
+                  <literal>tarsnap-keygen(1)</literal>, and then generate write
+                  only keys from those.
+
+                  The keyfile name should be given as a string and not a path, to
+                  avoid the key being copied into the Nix store.
+                '';
+              };
+
+              cachedir = mkOption {
+                type = types.nullOr types.path;
+                default = "/var/cache/tarsnap/${utils.escapeSystemdPath config.keyfile}";
+                description = ''
+                  The cache allows tarsnap to identify previously stored data
+                  blocks, reducing archival time and bandwidth usage.
+
+                  Should the cache become desynchronized or corrupted, tarsnap
+                  will refuse to run until you manually rebuild the cache with
+                  <command>tarsnap --fsck</command>.
+
+                  Set to <literal>null</literal> to disable caching.
+                '';
+              };
+
+              nodump = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  Exclude files with the <literal>nodump</literal> flag.
+                '';
+              };
+
+              printStats = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  Print global archive statistics upon completion.
+                  The output is available via
+                  <command>systemctl status tarsnap-archive-name</command>.
+                '';
+              };
+
+              checkpointBytes = mkOption {
+                type = types.nullOr types.str;
+                default = "1GB";
+                description = ''
+                  Create a checkpoint every <literal>checkpointBytes</literal>
+                  of uploaded data (optionally specified using an SI prefix).
+
+                  1GB is the minimum value. A higher value is recommended,
+                  as checkpointing is expensive.
+
+                  Set to <literal>null</literal> to disable checkpointing.
+                '';
+              };
+
+              period = mkOption {
+                type = types.str;
+                default = "01:15";
+                example = "hourly";
+                description = ''
+                  Create archive at this interval.
+
+                  The format is described in
+                  <citerefentry><refentrytitle>systemd.time</refentrytitle>
+                  <manvolnum>7</manvolnum></citerefentry>.
+                '';
+              };
+
+              aggressiveNetworking = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  Upload data over multiple TCP connections, potentially
+                  increasing tarsnap's bandwidth utilisation at the cost
+                  of slowing down all other network traffic. Not
+                  recommended unless TCP congestion is the dominant
+                  limiting factor.
+                '';
+              };
+
+              directories = mkOption {
+                type = types.listOf types.path;
+                default = [];
+                description = "List of filesystem paths to archive.";
+              };
+
+              excludes = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = ''
+                  Exclude files and directories matching these patterns.
+                '';
+              };
+
+              includes = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = ''
+                  Include only files and directories matching these
+                  patterns (the empty list includes everything).
+
+                  Exclusions have precedence over inclusions.
+                '';
+              };
+
+              lowmem = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  Reduce memory consumption by not caching small files.
+                  Possibly beneficial if the average file size is smaller
+                  than 1 MB and the number of files is lower than the
+                  total amount of RAM in KB.
+                '';
+              };
+
+              verylowmem = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  Reduce memory consumption by a factor of 2 beyond what
+                  <literal>lowmem</literal> does, at the cost of significantly
+                  slowing down the archiving process.
+                '';
+              };
+
+              maxbw = mkOption {
+                type = types.nullOr types.int;
+                default = null;
+                description = ''
+                  Abort archival if upstream bandwidth usage in bytes
+                  exceeds this threshold.
+                '';
+              };
+
+              maxbwRateUp = mkOption {
+                type = types.nullOr types.int;
+                default = null;
+                example = literalExample "25 * 1000";
+                description = ''
+                  Upload bandwidth rate limit in bytes.
+                '';
+              };
+
+              maxbwRateDown = mkOption {
+                type = types.nullOr types.int;
+                default = null;
+                example = literalExample "50 * 1000";
+                description = ''
+                  Download bandwidth rate limit in bytes.
+                '';
+              };
+
+              verbose = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  Whether to produce verbose logging output.
+                '';
+              };
+              explicitSymlinks = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  Whether to follow symlinks specified as archives.
+                '';
+              };
+              followSymlinks = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  Whether to follow all symlinks in archive trees.
+                '';
+              };
+            };
+          }
+        ));
+
+        default = {};
+
+        example = literalExample ''
+          {
+            nixos =
+              { directories = [ "/home" "/root/ssl" ];
+              };
+
+            gamedata =
+              { directories = [ "/var/lib/minecraft" ];
+                period      = "*:30";
+              };
+          }
+        '';
+
+        description = ''
+          Tarsnap archive configurations. Each attribute names an archive
+          to be created at a given time interval, according to the options
+          associated with it. When uploading to the tarsnap server,
+          archive names are suffixed by a 1 second resolution timestamp.
+
+          For each member of the set is created a timer which triggers the
+          instanced <literal>tarsnap-archive-name</literal> service unit. You may use
+          <command>systemctl start tarsnap-archive-name</command> to
+          manually trigger creation of <literal>archive-name</literal> at
+          any time.
+        '';
+      };
+    };
+  };
+
+  config = mkIf gcfg.enable {
+    assertions =
+      (mapAttrsToList (name: cfg:
+        { assertion = cfg.directories != [];
+          message = "Must specify paths for tarsnap to back up";
+        }) gcfg.archives) ++
+      (mapAttrsToList (name: cfg:
+        { assertion = !(cfg.lowmem && cfg.verylowmem);
+          message = "You cannot set both lowmem and verylowmem";
+        }) gcfg.archives);
+
+    systemd.services =
+      (mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}" {
+        description = "Tarsnap archive '${name}'";
+        requires    = [ "network-online.target" ];
+        after       = [ "network-online.target" ];
+
+        path = with pkgs; [ iputils tarsnap utillinux ];
+
+        # In order for the persistent tarsnap timer to work reliably, we have to
+        # make sure that the tarsnap server is reachable after systemd starts up
+        # the service - therefore we sleep in a loop until we can ping the
+        # endpoint.
+        preStart = ''
+          while ! ping -q -c 1 v1-0-0-server.tarsnap.com &> /dev/null; do sleep 3; done
+        '';
+
+        script = let
+          tarsnap = ''tarsnap --configfile "/etc/tarsnap/${name}.conf"'';
+          run = ''${tarsnap} -c -f "${name}-$(date +"%Y%m%d%H%M%S")" \
+                        ${optionalString cfg.verbose "-v"} \
+                        ${optionalString cfg.explicitSymlinks "-H"} \
+                        ${optionalString cfg.followSymlinks "-L"} \
+                        ${concatStringsSep " " cfg.directories}'';
+          in if (cfg.cachedir != null) then ''
+            mkdir -p ${cfg.cachedir}
+            chmod 0700 ${cfg.cachedir}
+
+            ( flock 9
+              if [ ! -e ${cfg.cachedir}/firstrun ]; then
+                ( flock 10
+                  flock -u 9
+                  ${tarsnap} --fsck
+                  flock 9
+                ) 10>${cfg.cachedir}/firstrun
+              fi
+            ) 9>${cfg.cachedir}/lockf
+
+             exec flock ${cfg.cachedir}/firstrun ${run}
+          '' else "exec ${run}";
+
+        serviceConfig = {
+          Type = "oneshot";
+          IOSchedulingClass = "idle";
+          NoNewPrivileges = "true";
+          CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
+          PermissionsStartOnly = "true";
+        };
+      }) gcfg.archives) //
+
+      (mapAttrs' (name: cfg: nameValuePair "tarsnap-restore-${name}"{
+        description = "Tarsnap restore '${name}'";
+        requires    = [ "network-online.target" ];
+
+        path = with pkgs; [ iputils tarsnap utillinux ];
+
+        script = let
+          tarsnap = ''tarsnap --configfile "/etc/tarsnap/${name}.conf"'';
+          lastArchive = ''$(${tarsnap} --list-archives | sort | tail -1)'';
+          run = ''${tarsnap} -x -f "${lastArchive}" ${optionalString cfg.verbose "-v"}'';
+
+        in if (cfg.cachedir != null) then ''
+          mkdir -p ${cfg.cachedir}
+          chmod 0700 ${cfg.cachedir}
+
+          ( flock 9
+            if [ ! -e ${cfg.cachedir}/firstrun ]; then
+              ( flock 10
+                flock -u 9
+                ${tarsnap} --fsck
+                flock 9
+              ) 10>${cfg.cachedir}/firstrun
+            fi
+          ) 9>${cfg.cachedir}/lockf
+
+           exec flock ${cfg.cachedir}/firstrun ${run}
+        '' else "exec ${run}";
+
+        serviceConfig = {
+          Type = "oneshot";
+          IOSchedulingClass = "idle";
+          NoNewPrivileges = "true";
+          CapabilityBoundingSet = [ "CAP_DAC_READ_SEARCH" ];
+          PermissionsStartOnly = "true";
+        };
+      }) gcfg.archives);
+
+    # Note: the timer must be Persistent=true, so that systemd will start it even
+    # if e.g. your laptop was asleep while the latest interval occurred.
+    systemd.timers = mapAttrs' (name: cfg: nameValuePair "tarsnap-${name}"
+      { timerConfig.OnCalendar = cfg.period;
+        timerConfig.Persistent = "true";
+        wantedBy = [ "timers.target" ];
+      }) gcfg.archives;
+
+    environment.etc =
+      mapAttrs' (name: cfg: nameValuePair "tarsnap/${name}.conf"
+        { text = configFile name cfg;
+        }) gcfg.archives;
+
+    environment.systemPackages = [ pkgs.tarsnap ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/tsm.nix b/nixpkgs/nixos/modules/services/backup/tsm.nix
new file mode 100644
index 000000000000..6c238745797e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/tsm.nix
@@ -0,0 +1,106 @@
+{ config, lib, ... }:
+
+let
+
+  inherit (lib.attrsets) hasAttr;
+  inherit (lib.modules) mkDefault mkIf;
+  inherit (lib.options) mkEnableOption mkOption;
+  inherit (lib.types) nullOr strMatching;
+
+  options.services.tsmBackup = {
+    enable = mkEnableOption ''
+      automatic backups with the
+      IBM Spectrum Protect (Tivoli Storage Manager, TSM) client.
+      This also enables
+      <option>programs.tsmClient.enable</option>
+    '';
+    command = mkOption {
+      type = strMatching ".+";
+      default = "backup";
+      example = "incr";
+      description = ''
+        The actual command passed to the
+        <literal>dsmc</literal> executable to start the backup.
+      '';
+    };
+    servername = mkOption {
+      type = strMatching ".+";
+      example = "mainTsmServer";
+      description = ''
+        Create a systemd system service
+        <literal>tsm-backup.service</literal> that starts
+        a backup based on the given servername's stanza.
+        Note that this server's
+        <option>passwdDir</option> will default to
+        <filename>/var/lib/tsm-backup/password</filename>
+        (but may be overridden);
+        also, the service will use
+        <filename>/var/lib/tsm-backup</filename> as
+        <literal>HOME</literal> when calling
+        <literal>dsmc</literal>.
+      '';
+    };
+    autoTime = mkOption {
+      type = nullOr (strMatching ".+");
+      default = null;
+      example = "12:00";
+      description = ''
+        The backup service will be invoked
+        automatically at the given date/time,
+        which must be in the format described in
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+        The default <literal>null</literal>
+        disables automatic backups.
+      '';
+    };
+  };
+
+  cfg = config.services.tsmBackup;
+  cfgPrg = config.programs.tsmClient;
+
+  assertions = [
+    {
+      assertion = hasAttr cfg.servername cfgPrg.servers;
+      message = "TSM service servername not found in list of servers";
+    }
+    {
+      assertion = cfgPrg.servers.${cfg.servername}.genPasswd;
+      message = "TSM service requires automatic password generation";
+    }
+  ];
+
+in
+
+{
+
+  inherit options;
+
+  config = mkIf cfg.enable {
+    inherit assertions;
+    programs.tsmClient.enable = true;
+    programs.tsmClient.servers.${cfg.servername}.passwdDir =
+      mkDefault "/var/lib/tsm-backup/password";
+    systemd.services.tsm-backup = {
+      description = "IBM Spectrum Protect (Tivoli Storage Manager) Backup";
+      # DSM_LOG needs a trailing slash to have it treated as a directory.
+      # `/var/log` would be littered with TSM log files otherwise.
+      environment.DSM_LOG = "/var/log/tsm-backup/";
+      # TSM needs a HOME dir to store certificates.
+      environment.HOME = "/var/lib/tsm-backup";
+      # for exit status description see
+      # https://www.ibm.com/support/knowledgecenter/en/SSEQVQ_8.1.8/client/c_sched_rtncode.html
+      serviceConfig.SuccessExitStatus = "4 8";
+      # The `-se` option must come after the command.
+      # The `-optfile` option suppresses a `dsm.opt`-not-found warning.
+      serviceConfig.ExecStart =
+        "${cfgPrg.wrappedPackage}/bin/dsmc ${cfg.command} -se='${cfg.servername}' -optfile=/dev/null";
+      serviceConfig.LogsDirectory = "tsm-backup";
+      serviceConfig.StateDirectory = "tsm-backup";
+      serviceConfig.StateDirectoryMode = "0750";
+      startAt = mkIf (cfg.autoTime!=null) cfg.autoTime;
+    };
+  };
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/backup/zfs-replication.nix b/nixpkgs/nixos/modules/services/backup/zfs-replication.nix
new file mode 100644
index 000000000000..5a64304275d5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/zfs-replication.nix
@@ -0,0 +1,90 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.zfs.autoReplication;
+  recursive = optionalString cfg.recursive " --recursive";
+  followDelete = optionalString cfg.followDelete " --follow-delete";
+in {
+  options = {
+    services.zfs.autoReplication = {
+      enable = mkEnableOption "ZFS snapshot replication.";
+
+      followDelete = mkOption {
+        description = "Remove remote snapshots that don't have a local correspondant.";
+        default = true;
+        type = types.bool;
+      };
+
+      host = mkOption {
+        description = "Remote host where snapshots should be sent.";
+        example = "example.com";
+        type = types.str;
+      };
+
+      identityFilePath = mkOption {
+        description = "Path to SSH key used to login to host.";
+        example = "/home/username/.ssh/id_rsa";
+        type = types.path;
+      };
+
+      localFilesystem = mkOption {
+        description = "Local ZFS fileystem from which snapshots should be sent.  Defaults to the attribute name.";
+        example = "pool/file/path";
+        type = types.str;
+      };
+
+      remoteFilesystem = mkOption {
+        description = "Remote ZFS filesystem where snapshots should be sent.";
+        example = "pool/file/path";
+        type = types.str;
+      };
+
+      recursive = mkOption {
+        description = "Recursively discover snapshots to send.";
+        default = true;
+        type = types.bool;
+      };
+
+      username = mkOption {
+        description = "Username used by SSH to login to remote host.";
+        example = "username";
+        type = types.str;
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    environment.systemPackages = [
+      pkgs.lz4
+    ];
+
+    systemd.services.zfs-replication = {
+      after = [
+        "zfs-snapshot-daily.service"
+        "zfs-snapshot-frequent.service"
+        "zfs-snapshot-hourly.service"
+        "zfs-snapshot-monthly.service"
+        "zfs-snapshot-weekly.service"
+      ];
+      description = "ZFS Snapshot Replication";
+      documentation = [
+        "https://github.com/alunduil/zfs-replicate"
+      ];
+      restartIfChanged = false;
+      serviceConfig.ExecStart = "${pkgs.zfs-replicate}/bin/zfs-replicate${recursive} -l ${escapeShellArg cfg.username} -i ${escapeShellArg cfg.identityFilePath}${followDelete} ${escapeShellArg cfg.host} ${escapeShellArg cfg.remoteFilesystem} ${escapeShellArg cfg.localFilesystem}";
+      wantedBy = [
+        "zfs-snapshot-daily.service"
+        "zfs-snapshot-frequent.service"
+        "zfs-snapshot-hourly.service"
+        "zfs-snapshot-monthly.service"
+        "zfs-snapshot-weekly.service"
+      ];
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ alunduil ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/backup/znapzend.nix b/nixpkgs/nixos/modules/services/backup/znapzend.nix
new file mode 100644
index 000000000000..8098617d11f3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/backup/znapzend.nix
@@ -0,0 +1,469 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with types;
+
+let
+
+  planDescription = ''
+      The znapzend backup plan to use for the source.
+
+      The plan specifies how often to backup and for how long to keep the
+      backups. It consists of a series of retention periodes to interval
+      associations:
+
+      <literal>
+        retA=>intA,retB=>intB,...
+      </literal>
+
+      Both intervals and retention periods are expressed in standard units
+      of time or multiples of them. You can use both the full name or a
+      shortcut according to the following listing:
+
+      <literal>
+        second|sec|s, minute|min, hour|h, day|d, week|w, month|mon|m, year|y
+      </literal>
+
+      See <citerefentry><refentrytitle>znapzendzetup</refentrytitle><manvolnum>1</manvolnum></citerefentry> for more info.
+  '';
+  planExample = "1h=>10min,1d=>1h,1w=>1d,1m=>1w,1y=>1m";
+
+  # A type for a string of the form number{b|k|M|G}
+  mbufferSizeType = str // {
+    check = x: str.check x && builtins.isList (builtins.match "^[0-9]+[bkMG]$" x);
+    description = "string of the form number{b|k|M|G}";
+  };
+
+  enabledFeatures = concatLists (mapAttrsToList (name: enabled: optional enabled name) cfg.features);
+
+  # Type for a string that must contain certain other strings (the list parameter).
+  # Note that these would need regex escaping.
+  stringContainingStrings = list: let
+    matching = s: map (str: builtins.match ".*${str}.*" s) list;
+  in str // {
+    check = x: str.check x && all isList (matching x);
+    description = "string containing all of the characters ${concatStringsSep ", " list}";
+  };
+
+  timestampType = stringContainingStrings [ "%Y" "%m" "%d" "%H" "%M" "%S" ];
+
+  destType = srcConfig: submodule ({ name, ... }: {
+    options = {
+
+      label = mkOption {
+        type = str;
+        description = "Label for this destination. Defaults to the attribute name.";
+      };
+
+      plan = mkOption {
+        type = str;
+        description = planDescription;
+        example = planExample;
+      };
+
+      dataset = mkOption {
+        type = str;
+        description = "Dataset name to send snapshots to.";
+        example = "tank/main";
+      };
+
+      host = mkOption {
+        type = nullOr str;
+        description = ''
+          Host to use for the destination dataset. Can be prefixed with
+          <literal>user@</literal> to specify the ssh user.
+        '';
+        default = null;
+        example = "john@example.com";
+      };
+
+      presend = mkOption {
+        type = nullOr str;
+        description = ''
+          Command to run before sending the snapshot to the destination.
+          Intended to run a remote script via <command>ssh</command> on the
+          destination, e.g. to bring up a backup disk or server or to put a
+          zpool online/offline. See also <option>postsend</option>.
+        '';
+        default = null;
+        example = "ssh root@bserv zpool import -Nf tank";
+      };
+
+      postsend = mkOption {
+        type = nullOr str;
+        description = ''
+          Command to run after sending the snapshot to the destination.
+          Intended to run a remote script via <command>ssh</command> on the
+          destination, e.g. to bring up a backup disk or server or to put a
+          zpool online/offline. See also <option>presend</option>.
+        '';
+        default = null;
+        example = "ssh root@bserv zpool export tank";
+      };
+    };
+
+    config = {
+      label = mkDefault name;
+      plan = mkDefault srcConfig.plan;
+    };
+  });
+
+
+
+  srcType = submodule ({ name, config, ... }: {
+    options = {
+
+      enable = mkOption {
+        type = bool;
+        description = "Whether to enable this source.";
+        default = true;
+      };
+
+      recursive = mkOption {
+        type = bool;
+        description = "Whether to do recursive snapshots.";
+        default = false;
+      };
+
+      mbuffer = {
+        enable = mkOption {
+          type = bool;
+          description = "Whether to use <command>mbuffer</command>.";
+          default = false;
+        };
+
+        port = mkOption {
+          type = nullOr ints.u16;
+          description = ''
+              Port to use for <command>mbuffer</command>.
+
+              If this is null, it will run <command>mbuffer</command> through
+              ssh.
+
+              If this is not null, it will run <command>mbuffer</command>
+              directly through TCP, which is not encrypted but faster. In that
+              case the given port needs to be open on the destination host.
+          '';
+          default = null;
+        };
+
+        size = mkOption {
+          type = mbufferSizeType;
+          description = ''
+            The size for <command>mbuffer</command>.
+            Supports the units b, k, M, G.
+          '';
+          default = "1G";
+          example = "128M";
+        };
+      };
+
+      presnap = mkOption {
+        type = nullOr str;
+        description = ''
+          Command to run before snapshots are taken on the source dataset,
+          e.g. for database locking/flushing. See also
+          <option>postsnap</option>.
+        '';
+        default = null;
+        example = literalExample ''
+          ''${pkgs.mariadb}/bin/mysql -e "set autocommit=0;flush tables with read lock;\\! ''${pkgs.coreutils}/bin/sleep 600" &  ''${pkgs.coreutils}/bin/echo $! > /tmp/mariadblock.pid ; sleep 10
+        '';
+      };
+
+      postsnap = mkOption {
+        type = nullOr str;
+        description = ''
+          Command to run after snapshots are taken on the source dataset,
+          e.g. for database unlocking. See also <option>presnap</option>.
+        '';
+        default = null;
+        example = literalExample ''
+          ''${pkgs.coreutils}/bin/kill `''${pkgs.coreutils}/bin/cat /tmp/mariadblock.pid`;''${pkgs.coreutils}/bin/rm /tmp/mariadblock.pid
+        '';
+      };
+
+      timestampFormat = mkOption {
+        type = timestampType;
+        description = ''
+          The timestamp format to use for constructing snapshot names.
+          The syntax is <literal>strftime</literal>-like. The string must
+          consist of the mandatory <literal>%Y %m %d %H %M %S</literal>.
+          Optionally  <literal>- _ . :</literal>  characters as well as any
+          alphanumeric character are allowed. If suffixed by a
+          <literal>Z</literal>, times will be in UTC.
+        '';
+        default = "%Y-%m-%d-%H%M%S";
+        example = "znapzend-%m.%d.%Y-%H%M%SZ";
+      };
+
+      sendDelay = mkOption {
+        type = int;
+        description = ''
+          Specify delay (in seconds) before sending snaps to the destination.
+          May be useful if you want to control sending time.
+        '';
+        default = 0;
+        example = 60;
+      };
+
+      plan = mkOption {
+        type = str;
+        description = planDescription;
+        example = planExample;
+      };
+
+      dataset = mkOption {
+        type = str;
+        description = "The dataset to use for this source.";
+        example = "tank/home";
+      };
+
+      destinations = mkOption {
+        type = loaOf (destType config);
+        description = "Additional destinations.";
+        default = {};
+        example = literalExample ''
+          {
+            local = {
+              dataset = "btank/backup";
+              presend = "zpool import -N btank";
+              postsend = "zpool export btank";
+            };
+            remote = {
+              host = "john@example.com";
+              dataset = "tank/john";
+            };
+          };
+        '';
+      };
+    };
+
+    config = {
+      dataset = mkDefault name;
+    };
+
+  });
+
+  ### Generating the configuration from here
+
+  cfg = config.services.znapzend;
+
+  onOff = b: if b then "on" else "off";
+  nullOff = b: if b == null then "off" else toString b;
+  stripSlashes = replaceStrings [ "/" ] [ "." ];
+
+  attrsToFile = config: concatStringsSep "\n" (builtins.attrValues (
+    mapAttrs (n: v: "${n}=${v}") config));
+
+  mkDestAttrs = dst: with dst;
+    mapAttrs' (n: v: nameValuePair "dst_${label}${n}" v) ({
+      "" = optionalString (host != null) "${host}:" + dataset;
+      _plan = plan;
+    } // optionalAttrs (presend != null) {
+      _precmd = presend;
+    } // optionalAttrs (postsend != null) {
+      _pstcmd = postsend;
+    });
+
+  mkSrcAttrs = srcCfg: with srcCfg; {
+    enabled = onOff enable;
+    # mbuffer is not referenced by its full path to accomodate non-NixOS systems or differing mbuffer versions between source and target
+    mbuffer = with mbuffer; if enable then "mbuffer"
+        + optionalString (port != null) ":${toString port}" else "off";
+    mbuffer_size = mbuffer.size;
+    post_znap_cmd = nullOff postsnap;
+    pre_znap_cmd = nullOff presnap;
+    recursive = onOff recursive;
+    src = dataset;
+    src_plan = plan;
+    tsformat = timestampFormat;
+    zend_delay = toString sendDelay;
+  } // fold (a: b: a // b) {} (
+    map mkDestAttrs (builtins.attrValues destinations)
+  );
+
+  files = mapAttrs' (n: srcCfg: let
+    fileText = attrsToFile (mkSrcAttrs srcCfg);
+  in {
+    name = srcCfg.dataset;
+    value = pkgs.writeText (stripSlashes srcCfg.dataset) fileText;
+  }) cfg.zetup;
+
+in
+{
+  options = {
+    services.znapzend = {
+      enable = mkEnableOption "ZnapZend ZFS backup daemon";
+
+      logLevel = mkOption {
+        default = "debug";
+        example = "warning";
+        type = enum ["debug" "info" "warning" "err" "alert"];
+        description = ''
+          The log level when logging to file. Any of debug, info, warning, err,
+          alert. Default in daemonized form is debug.
+        '';
+      };
+
+      logTo = mkOption {
+        type = str;
+        default = "syslog::daemon";
+        example = "/var/log/znapzend.log";
+        description = ''
+          Where to log to (syslog::&lt;facility&gt; or &lt;filepath&gt;).
+        '';
+      };
+
+      noDestroy = mkOption {
+        type = bool;
+        default = false;
+        description = "Does all changes to the filesystem except destroy.";
+      };
+
+      autoCreation = mkOption {
+        type = bool;
+        default = false;
+        description = "Automatically create the destination dataset if it does not exists.";
+      };
+
+      zetup = mkOption {
+        type = loaOf srcType;
+        description = "Znapzend configuration.";
+        default = {};
+        example = literalExample ''
+          {
+            "tank/home" = {
+              # Make snapshots of tank/home every hour, keep those for 1 day,
+              # keep every days snapshot for 1 month, etc.
+              plan = "1d=>1h,1m=>1d,1y=>1m";
+              recursive = true;
+              # Send all those snapshots to john@example.com:rtank/john as well
+              destinations.remote = {
+                host = "john@example.com";
+                dataset = "rtank/john";
+              };
+            };
+          };
+        '';
+      };
+
+      pure = mkOption {
+        type = bool;
+        description = ''
+          Do not persist any stateful znapzend setups. If this option is
+          enabled, your previously set znapzend setups will be cleared and only
+          the ones defined with this module will be applied.
+        '';
+        default = false;
+      };
+
+      features.oracleMode = mkEnableOption ''
+        Destroy snapshots one by one instead of using one long argument list.
+        If source and destination are out of sync for a long time, you may have
+        so many snapshots to destroy that the argument gets is too long and the
+        command fails.
+      '';
+      features.recvu = mkEnableOption ''
+        recvu feature which uses <literal>-u</literal> on the receiving end to keep the destination
+        filesystem unmounted.
+      '';
+      features.compressed = mkEnableOption ''
+        compressed feature which adds the options <literal>-Lce</literal> to
+        the <command>zfs send</command> command. When this is enabled, make
+        sure that both the sending and receiving pool have the same relevant
+        features enabled. Using <literal>-c</literal> will skip unneccessary
+        decompress-compress stages, <literal>-L</literal> is for large block
+        support and -e is for embedded data support. see
+        <citerefentry><refentrytitle>znapzend</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        and <citerefentry><refentrytitle>zfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        for more info.
+      '';
+      features.sendRaw = mkEnableOption ''
+        sendRaw feature which adds the options <literal>-w</literal> to the
+        <command>zfs send</command> command. For encrypted source datasets this
+        instructs zfs not to decrypt before sending which results in a remote
+        backup that can't be read without the encryption key/passphrase, useful
+        when the remote isn't fully trusted or not physically secure. This
+        option must be used consistently, raw incrementals cannot be based on
+        non-raw snapshots and vice versa.
+      '';
+      features.skipIntermediates = mkEnableOption ''
+        Enable the skipIntermediates feature to send a single increment
+        between latest common snapshot and the newly made one. It may skip
+        several source snaps if the destination was offline for some time, and
+        it should skip snapshots not managed by znapzend. Normally for online
+        destinations, the new snapshot is sent as soon as it is created on the
+        source, so there are no automatic increments to skip.
+      '';
+      features.lowmemRecurse = mkEnableOption ''
+        use lowmemRecurse on systems where you have too many datasets, so a
+        recursive listing of attributes to find backup plans exhausts the
+        memory available to <command>znapzend</command>: instead, go the slower
+        way to first list all impacted dataset names, and then query their
+        configs one by one.
+      '';
+      features.zfsGetType = mkEnableOption ''
+        use zfsGetType if your <command>zfs get</command> supports a
+        <literal>-t</literal> argument for filtering by dataset type at all AND
+        lists properties for snapshots by default when recursing, so that there
+        is too much data to process while searching for backup plans.
+        If these two conditions apply to your system, the time needed for a
+        <literal>--recursive</literal> search for backup plans can literally
+        differ by hundreds of times (depending on the amount of snapshots in
+        that dataset tree... and a decent backup plan will ensure you have a lot
+        of those), so you would benefit from requesting this feature.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.znapzend ];
+
+    systemd.services = {
+      znapzend = {
+        description = "ZnapZend - ZFS Backup System";
+        wantedBy    = [ "zfs.target" ];
+        after       = [ "zfs.target" ];
+
+        path = with pkgs; [ zfs mbuffer openssh ];
+
+        preStart = optionalString cfg.pure ''
+          echo Resetting znapzend zetups
+          ${pkgs.znapzend}/bin/znapzendzetup list \
+            | grep -oP '(?<=\*\*\* backup plan: ).*(?= \*\*\*)' \
+            | xargs -I{} ${pkgs.znapzend}/bin/znapzendzetup delete "{}"
+        '' + concatStringsSep "\n" (mapAttrsToList (dataset: config: ''
+          echo Importing znapzend zetup ${config} for dataset ${dataset}
+          ${pkgs.znapzend}/bin/znapzendzetup import --write ${dataset} ${config} &
+        '') files) + ''
+          wait
+        '';
+
+        serviceConfig = {
+          # znapzendzetup --import apparently tries to connect to the backup
+          # host 3 times with a timeout of 30 seconds, leading to a startup
+          # delay of >90s when the host is down, which is just above the default
+          # service timeout of 90 seconds. Increase the timeout so it doesn't
+          # make the service fail in that case.
+          TimeoutStartSec = 180;
+          # Needs to have write access to ZFS
+          User = "root";
+          ExecStart = let
+            args = concatStringsSep " " [
+              "--logto=${cfg.logTo}"
+              "--loglevel=${cfg.logLevel}"
+              (optionalString cfg.noDestroy "--nodestroy")
+              (optionalString cfg.autoCreation "--autoCreation")
+              (optionalString (enabledFeatures != [])
+                "--features=${concatStringsSep "," enabledFeatures}")
+            ]; in "${pkgs.znapzend}/bin/znapzend ${args}";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+          Restart = "on-failure";
+        };
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ infinisil SlothOfAnarchy ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix
new file mode 100644
index 000000000000..38db10406b9a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/conf.nix
@@ -0,0 +1,31 @@
+{ hadoop, pkgs }:
+let
+  propertyXml = name: value: ''
+    <property>
+      <name>${name}</name>
+      <value>${builtins.toString value}</value>
+    </property>
+  '';
+  siteXml = fileName: properties: pkgs.writeTextDir fileName ''
+    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+    <!-- generated by NixOS -->
+    <configuration>
+      ${builtins.concatStringsSep "\n" (pkgs.lib.mapAttrsToList propertyXml properties)}
+    </configuration>
+  '';
+  userFunctions = ''
+    hadoop_verify_logdir() {
+      echo Skipping verification of log directory
+    }
+  '';
+in
+pkgs.buildEnv {
+  name = "hadoop-conf";
+  paths = [
+    (siteXml "core-site.xml" hadoop.coreSite)
+    (siteXml "hdfs-site.xml" hadoop.hdfsSite)
+    (siteXml "mapred-site.xml" hadoop.mapredSite)
+    (siteXml "yarn-site.xml" hadoop.yarnSite)
+    (pkgs.writeTextDir "hadoop-user-functions.sh" userFunctions)
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
new file mode 100644
index 000000000000..bfb73f683715
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/default.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+{
+  imports = [ ./yarn.nix ./hdfs.nix ];
+
+  options.services.hadoop = {
+    coreSite = mkOption {
+      default = {};
+      example = literalExample ''
+        {
+          "fs.defaultFS" = "hdfs://localhost";
+        }
+      '';
+      description = "Hadoop core-site.xml definition";
+    };
+
+    hdfsSite = mkOption {
+      default = {};
+      example = literalExample ''
+        {
+          "dfs.nameservices" = "namenode1";
+        }
+      '';
+      description = "Hadoop hdfs-site.xml definition";
+    };
+
+    mapredSite = mkOption {
+      default = {};
+      example = literalExample ''
+        {
+          "mapreduce.map.cpu.vcores" = "1";
+        }
+      '';
+      description = "Hadoop mapred-site.xml definition";
+    };
+
+    yarnSite = mkOption {
+      default = {};
+      example = literalExample ''
+        {
+          "yarn.resourcemanager.ha.id" = "resourcemanager1";
+        }
+      '';
+      description = "Hadoop yarn-site.xml definition";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.hadoop;
+      defaultText = "pkgs.hadoop";
+      example = literalExample "pkgs.hadoop";
+      description = ''
+      '';
+    };
+  };
+
+
+  config = mkMerge [
+    (mkIf (builtins.hasAttr "yarn" config.users.users ||
+           builtins.hasAttr "hdfs" config.users.users) {
+      users.groups.hadoop = {
+        gid = config.ids.gids.hadoop;
+      };
+    })
+
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix
new file mode 100644
index 000000000000..4f4b0a92108f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/hdfs.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ...}:
+let
+  cfg = config.services.hadoop;
+  hadoopConf = import ./conf.nix { hadoop = cfg; pkgs = pkgs; };
+in
+with lib;
+{
+  options.services.hadoop.hdfs = {
+    namenode.enabled = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run the Hadoop YARN NameNode
+      '';
+    };
+    datanode.enabled = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run the Hadoop YARN DataNode
+      '';
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.hdfs.namenode.enabled {
+      systemd.services.hdfs-namenode = {
+        description = "Hadoop HDFS NameNode";
+        wantedBy = [ "multi-user.target" ];
+
+        environment = {
+          HADOOP_HOME = "${cfg.package}";
+        };
+
+        preStart = ''
+          ${cfg.package}/bin/hdfs --config ${hadoopConf} namenode -format -nonInteractive || true
+        '';
+
+        serviceConfig = {
+          User = "hdfs";
+          SyslogIdentifier = "hdfs-namenode";
+          ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} namenode";
+        };
+      };
+    })
+    (mkIf cfg.hdfs.datanode.enabled {
+      systemd.services.hdfs-datanode = {
+        description = "Hadoop HDFS DataNode";
+        wantedBy = [ "multi-user.target" ];
+
+        environment = {
+          HADOOP_HOME = "${cfg.package}";
+        };
+
+        serviceConfig = {
+          User = "hdfs";
+          SyslogIdentifier = "hdfs-datanode";
+          ExecStart = "${cfg.package}/bin/hdfs --config ${hadoopConf} datanode";
+        };
+      };
+    })
+    (mkIf (
+        cfg.hdfs.namenode.enabled || cfg.hdfs.datanode.enabled
+    ) {
+      users.users.hdfs = {
+        description = "Hadoop HDFS user";
+        group = "hadoop";
+        uid = config.ids.uids.hdfs;
+      };
+    })
+
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix b/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix
new file mode 100644
index 000000000000..c92020637e47
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/hadoop/yarn.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ...}:
+let
+  cfg = config.services.hadoop;
+  hadoopConf = import ./conf.nix { hadoop = cfg; pkgs = pkgs; };
+in
+with lib;
+{
+  options.services.hadoop.yarn = {
+    resourcemanager.enabled = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run the Hadoop YARN ResourceManager
+      '';
+    };
+    nodemanager.enabled = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run the Hadoop YARN NodeManager
+      '';
+    };
+  };
+
+  config = mkMerge [
+    (mkIf (
+        cfg.yarn.resourcemanager.enabled || cfg.yarn.nodemanager.enabled
+    ) {
+
+      users.users.yarn = {
+        description = "Hadoop YARN user";
+        group = "hadoop";
+        uid = config.ids.uids.yarn;
+      };
+    })
+
+    (mkIf cfg.yarn.resourcemanager.enabled {
+      systemd.services.yarn-resourcemanager = {
+        description = "Hadoop YARN ResourceManager";
+        wantedBy = [ "multi-user.target" ];
+
+        environment = {
+          HADOOP_HOME = "${cfg.package}";
+        };
+
+        serviceConfig = {
+          User = "yarn";
+          SyslogIdentifier = "yarn-resourcemanager";
+          ExecStart = "${cfg.package}/bin/yarn --config ${hadoopConf} " +
+                      " resourcemanager";
+        };
+      };
+    })
+
+    (mkIf cfg.yarn.nodemanager.enabled {
+      systemd.services.yarn-nodemanager = {
+        description = "Hadoop YARN NodeManager";
+        wantedBy = [ "multi-user.target" ];
+
+        environment = {
+          HADOOP_HOME = "${cfg.package}";
+        };
+
+        serviceConfig = {
+          User = "yarn";
+          SyslogIdentifier = "yarn-nodemanager";
+          ExecStart = "${cfg.package}/bin/yarn --config ${hadoopConf} " +
+                      " nodemanager";
+        };
+      };
+    })
+
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/k3s/default.nix b/nixpkgs/nixos/modules/services/cluster/k3s/default.nix
new file mode 100644
index 000000000000..2e8bf20a68fc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/k3s/default.nix
@@ -0,0 +1,101 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.k3s;
+in
+{
+  # interface
+  options.services.k3s = {
+    enable = mkEnableOption "k3s";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.k3s;
+      defaultText = "pkgs.k3s";
+      example = literalExample "pkgs.k3s";
+      description = "Package that should be used for k3s";
+    };
+
+    role = mkOption {
+      description = ''
+        Whether k3s should run as a server or agent.
+        Note that the server, by default, also runs as an agent.
+      '';
+      default = "server";
+      type = types.enum [ "server" "agent" ];
+    };
+
+    serverAddr = mkOption {
+      type = types.str;
+      description = "The k3s server to connect to. This option only makes sense for an agent.";
+      example = "https://10.0.0.10:6443";
+      default = "";
+    };
+
+    token = mkOption {
+      type = types.str;
+      description = "The k3s token to use when connecting to the server. This option only makes sense for an agent.";
+      default = "";
+    };
+
+    docker = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Use docker to run containers rather than the built-in containerd.";
+    };
+
+    extraFlags = mkOption {
+      description = "Extra flags to pass to the k3s command.";
+      default = "";
+      example = "--no-deploy traefik --cluster-cidr 10.24.0.0/16";
+    };
+
+    disableAgent = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Only run the server. This option only makes sense for a server.";
+    };
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.role == "agent" -> cfg.serverAddr != "";
+        message = "serverAddr should be set if role is 'agent'";
+      }
+      {
+        assertion = cfg.role == "agent" -> cfg.token != "";
+        message = "token should be set if role is 'agent'";
+      }
+    ];
+
+    virtualisation.docker = mkIf cfg.docker {
+      enable = mkDefault true;
+    };
+
+    systemd.services.k3s = {
+      description = "k3s service";
+      after = mkIf cfg.docker [ "docker.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        # See: https://github.com/rancher/k3s/blob/dddbd16305284ae4bd14c0aade892412310d7edc/install.sh#L197
+        Type = if cfg.role == "agent" then "exec" else "notify";
+        KillMode = "process";
+        Delegate = "yes";
+        Restart = "always";
+        RestartSec = "5s";
+        ExecStart = concatStringsSep " \\\n " (
+          [
+            "${cfg.package}/bin/k3s ${cfg.role}"
+          ] ++ (optional cfg.docker "--docker")
+          ++ (optional cfg.disableAgent "--disable-agent")
+          ++ (optional (cfg.role == "agent") "--server ${cfg.serverAddr} --token ${cfg.token}")
+          ++ [ cfg.extraFlags ]
+        );
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
new file mode 100644
index 000000000000..f55079300b15
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -0,0 +1,167 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  top = config.services.kubernetes;
+  cfg = top.addonManager;
+
+  isRBACEnabled = elem "RBAC" top.apiserver.authorizationMode;
+
+  addons = pkgs.runCommand "kubernetes-addons" { } ''
+    mkdir -p $out
+    # since we are mounting the addons to the addon manager, they need to be copied
+    ${concatMapStringsSep ";" (a: "cp -v ${a}/* $out/") (mapAttrsToList (name: addon:
+      pkgs.writeTextDir "${name}.json" (builtins.toJSON addon)
+    ) (cfg.addons))}
+  '';
+in
+{
+  ###### interface
+  options.services.kubernetes.addonManager = with lib.types; {
+
+    bootstrapAddons = mkOption {
+      description = ''
+        Bootstrap addons are like regular addons, but they are applied with cluster-admin rigths.
+        They are applied at addon-manager startup only.
+      '';
+      default = { };
+      type = attrsOf attrs;
+      example = literalExample ''
+        {
+          "my-service" = {
+            "apiVersion" = "v1";
+            "kind" = "Service";
+            "metadata" = {
+              "name" = "my-service";
+              "namespace" = "default";
+            };
+            "spec" = { ... };
+          };
+        }
+      '';
+    };
+
+    addons = mkOption {
+      description = "Kubernetes addons (any kind of Kubernetes resource can be an addon).";
+      default = { };
+      type = attrsOf (either attrs (listOf attrs));
+      example = literalExample ''
+        {
+          "my-service" = {
+            "apiVersion" = "v1";
+            "kind" = "Service";
+            "metadata" = {
+              "name" = "my-service";
+              "namespace" = "default";
+            };
+            "spec" = { ... };
+          };
+        }
+        // import <nixpkgs/nixos/modules/services/cluster/kubernetes/dashboard.nix> { cfg = config.services.kubernetes; };
+      '';
+    };
+
+    enable = mkEnableOption "Whether to enable Kubernetes addon manager.";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    environment.etc."kubernetes/addons".source = "${addons}/";
+
+    systemd.services.kube-addon-manager = {
+      description = "Kubernetes addon manager";
+      wantedBy = [ "kubernetes.target" ];
+      after = [ "kube-apiserver.service" ];
+      environment.ADDON_PATH = "/etc/kubernetes/addons/";
+      path = [ pkgs.gawk ];
+      serviceConfig = {
+        Slice = "kubernetes.slice";
+        ExecStart = "${top.package}/bin/kube-addons";
+        WorkingDirectory = top.dataDir;
+        User = "kubernetes";
+        Group = "kubernetes";
+        Restart = "on-failure";
+        RestartSec = 10;
+      };
+    };
+
+    services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled
+    (let
+      name = "system:kube-addon-manager";
+      namespace = "kube-system";
+    in
+    {
+
+      kube-addon-manager-r = {
+        apiVersion = "rbac.authorization.k8s.io/v1";
+        kind = "Role";
+        metadata = {
+          inherit name namespace;
+        };
+        rules = [{
+          apiGroups = ["*"];
+          resources = ["*"];
+          verbs = ["*"];
+        }];
+      };
+
+      kube-addon-manager-rb = {
+        apiVersion = "rbac.authorization.k8s.io/v1";
+        kind = "RoleBinding";
+        metadata = {
+          inherit name namespace;
+        };
+        roleRef = {
+          apiGroup = "rbac.authorization.k8s.io";
+          kind = "Role";
+          inherit name;
+        };
+        subjects = [{
+          apiGroup = "rbac.authorization.k8s.io";
+          kind = "User";
+          inherit name;
+        }];
+      };
+
+      kube-addon-manager-cluster-lister-cr = {
+        apiVersion = "rbac.authorization.k8s.io/v1";
+        kind = "ClusterRole";
+        metadata = {
+          name = "${name}:cluster-lister";
+        };
+        rules = [{
+          apiGroups = ["*"];
+          resources = ["*"];
+          verbs = ["list"];
+        }];
+      };
+
+      kube-addon-manager-cluster-lister-crb = {
+        apiVersion = "rbac.authorization.k8s.io/v1";
+        kind = "ClusterRoleBinding";
+        metadata = {
+          name = "${name}:cluster-lister";
+        };
+        roleRef = {
+          apiGroup = "rbac.authorization.k8s.io";
+          kind = "ClusterRole";
+          name = "${name}:cluster-lister";
+        };
+        subjects = [{
+          kind = "User";
+          inherit name;
+        }];
+      };
+    });
+
+    services.kubernetes.pki.certs = {
+      addonManager = top.lib.mkCert {
+        name = "kube-addon-manager";
+        CN = "system:kube-addon-manager";
+        action = "systemctl restart kube-addon-manager.service";
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
new file mode 100644
index 000000000000..2ed7742eda09
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
@@ -0,0 +1,332 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.kubernetes.addons.dashboard;
+in {
+  imports = [
+    (mkRenamedOptionModule [ "services" "kubernetes" "addons" "dashboard" "enableRBAC" ] [ "services" "kubernetes" "addons" "dashboard" "rbac" "enable" ])
+  ];
+
+  options.services.kubernetes.addons.dashboard = {
+    enable = mkEnableOption "kubernetes dashboard addon";
+
+    extraArgs = mkOption {
+      description = "Extra arguments to append to the dashboard cmdline";
+      type = types.listOf types.str;
+      default = [];
+      example = ["--enable-skip-login"];
+    };
+
+    rbac = mkOption {
+      description = "Role-based access control (RBAC) options";
+      default = {};
+      type = types.submodule {
+        options = {
+          enable = mkOption {
+            description = "Whether to enable role based access control is enabled for kubernetes dashboard";
+            type = types.bool;
+            default = elem "RBAC" config.services.kubernetes.apiserver.authorizationMode;
+          };
+
+          clusterAdmin = mkOption {
+            description = "Whether to assign cluster admin rights to the kubernetes dashboard";
+            type = types.bool;
+            default = false;
+          };
+        };
+      };
+    };
+
+    version = mkOption {
+      description = "Which version of the kubernetes dashboard to deploy";
+      type = types.str;
+      default = "v1.10.1";
+    };
+
+    image = mkOption {
+      description = "Docker image to seed for the kubernetes dashboard container.";
+      type = types.attrs;
+      default = {
+        imageName = "k8s.gcr.io/kubernetes-dashboard-amd64";
+        imageDigest = "sha256:0ae6b69432e78069c5ce2bcde0fe409c5c4d6f0f4d9cd50a17974fea38898747";
+        finalImageTag = cfg.version;
+        sha256 = "01xrr4pwgr2hcjrjsi3d14ifpzdfbxzqpzxbk2fkbjb9zkv38zxy";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.kubernetes.kubelet.seedDockerImages = [(pkgs.dockerTools.pullImage cfg.image)];
+
+    services.kubernetes.addonManager.addons = {
+      kubernetes-dashboard-deployment = {
+        kind = "Deployment";
+        apiVersion = "apps/v1";
+        metadata = {
+          labels = {
+            k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+            k8s-app = "kubernetes-dashboard";
+            version = cfg.version;
+            "kubernetes.io/cluster-service" = "true";
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+          };
+          name = "kubernetes-dashboard";
+          namespace = "kube-system";
+        };
+        spec = {
+          replicas = 1;
+          revisionHistoryLimit = 10;
+          selector.matchLabels.k8s-app = "kubernetes-dashboard";
+          template = {
+            metadata = {
+              labels = {
+                k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+                k8s-app = "kubernetes-dashboard";
+                version = cfg.version;
+                "kubernetes.io/cluster-service" = "true";
+              };
+              annotations = {
+                "scheduler.alpha.kubernetes.io/critical-pod" = "";
+              };
+            };
+            spec = {
+              priorityClassName = "system-cluster-critical";
+              containers = [{
+                name = "kubernetes-dashboard";
+                image = with cfg.image; "${imageName}:${finalImageTag}";
+                ports = [{
+                  containerPort = 8443;
+                  protocol = "TCP";
+                }];
+                resources = {
+                  limits = {
+                    cpu = "100m";
+                    memory = "300Mi";
+                  };
+                  requests = {
+                    cpu = "100m";
+                    memory = "100Mi";
+                  };
+                };
+                args = ["--auto-generate-certificates"] ++ cfg.extraArgs;
+                volumeMounts = [{
+                  name = "tmp-volume";
+                  mountPath = "/tmp";
+                } {
+                  name = "kubernetes-dashboard-certs";
+                  mountPath = "/certs";
+                }];
+                livenessProbe = {
+                  httpGet = {
+                    scheme = "HTTPS";
+                    path = "/";
+                    port = 8443;
+                  };
+                  initialDelaySeconds = 30;
+                  timeoutSeconds = 30;
+                };
+              }];
+              volumes = [{
+                name = "kubernetes-dashboard-certs";
+                secret = {
+                  secretName = "kubernetes-dashboard-certs";
+                };
+              } {
+                name = "tmp-volume";
+                emptyDir = {};
+              }];
+              serviceAccountName = "kubernetes-dashboard";
+              tolerations = [{
+                key = "node-role.kubernetes.io/master";
+                effect = "NoSchedule";
+              } {
+                key = "CriticalAddonsOnly";
+                operator = "Exists";
+              }];
+            };
+          };
+        };
+      };
+
+      kubernetes-dashboard-svc = {
+        apiVersion = "v1";
+        kind = "Service";
+        metadata = {
+          labels = {
+            k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+            k8s-app = "kubernetes-dashboard";
+            "kubernetes.io/cluster-service" = "true";
+            "kubernetes.io/name" = "KubeDashboard";
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+          };
+          name = "kubernetes-dashboard";
+          namespace  = "kube-system";
+        };
+        spec = {
+          ports = [{
+            port = 443;
+            targetPort = 8443;
+          }];
+          selector.k8s-app = "kubernetes-dashboard";
+        };
+      };
+
+      kubernetes-dashboard-sa = {
+        apiVersion = "v1";
+        kind = "ServiceAccount";
+        metadata = {
+          labels = {
+            k8s-app = "kubernetes-dashboard";
+            k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+          };
+          name = "kubernetes-dashboard";
+          namespace = "kube-system";
+        };
+      };
+      kubernetes-dashboard-sec-certs = {
+        apiVersion = "v1";
+        kind = "Secret";
+        metadata = {
+          labels = {
+            k8s-app = "kubernetes-dashboard";
+            # Allows editing resource and makes sure it is created first.
+            "addonmanager.kubernetes.io/mode" = "EnsureExists";
+          };
+          name = "kubernetes-dashboard-certs";
+          namespace = "kube-system";
+        };
+        type = "Opaque";
+      };
+      kubernetes-dashboard-sec-kholder = {
+        apiVersion = "v1";
+        kind = "Secret";
+        metadata = {
+          labels = {
+            k8s-app = "kubernetes-dashboard";
+            # Allows editing resource and makes sure it is created first.
+            "addonmanager.kubernetes.io/mode" = "EnsureExists";
+          };
+          name = "kubernetes-dashboard-key-holder";
+          namespace = "kube-system";
+        };
+        type = "Opaque";
+      };
+      kubernetes-dashboard-cm = {
+        apiVersion = "v1";
+        kind = "ConfigMap";
+        metadata = {
+          labels = {
+            k8s-app = "kubernetes-dashboard";
+            # Allows editing resource and makes sure it is created first.
+            "addonmanager.kubernetes.io/mode" = "EnsureExists";
+          };
+          name = "kubernetes-dashboard-settings";
+          namespace = "kube-system";
+        };
+      };
+    } // (optionalAttrs cfg.rbac.enable
+      (let
+        subjects = [{
+          kind = "ServiceAccount";
+          name = "kubernetes-dashboard";
+          namespace = "kube-system";
+        }];
+        labels = {
+          k8s-app = "kubernetes-dashboard";
+          k8s-addon = "kubernetes-dashboard.addons.k8s.io";
+          "addonmanager.kubernetes.io/mode" = "Reconcile";
+        };
+      in
+        (if cfg.rbac.clusterAdmin then {
+          kubernetes-dashboard-crb = {
+            apiVersion = "rbac.authorization.k8s.io/v1";
+            kind = "ClusterRoleBinding";
+            metadata = {
+              name = "kubernetes-dashboard";
+              inherit labels;
+            };
+            roleRef = {
+              apiGroup = "rbac.authorization.k8s.io";
+              kind = "ClusterRole";
+              name = "cluster-admin";
+            };
+            inherit subjects;
+          };
+        }
+        else
+        {
+          # Upstream role- and rolebinding as per:
+          # https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/alternative/kubernetes-dashboard.yaml
+          kubernetes-dashboard-role = {
+            apiVersion = "rbac.authorization.k8s.io/v1";
+            kind = "Role";
+            metadata = {
+              name = "kubernetes-dashboard-minimal";
+              namespace = "kube-system";
+              inherit labels;
+            };
+            rules = [
+              # Allow Dashboard to create 'kubernetes-dashboard-key-holder' secret.
+              {
+                apiGroups = [""];
+                resources = ["secrets"];
+                verbs = ["create"];
+              }
+              # Allow Dashboard to create 'kubernetes-dashboard-settings' config map.
+              {
+                apiGroups = [""];
+                resources = ["configmaps"];
+                verbs = ["create"];
+              }
+              # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
+              {
+                apiGroups = [""];
+                resources = ["secrets"];
+                resourceNames = ["kubernetes-dashboard-key-holder"];
+                verbs = ["get" "update" "delete"];
+              }
+              # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
+              {
+                apiGroups = [""];
+                resources = ["configmaps"];
+                resourceNames = ["kubernetes-dashboard-settings"];
+                verbs = ["get" "update"];
+              }
+              # Allow Dashboard to get metrics from heapster.
+              {
+                apiGroups = [""];
+                resources = ["services"];
+                resourceNames = ["heapster"];
+                verbs = ["proxy"];
+              }
+              {
+                apiGroups = [""];
+                resources = ["services/proxy"];
+                resourceNames = ["heapster" "http:heapster:" "https:heapster:"];
+                verbs = ["get"];
+              }
+            ];
+          };
+
+          kubernetes-dashboard-rb = {
+            apiVersion = "rbac.authorization.k8s.io/v1";
+            kind = "RoleBinding";
+            metadata = {
+              name = "kubernetes-dashboard-minimal";
+              namespace = "kube-system";
+              inherit labels;
+            };
+            roleRef = {
+              apiGroup = "rbac.authorization.k8s.io";
+              kind = "Role";
+              name = "kubernetes-dashboard-minimal";
+            };
+            inherit subjects;
+          };
+        })
+    ));
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
new file mode 100644
index 000000000000..f12e866930da
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -0,0 +1,334 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  version = "1.6.4";
+  cfg = config.services.kubernetes.addons.dns;
+  ports = {
+    dns = 10053;
+    health = 10054;
+    metrics = 10055;
+  };
+in {
+  options.services.kubernetes.addons.dns = {
+    enable = mkEnableOption "kubernetes dns addon";
+
+    clusterIp = mkOption {
+      description = "Dns addon clusterIP";
+
+      # this default is also what kubernetes users
+      default = (
+        concatStringsSep "." (
+          take 3 (splitString "." config.services.kubernetes.apiserver.serviceClusterIpRange
+        ))
+      ) + ".254";
+      type = types.str;
+    };
+
+    clusterDomain = mkOption {
+      description = "Dns cluster domain";
+      default = "cluster.local";
+      type = types.str;
+    };
+
+    replicas = mkOption {
+      description = "Number of DNS pod replicas to deploy in the cluster.";
+      default = 2;
+      type = types.int;
+    };
+
+    reconcileMode = mkOption {
+      description = ''
+        Controls the addon manager reconciliation mode for the DNS addon.
+
+        Setting reconcile mode to EnsureExists makes it possible to tailor DNS behavior by editing the coredns ConfigMap.
+
+        See: <link xlink:href="https://github.com/kubernetes/kubernetes/blob/master/cluster/addons/addon-manager/README.md"/>.
+      '';
+      default = "Reconcile";
+      type = types.enum [ "Reconcile" "EnsureExists" ];
+    };
+
+    coredns = mkOption {
+      description = "Docker image to seed for the CoreDNS container.";
+      type = types.attrs;
+      default = {
+        imageName = "coredns/coredns";
+        imageDigest = "sha256:493ee88e1a92abebac67cbd4b5658b4730e0f33512461442d8d9214ea6734a9b";
+        finalImageTag = version;
+        sha256 = "0fm9zdjavpf5hni8g7fkdd3csjbhd7n7py7llxjc66sbii087028";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.kubernetes.kubelet.seedDockerImages =
+      singleton (pkgs.dockerTools.pullImage cfg.coredns);
+
+    services.kubernetes.addonManager.bootstrapAddons = {
+      coredns-cr = {
+        apiVersion = "rbac.authorization.k8s.io/v1";
+        kind = "ClusterRole";
+        metadata = {
+          labels = {
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+            k8s-app = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+            "kubernetes.io/bootstrapping" = "rbac-defaults";
+          };
+          name = "system:coredns";
+        };
+        rules = [
+          {
+            apiGroups = [ "" ];
+            resources = [ "endpoints" "services" "pods" "namespaces" ];
+            verbs = [ "list" "watch" ];
+          }
+          {
+            apiGroups = [ "" ];
+            resources = [ "nodes" ];
+            verbs = [ "get" ];
+          }
+        ];
+      };
+
+      coredns-crb = {
+        apiVersion = "rbac.authorization.k8s.io/v1";
+        kind = "ClusterRoleBinding";
+        metadata = {
+          annotations = {
+            "rbac.authorization.kubernetes.io/autoupdate" = "true";
+          };
+          labels = {
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+            k8s-app = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+            "kubernetes.io/bootstrapping" = "rbac-defaults";
+          };
+          name = "system:coredns";
+        };
+        roleRef = {
+          apiGroup = "rbac.authorization.k8s.io";
+          kind = "ClusterRole";
+          name = "system:coredns";
+        };
+        subjects = [
+          {
+            kind = "ServiceAccount";
+            name = "coredns";
+            namespace = "kube-system";
+          }
+        ];
+      };
+    };
+
+    services.kubernetes.addonManager.addons = {
+      coredns-sa = {
+        apiVersion = "v1";
+        kind = "ServiceAccount";
+        metadata = {
+          labels = {
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+            k8s-app = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+          };
+          name = "coredns";
+          namespace = "kube-system";
+        };
+      };
+
+      coredns-cm = {
+        apiVersion = "v1";
+        kind = "ConfigMap";
+        metadata = {
+          labels = {
+            "addonmanager.kubernetes.io/mode" = cfg.reconcileMode;
+            k8s-app = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+          };
+          name = "coredns";
+          namespace = "kube-system";
+        };
+        data = {
+          Corefile = ".:${toString ports.dns} {
+            errors
+            health :${toString ports.health}
+            kubernetes ${cfg.clusterDomain} in-addr.arpa ip6.arpa {
+              pods insecure
+              upstream
+              fallthrough in-addr.arpa ip6.arpa
+            }
+            prometheus :${toString ports.metrics}
+            forward . /etc/resolv.conf
+            cache 30
+            loop
+            reload
+            loadbalance
+          }";
+        };
+      };
+
+      coredns-deploy = {
+        apiVersion = "apps/v1";
+        kind = "Deployment";
+        metadata = {
+          labels = {
+            "addonmanager.kubernetes.io/mode" = cfg.reconcileMode;
+            k8s-app = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+            "kubernetes.io/name" = "CoreDNS";
+          };
+          name = "coredns";
+          namespace = "kube-system";
+        };
+        spec = {
+          replicas = cfg.replicas;
+          selector = {
+            matchLabels = { k8s-app = "kube-dns"; };
+          };
+          strategy = {
+            rollingUpdate = { maxUnavailable = 1; };
+            type = "RollingUpdate";
+          };
+          template = {
+            metadata = {
+              labels = {
+                k8s-app = "kube-dns";
+              };
+            };
+            spec = {
+              containers = [
+                {
+                  args = [ "-conf" "/etc/coredns/Corefile" ];
+                  image = with cfg.coredns; "${imageName}:${finalImageTag}";
+                  imagePullPolicy = "Never";
+                  livenessProbe = {
+                    failureThreshold = 5;
+                    httpGet = {
+                      path = "/health";
+                      port = ports.health;
+                      scheme = "HTTP";
+                    };
+                    initialDelaySeconds = 60;
+                    successThreshold = 1;
+                    timeoutSeconds = 5;
+                  };
+                  name = "coredns";
+                  ports = [
+                    {
+                      containerPort = ports.dns;
+                      name = "dns";
+                      protocol = "UDP";
+                    }
+                    {
+                      containerPort = ports.dns;
+                      name = "dns-tcp";
+                      protocol = "TCP";
+                    }
+                    {
+                      containerPort = ports.metrics;
+                      name = "metrics";
+                      protocol = "TCP";
+                    }
+                  ];
+                  resources = {
+                    limits = {
+                      memory = "170Mi";
+                    };
+                    requests = {
+                      cpu = "100m";
+                      memory = "70Mi";
+                    };
+                  };
+                  securityContext = {
+                    allowPrivilegeEscalation = false;
+                    capabilities = {
+                      drop = [ "all" ];
+                    };
+                    readOnlyRootFilesystem = true;
+                  };
+                  volumeMounts = [
+                    {
+                      mountPath = "/etc/coredns";
+                      name = "config-volume";
+                      readOnly = true;
+                    }
+                  ];
+                }
+              ];
+              dnsPolicy = "Default";
+              nodeSelector = {
+                "beta.kubernetes.io/os" = "linux";
+              };
+              serviceAccountName = "coredns";
+              tolerations = [
+                {
+                  effect = "NoSchedule";
+                  key = "node-role.kubernetes.io/master";
+                }
+                {
+                  key = "CriticalAddonsOnly";
+                  operator = "Exists";
+                }
+              ];
+              volumes = [
+                {
+                  configMap = {
+                    items = [
+                      {
+                        key = "Corefile";
+                        path = "Corefile";
+                      }
+                    ];
+                    name = "coredns";
+                  };
+                  name = "config-volume";
+                }
+              ];
+            };
+          };
+        };
+      };
+
+      coredns-svc = {
+        apiVersion = "v1";
+        kind = "Service";
+        metadata = {
+          annotations = {
+            "prometheus.io/port" = toString ports.metrics;
+            "prometheus.io/scrape" = "true";
+          };
+          labels = {
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+            k8s-app = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+            "kubernetes.io/name" = "CoreDNS";
+          };
+          name = "kube-dns";
+          namespace = "kube-system";
+        };
+        spec = {
+          clusterIP = cfg.clusterIp;
+          ports = [
+            {
+              name = "dns";
+              port = 53;
+              targetPort = ports.dns;
+              protocol = "UDP";
+            }
+            {
+              name = "dns-tcp";
+              port = 53;
+              targetPort = ports.dns;
+              protocol = "TCP";
+            }
+          ];
+          selector = { k8s-app = "kube-dns"; };
+        };
+      };
+    };
+
+    services.kubernetes.kubelet.clusterDns = mkDefault cfg.clusterIp;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
new file mode 100644
index 000000000000..95bdb4c0d14e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -0,0 +1,469 @@
+  { config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  top = config.services.kubernetes;
+  cfg = top.apiserver;
+
+  isRBACEnabled = elem "RBAC" cfg.authorizationMode;
+
+  apiserverServiceIP = (concatStringsSep "." (
+    take 3 (splitString "." cfg.serviceClusterIpRange
+  )) + ".1");
+in
+{
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "admissionControl" ] [ "services" "kubernetes" "apiserver" "enableAdmissionPlugins" ])
+    (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "address" ] ["services" "kubernetes" "apiserver" "bindAddress"])
+    (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "port" ] ["services" "kubernetes" "apiserver" "insecurePort"])
+    (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "")
+    (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "servers" ] [ "services" "kubernetes" "apiserver" "etcd" "servers" ])
+    (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "keyFile" ] [ "services" "kubernetes" "apiserver" "etcd" "keyFile" ])
+    (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "certFile" ] [ "services" "kubernetes" "apiserver" "etcd" "certFile" ])
+    (mkRenamedOptionModule [ "services" "kubernetes" "etcd" "caFile" ] [ "services" "kubernetes" "apiserver" "etcd" "caFile" ])
+  ];
+
+  ###### interface
+  options.services.kubernetes.apiserver = with lib.types; {
+
+    advertiseAddress = mkOption {
+      description = ''
+        Kubernetes apiserver IP address on which to advertise the apiserver
+        to members of the cluster. This address must be reachable by the rest
+        of the cluster.
+      '';
+      default = null;
+      type = nullOr str;
+    };
+
+    allowPrivileged = mkOption {
+      description = "Whether to allow privileged containers on Kubernetes.";
+      default = false;
+      type = bool;
+    };
+
+    authorizationMode = mkOption {
+      description = ''
+        Kubernetes apiserver authorization mode (AlwaysAllow/AlwaysDeny/ABAC/Webhook/RBAC/Node). See
+        <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authorization/"/>
+      '';
+      default = ["RBAC" "Node"]; # Enabling RBAC by default, although kubernetes default is AllowAllow
+      type = listOf (enum ["AlwaysAllow" "AlwaysDeny" "ABAC" "Webhook" "RBAC" "Node"]);
+    };
+
+    authorizationPolicy = mkOption {
+      description = ''
+        Kubernetes apiserver authorization policy file. See
+        <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authorization/"/>
+      '';
+      default = [];
+      type = listOf attrs;
+    };
+
+    basicAuthFile = mkOption {
+      description = ''
+        Kubernetes apiserver basic authentication file. See
+        <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authentication"/>
+      '';
+      default = null;
+      type = nullOr path;
+    };
+
+    bindAddress = mkOption {
+      description = ''
+        The IP address on which to listen for the --secure-port port.
+        The associated interface(s) must be reachable by the rest
+        of the cluster, and by CLI/web clients.
+      '';
+      default = "0.0.0.0";
+      type = str;
+    };
+
+    clientCaFile = mkOption {
+      description = "Kubernetes apiserver CA file for client auth.";
+      default = top.caFile;
+      type = nullOr path;
+    };
+
+    disableAdmissionPlugins = mkOption {
+      description = ''
+        Kubernetes admission control plugins to disable. See
+        <link xlink:href="https://kubernetes.io/docs/admin/admission-controllers/"/>
+      '';
+      default = [];
+      type = listOf str;
+    };
+
+    enable = mkEnableOption "Kubernetes apiserver";
+
+    enableAdmissionPlugins = mkOption {
+      description = ''
+        Kubernetes admission control plugins to enable. See
+        <link xlink:href="https://kubernetes.io/docs/admin/admission-controllers/"/>
+      '';
+      default = [
+        "NamespaceLifecycle" "LimitRanger" "ServiceAccount"
+        "ResourceQuota" "DefaultStorageClass" "DefaultTolerationSeconds"
+        "NodeRestriction"
+      ];
+      example = [
+        "NamespaceLifecycle" "NamespaceExists" "LimitRanger"
+        "SecurityContextDeny" "ServiceAccount" "ResourceQuota"
+        "PodSecurityPolicy" "NodeRestriction" "DefaultStorageClass"
+      ];
+      type = listOf str;
+    };
+
+    etcd = {
+      servers = mkOption {
+        description = "List of etcd servers.";
+        default = ["http://127.0.0.1:2379"];
+        type = types.listOf types.str;
+      };
+
+      keyFile = mkOption {
+        description = "Etcd key file.";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      certFile = mkOption {
+        description = "Etcd cert file.";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      caFile = mkOption {
+        description = "Etcd ca file.";
+        default = top.caFile;
+        type = types.nullOr types.path;
+      };
+    };
+
+    extraOpts = mkOption {
+      description = "Kubernetes apiserver extra command line options.";
+      default = "";
+      type = str;
+    };
+
+    extraSANs = mkOption {
+      description = "Extra x509 Subject Alternative Names to be added to the kubernetes apiserver tls cert.";
+      default = [];
+      type = listOf str;
+    };
+
+    featureGates = mkOption {
+      description = "List set of feature gates";
+      default = top.featureGates;
+      type = listOf str;
+    };
+
+    insecureBindAddress = mkOption {
+      description = "The IP address on which to serve the --insecure-port.";
+      default = "127.0.0.1";
+      type = str;
+    };
+
+    insecurePort = mkOption {
+      description = "Kubernetes apiserver insecure listening port. (0 = disabled)";
+      default = 0;
+      type = int;
+    };
+
+    kubeletClientCaFile = mkOption {
+      description = "Path to a cert file for connecting to kubelet.";
+      default = top.caFile;
+      type = nullOr path;
+    };
+
+    kubeletClientCertFile = mkOption {
+      description = "Client certificate to use for connections to kubelet.";
+      default = null;
+      type = nullOr path;
+    };
+
+    kubeletClientKeyFile = mkOption {
+      description = "Key to use for connections to kubelet.";
+      default = null;
+      type = nullOr path;
+    };
+
+    kubeletHttps = mkOption {
+      description = "Whether to use https for connections to kubelet.";
+      default = true;
+      type = bool;
+    };
+
+    preferredAddressTypes = mkOption {
+      description = "List of the preferred NodeAddressTypes to use for kubelet connections.";
+      type = nullOr str;
+      default = null;
+    };
+
+    proxyClientCertFile = mkOption {
+      description = "Client certificate to use for connections to proxy.";
+      default = null;
+      type = nullOr path;
+    };
+
+    proxyClientKeyFile = mkOption {
+      description = "Key to use for connections to proxy.";
+      default = null;
+      type = nullOr path;
+    };
+
+    runtimeConfig = mkOption {
+      description = ''
+        Api runtime configuration. See
+        <link xlink:href="https://kubernetes.io/docs/tasks/administer-cluster/cluster-management/"/>
+      '';
+      default = "authentication.k8s.io/v1beta1=true";
+      example = "api/all=false,api/v1=true";
+      type = str;
+    };
+
+    storageBackend = mkOption {
+      description = ''
+        Kubernetes apiserver storage backend.
+      '';
+      default = "etcd3";
+      type = enum ["etcd2" "etcd3"];
+    };
+
+    securePort = mkOption {
+      description = "Kubernetes apiserver secure port.";
+      default = 6443;
+      type = int;
+    };
+
+    serviceAccountKeyFile = mkOption {
+      description = ''
+        Kubernetes apiserver PEM-encoded x509 RSA private or public key file,
+        used to verify ServiceAccount tokens. By default tls private key file
+        is used.
+      '';
+      default = null;
+      type = nullOr path;
+    };
+
+    serviceClusterIpRange = mkOption {
+      description = ''
+        A CIDR notation IP range from which to assign service cluster IPs.
+        This must not overlap with any IP ranges assigned to nodes for pods.
+      '';
+      default = "10.0.0.0/24";
+      type = str;
+    };
+
+    tlsCertFile = mkOption {
+      description = "Kubernetes apiserver certificate file.";
+      default = null;
+      type = nullOr path;
+    };
+
+    tlsKeyFile = mkOption {
+      description = "Kubernetes apiserver private key file.";
+      default = null;
+      type = nullOr path;
+    };
+
+    tokenAuthFile = mkOption {
+      description = ''
+        Kubernetes apiserver token authentication file. See
+        <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/authentication"/>
+      '';
+      default = null;
+      type = nullOr path;
+    };
+
+    verbosity = mkOption {
+      description = ''
+        Optional glog verbosity level for logging statements. See
+        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+      '';
+      default = null;
+      type = nullOr int;
+    };
+
+    webhookConfig = mkOption {
+      description = ''
+        Kubernetes apiserver Webhook config file. It uses the kubeconfig file format.
+        See <link xlink:href="https://kubernetes.io/docs/reference/access-authn-authz/webhook/"/>
+      '';
+      default = null;
+      type = nullOr path;
+    };
+
+  };
+
+
+  ###### implementation
+  config = mkMerge [
+
+    (mkIf cfg.enable {
+        systemd.services.kube-apiserver = {
+          description = "Kubernetes APIServer Service";
+          wantedBy = [ "kubernetes.target" ];
+          after = [ "network.target" ];
+          serviceConfig = {
+            Slice = "kubernetes.slice";
+            ExecStart = ''${top.package}/bin/kube-apiserver \
+              --allow-privileged=${boolToString cfg.allowPrivileged} \
+              --authorization-mode=${concatStringsSep "," cfg.authorizationMode} \
+                ${optionalString (elem "ABAC" cfg.authorizationMode)
+                  "--authorization-policy-file=${
+                    pkgs.writeText "kube-auth-policy.jsonl"
+                    (concatMapStringsSep "\n" (l: builtins.toJSON l) cfg.authorizationPolicy)
+                  }"
+                } \
+                ${optionalString (elem "Webhook" cfg.authorizationMode)
+                  "--authorization-webhook-config-file=${cfg.webhookConfig}"
+                } \
+              --bind-address=${cfg.bindAddress} \
+              ${optionalString (cfg.advertiseAddress != null)
+                "--advertise-address=${cfg.advertiseAddress}"} \
+              ${optionalString (cfg.clientCaFile != null)
+                "--client-ca-file=${cfg.clientCaFile}"} \
+              --disable-admission-plugins=${concatStringsSep "," cfg.disableAdmissionPlugins} \
+              --enable-admission-plugins=${concatStringsSep "," cfg.enableAdmissionPlugins} \
+              --etcd-servers=${concatStringsSep "," cfg.etcd.servers} \
+              ${optionalString (cfg.etcd.caFile != null)
+                "--etcd-cafile=${cfg.etcd.caFile}"} \
+              ${optionalString (cfg.etcd.certFile != null)
+                "--etcd-certfile=${cfg.etcd.certFile}"} \
+              ${optionalString (cfg.etcd.keyFile != null)
+                "--etcd-keyfile=${cfg.etcd.keyFile}"} \
+              ${optionalString (cfg.featureGates != [])
+                "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+              ${optionalString (cfg.basicAuthFile != null)
+                "--basic-auth-file=${cfg.basicAuthFile}"} \
+              --kubelet-https=${boolToString cfg.kubeletHttps} \
+              ${optionalString (cfg.kubeletClientCaFile != null)
+                "--kubelet-certificate-authority=${cfg.kubeletClientCaFile}"} \
+              ${optionalString (cfg.kubeletClientCertFile != null)
+                "--kubelet-client-certificate=${cfg.kubeletClientCertFile}"} \
+              ${optionalString (cfg.kubeletClientKeyFile != null)
+                "--kubelet-client-key=${cfg.kubeletClientKeyFile}"} \
+              ${optionalString (cfg.preferredAddressTypes != null)
+                "--kubelet-preferred-address-types=${cfg.preferredAddressTypes}"} \
+              ${optionalString (cfg.proxyClientCertFile != null)
+                "--proxy-client-cert-file=${cfg.proxyClientCertFile}"} \
+              ${optionalString (cfg.proxyClientKeyFile != null)
+                "--proxy-client-key-file=${cfg.proxyClientKeyFile}"} \
+              --insecure-bind-address=${cfg.insecureBindAddress} \
+              --insecure-port=${toString cfg.insecurePort} \
+              ${optionalString (cfg.runtimeConfig != "")
+                "--runtime-config=${cfg.runtimeConfig}"} \
+              --secure-port=${toString cfg.securePort} \
+              ${optionalString (cfg.serviceAccountKeyFile!=null)
+                "--service-account-key-file=${cfg.serviceAccountKeyFile}"} \
+              --service-cluster-ip-range=${cfg.serviceClusterIpRange} \
+              --storage-backend=${cfg.storageBackend} \
+              ${optionalString (cfg.tlsCertFile != null)
+                "--tls-cert-file=${cfg.tlsCertFile}"} \
+              ${optionalString (cfg.tlsKeyFile != null)
+                "--tls-private-key-file=${cfg.tlsKeyFile}"} \
+              ${optionalString (cfg.tokenAuthFile != null)
+                "--token-auth-file=${cfg.tokenAuthFile}"} \
+              ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+              ${cfg.extraOpts}
+            '';
+            WorkingDirectory = top.dataDir;
+            User = "kubernetes";
+            Group = "kubernetes";
+            AmbientCapabilities = "cap_net_bind_service";
+            Restart = "on-failure";
+            RestartSec = 5;
+          };
+        };
+
+        services.etcd = {
+          clientCertAuth = mkDefault true;
+          peerClientCertAuth = mkDefault true;
+          listenClientUrls = mkDefault ["https://0.0.0.0:2379"];
+          listenPeerUrls = mkDefault ["https://0.0.0.0:2380"];
+          advertiseClientUrls = mkDefault ["https://${top.masterAddress}:2379"];
+          initialCluster = mkDefault ["${top.masterAddress}=https://${top.masterAddress}:2380"];
+          name = mkDefault top.masterAddress;
+          initialAdvertisePeerUrls = mkDefault ["https://${top.masterAddress}:2380"];
+        };
+
+        services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled {
+
+          apiserver-kubelet-api-admin-crb = {
+            apiVersion = "rbac.authorization.k8s.io/v1";
+            kind = "ClusterRoleBinding";
+            metadata = {
+              name = "system:kube-apiserver:kubelet-api-admin";
+            };
+            roleRef = {
+              apiGroup = "rbac.authorization.k8s.io";
+              kind = "ClusterRole";
+              name = "system:kubelet-api-admin";
+            };
+            subjects = [{
+              kind = "User";
+              name = "system:kube-apiserver";
+            }];
+          };
+
+        };
+
+      services.kubernetes.pki.certs = with top.lib; {
+        apiServer = mkCert {
+          name = "kube-apiserver";
+          CN = "kubernetes";
+          hosts = [
+                    "kubernetes.default.svc"
+                    "kubernetes.default.svc.${top.addons.dns.clusterDomain}"
+                    cfg.advertiseAddress
+                    top.masterAddress
+                    apiserverServiceIP
+                    "127.0.0.1"
+                  ] ++ cfg.extraSANs;
+          action = "systemctl restart kube-apiserver.service";
+        };
+        apiserverProxyClient = mkCert {
+          name = "kube-apiserver-proxy-client";
+          CN = "front-proxy-client";
+          action = "systemctl restart kube-apiserver.service";
+        };
+        apiserverKubeletClient = mkCert {
+          name = "kube-apiserver-kubelet-client";
+          CN = "system:kube-apiserver";
+          action = "systemctl restart kube-apiserver.service";
+        };
+        apiserverEtcdClient = mkCert {
+          name = "kube-apiserver-etcd-client";
+          CN = "etcd-client";
+          action = "systemctl restart kube-apiserver.service";
+        };
+        clusterAdmin = mkCert {
+          name = "cluster-admin";
+          CN = "cluster-admin";
+          fields = {
+            O = "system:masters";
+          };
+          privateKeyOwner = "root";
+        };
+        etcd = mkCert {
+          name = "etcd";
+          CN = top.masterAddress;
+          hosts = [
+                    "etcd.local"
+                    "etcd.${top.addons.dns.clusterDomain}"
+                    top.masterAddress
+                    cfg.advertiseAddress
+                  ];
+          privateKeyOwner = "etcd";
+          action = "systemctl restart etcd.service";
+        };
+      };
+
+    })
+
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
new file mode 100644
index 000000000000..a99ef6640e97
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -0,0 +1,167 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  top = config.services.kubernetes;
+  cfg = top.controllerManager;
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "address" ] ["services" "kubernetes" "controllerManager" "bindAddress"])
+    (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "port" ] ["services" "kubernetes" "controllerManager" "insecurePort"])
+  ];
+
+  ###### interface
+  options.services.kubernetes.controllerManager = with lib.types; {
+
+    allocateNodeCIDRs = mkOption {
+      description = "Whether to automatically allocate CIDR ranges for cluster nodes.";
+      default = true;
+      type = bool;
+    };
+
+    bindAddress = mkOption {
+      description = "Kubernetes controller manager listening address.";
+      default = "127.0.0.1";
+      type = str;
+    };
+
+    clusterCidr = mkOption {
+      description = "Kubernetes CIDR Range for Pods in cluster.";
+      default = top.clusterCidr;
+      type = str;
+    };
+
+    enable = mkEnableOption "Kubernetes controller manager";
+
+    extraOpts = mkOption {
+      description = "Kubernetes controller manager extra command line options.";
+      default = "";
+      type = str;
+    };
+
+    featureGates = mkOption {
+      description = "List set of feature gates";
+      default = top.featureGates;
+      type = listOf str;
+    };
+
+    insecurePort = mkOption {
+      description = "Kubernetes controller manager insecure listening port.";
+      default = 0;
+      type = int;
+    };
+
+    kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes controller manager";
+
+    leaderElect = mkOption {
+      description = "Whether to start leader election before executing main loop.";
+      type = bool;
+      default = true;
+    };
+
+    rootCaFile = mkOption {
+      description = ''
+        Kubernetes controller manager certificate authority file included in
+        service account's token secret.
+      '';
+      default = top.caFile;
+      type = nullOr path;
+    };
+
+    securePort = mkOption {
+      description = "Kubernetes controller manager secure listening port.";
+      default = 10252;
+      type = int;
+    };
+
+    serviceAccountKeyFile = mkOption {
+      description = ''
+        Kubernetes controller manager PEM-encoded private RSA key file used to
+        sign service account tokens
+      '';
+      default = null;
+      type = nullOr path;
+    };
+
+    tlsCertFile = mkOption {
+      description = "Kubernetes controller-manager certificate file.";
+      default = null;
+      type = nullOr path;
+    };
+
+    tlsKeyFile = mkOption {
+      description = "Kubernetes controller-manager private key file.";
+      default = null;
+      type = nullOr path;
+    };
+
+    verbosity = mkOption {
+      description = ''
+        Optional glog verbosity level for logging statements. See
+        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+      '';
+      default = null;
+      type = nullOr int;
+    };
+
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    systemd.services.kube-controller-manager = {
+      description = "Kubernetes Controller Manager Service";
+      wantedBy = [ "kubernetes.target" ];
+      after = [ "kube-apiserver.service" ];
+      serviceConfig = {
+        RestartSec = "30s";
+        Restart = "on-failure";
+        Slice = "kubernetes.slice";
+        ExecStart = ''${top.package}/bin/kube-controller-manager \
+          --allocate-node-cidrs=${boolToString cfg.allocateNodeCIDRs} \
+          --bind-address=${cfg.bindAddress} \
+          ${optionalString (cfg.clusterCidr!=null)
+            "--cluster-cidr=${cfg.clusterCidr}"} \
+          ${optionalString (cfg.featureGates != [])
+            "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+          --kubeconfig=${top.lib.mkKubeConfig "kube-controller-manager" cfg.kubeconfig} \
+          --leader-elect=${boolToString cfg.leaderElect} \
+          ${optionalString (cfg.rootCaFile!=null)
+            "--root-ca-file=${cfg.rootCaFile}"} \
+          --port=${toString cfg.insecurePort} \
+          --secure-port=${toString cfg.securePort} \
+          ${optionalString (cfg.serviceAccountKeyFile!=null)
+            "--service-account-private-key-file=${cfg.serviceAccountKeyFile}"} \
+          ${optionalString (cfg.tlsCertFile!=null)
+            "--tls-cert-file=${cfg.tlsCertFile}"} \
+          ${optionalString (cfg.tlsKeyFile!=null)
+            "--tls-private-key-file=${cfg.tlsKeyFile}"} \
+          ${optionalString (elem "RBAC" top.apiserver.authorizationMode)
+            "--use-service-account-credentials"} \
+          ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+          ${cfg.extraOpts}
+        '';
+        WorkingDirectory = top.dataDir;
+        User = "kubernetes";
+        Group = "kubernetes";
+      };
+      path = top.path;
+    };
+
+    services.kubernetes.pki.certs = with top.lib; {
+      controllerManager = mkCert {
+        name = "kube-controller-manager";
+        CN = "kube-controller-manager";
+        action = "systemctl restart kube-controller-manager.service";
+      };
+      controllerManagerClient = mkCert {
+        name = "kube-controller-manager-client";
+        CN = "system:kube-controller-manager";
+        action = "systemctl restart kube-controller-manager.service";
+      };
+    };
+
+    services.kubernetes.controllerManager.kubeconfig.server = mkDefault top.apiserverAddress;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix
new file mode 100644
index 000000000000..3a11a6513a49
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/default.nix
@@ -0,0 +1,287 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.kubernetes;
+
+  mkKubeConfig = name: conf: pkgs.writeText "${name}-kubeconfig" (builtins.toJSON {
+    apiVersion = "v1";
+    kind = "Config";
+    clusters = [{
+      name = "local";
+      cluster.certificate-authority = conf.caFile or cfg.caFile;
+      cluster.server = conf.server;
+    }];
+    users = [{
+      inherit name;
+      user = {
+        client-certificate = conf.certFile;
+        client-key = conf.keyFile;
+      };
+    }];
+    contexts = [{
+      context = {
+        cluster = "local";
+        user = name;
+      };
+      current-context = "local";
+    }];
+  });
+
+  caCert = secret "ca";
+
+  etcdEndpoints = ["https://${cfg.masterAddress}:2379"];
+
+  mkCert = { name, CN, hosts ? [], fields ? {}, action ? "",
+             privateKeyOwner ? "kubernetes" }: rec {
+    inherit name caCert CN hosts fields action;
+    cert = secret name;
+    key = secret "${name}-key";
+    privateKeyOptions = {
+      owner = privateKeyOwner;
+      group = "nogroup";
+      mode = "0600";
+      path = key;
+    };
+  };
+
+  secret = name: "${cfg.secretsPath}/${name}.pem";
+
+  mkKubeConfigOptions = prefix: {
+    server = mkOption {
+      description = "${prefix} kube-apiserver server address.";
+      type = types.str;
+    };
+
+    caFile = mkOption {
+      description = "${prefix} certificate authority file used to connect to kube-apiserver.";
+      type = types.nullOr types.path;
+      default = cfg.caFile;
+    };
+
+    certFile = mkOption {
+      description = "${prefix} client certificate file used to connect to kube-apiserver.";
+      type = types.nullOr types.path;
+      default = null;
+    };
+
+    keyFile = mkOption {
+      description = "${prefix} client key file used to connect to kube-apiserver.";
+      type = types.nullOr types.path;
+      default = null;
+    };
+  };
+in {
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "kubernetes" "verbose" ] "")
+  ];
+
+  ###### interface
+
+  options.services.kubernetes = {
+    roles = mkOption {
+      description = ''
+        Kubernetes role that this machine should take.
+
+        Master role will enable etcd, apiserver, scheduler, controller manager
+        addon manager, flannel and proxy services.
+        Node role will enable flannel, docker, kubelet and proxy services.
+      '';
+      default = [];
+      type = types.listOf (types.enum ["master" "node"]);
+    };
+
+    package = mkOption {
+      description = "Kubernetes package to use.";
+      type = types.package;
+      default = pkgs.kubernetes;
+      defaultText = "pkgs.kubernetes";
+    };
+
+    kubeconfig = mkKubeConfigOptions "Default kubeconfig";
+
+    apiserverAddress = mkOption {
+      description = ''
+        Clusterwide accessible address for the kubernetes apiserver,
+        including protocol and optional port.
+      '';
+      example = "https://kubernetes-apiserver.example.com:6443";
+      type = types.str;
+    };
+
+    caFile = mkOption {
+      description = "Default kubernetes certificate authority";
+      type = types.nullOr types.path;
+      default = null;
+    };
+
+    dataDir = mkOption {
+      description = "Kubernetes root directory for managing kubelet files.";
+      default = "/var/lib/kubernetes";
+      type = types.path;
+    };
+
+    easyCerts = mkOption {
+      description = "Automatically setup x509 certificates and keys for the entire cluster.";
+      default = false;
+      type = types.bool;
+    };
+
+    featureGates = mkOption {
+      description = "List set of feature gates.";
+      default = [];
+      type = types.listOf types.str;
+    };
+
+    masterAddress = mkOption {
+      description = "Clusterwide available network address or hostname for the kubernetes master server.";
+      example = "master.example.com";
+      type = types.str;
+    };
+
+    path = mkOption {
+      description = "Packages added to the services' PATH environment variable. Both the bin and sbin subdirectories of each package are added.";
+      type = types.listOf types.package;
+      default = [];
+    };
+
+    clusterCidr = mkOption {
+      description = "Kubernetes controller manager and proxy CIDR Range for Pods in cluster.";
+      default = "10.1.0.0/16";
+      type = types.nullOr types.str;
+    };
+
+    lib = mkOption {
+      description = "Common functions for the kubernetes modules.";
+      default = {
+        inherit mkCert;
+        inherit mkKubeConfig;
+        inherit mkKubeConfigOptions;
+      };
+      type = types.attrs;
+    };
+
+    secretsPath = mkOption {
+      description = "Default location for kubernetes secrets. Not a store location.";
+      type = types.path;
+      default = cfg.dataDir + "/secrets";
+    };
+  };
+
+  ###### implementation
+
+  config = mkMerge [
+
+    (mkIf cfg.easyCerts {
+      services.kubernetes.pki.enable = mkDefault true;
+      services.kubernetes.caFile = caCert;
+    })
+
+    (mkIf (elem "master" cfg.roles) {
+      services.kubernetes.apiserver.enable = mkDefault true;
+      services.kubernetes.scheduler.enable = mkDefault true;
+      services.kubernetes.controllerManager.enable = mkDefault true;
+      services.kubernetes.addonManager.enable = mkDefault true;
+      services.kubernetes.proxy.enable = mkDefault true;
+      services.etcd.enable = true; # Cannot mkDefault because of flannel default options
+      services.kubernetes.kubelet = {
+        enable = mkDefault true;
+        taints = mkIf (!(elem "node" cfg.roles)) {
+          master = {
+            key = "node-role.kubernetes.io/master";
+            value = "true";
+            effect = "NoSchedule";
+          };
+        };
+      };
+    })
+
+
+    (mkIf (all (el: el == "master") cfg.roles) {
+      # if this node is only a master make it unschedulable by default
+      services.kubernetes.kubelet.unschedulable = mkDefault true;
+    })
+
+    (mkIf (elem "node" cfg.roles) {
+      services.kubernetes.kubelet.enable = mkDefault true;
+      services.kubernetes.proxy.enable = mkDefault true;
+    })
+
+    # Using "services.kubernetes.roles" will automatically enable easyCerts and flannel
+    (mkIf (cfg.roles != []) {
+      services.kubernetes.flannel.enable = mkDefault true;
+      services.flannel.etcd.endpoints = mkDefault etcdEndpoints;
+      services.kubernetes.easyCerts = mkDefault true;
+    })
+
+    (mkIf cfg.apiserver.enable {
+      services.kubernetes.pki.etcClusterAdminKubeconfig = mkDefault "kubernetes/cluster-admin.kubeconfig";
+      services.kubernetes.apiserver.etcd.servers = mkDefault etcdEndpoints;
+    })
+
+    (mkIf cfg.kubelet.enable {
+      virtualisation.docker = {
+        enable = mkDefault true;
+
+        # kubernetes needs access to logs
+        logDriver = mkDefault "json-file";
+
+        # iptables must be disabled for kubernetes
+        extraOptions = "--iptables=false --ip-masq=false";
+      };
+    })
+
+    (mkIf (cfg.apiserver.enable || cfg.controllerManager.enable) {
+      services.kubernetes.pki.certs = {
+        serviceAccount = mkCert {
+          name = "service-account";
+          CN = "system:service-account-signer";
+          action = ''
+            systemctl reload \
+              kube-apiserver.service \
+              kube-controller-manager.service
+          '';
+        };
+      };
+    })
+
+    (mkIf (
+        cfg.apiserver.enable ||
+        cfg.scheduler.enable ||
+        cfg.controllerManager.enable ||
+        cfg.kubelet.enable ||
+        cfg.proxy.enable ||
+        cfg.addonManager.enable
+    ) {
+      systemd.targets.kubernetes = {
+        description = "Kubernetes";
+        wantedBy = [ "multi-user.target" ];
+      };
+
+      systemd.tmpfiles.rules = [
+        "d /opt/cni/bin 0755 root root -"
+        "d /run/kubernetes 0755 kubernetes kubernetes -"
+        "d /var/lib/kubernetes 0755 kubernetes kubernetes -"
+      ];
+
+      users.users.kubernetes = {
+        uid = config.ids.uids.kubernetes;
+        description = "Kubernetes user";
+        extraGroups = [ "docker" ];
+        group = "kubernetes";
+        home = cfg.dataDir;
+        createHome = true;
+      };
+      users.groups.kubernetes.gid = config.ids.gids.kubernetes;
+
+      # dns addon is enabled by default
+      services.kubernetes.addons.dns.enable = mkDefault true;
+
+      services.kubernetes.apiserverAddress = mkDefault ("https://${if cfg.apiserver.advertiseAddress != null
+                          then cfg.apiserver.advertiseAddress
+                          else "${cfg.masterAddress}:${toString cfg.apiserver.securePort}"}");
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix
new file mode 100644
index 000000000000..548ffed1ddb5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/flannel.nix
@@ -0,0 +1,134 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  top = config.services.kubernetes;
+  cfg = top.flannel;
+
+  # we want flannel to use kubernetes itself as configuration backend, not direct etcd
+  storageBackend = "kubernetes";
+
+  # needed for flannel to pass options to docker
+  mkDockerOpts = pkgs.runCommand "mk-docker-opts" {
+    buildInputs = [ pkgs.makeWrapper ];
+  } ''
+    mkdir -p $out
+
+    # bashInteractive needed for `compgen`
+    makeWrapper ${pkgs.bashInteractive}/bin/bash $out/mk-docker-opts --add-flags "${pkgs.kubernetes}/bin/mk-docker-opts.sh"
+  '';
+in
+{
+  ###### interface
+  options.services.kubernetes.flannel = {
+    enable = mkEnableOption "enable flannel networking";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.flannel = {
+
+      enable = mkDefault true;
+      network = mkDefault top.clusterCidr;
+      inherit storageBackend;
+      nodeName = config.services.kubernetes.kubelet.hostname;
+    };
+
+    services.kubernetes.kubelet = {
+      networkPlugin = mkDefault "cni";
+      cni.config = mkDefault [{
+        name = "mynet";
+        type = "flannel";
+        cniVersion = "0.3.1";
+        delegate = {
+          isDefaultGateway = true;
+          bridge = "docker0";
+        };
+      }];
+    };
+
+    systemd.services.mk-docker-opts = {
+      description = "Pre-Docker Actions";
+      path = with pkgs; [ gawk gnugrep ];
+      script = ''
+        ${mkDockerOpts}/mk-docker-opts -d /run/flannel/docker
+        systemctl restart docker
+      '';
+      serviceConfig.Type = "oneshot";
+    };
+
+    systemd.paths.flannel-subnet-env = {
+      wantedBy = [ "flannel.service" ];
+      pathConfig = {
+        PathModified = "/run/flannel/subnet.env";
+        Unit = "mk-docker-opts.service";
+      };
+    };
+
+    systemd.services.docker = {
+      environment.DOCKER_OPTS = "-b none";
+      serviceConfig.EnvironmentFile = "-/run/flannel/docker";
+    };
+
+    # read environment variables generated by mk-docker-opts
+    virtualisation.docker.extraOptions = "$DOCKER_OPTS";
+
+    networking = {
+      firewall.allowedUDPPorts = [
+        8285  # flannel udp
+        8472  # flannel vxlan
+      ];
+      dhcpcd.denyInterfaces = [ "docker*" "flannel*" ];
+    };
+
+    services.kubernetes.pki.certs = {
+      flannelClient = top.lib.mkCert {
+        name = "flannel-client";
+        CN = "flannel-client";
+        action = "systemctl restart flannel.service";
+      };
+    };
+
+    # give flannel som kubernetes rbac permissions if applicable
+    services.kubernetes.addonManager.bootstrapAddons = mkIf ((storageBackend == "kubernetes") && (elem "RBAC" top.apiserver.authorizationMode)) {
+
+      flannel-cr = {
+        apiVersion = "rbac.authorization.k8s.io/v1beta1";
+        kind = "ClusterRole";
+        metadata = { name = "flannel"; };
+        rules = [{
+          apiGroups = [ "" ];
+          resources = [ "pods" ];
+          verbs = [ "get" ];
+        }
+        {
+          apiGroups = [ "" ];
+          resources = [ "nodes" ];
+          verbs = [ "list" "watch" ];
+        }
+        {
+          apiGroups = [ "" ];
+          resources = [ "nodes/status" ];
+          verbs = [ "patch" ];
+        }];
+      };
+
+      flannel-crb = {
+        apiVersion = "rbac.authorization.k8s.io/v1beta1";
+        kind = "ClusterRoleBinding";
+        metadata = { name = "flannel"; };
+        roleRef = {
+          apiGroup = "rbac.authorization.k8s.io";
+          kind = "ClusterRole";
+          name = "flannel";
+        };
+        subjects = [{
+          kind = "User";
+          name = "flannel-client";
+        }];
+      };
+
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
new file mode 100644
index 000000000000..c3d67552cc8c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -0,0 +1,350 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  top = config.services.kubernetes;
+  cfg = top.kubelet;
+
+  cniConfig =
+    if cfg.cni.config != [] && cfg.cni.configDir != null then
+      throw "Verbatim CNI-config and CNI configDir cannot both be set."
+    else if cfg.cni.configDir != null then
+      cfg.cni.configDir
+    else
+      (pkgs.buildEnv {
+        name = "kubernetes-cni-config";
+        paths = imap (i: entry:
+          pkgs.writeTextDir "${toString (10+i)}-${entry.type}.conf" (builtins.toJSON entry)
+        ) cfg.cni.config;
+      });
+
+  infraContainer = pkgs.dockerTools.buildImage {
+    name = "pause";
+    tag = "latest";
+    contents = top.package.pause;
+    config.Cmd = "/bin/pause";
+  };
+
+  kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig;
+
+  manifestPath = "kubernetes/manifests";
+
+  taintOptions = with lib.types; { name, ... }: {
+    options = {
+      key = mkOption {
+        description = "Key of taint.";
+        default = name;
+        type = str;
+      };
+      value = mkOption {
+        description = "Value of taint.";
+        type = str;
+      };
+      effect = mkOption {
+        description = "Effect of taint.";
+        example = "NoSchedule";
+        type = enum ["NoSchedule" "PreferNoSchedule" "NoExecute"];
+      };
+    };
+  };
+
+  taints = concatMapStringsSep "," (v: "${v.key}=${v.value}:${v.effect}") (mapAttrsToList (n: v: v) cfg.taints);
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "")
+    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "")
+    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "allowPrivileged" ] "")
+  ];
+
+  ###### interface
+  options.services.kubernetes.kubelet = with lib.types; {
+
+    address = mkOption {
+      description = "Kubernetes kubelet info server listening address.";
+      default = "0.0.0.0";
+      type = str;
+    };
+
+    clusterDns = mkOption {
+      description = "Use alternative DNS.";
+      default = "10.1.0.1";
+      type = str;
+    };
+
+    clusterDomain = mkOption {
+      description = "Use alternative domain.";
+      default = config.services.kubernetes.addons.dns.clusterDomain;
+      type = str;
+    };
+
+    clientCaFile = mkOption {
+      description = "Kubernetes apiserver CA file for client authentication.";
+      default = top.caFile;
+      type = nullOr path;
+    };
+
+    cni = {
+      packages = mkOption {
+        description = "List of network plugin packages to install.";
+        type = listOf package;
+        default = [];
+      };
+
+      config = mkOption {
+        description = "Kubernetes CNI configuration.";
+        type = listOf attrs;
+        default = [];
+        example = literalExample ''
+          [{
+            "cniVersion": "0.3.1",
+            "name": "mynet",
+            "type": "bridge",
+            "bridge": "cni0",
+            "isGateway": true,
+            "ipMasq": true,
+            "ipam": {
+                "type": "host-local",
+                "subnet": "10.22.0.0/16",
+                "routes": [
+                    { "dst": "0.0.0.0/0" }
+                ]
+            }
+          } {
+            "cniVersion": "0.3.1",
+            "type": "loopback"
+          }]
+        '';
+      };
+
+      configDir = mkOption {
+        description = "Path to Kubernetes CNI configuration directory.";
+        type = nullOr path;
+        default = null;
+      };
+    };
+
+    enable = mkEnableOption "Kubernetes kubelet.";
+
+    extraOpts = mkOption {
+      description = "Kubernetes kubelet extra command line options.";
+      default = "";
+      type = str;
+    };
+
+    featureGates = mkOption {
+      description = "List set of feature gates";
+      default = top.featureGates;
+      type = listOf str;
+    };
+
+    healthz = {
+      bind = mkOption {
+        description = "Kubernetes kubelet healthz listening address.";
+        default = "127.0.0.1";
+        type = str;
+      };
+
+      port = mkOption {
+        description = "Kubernetes kubelet healthz port.";
+        default = 10248;
+        type = int;
+      };
+    };
+
+    hostname = mkOption {
+      description = "Kubernetes kubelet hostname override.";
+      default = config.networking.hostName;
+      type = str;
+    };
+
+    kubeconfig = top.lib.mkKubeConfigOptions "Kubelet";
+
+    manifests = mkOption {
+      description = "List of manifests to bootstrap with kubelet (only pods can be created as manifest entry)";
+      type = attrsOf attrs;
+      default = {};
+    };
+
+    networkPlugin = mkOption {
+      description = "Network plugin to use by Kubernetes.";
+      type = nullOr (enum ["cni" "kubenet"]);
+      default = "kubenet";
+    };
+
+    nodeIp = mkOption {
+      description = "IP address of the node. If set, kubelet will use this IP address for the node.";
+      default = null;
+      type = nullOr str;
+    };
+
+    registerNode = mkOption {
+      description = "Whether to auto register kubelet with API server.";
+      default = true;
+      type = bool;
+    };
+
+    port = mkOption {
+      description = "Kubernetes kubelet info server listening port.";
+      default = 10250;
+      type = int;
+    };
+
+    seedDockerImages = mkOption {
+      description = "List of docker images to preload on system";
+      default = [];
+      type = listOf package;
+    };
+
+    taints = mkOption {
+      description = "Node taints (https://kubernetes.io/docs/concepts/configuration/assign-pod-node/).";
+      default = {};
+      type = attrsOf (submodule [ taintOptions ]);
+    };
+
+    tlsCertFile = mkOption {
+      description = "File containing x509 Certificate for HTTPS.";
+      default = null;
+      type = nullOr path;
+    };
+
+    tlsKeyFile = mkOption {
+      description = "File containing x509 private key matching tlsCertFile.";
+      default = null;
+      type = nullOr path;
+    };
+
+    unschedulable = mkOption {
+      description = "Whether to set node taint to unschedulable=true as it is the case of node that has only master role.";
+      default = false;
+      type = bool;
+    };
+
+    verbosity = mkOption {
+      description = ''
+        Optional glog verbosity level for logging statements. See
+        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+      '';
+      default = null;
+      type = nullOr int;
+    };
+
+  };
+
+  ###### implementation
+  config = mkMerge [
+    (mkIf cfg.enable {
+      services.kubernetes.kubelet.seedDockerImages = [infraContainer];
+
+      systemd.services.kubelet = {
+        description = "Kubernetes Kubelet Service";
+        wantedBy = [ "kubernetes.target" ];
+        after = [ "network.target" "docker.service" "kube-apiserver.service" ];
+        path = with pkgs; [ gitMinimal openssh docker utillinux iproute ethtool thin-provisioning-tools iptables socat ] ++ top.path;
+        preStart = ''
+          ${concatMapStrings (img: ''
+            echo "Seeding docker image: ${img}"
+            docker load <${img}
+          '') cfg.seedDockerImages}
+
+          rm /opt/cni/bin/* || true
+          ${concatMapStrings (package: ''
+            echo "Linking cni package: ${package}"
+            ln -fs ${package}/bin/* /opt/cni/bin
+          '') cfg.cni.packages}
+        '';
+        serviceConfig = {
+          Slice = "kubernetes.slice";
+          CPUAccounting = true;
+          MemoryAccounting = true;
+          Restart = "on-failure";
+          RestartSec = "1000ms";
+          ExecStart = ''${top.package}/bin/kubelet \
+            --address=${cfg.address} \
+            --authentication-token-webhook \
+            --authentication-token-webhook-cache-ttl="10s" \
+            --authorization-mode=Webhook \
+            ${optionalString (cfg.clientCaFile != null)
+              "--client-ca-file=${cfg.clientCaFile}"} \
+            ${optionalString (cfg.clusterDns != "")
+              "--cluster-dns=${cfg.clusterDns}"} \
+            ${optionalString (cfg.clusterDomain != "")
+              "--cluster-domain=${cfg.clusterDomain}"} \
+            --cni-conf-dir=${cniConfig} \
+            ${optionalString (cfg.featureGates != [])
+              "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+            --hairpin-mode=hairpin-veth \
+            --healthz-bind-address=${cfg.healthz.bind} \
+            --healthz-port=${toString cfg.healthz.port} \
+            --hostname-override=${cfg.hostname} \
+            --kubeconfig=${kubeconfig} \
+            ${optionalString (cfg.networkPlugin != null)
+              "--network-plugin=${cfg.networkPlugin}"} \
+            ${optionalString (cfg.nodeIp != null)
+              "--node-ip=${cfg.nodeIp}"} \
+            --pod-infra-container-image=pause \
+            ${optionalString (cfg.manifests != {})
+              "--pod-manifest-path=/etc/${manifestPath}"} \
+            --port=${toString cfg.port} \
+            --register-node=${boolToString cfg.registerNode} \
+            ${optionalString (taints != "")
+              "--register-with-taints=${taints}"} \
+            --root-dir=${top.dataDir} \
+            ${optionalString (cfg.tlsCertFile != null)
+              "--tls-cert-file=${cfg.tlsCertFile}"} \
+            ${optionalString (cfg.tlsKeyFile != null)
+              "--tls-private-key-file=${cfg.tlsKeyFile}"} \
+            ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+            ${cfg.extraOpts}
+          '';
+          WorkingDirectory = top.dataDir;
+        };
+      };
+
+      # Allways include cni plugins
+      services.kubernetes.kubelet.cni.packages = [pkgs.cni-plugins];
+
+      boot.kernelModules = ["br_netfilter"];
+
+      services.kubernetes.kubelet.hostname = with config.networking;
+        mkDefault (hostName + optionalString (domain != null) ".${domain}");
+
+      services.kubernetes.pki.certs = with top.lib; {
+        kubelet = mkCert {
+          name = "kubelet";
+          CN = top.kubelet.hostname;
+          action = "systemctl restart kubelet.service";
+
+        };
+        kubeletClient = mkCert {
+          name = "kubelet-client";
+          CN = "system:node:${top.kubelet.hostname}";
+          fields = {
+            O = "system:nodes";
+          };
+          action = "systemctl restart kubelet.service";
+        };
+      };
+
+      services.kubernetes.kubelet.kubeconfig.server = mkDefault top.apiserverAddress;
+    })
+
+    (mkIf (cfg.enable && cfg.manifests != {}) {
+      environment.etc = mapAttrs' (name: manifest:
+        nameValuePair "${manifestPath}/${name}.json" {
+          text = builtins.toJSON manifest;
+          mode = "0755";
+        }
+      ) cfg.manifests;
+    })
+
+    (mkIf (cfg.unschedulable && cfg.enable) {
+      services.kubernetes.kubelet.taints.unschedulable = {
+        value = "true";
+        effect = "NoSchedule";
+      };
+    })
+
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
new file mode 100644
index 000000000000..4275563f1a36
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -0,0 +1,400 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  top = config.services.kubernetes;
+  cfg = top.pki;
+
+  csrCA = pkgs.writeText "kube-pki-cacert-csr.json" (builtins.toJSON {
+    key = {
+        algo = "rsa";
+        size = 2048;
+    };
+    names = singleton cfg.caSpec;
+  });
+
+  csrCfssl = pkgs.writeText "kube-pki-cfssl-csr.json" (builtins.toJSON {
+    key = {
+        algo = "rsa";
+        size = 2048;
+    };
+    CN = top.masterAddress;
+    hosts = cfg.cfsslAPIExtraSANs;
+  });
+
+  cfsslAPITokenBaseName = "apitoken.secret";
+  cfsslAPITokenPath = "${config.services.cfssl.dataDir}/${cfsslAPITokenBaseName}";
+  certmgrAPITokenPath = "${top.secretsPath}/${cfsslAPITokenBaseName}";
+  cfsslAPITokenLength = 32;
+
+  clusterAdminKubeconfig = with cfg.certs.clusterAdmin;
+    top.lib.mkKubeConfig "cluster-admin" {
+        server = top.apiserverAddress;
+        certFile = cert;
+        keyFile = key;
+    };
+
+  remote = with config.services; "https://${kubernetes.masterAddress}:${toString cfssl.port}";
+in
+{
+  ###### interface
+  options.services.kubernetes.pki = with lib.types; {
+
+    enable = mkEnableOption "easyCert issuer service";
+
+    certs = mkOption {
+      description = "List of certificate specs to feed to cert generator.";
+      default = {};
+      type = attrs;
+    };
+
+    genCfsslCACert = mkOption {
+      description = ''
+        Whether to automatically generate cfssl CA certificate and key,
+        if they don't exist.
+      '';
+      default = true;
+      type = bool;
+    };
+
+    genCfsslAPICerts = mkOption {
+      description = ''
+        Whether to automatically generate cfssl API webserver TLS cert and key,
+        if they don't exist.
+      '';
+      default = true;
+      type = bool;
+    };
+
+    cfsslAPIExtraSANs = mkOption {
+      description = ''
+        Extra x509 Subject Alternative Names to be added to the cfssl API webserver TLS cert.
+      '';
+      default = [];
+      example = [ "subdomain.example.com" ];
+      type = listOf str;
+    };
+
+    genCfsslAPIToken = mkOption {
+      description = ''
+        Whether to automatically generate cfssl API-token secret,
+        if they doesn't exist.
+      '';
+      default = true;
+      type = bool;
+    };
+
+    pkiTrustOnBootstrap = mkOption {
+      description = "Whether to always trust remote cfssl server upon initial PKI bootstrap.";
+      default = true;
+      type = bool;
+    };
+
+    caCertPathPrefix = mkOption {
+      description = ''
+        Path-prefrix for the CA-certificate to be used for cfssl signing.
+        Suffixes ".pem" and "-key.pem" will be automatically appended for
+        the public and private keys respectively.
+      '';
+      default = "${config.services.cfssl.dataDir}/ca";
+      type = str;
+    };
+
+    caSpec = mkOption {
+      description = "Certificate specification for the auto-generated CAcert.";
+      default = {
+        CN = "kubernetes-cluster-ca";
+        O = "NixOS";
+        OU = "services.kubernetes.pki.caSpec";
+        L = "auto-generated";
+      };
+      type = attrs;
+    };
+
+    etcClusterAdminKubeconfig = mkOption {
+      description = ''
+        Symlink a kubeconfig with cluster-admin privileges to environment path
+        (/etc/&lt;path&gt;).
+      '';
+      default = null;
+      type = nullOr str;
+    };
+
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable
+  (let
+    cfsslCertPathPrefix = "${config.services.cfssl.dataDir}/cfssl";
+    cfsslCert = "${cfsslCertPathPrefix}.pem";
+    cfsslKey = "${cfsslCertPathPrefix}-key.pem";
+  in
+  {
+
+    services.cfssl = mkIf (top.apiserver.enable) {
+      enable = true;
+      address = "0.0.0.0";
+      tlsCert = cfsslCert;
+      tlsKey = cfsslKey;
+      configFile = toString (pkgs.writeText "cfssl-config.json" (builtins.toJSON {
+        signing = {
+          profiles = {
+            default = {
+              usages = ["digital signature"];
+              auth_key = "default";
+              expiry = "720h";
+            };
+          };
+        };
+        auth_keys = {
+          default = {
+            type = "standard";
+            key = "file:${cfsslAPITokenPath}";
+          };
+        };
+      }));
+    };
+
+    systemd.services.cfssl.preStart = with pkgs; with config.services.cfssl; mkIf (top.apiserver.enable)
+    (concatStringsSep "\n" [
+      "set -e"
+      (optionalString cfg.genCfsslCACert ''
+        if [ ! -f "${cfg.caCertPathPrefix}.pem" ]; then
+          ${cfssl}/bin/cfssl genkey -initca ${csrCA} | \
+            ${cfssl}/bin/cfssljson -bare ${cfg.caCertPathPrefix}
+        fi
+      '')
+      (optionalString cfg.genCfsslAPICerts ''
+        if [ ! -f "${dataDir}/cfssl.pem" ]; then
+          ${cfssl}/bin/cfssl gencert -ca "${cfg.caCertPathPrefix}.pem" -ca-key "${cfg.caCertPathPrefix}-key.pem" ${csrCfssl} | \
+            ${cfssl}/bin/cfssljson -bare ${cfsslCertPathPrefix}
+        fi
+      '')
+      (optionalString cfg.genCfsslAPIToken ''
+        if [ ! -f "${cfsslAPITokenPath}" ]; then
+          head -c ${toString (cfsslAPITokenLength / 2)} /dev/urandom | od -An -t x | tr -d ' ' >"${cfsslAPITokenPath}"
+        fi
+        chown cfssl "${cfsslAPITokenPath}" && chmod 400 "${cfsslAPITokenPath}"
+      '')]);
+
+    systemd.services.kube-certmgr-bootstrap = {
+      description = "Kubernetes certmgr bootstrapper";
+      wantedBy = [ "certmgr.service" ];
+      after = [ "cfssl.target" ];
+      script = concatStringsSep "\n" [''
+        set -e
+
+        # If there's a cfssl (cert issuer) running locally, then don't rely on user to
+        # manually paste it in place. Just symlink.
+        # otherwise, create the target file, ready for users to insert the token
+
+        if [ -f "${cfsslAPITokenPath}" ]; then
+          ln -fs "${cfsslAPITokenPath}" "${certmgrAPITokenPath}"
+        else
+          touch "${certmgrAPITokenPath}" && chmod 600 "${certmgrAPITokenPath}"
+        fi
+      ''
+      (optionalString (cfg.pkiTrustOnBootstrap) ''
+        if [ ! -f "${top.caFile}" ] || [ $(cat "${top.caFile}" | wc -c) -lt 1 ]; then
+          ${pkgs.curl}/bin/curl --fail-early -f -kd '{}' ${remote}/api/v1/cfssl/info | \
+            ${pkgs.cfssl}/bin/cfssljson -stdout >${top.caFile}
+        fi
+      '')
+      ];
+      serviceConfig = {
+        RestartSec = "10s";
+        Restart = "on-failure";
+      };
+    };
+
+    services.certmgr = {
+      enable = true;
+      package = pkgs.certmgr-selfsigned;
+      svcManager = "command";
+      specs =
+        let
+          mkSpec = _: cert: {
+            inherit (cert) action;
+            authority = {
+              inherit remote;
+              file.path = cert.caCert;
+              root_ca = cert.caCert;
+              profile = "default";
+              auth_key_file = certmgrAPITokenPath;
+            };
+            certificate = {
+              path = cert.cert;
+            };
+            private_key = cert.privateKeyOptions;
+            request = {
+              inherit (cert) CN hosts;
+              key = {
+                algo = "rsa";
+                size = 2048;
+              };
+              names = [ cert.fields ];
+            };
+          };
+        in
+          mapAttrs mkSpec cfg.certs;
+      };
+
+      #TODO: Get rid of kube-addon-manager in the future for the following reasons
+      # - it is basically just a shell script wrapped around kubectl
+      # - it assumes that it is clusterAdmin or can gain clusterAdmin rights through serviceAccount
+      # - it is designed to be used with k8s system components only
+      # - it would be better with a more Nix-oriented way of managing addons
+      systemd.services.kube-addon-manager = mkIf top.addonManager.enable (mkMerge [{
+        environment.KUBECONFIG = with cfg.certs.addonManager;
+          top.lib.mkKubeConfig "addon-manager" {
+            server = top.apiserverAddress;
+            certFile = cert;
+            keyFile = key;
+          };
+        }
+
+        (optionalAttrs (top.addonManager.bootstrapAddons != {}) {
+          serviceConfig.PermissionsStartOnly = true;
+          preStart = with pkgs;
+          let
+            files = mapAttrsToList (n: v: writeText "${n}.json" (builtins.toJSON v))
+              top.addonManager.bootstrapAddons;
+          in
+          ''
+            export KUBECONFIG=${clusterAdminKubeconfig}
+            ${kubectl}/bin/kubectl apply -f ${concatStringsSep " \\\n -f " files}
+          '';
+        })]);
+
+      environment.etc.${cfg.etcClusterAdminKubeconfig}.source = mkIf (!isNull cfg.etcClusterAdminKubeconfig)
+        clusterAdminKubeconfig;
+
+      environment.systemPackages = mkIf (top.kubelet.enable || top.proxy.enable) [
+      (pkgs.writeScriptBin "nixos-kubernetes-node-join" ''
+        set -e
+        exec 1>&2
+
+        if [ $# -gt 0 ]; then
+          echo "Usage: $(basename $0)"
+          echo ""
+          echo "No args. Apitoken must be provided on stdin."
+          echo "To get the apitoken, execute: 'sudo cat ${certmgrAPITokenPath}' on the master node."
+          exit 1
+        fi
+
+        if [ $(id -u) != 0 ]; then
+          echo "Run as root please."
+          exit 1
+        fi
+
+        read -r token
+        if [ ''${#token} != ${toString cfsslAPITokenLength} ]; then
+          echo "Token must be of length ${toString cfsslAPITokenLength}."
+          exit 1
+        fi
+
+        echo $token > ${certmgrAPITokenPath}
+        chmod 600 ${certmgrAPITokenPath}
+
+        echo "Restarting certmgr..." >&1
+        systemctl restart certmgr
+
+        echo "Waiting for certs to appear..." >&1
+
+        ${optionalString top.kubelet.enable ''
+          while [ ! -f ${cfg.certs.kubelet.cert} ]; do sleep 1; done
+          echo "Restarting kubelet..." >&1
+          systemctl restart kubelet
+        ''}
+
+        ${optionalString top.proxy.enable ''
+          while [ ! -f ${cfg.certs.kubeProxyClient.cert} ]; do sleep 1; done
+          echo "Restarting kube-proxy..." >&1
+          systemctl restart kube-proxy
+        ''}
+
+        ${optionalString top.flannel.enable ''
+          while [ ! -f ${cfg.certs.flannelClient.cert} ]; do sleep 1; done
+          echo "Restarting flannel..." >&1
+          systemctl restart flannel
+        ''}
+
+        echo "Node joined succesfully"
+      '')];
+
+      # isolate etcd on loopback at the master node
+      # easyCerts doesn't support multimaster clusters anyway atm.
+      services.etcd = with cfg.certs.etcd; {
+        listenClientUrls = ["https://127.0.0.1:2379"];
+        listenPeerUrls = ["https://127.0.0.1:2380"];
+        advertiseClientUrls = ["https://etcd.local:2379"];
+        initialCluster = ["${top.masterAddress}=https://etcd.local:2380"];
+        initialAdvertisePeerUrls = ["https://etcd.local:2380"];
+        certFile = mkDefault cert;
+        keyFile = mkDefault key;
+        trustedCaFile = mkDefault caCert;
+      };
+      networking.extraHosts = mkIf (config.services.etcd.enable) ''
+        127.0.0.1 etcd.${top.addons.dns.clusterDomain} etcd.local
+      '';
+
+      services.flannel = with cfg.certs.flannelClient; {
+        kubeconfig = top.lib.mkKubeConfig "flannel" {
+          server = top.apiserverAddress;
+          certFile = cert;
+          keyFile = key;
+        };
+      };
+
+      services.kubernetes = {
+
+        apiserver = mkIf top.apiserver.enable (with cfg.certs.apiServer; {
+          etcd = with cfg.certs.apiserverEtcdClient; {
+            servers = ["https://etcd.local:2379"];
+            certFile = mkDefault cert;
+            keyFile = mkDefault key;
+            caFile = mkDefault caCert;
+          };
+          clientCaFile = mkDefault caCert;
+          tlsCertFile = mkDefault cert;
+          tlsKeyFile = mkDefault key;
+          serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.cert;
+          kubeletClientCaFile = mkDefault caCert;
+          kubeletClientCertFile = mkDefault cfg.certs.apiserverKubeletClient.cert;
+          kubeletClientKeyFile = mkDefault cfg.certs.apiserverKubeletClient.key;
+          proxyClientCertFile = mkDefault cfg.certs.apiserverProxyClient.cert;
+          proxyClientKeyFile = mkDefault cfg.certs.apiserverProxyClient.key;
+        });
+        controllerManager = mkIf top.controllerManager.enable {
+          serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.key;
+          rootCaFile = cfg.certs.controllerManagerClient.caCert;
+          kubeconfig = with cfg.certs.controllerManagerClient; {
+            certFile = mkDefault cert;
+            keyFile = mkDefault key;
+          };
+        };
+        scheduler = mkIf top.scheduler.enable {
+          kubeconfig = with cfg.certs.schedulerClient; {
+            certFile = mkDefault cert;
+            keyFile = mkDefault key;
+          };
+        };
+        kubelet = mkIf top.kubelet.enable {
+          clientCaFile = mkDefault cfg.certs.kubelet.caCert;
+          tlsCertFile = mkDefault cfg.certs.kubelet.cert;
+          tlsKeyFile = mkDefault cfg.certs.kubelet.key;
+          kubeconfig = with cfg.certs.kubeletClient; {
+            certFile = mkDefault cert;
+            keyFile = mkDefault key;
+          };
+        };
+        proxy = mkIf top.proxy.enable {
+          kubeconfig = with cfg.certs.kubeProxyClient; {
+            certFile = mkDefault cert;
+            keyFile = mkDefault key;
+          };
+        };
+      };
+    });
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
new file mode 100644
index 000000000000..86d1dc2439bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  top = config.services.kubernetes;
+  cfg = top.proxy;
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "kubernetes" "proxy" "address" ] ["services" "kubernetes" "proxy" "bindAddress"])
+  ];
+
+  ###### interface
+  options.services.kubernetes.proxy = with lib.types; {
+
+    bindAddress = mkOption {
+      description = "Kubernetes proxy listening address.";
+      default = "0.0.0.0";
+      type = str;
+    };
+
+    enable = mkEnableOption "Kubernetes proxy";
+
+    extraOpts = mkOption {
+      description = "Kubernetes proxy extra command line options.";
+      default = "";
+      type = str;
+    };
+
+    featureGates = mkOption {
+      description = "List set of feature gates";
+      default = top.featureGates;
+      type = listOf str;
+    };
+
+    hostname = mkOption {
+      description = "Kubernetes proxy hostname override.";
+      default = config.networking.hostName;
+      type = str;
+    };
+
+    kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes proxy";
+
+    verbosity = mkOption {
+      description = ''
+        Optional glog verbosity level for logging statements. See
+        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+      '';
+      default = null;
+      type = nullOr int;
+    };
+
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    systemd.services.kube-proxy = {
+      description = "Kubernetes Proxy Service";
+      wantedBy = [ "kubernetes.target" ];
+      after = [ "kube-apiserver.service" ];
+      path = with pkgs; [ iptables conntrack_tools ];
+      serviceConfig = {
+        Slice = "kubernetes.slice";
+        ExecStart = ''${top.package}/bin/kube-proxy \
+          --bind-address=${cfg.bindAddress} \
+          ${optionalString (top.clusterCidr!=null)
+            "--cluster-cidr=${top.clusterCidr}"} \
+          ${optionalString (cfg.featureGates != [])
+            "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+          --hostname-override=${cfg.hostname} \
+          --kubeconfig=${top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig} \
+          ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+          ${cfg.extraOpts}
+        '';
+        WorkingDirectory = top.dataDir;
+        Restart = "on-failure";
+        RestartSec = 5;
+      };
+    };
+
+    services.kubernetes.proxy.hostname = with config.networking; mkDefault hostName;
+
+    services.kubernetes.pki.certs = {
+      kubeProxyClient = top.lib.mkCert {
+        name = "kube-proxy-client";
+        CN = "system:kube-proxy";
+        action = "systemctl restart kube-proxy.service";
+      };
+    };
+
+    services.kubernetes.proxy.kubeconfig.server = mkDefault top.apiserverAddress;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
new file mode 100644
index 000000000000..5f6113227d9d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  top = config.services.kubernetes;
+  cfg = top.scheduler;
+in
+{
+  ###### interface
+  options.services.kubernetes.scheduler = with lib.types; {
+
+    address = mkOption {
+      description = "Kubernetes scheduler listening address.";
+      default = "127.0.0.1";
+      type = str;
+    };
+
+    enable = mkEnableOption "Kubernetes scheduler";
+
+    extraOpts = mkOption {
+      description = "Kubernetes scheduler extra command line options.";
+      default = "";
+      type = str;
+    };
+
+    featureGates = mkOption {
+      description = "List set of feature gates";
+      default = top.featureGates;
+      type = listOf str;
+    };
+
+    kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes scheduler";
+
+    leaderElect = mkOption {
+      description = "Whether to start leader election before executing main loop.";
+      type = bool;
+      default = true;
+    };
+
+    port = mkOption {
+      description = "Kubernetes scheduler listening port.";
+      default = 10251;
+      type = int;
+    };
+
+    verbosity = mkOption {
+      description = ''
+        Optional glog verbosity level for logging statements. See
+        <link xlink:href="https://github.com/kubernetes/community/blob/master/contributors/devel/logging.md"/>
+      '';
+      default = null;
+      type = nullOr int;
+    };
+
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    systemd.services.kube-scheduler = {
+      description = "Kubernetes Scheduler Service";
+      wantedBy = [ "kubernetes.target" ];
+      after = [ "kube-apiserver.service" ];
+      serviceConfig = {
+        Slice = "kubernetes.slice";
+        ExecStart = ''${top.package}/bin/kube-scheduler \
+          --address=${cfg.address} \
+          ${optionalString (cfg.featureGates != [])
+            "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
+          --kubeconfig=${top.lib.mkKubeConfig "kube-scheduler" cfg.kubeconfig} \
+          --leader-elect=${boolToString cfg.leaderElect} \
+          --port=${toString cfg.port} \
+          ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
+          ${cfg.extraOpts}
+        '';
+        WorkingDirectory = top.dataDir;
+        User = "kubernetes";
+        Group = "kubernetes";
+        Restart = "on-failure";
+        RestartSec = 5;
+      };
+    };
+
+    services.kubernetes.pki.certs = {
+      schedulerClient = top.lib.mkCert {
+        name = "kube-scheduler-client";
+        CN = "system:kube-scheduler";
+        action = "systemctl restart kube-scheduler.service";
+      };
+    };
+
+    services.kubernetes.scheduler.kubeconfig.server = mkDefault top.apiserverAddress;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/computing/boinc/client.nix b/nixpkgs/nixos/modules/services/computing/boinc/client.nix
new file mode 100644
index 000000000000..7becf6240710
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/computing/boinc/client.nix
@@ -0,0 +1,131 @@
+{config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfg = config.services.boinc;
+  allowRemoteGuiRpcFlag = optionalString cfg.allowRemoteGuiRpc "--allow_remote_gui_rpc";
+
+  fhsEnv = pkgs.buildFHSUserEnv {
+    name = "boinc-fhs-env";
+    targetPkgs = pkgs': [ cfg.package ] ++ cfg.extraEnvPackages;
+    runScript = "/bin/boinc_client";
+  };
+  fhsEnvExecutable = "${fhsEnv}/bin/${fhsEnv.name}";
+
+in
+  {
+    options.services.boinc = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the BOINC distributed computing client. If this
+          option is set to true, the boinc_client daemon will be run as a
+          background service. The boinccmd command can be used to control the
+          daemon.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.boinc;
+        defaultText = "pkgs.boinc";
+        description = ''
+          Which BOINC package to use.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/boinc";
+        description = ''
+          The directory in which to store BOINC's configuration and data files.
+        '';
+      };
+
+      allowRemoteGuiRpc = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If set to true, any remote host can connect to and control this BOINC
+          client (subject to password authentication). If instead set to false,
+          only the hosts listed in <varname>dataDir</varname>/remote_hosts.cfg will be allowed to
+          connect.
+
+          See also: <link xlink:href="http://boinc.berkeley.edu/wiki/Controlling_BOINC_remotely#Remote_access"/>
+        '';
+      };
+
+      extraEnvPackages = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = "[ pkgs.virtualbox ]";
+        description = ''
+          Additional packages to make available in the environment in which
+          BOINC will run. Common choices are:
+          <variablelist>
+            <varlistentry>
+              <term><varname>pkgs.virtualbox</varname></term>
+              <listitem><para>
+                The VirtualBox virtual machine framework. Required by some BOINC
+                projects, such as ATLAS@home.
+              </para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><varname>pkgs.ocl-icd</varname></term>
+              <listitem><para>
+                OpenCL infrastructure library. Required by BOINC projects that
+                use OpenCL, in addition to a device-specific OpenCL driver.
+              </para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><varname>pkgs.linuxPackages.nvidia_x11</varname></term>
+              <listitem><para>
+                Provides CUDA libraries. Required by BOINC projects that use
+                CUDA. Note that this requires an NVIDIA graphics device to be
+                present on the system.
+              </para><para>
+                Also provides OpenCL drivers for NVIDIA GPUs;
+                <varname>pkgs.ocl-icd</varname> is also needed in this case.
+              </para></listitem>
+            </varlistentry>
+          </variablelist>
+        '';
+      };
+    };
+
+    config = mkIf cfg.enable {
+      environment.systemPackages = [cfg.package];
+
+      users.users.boinc = {
+        group = "boinc";
+        createHome = false;
+        description = "BOINC Client";
+        home = cfg.dataDir;
+        isSystemUser = true;
+      };
+      users.groups.boinc = {};
+
+      systemd.tmpfiles.rules = [
+        "d '${cfg.dataDir}' - boinc boinc - -"
+      ];
+
+      systemd.services.boinc = {
+        description = "BOINC Client";
+        after = ["network.target"];
+        wantedBy = ["multi-user.target"];
+        script = ''
+          ${fhsEnvExecutable} --dir ${cfg.dataDir} ${allowRemoteGuiRpcFlag}
+        '';
+        serviceConfig = {
+          User = "boinc";
+          Nice = 10;
+        };
+      };
+    };
+
+    meta = {
+      maintainers = with lib.maintainers; [kierdavis];
+    };
+  }
diff --git a/nixpkgs/nixos/modules/services/computing/foldingathome/client.nix b/nixpkgs/nixos/modules/services/computing/foldingathome/client.nix
new file mode 100644
index 000000000000..9f99af48c48a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/computing/foldingathome/client.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.foldingathome;
+
+  args =
+    ["--team" "${toString cfg.team}"]
+    ++ lib.optionals (cfg.user != null) ["--user" cfg.user]
+    ++ cfg.extraArgs
+    ;
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "foldingAtHome" ] [ "services" "foldingathome" ])
+    (mkRenamedOptionModule [ "services" "foldingathome" "nickname" ] [ "services" "foldingathome" "user" ])
+    (mkRemovedOptionModule [ "services" "foldingathome" "config" ] ''
+      Use <literal>services.foldingathome.extraArgs instead<literal>
+    '')
+  ];
+  options.services.foldingathome = {
+    enable = mkEnableOption "Enable the Folding@home client";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.fahclient;
+      defaultText = "pkgs.fahclient";
+      description = ''
+        Which Folding@home client to use.
+      '';
+    };
+
+    user = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The user associated with the reported computation results. This will
+        be used in the ranking statistics.
+      '';
+    };
+
+    team = mkOption {
+      type = types.int;
+      default = 236565;
+      description = ''
+        The team ID associated with the reported computation results. This
+        will be used in the ranking statistics.
+
+        By default, use the NixOS folding@home team ID is being used.
+      '';
+    };
+
+    extraArgs = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra startup options for the FAHClient. Run
+        <literal>FAHClient --help</literal> to find all the available options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.foldingathome = {
+      description = "Folding@home client";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        exec ${cfg.package}/bin/FAHClient ${lib.escapeShellArgs args}
+      '';
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = "foldingathome";
+        WorkingDirectory = "%S/foldingathome";
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ zimbatm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
new file mode 100644
index 000000000000..050872e933fb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/computing/slurm/slurm.nix
@@ -0,0 +1,413 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.slurm;
+  # configuration file can be generated by http://slurm.schedmd.com/configurator.html
+
+  defaultUser = "slurm";
+
+  configFile = pkgs.writeTextDir "slurm.conf"
+    ''
+      ClusterName=${cfg.clusterName}
+      StateSaveLocation=${cfg.stateSaveLocation}
+      SlurmUser=${cfg.user}
+      ${optionalString (cfg.controlMachine != null) ''controlMachine=${cfg.controlMachine}''}
+      ${optionalString (cfg.controlAddr != null) ''controlAddr=${cfg.controlAddr}''}
+      ${toString (map (x: "NodeName=${x}\n") cfg.nodeName)}
+      ${toString (map (x: "PartitionName=${x}\n") cfg.partitionName)}
+      PlugStackConfig=${plugStackConfig}/plugstack.conf
+      ProctrackType=${cfg.procTrackType}
+      ${cfg.extraConfig}
+    '';
+
+  plugStackConfig = pkgs.writeTextDir "plugstack.conf"
+    ''
+      ${optionalString cfg.enableSrunX11 ''optional ${pkgs.slurm-spank-x11}/lib/x11.so''}
+      ${cfg.extraPlugstackConfig}
+    '';
+
+  cgroupConfig = pkgs.writeTextDir "cgroup.conf"
+   ''
+     ${cfg.extraCgroupConfig}
+   '';
+
+  slurmdbdConf = pkgs.writeTextDir "slurmdbd.conf"
+   ''
+     DbdHost=${cfg.dbdserver.dbdHost}
+     SlurmUser=${cfg.user}
+     StorageType=accounting_storage/mysql
+     StorageUser=${cfg.dbdserver.storageUser}
+     ${optionalString (cfg.dbdserver.storagePass != null) "StoragePass=${cfg.dbdserver.storagePass}"}
+     ${cfg.dbdserver.extraConfig}
+   '';
+
+  # slurm expects some additional config files to be
+  # in the same directory as slurm.conf
+  etcSlurm = pkgs.symlinkJoin {
+    name = "etc-slurm";
+    paths = [ configFile cgroupConfig plugStackConfig ] ++ cfg.extraConfigPaths;
+  };
+in
+
+{
+
+  ###### interface
+
+  meta.maintainers = [ maintainers.markuskowa ];
+
+  options = {
+
+    services.slurm = {
+
+      server = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Wether to enable the slurm control daemon.
+            Note that the standard authentication method is "munge".
+            The "munge" service needs to be provided with a password file in order for
+            slurm to work properly (see <literal>services.munge.password</literal>).
+          '';
+        };
+      };
+
+      dbdserver = {
+        enable = mkEnableOption "SlurmDBD service";
+
+        dbdHost = mkOption {
+          type = types.str;
+          default = config.networking.hostName;
+          description = ''
+            Hostname of the machine where <literal>slurmdbd</literal>
+            is running (i.e. name returned by <literal>hostname -s</literal>).
+          '';
+        };
+
+        storageUser = mkOption {
+          type = types.str;
+          default = cfg.user;
+          description = ''
+            Database user name.
+          '';
+        };
+
+        storagePass = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Database password. Note that this password will be publicable
+            readable in the nix store. Use <option>configFile</option>
+            to store the and config file and password outside the nix store.
+          '';
+        };
+
+        configFile = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Path to <literal>slurmdbd.conf</literal>. The password for the database connection
+            is stored in the config file. Use this option to specfify a path
+            outside the nix store. If this option is unset a configuration file
+            will be generated. See also:
+            <citerefentry><refentrytitle>slurmdbd.conf</refentrytitle>
+            <manvolnum>8</manvolnum></citerefentry>.
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Extra configuration for <literal>slurmdbd.conf</literal>
+          '';
+        };
+      };
+
+      client = {
+        enable = mkEnableOption "slurm client daemon";
+      };
+
+      enableStools = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Wether to provide a slurm.conf file.
+          Enable this option if you do not run a slurm daemon on this host
+          (i.e. <literal>server.enable</literal> and <literal>client.enable</literal> are <literal>false</literal>)
+          but you still want to run slurm commands from this host.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.slurm.override { enableX11 = ! cfg.enableSrunX11; };
+        defaultText = "pkgs.slurm";
+        example = literalExample "pkgs.slurm-full";
+        description = ''
+          The package to use for slurm binaries.
+        '';
+      };
+
+      controlMachine = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = null;
+        description = ''
+          The short hostname of the machine where SLURM control functions are
+          executed (i.e. the name returned by the command "hostname -s", use "tux001"
+          rather than "tux001.my.com").
+        '';
+      };
+
+      controlAddr = mkOption {
+        type = types.nullOr types.str;
+        default = cfg.controlMachine;
+        example = null;
+        description = ''
+          Name that ControlMachine should be referred to in establishing a
+          communications path.
+        '';
+      };
+
+      clusterName = mkOption {
+        type = types.str;
+        default = "default";
+        example = "myCluster";
+        description = ''
+          Necessary to distinguish accounting records in a multi-cluster environment.
+        '';
+      };
+
+      nodeName = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = literalExample ''[ "linux[1-32] CPUs=1 State=UNKNOWN" ];'';
+        description = ''
+          Name that SLURM uses to refer to a node (or base partition for BlueGene
+          systems). Typically this would be the string that "/bin/hostname -s"
+          returns. Note that now you have to write node's parameters after the name.
+        '';
+      };
+
+      partitionName = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = literalExample ''[ "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP" ];'';
+        description = ''
+          Name by which the partition may be referenced. Note that now you have
+          to write the partition's parameters after the name.
+        '';
+      };
+
+      enableSrunX11 = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If enabled srun will accept the option "--x11" to allow for X11 forwarding
+          from within an interactive session or a batch job. This activates the
+          slurm-spank-x11 module. Note that this option also enables
+          <option>services.openssh.forwardX11</option> on the client.
+
+          This option requires slurm to be compiled without native X11 support.
+          The default behavior is to re-compile the slurm package with native X11
+          support disabled if this option is set to true.
+
+          To use the native X11 support add <literal>PrologFlags=X11</literal> in <option>extraConfig</option>.
+          Note that this method will only work RSA SSH host keys.
+        '';
+      };
+
+      procTrackType = mkOption {
+        type = types.str;
+        default = "proctrack/linuxproc";
+        description = ''
+          Plugin to be used for process tracking on a job step basis.
+          The slurmd daemon uses this mechanism to identify all processes
+          which are children of processes it spawns for a user job step.
+        '';
+      };
+
+      stateSaveLocation = mkOption {
+        type = types.str;
+        default = "/var/spool/slurmctld";
+        description = ''
+          Directory into which the Slurm controller, slurmctld, saves its state.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = defaultUser;
+        description = ''
+          Set this option when you want to run the slurmctld daemon
+          as something else than the default slurm user "slurm".
+          Note that the UID of this user needs to be the same
+          on all nodes.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration options that will be added verbatim at
+          the end of the slurm configuration file.
+        '';
+      };
+
+      extraPlugstackConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration that will be added to the end of <literal>plugstack.conf</literal>.
+        '';
+      };
+
+      extraCgroupConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra configuration for <literal>cgroup.conf</literal>. This file is
+          used when <literal>procTrackType=proctrack/cgroup</literal>.
+        '';
+      };
+
+      extraConfigPaths = mkOption {
+        type = with types; listOf path;
+        default = [];
+        description = ''
+          Slurm expects config files for plugins in the same path
+          as <literal>slurm.conf</literal>. Add extra nix store
+          paths that should be merged into same directory as
+          <literal>slurm.conf</literal>.
+        '';
+      };
+
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config =
+    let
+      wrappedSlurm = pkgs.stdenv.mkDerivation {
+        name = "wrappedSlurm";
+
+        builder = pkgs.writeText "builder.sh" ''
+          source $stdenv/setup
+          mkdir -p $out/bin
+          find  ${getBin cfg.package}/bin -type f -executable | while read EXE
+          do
+            exename="$(basename $EXE)"
+            wrappername="$out/bin/$exename"
+            cat > "$wrappername" <<EOT
+          #!/bin/sh
+          if [ -z "$SLURM_CONF" ]
+          then
+            SLURM_CONF="${etcSlurm}/slurm.conf" "$EXE" "\$@"
+          else
+            "$EXE" "\$0"
+          fi
+          EOT
+            chmod +x "$wrappername"
+          done
+
+          mkdir -p $out/share
+          ln -s ${getBin cfg.package}/share/man $out/share/man
+        '';
+      };
+
+  in mkIf ( cfg.enableStools ||
+            cfg.client.enable ||
+            cfg.server.enable ||
+            cfg.dbdserver.enable ) {
+
+    environment.systemPackages = [ wrappedSlurm ];
+
+    services.munge.enable = mkDefault true;
+
+    # use a static uid as default to ensure it is the same on all nodes
+    users.users.slurm = mkIf (cfg.user == defaultUser) {
+      name = defaultUser;
+      group = "slurm";
+      uid = config.ids.uids.slurm;
+    };
+
+    users.groups.slurm.gid = config.ids.uids.slurm;
+
+    systemd.services.slurmd = mkIf (cfg.client.enable) {
+      path = with pkgs; [ wrappedSlurm coreutils ]
+        ++ lib.optional cfg.enableSrunX11 slurm-spank-x11;
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "systemd-tmpfiles-clean.service" ];
+
+      serviceConfig = {
+        Type = "forking";
+        KillMode = "process";
+        ExecStart = "${wrappedSlurm}/bin/slurmd";
+        PIDFile = "/run/slurmd.pid";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LimitMEMLOCK = "infinity";
+      };
+
+      preStart = ''
+        mkdir -p /var/spool
+      '';
+    };
+
+    services.openssh.forwardX11 = mkIf cfg.client.enable (mkDefault true);
+
+    systemd.services.slurmctld = mkIf (cfg.server.enable) {
+      path = with pkgs; [ wrappedSlurm munge coreutils ]
+        ++ lib.optional cfg.enableSrunX11 slurm-spank-x11;
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "munged.service" ];
+      requires = [ "munged.service" ];
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${wrappedSlurm}/bin/slurmctld";
+        PIDFile = "/run/slurmctld.pid";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+
+      preStart = ''
+        mkdir -p ${cfg.stateSaveLocation}
+        chown -R ${cfg.user}:slurm ${cfg.stateSaveLocation}
+      '';
+    };
+
+    systemd.services.slurmdbd = mkIf (cfg.dbdserver.enable) {
+      path = with pkgs; [ wrappedSlurm munge coreutils ];
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "munged.service" "mysql.service" ];
+      requires = [ "munged.service" "mysql.service" ];
+
+      # slurm strips the last component off the path
+      environment.SLURM_CONF =
+        if (cfg.dbdserver.configFile == null) then
+          "${slurmdbdConf}/slurm.conf"
+        else
+          cfg.dbdserver.configFile;
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${cfg.package}/bin/slurmdbd";
+        PIDFile = "/run/slurmdbd.pid";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/computing/torque/mom.nix b/nixpkgs/nixos/modules/services/computing/torque/mom.nix
new file mode 100644
index 000000000000..83772539a7ab
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/computing/torque/mom.nix
@@ -0,0 +1,63 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.torque.mom;
+  torque = pkgs.torque;
+
+  momConfig = pkgs.writeText "torque-mom-config" ''
+    $pbsserver ${cfg.serverNode}
+    $logevent 225
+  '';
+
+in
+{
+  options = {
+
+    services.torque.mom = {
+      enable = mkEnableOption "torque computing node";
+
+      serverNode = mkOption {
+        type = types.str;
+        description = "Hostname running pbs server.";
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.torque ];
+
+    systemd.services.torque-mom-init = {
+      path = with pkgs; [ torque utillinux procps inetutils ];
+
+      script = ''
+        pbs_mkdirs -v aux
+        pbs_mkdirs -v mom
+        hostname > /var/spool/torque/server_name
+        cp -v ${momConfig} /var/spool/torque/mom_priv/config
+      '';
+
+      serviceConfig.Type = "oneshot";
+      unitConfig.ConditionPathExists = "!/var/spool/torque";
+    };
+
+    systemd.services.torque-mom = {
+      path = [ torque ];
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "torque-mom-init.service" ];
+      after = [ "torque-mom-init.service" "network.target" ];
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${torque}/bin/pbs_mom";
+        PIDFile = "/var/spool/torque/mom_priv/mom.lock";
+      };
+    };
+
+  };
+}      
diff --git a/nixpkgs/nixos/modules/services/computing/torque/server.nix b/nixpkgs/nixos/modules/services/computing/torque/server.nix
new file mode 100644
index 000000000000..655d1500497e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/computing/torque/server.nix
@@ -0,0 +1,96 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.torque.server;
+  torque = pkgs.torque;
+in
+{
+  options = {
+
+    services.torque.server = {
+
+      enable = mkEnableOption "torque server";
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.torque ];
+
+    systemd.services.torque-server-init = {
+      path = with pkgs; [ torque utillinux procps inetutils ];
+
+      script = ''
+        tmpsetup=$(mktemp -t torque-XXXX)
+        cp -p ${torque}/bin/torque.setup $tmpsetup
+        sed -i $tmpsetup -e 's/pbs_server -t create/pbs_server -f -t create/'
+
+        pbs_mkdirs -v aux
+        pbs_mkdirs -v server
+        hostname > /var/spool/torque/server_name
+        cp -prv ${torque}/var/spool/torque/* /var/spool/torque/
+        $tmpsetup root
+
+        sleep 1
+        rm -f $tmpsetup
+        kill $(pgrep pbs_server) 2>/dev/null
+        kill $(pgrep trqauthd) 2>/dev/null
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+
+      unitConfig = {
+        ConditionPathExists = "!/var/spool/torque";
+      };
+    };
+
+    systemd.services.trqauthd = {
+      path = [ torque ];
+
+      requires = [ "torque-server-init.service" ];
+      after = [ "torque-server-init.service" ];
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${torque}/bin/trqauthd";
+      };
+    };
+
+    systemd.services.torque-server = {
+      path = [ torque ];
+
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "torque-scheduler.service" "trqauthd.service" ];
+      before = [ "trqauthd.service" ];
+      requires = [ "torque-server-init.service" ];
+      after = [ "torque-server-init.service" "network.target" ];
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${torque}/bin/pbs_server";
+        ExecStop = "${torque}/bin/qterm";
+        PIDFile = "/var/spool/torque/server_priv/server.lock";
+      };
+    };
+
+    systemd.services.torque-scheduler = {
+      path = [ torque ];
+
+      requires = [ "torque-server-init.service" ];
+      after = [ "torque-server-init.service" ];
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${torque}/bin/pbs_sched";
+        PIDFile = "/var/spool/torque/sched_priv/sched.lock";
+      };
+    };
+
+  };
+}      
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
new file mode 100644
index 000000000000..0185f490b0c2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -0,0 +1,282 @@
+# NixOS module for Buildbot continous integration server.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.buildbot-master;
+
+  python = cfg.package.pythonModule;
+
+  escapeStr = s: escape ["'"] s;
+
+  defaultMasterCfg = pkgs.writeText "master.cfg" ''
+    from buildbot.plugins import *
+    factory = util.BuildFactory()
+    c = BuildmasterConfig = dict(
+     workers       = [${concatStringsSep "," cfg.workers}],
+     protocols     = { 'pb': {'port': ${toString cfg.pbPort} } },
+     title         = '${escapeStr cfg.title}',
+     titleURL      = '${escapeStr cfg.titleUrl}',
+     buildbotURL   = '${escapeStr cfg.buildbotUrl}',
+     db            = dict(db_url='${escapeStr cfg.dbUrl}'),
+     www           = dict(port=${toString cfg.port}),
+     change_source = [ ${concatStringsSep "," cfg.changeSource} ],
+     schedulers    = [ ${concatStringsSep "," cfg.schedulers} ],
+     builders      = [ ${concatStringsSep "," cfg.builders} ],
+     status        = [ ${concatStringsSep "," cfg.status} ],
+    )
+    for step in [ ${concatStringsSep "," cfg.factorySteps} ]:
+      factory.addStep(step)
+
+    ${cfg.extraConfig}
+  '';
+
+  tacFile = pkgs.writeText "buildbot-master.tac" ''
+    import os
+
+    from twisted.application import service
+    from buildbot.master import BuildMaster
+
+    basedir = '${cfg.buildbotDir}'
+
+    configfile = '${cfg.masterCfg}'
+
+    # Default umask for server
+    umask = None
+
+    # note: this line is matched against to check that this is a buildmaster
+    # directory; do not edit it.
+    application = service.Application('buildmaster')
+
+    m = BuildMaster(basedir, configfile, umask)
+    m.setServiceParent(application)
+  '';
+
+in {
+  options = {
+    services.buildbot-master = {
+
+      factorySteps = mkOption {
+        type = types.listOf types.str;
+        description = "Factory Steps";
+        default = [];
+        example = [
+          "steps.Git(repourl='git://github.com/buildbot/pyflakes.git', mode='incremental')"
+          "steps.ShellCommand(command=['trial', 'pyflakes'])"
+        ];
+      };
+
+      changeSource = mkOption {
+        type = types.listOf types.str;
+        description = "List of Change Sources.";
+        default = [];
+        example = [
+          "changes.GitPoller('git://github.com/buildbot/pyflakes.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
+        ];
+      };
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the Buildbot continuous integration server.";
+      };
+
+      extraConfig = mkOption {
+        type = types.str;
+        description = "Extra configuration to append to master.cfg";
+        default = "c['buildbotNetUsageData'] = None";
+      };
+
+      masterCfg = mkOption {
+        type = types.path;
+        description = "Optionally pass master.cfg path. Other options in this configuration will be ignored.";
+        default = defaultMasterCfg;
+        example = "/etc/nixos/buildbot/master.cfg";
+      };
+
+      schedulers = mkOption {
+        type = types.listOf types.str;
+        description = "List of Schedulers.";
+        default = [
+          "schedulers.SingleBranchScheduler(name='all', change_filter=util.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=['runtests'])"
+          "schedulers.ForceScheduler(name='force',builderNames=['runtests'])"
+        ];
+      };
+
+      builders = mkOption {
+        type = types.listOf types.str;
+        description = "List of Builders.";
+        default = [
+          "util.BuilderConfig(name='runtests',workernames=['example-worker'],factory=factory)"
+        ];
+      };
+
+      workers = mkOption {
+        type = types.listOf types.str;
+        description = "List of Workers.";
+        default = [ "worker.Worker('example-worker', 'pass')" ];
+      };
+
+      status = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        description = "List of status notification endpoints.";
+      };
+
+      user = mkOption {
+        default = "buildbot";
+        type = types.str;
+        description = "User the buildbot server should execute under.";
+      };
+
+      group = mkOption {
+        default = "buildbot";
+        type = types.str;
+        description = "Primary group of buildbot user.";
+      };
+
+      extraGroups = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "List of extra groups that the buildbot user should be a part of.";
+      };
+
+      home = mkOption {
+        default = "/home/buildbot";
+        type = types.path;
+        description = "Buildbot home directory.";
+      };
+
+      buildbotDir = mkOption {
+        default = "${cfg.home}/master";
+        type = types.path;
+        description = "Specifies the Buildbot directory.";
+      };
+
+      pbPort = mkOption {
+        default = 9989;
+        type = types.either types.str types.int;
+        example = "'tcp:9990:interface=127.0.0.1'";
+        description = ''
+          The buildmaster will listen on a TCP port of your choosing
+          for connections from workers.
+          It can also use this port for connections from remote Change Sources,
+          status clients, and debug tools.
+          This port should be visible to the outside world, and you’ll need to tell
+          your worker admins about your choice.
+          If put in (single) quotes, this can also be used as a connection string,
+          as defined in the <link xlink:href="https://twistedmatrix.com/documents/current/core/howto/endpoints.html">ConnectionStrings guide</link>.
+        '';
+      };
+
+      listenAddress = mkOption {
+        default = "0.0.0.0";
+        type = types.str;
+        description = "Specifies the bind address on which the buildbot HTTP interface listens.";
+      };
+
+      buildbotUrl = mkOption {
+        default = "http://localhost:8010/";
+        type = types.str;
+        description = "Specifies the Buildbot URL.";
+      };
+
+      title = mkOption {
+        default = "Buildbot";
+        type = types.str;
+        description = "Specifies the Buildbot Title.";
+      };
+
+      titleUrl = mkOption {
+        default = "Buildbot";
+        type = types.str;
+        description = "Specifies the Buildbot TitleURL.";
+      };
+
+      dbUrl = mkOption {
+        default = "sqlite:///state.sqlite";
+        type = types.str;
+        description = "Specifies the database connection string.";
+      };
+
+      port = mkOption {
+        default = 8010;
+        type = types.int;
+        description = "Specifies port number on which the buildbot HTTP interface listens.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.python3Packages.buildbot-full;
+        defaultText = "pkgs.python3Packages.buildbot-full";
+        description = "Package to use for buildbot.";
+        example = literalExample "pkgs.python3Packages.buildbot";
+      };
+
+      packages = mkOption {
+        default = [ pkgs.git ];
+        example = literalExample "[ pkgs.git ]";
+        type = types.listOf types.package;
+        description = "Packages to add to PATH for the buildbot process.";
+      };
+
+      pythonPackages = mkOption {
+        default = pythonPackages: with pythonPackages; [ ];
+        defaultText = "pythonPackages: with pythonPackages; [ ]";
+        description = "Packages to add the to the PYTHONPATH of the buildbot process.";
+        example = literalExample "pythonPackages: with pythonPackages; [ requests ]";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups = optionalAttrs (cfg.group == "buildbot") {
+      buildbot = { };
+    };
+
+    users.users = optionalAttrs (cfg.user == "buildbot") {
+      buildbot = {
+        description = "Buildbot User.";
+        isNormalUser = true;
+        createHome = true;
+        home = cfg.home;
+        group = cfg.group;
+        extraGroups = cfg.extraGroups;
+        useDefaultShell = true;
+      };
+    };
+
+    systemd.services.buildbot-master = {
+      description = "Buildbot Continuous Integration Server.";
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = cfg.packages ++ cfg.pythonPackages python.pkgs;
+      environment.PYTHONPATH = "${python.withPackages (self: cfg.pythonPackages self ++ [ cfg.package ])}/${python.sitePackages}";
+
+      preStart = ''
+        mkdir -vp "${cfg.buildbotDir}"
+        # Link the tac file so buildbot command line tools recognize the directory
+        ln -sf "${tacFile}" "${cfg.buildbotDir}/buildbot.tac"
+        ${cfg.package}/bin/buildbot create-master --db "${cfg.dbUrl}" "${cfg.buildbotDir}"
+        rm -f buildbot.tac.new master.cfg.sample
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.home;
+        # NOTE: call twistd directly with stdout logging for systemd
+        ExecStart = "${python.pkgs.twisted}/bin/twistd -o --nodaemon --pidfile= --logfile - --python ${tacFile}";
+      };
+    };
+  };
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "buildbot-master" "bpPort" ] [ "services" "buildbot-master" "pbPort" ])
+  ];
+
+  meta.maintainers = with lib.maintainers; [ nand0p mic92 ];
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
new file mode 100644
index 000000000000..52f24b8cee3c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -0,0 +1,187 @@
+# NixOS module for Buildbot Worker.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.buildbot-worker;
+
+  python = cfg.package.pythonModule;
+
+  tacFile = pkgs.writeText "aur-buildbot-worker.tac" ''
+    import os
+    from io import open
+
+    from buildbot_worker.bot import Worker
+    from twisted.application import service
+
+    basedir = '${cfg.buildbotDir}'
+
+    # note: this line is matched against to check that this is a worker
+    # directory; do not edit it.
+    application = service.Application('buildbot-worker')
+
+    master_url_split = '${cfg.masterUrl}'.split(':')
+    buildmaster_host = master_url_split[0]
+    port = int(master_url_split[1])
+    workername = '${cfg.workerUser}'
+
+    with open('${cfg.workerPassFile}', 'r', encoding='utf-8') as passwd_file:
+        passwd = passwd_file.read().strip('\r\n')
+    keepalive = 600
+    umask = None
+    maxdelay = 300
+    numcpus = None
+    allow_shutdown = None
+
+    s = Worker(buildmaster_host, port, workername, passwd, basedir,
+               keepalive, umask=umask, maxdelay=maxdelay,
+               numcpus=numcpus, allow_shutdown=allow_shutdown)
+    s.setServiceParent(application)
+  '';
+
+in {
+  options = {
+    services.buildbot-worker = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the Buildbot Worker.";
+      };
+
+      user = mkOption {
+        default = "bbworker";
+        type = types.str;
+        description = "User the buildbot Worker should execute under.";
+      };
+
+      group = mkOption {
+        default = "bbworker";
+        type = types.str;
+        description = "Primary group of buildbot Worker user.";
+      };
+
+      extraGroups = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "List of extra groups that the Buildbot Worker user should be a part of.";
+      };
+
+      home = mkOption {
+        default = "/home/bbworker";
+        type = types.path;
+        description = "Buildbot home directory.";
+      };
+
+      buildbotDir = mkOption {
+        default = "${cfg.home}/worker";
+        type = types.path;
+        description = "Specifies the Buildbot directory.";
+      };
+
+      workerUser = mkOption {
+        default = "example-worker";
+        type = types.str;
+        description = "Specifies the Buildbot Worker user.";
+      };
+
+      workerPass = mkOption {
+        default = "pass";
+        type = types.str;
+        description = "Specifies the Buildbot Worker password.";
+      };
+
+      workerPassFile = mkOption {
+        type = types.path;
+        description = "File used to store the Buildbot Worker password";
+      };
+
+      hostMessage = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = "Description of this worker";
+      };
+
+      adminMessage = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = "Name of the administrator of this worker";
+      };
+
+      masterUrl = mkOption {
+        default = "localhost:9989";
+        type = types.str;
+        description = "Specifies the Buildbot Worker connection string.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.python3Packages.buildbot-worker;
+        defaultText = "pkgs.python3Packages.buildbot-worker";
+        description = "Package to use for buildbot worker.";
+        example = literalExample "pkgs.python2Packages.buildbot-worker";
+      };
+
+      packages = mkOption {
+        default = with pkgs; [ git ];
+        example = literalExample "[ pkgs.git ]";
+        type = types.listOf types.package;
+        description = "Packages to add to PATH for the buildbot process.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.buildbot-worker.workerPassFile = mkDefault (pkgs.writeText "buildbot-worker-password" cfg.workerPass);
+
+    users.groups = optionalAttrs (cfg.group == "bbworker") {
+      bbworker = { };
+    };
+
+    users.users = optionalAttrs (cfg.user == "bbworker") {
+      bbworker = {
+        description = "Buildbot Worker User.";
+        isNormalUser = true;
+        createHome = true;
+        home = cfg.home;
+        group = cfg.group;
+        extraGroups = cfg.extraGroups;
+        useDefaultShell = true;
+      };
+    };
+
+    systemd.services.buildbot-worker = {
+      description = "Buildbot Worker.";
+      after = [ "network.target" "buildbot-master.service" ];
+      wantedBy = [ "multi-user.target" ];
+      path = cfg.packages;
+      environment.PYTHONPATH = "${python.withPackages (p: [ cfg.package ])}/${python.sitePackages}";
+
+      preStart = ''
+        mkdir -vp "${cfg.buildbotDir}/info"
+        ${optionalString (cfg.hostMessage != null) ''
+          ln -sf "${pkgs.writeText "buildbot-worker-host" cfg.hostMessage}" "${cfg.buildbotDir}/info/host"
+        ''}
+        ${optionalString (cfg.adminMessage != null) ''
+          ln -sf "${pkgs.writeText "buildbot-worker-admin" cfg.adminMessage}" "${cfg.buildbotDir}/info/admin"
+        ''}
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.home;
+
+        # NOTE: call twistd directly with stdout logging for systemd
+        ExecStart = "${python.pkgs.twisted}/bin/twistd --nodaemon --pidfile= --logfile - --python ${tacFile}";
+      };
+
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ nand0p ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agents.nix b/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agents.nix
new file mode 100644
index 000000000000..b0045409ae60
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/buildkite-agents.nix
@@ -0,0 +1,276 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.buildkite-agents;
+
+  mkHookOption = { name, description, example ? null }: {
+    inherit name;
+    value = mkOption {
+      default = null;
+      inherit description;
+      type = types.nullOr types.lines;
+    } // (if example == null then {} else { inherit example; });
+  };
+  mkHookOptions = hooks: listToAttrs (map mkHookOption hooks);
+
+  hooksDir = cfg: let
+    mkHookEntry = name: value: ''
+      cat > $out/${name} <<'EOF'
+      #! ${pkgs.runtimeShell}
+      set -e
+      ${value}
+      EOF
+      chmod 755 $out/${name}
+    '';
+  in pkgs.runCommand "buildkite-agent-hooks" { preferLocalBuild = true; } ''
+    mkdir $out
+    ${concatStringsSep "\n" (mapAttrsToList mkHookEntry (filterAttrs (n: v: v != null) cfg.hooks))}
+  '';
+
+  buildkiteOptions = { name ? "", config, ... }: {
+    options = {
+      enable = mkOption {
+        default = true;
+        type = types.bool;
+        description = "Whether to enable this buildkite agent";
+      };
+
+      package = mkOption {
+        default = pkgs.buildkite-agent;
+        defaultText = "pkgs.buildkite-agent";
+        description = "Which buildkite-agent derivation to use";
+        type = types.package;
+      };
+
+      dataDir = mkOption {
+        default = "/var/lib/buildkite-agent-${name}";
+        description = "The workdir for the agent";
+        type = types.str;
+      };
+
+      runtimePackages = mkOption {
+        default = [ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ];
+        defaultText = "[ pkgs.bash pkgs.gnutar pkgs.gzip pkgs.git pkgs.nix ]";
+        description = "Add programs to the buildkite-agent environment";
+        type = types.listOf types.package;
+      };
+
+      tokenPath = mkOption {
+        type = types.path;
+        description = ''
+          The token from your Buildkite "Agents" page.
+
+          A run-time path to the token file, which is supposed to be provisioned
+          outside of Nix store.
+        '';
+      };
+
+      name = mkOption {
+        type = types.str;
+        default = "%hostname-${name}-%n";
+        description = ''
+          The name of the agent as seen in the buildkite dashboard.
+        '';
+      };
+
+      tags = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        example = { queue = "default"; docker = "true"; ruby2 ="true"; };
+        description = ''
+          Tags for the agent.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = "debug=true";
+        description = ''
+          Extra lines to be added verbatim to the configuration file.
+        '';
+      };
+
+      privateSshKeyPath = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        ## maximum care is taken so that secrets (ssh keys and the CI token)
+        ## don't end up in the Nix store.
+        apply = final: if final == null then null else toString final;
+
+        description = ''
+          OpenSSH private key
+
+          A run-time path to the key file, which is supposed to be provisioned
+          outside of Nix store.
+        '';
+      };
+
+      hooks = mkHookOptions [
+        { name = "checkout";
+          description = ''
+            The `checkout` hook script will replace the default checkout routine of the
+            bootstrap.sh script. You can use this hook to do your own SCM checkout
+            behaviour
+          ''; }
+        { name = "command";
+          description = ''
+            The `command` hook script will replace the default implementation of running
+            the build command.
+          ''; }
+        { name = "environment";
+          description = ''
+            The `environment` hook will run before all other commands, and can be used
+            to set up secrets, data, etc. Anything exported in hooks will be available
+            to the build script.
+
+            Note: the contents of this file will be copied to the world-readable
+            Nix store.
+          '';
+          example = ''
+            export SECRET_VAR=`head -1 /run/keys/secret`
+          ''; }
+        { name = "post-artifact";
+          description = ''
+            The `post-artifact` hook will run just after artifacts are uploaded
+          ''; }
+        { name = "post-checkout";
+          description = ''
+            The `post-checkout` hook will run after the bootstrap script has checked out
+            your projects source code.
+          ''; }
+        { name = "post-command";
+          description = ''
+            The `post-command` hook will run after the bootstrap script has run your
+            build commands
+          ''; }
+        { name = "pre-artifact";
+          description = ''
+            The `pre-artifact` hook will run just before artifacts are uploaded
+          ''; }
+        { name = "pre-checkout";
+          description = ''
+            The `pre-checkout` hook will run just before your projects source code is
+            checked out from your SCM provider
+          ''; }
+        { name = "pre-command";
+          description = ''
+            The `pre-command` hook will run just before your build command runs
+          ''; }
+        { name = "pre-exit";
+          description = ''
+            The `pre-exit` hook will run just before your build job finishes
+          ''; }
+      ];
+
+      hooksPath = mkOption {
+        type = types.path;
+        default = hooksDir config;
+        defaultText = "generated from services.buildkite-agents.<name>.hooks";
+        description = ''
+          Path to the directory storing the hooks.
+          Consider using <option>services.buildkite-agents.&lt;name&gt;.hooks.&lt;name&gt;</option>
+          instead.
+        '';
+      };
+
+      shell = mkOption {
+        type = types.str;
+        default = "${pkgs.bash}/bin/bash -e -c";
+        description = ''
+          Command that buildkite-agent 3 will execute when it spawns a shell.
+        '';
+      };
+    };
+  };
+  enabledAgents = lib.filterAttrs (n: v: v.enable) cfg;
+  mapAgents = function: lib.mkMerge (lib.mapAttrsToList function enabledAgents);
+in
+{
+  options.services.buildkite-agents = mkOption {
+    type = types.attrsOf (types.submodule buildkiteOptions);
+    default = {};
+    description = ''
+      Attribute set of buildkite agents.
+      The attribute key is combined with the hostname and a unique integer to
+      create the final agent name. This can be overridden by setting the `name`
+      attribute.
+    '';
+  };
+
+  config.users.users = mapAgents (name: cfg: {
+    "buildkite-agent-${name}" = {
+      name = "buildkite-agent-${name}";
+      home = cfg.dataDir;
+      createHome = true;
+      description = "Buildkite agent user";
+      extraGroups = [ "keys" ];
+      isSystemUser = true;
+      group = "buildkite-agent-${name}";
+    };
+  });
+  config.users.groups = mapAgents (name: cfg: {
+    "buildkite-agent-${name}" = {};
+  });
+
+  config.systemd.services = mapAgents (name: cfg: {
+    "buildkite-agent-${name}" =
+      { description = "Buildkite Agent";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        path = cfg.runtimePackages ++ [ cfg.package pkgs.coreutils ];
+        environment = config.networking.proxy.envVars // {
+          HOME = cfg.dataDir;
+          NIX_REMOTE = "daemon";
+        };
+
+        ## NB: maximum care is taken so that secrets (ssh keys and the CI token)
+        ##     don't end up in the Nix store.
+        preStart = let
+          sshDir = "${cfg.dataDir}/.ssh";
+          tagStr = lib.concatStringsSep "," (lib.mapAttrsToList (name: value: "${name}=${value}") cfg.tags);
+        in
+          optionalString (cfg.privateSshKeyPath != null) ''
+            mkdir -m 0700 -p "${sshDir}"
+            cp -f "${toString cfg.privateSshKeyPath}" "${sshDir}/id_rsa"
+            chmod 600 "${sshDir}"/id_rsa
+          '' + ''
+            cat > "${cfg.dataDir}/buildkite-agent.cfg" <<EOF
+            token="$(cat ${toString cfg.tokenPath})"
+            name="${cfg.name}"
+            shell="${cfg.shell}"
+            tags="${tagStr}"
+            build-path="${cfg.dataDir}/builds"
+            hooks-path="${cfg.hooksPath}"
+            ${cfg.extraConfig}
+            EOF
+          '';
+
+        serviceConfig =
+          { ExecStart = "${cfg.package}/bin/buildkite-agent start --config ${cfg.dataDir}/buildkite-agent.cfg";
+            User = "buildkite-agent-${name}";
+            RestartSec = 5;
+            Restart = "on-failure";
+            TimeoutSec = 10;
+            # set a long timeout to give buildkite-agent a chance to finish current builds
+            TimeoutStopSec = "2 min";
+            KillMode = "mixed";
+          };
+      };
+  });
+
+  config.assertions = mapAgents (name: cfg: [
+      { assertion = cfg.hooksPath == (hooksDir cfg) || all (v: v == null) (attrValues cfg.hooks);
+        message = ''
+          Options `services.buildkite-agents.${name}.hooksPath' and
+          `services.buildkite-agents.${name}.hooks.<name>' are mutually exclusive.
+        '';
+      }
+  ]);
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "buildkite-agent"] "services.buildkite-agent has been upgraded from version 2 to version 3 and moved to an attribute set at services.buildkite-agents. Please consult the 20.03 release notes for more information.")
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix
new file mode 100644
index 000000000000..eacfed85ddff
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -0,0 +1,494 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.gitlab-runner;
+  hasDocker = config.virtualisation.docker.enable;
+  hashedServices = with builtins; (mapAttrs' (name: service: nameValuePair
+    "${name}_${config.networking.hostName}_${
+        substring 0 12
+        (hashString "md5" (unsafeDiscardStringContext (toJSON service)))}"
+      service)
+    cfg.services);
+  configPath = "$HOME/.gitlab-runner/config.toml";
+  configureScript = pkgs.writeShellScriptBin "gitlab-runner-configure" (
+    if (cfg.configFile != null) then ''
+      mkdir -p $(dirname ${configPath})
+      cp ${cfg.configFile} ${configPath}
+      # make config file readable by service
+      chown -R --reference=$HOME $(dirname ${configPath})
+    '' else ''
+      export CONFIG_FILE=${configPath}
+
+      mkdir -p $(dirname ${configPath})
+
+      # remove no longer existing services
+      gitlab-runner verify --delete
+
+      # current and desired state
+      NEEDED_SERVICES=$(echo ${concatStringsSep " " (attrNames hashedServices)} | tr " " "\n")
+      REGISTERED_SERVICES=$(gitlab-runner list 2>&1 | grep 'Executor' | awk '{ print $1 }')
+
+      # difference between current and desired state
+      NEW_SERVICES=$(grep -vxF -f <(echo "$REGISTERED_SERVICES") <(echo "$NEEDED_SERVICES") || true)
+      OLD_SERVICES=$(grep -vxF -f <(echo "$NEEDED_SERVICES") <(echo "$REGISTERED_SERVICES") || true)
+
+      # register new services
+      ${concatStringsSep "\n" (mapAttrsToList (name: service: ''
+        if echo "$NEW_SERVICES" | grep -xq ${name}; then
+          bash -c ${escapeShellArg (concatStringsSep " \\\n " ([
+            "set -a && source ${service.registrationConfigFile} &&"
+            "gitlab-runner register"
+            "--non-interactive"
+            "--name ${name}"
+            "--executor ${service.executor}"
+            "--limit ${toString service.limit}"
+            "--request-concurrency ${toString service.requestConcurrency}"
+            "--maximum-timeout ${toString service.maximumTimeout}"
+          ] ++ service.registrationFlags
+            ++ optional (service.buildsDir != null)
+            "--builds-dir ${service.buildsDir}"
+            ++ optional (service.preCloneScript != null)
+            "--pre-clone-script ${service.preCloneScript}"
+            ++ optional (service.preBuildScript != null)
+            "--pre-build-script ${service.preBuildScript}"
+            ++ optional (service.postBuildScript != null)
+            "--post-build-script ${service.postBuildScript}"
+            ++ optional (service.tagList != [ ])
+            "--tag-list ${concatStringsSep "," service.tagList}"
+            ++ optional service.runUntagged
+            "--run-untagged"
+            ++ optional service.protected
+            "--access-level ref_protected"
+            ++ optional service.debugTraceDisabled
+            "--debug-trace-disabled"
+            ++ map (e: "--env ${escapeShellArg e}") (mapAttrsToList (name: value: "${name}=${value}") service.environmentVariables)
+            ++ optionals (service.executor == "docker") (
+              assert (
+                assertMsg (service.dockerImage != null)
+                  "dockerImage option is required for docker executor (${name})");
+              [ "--docker-image ${service.dockerImage}" ]
+              ++ optional service.dockerDisableCache
+              "--docker-disable-cache"
+              ++ optional service.dockerPrivileged
+              "--docker-privileged"
+              ++ map (v: "--docker-volumes ${escapeShellArg v}") service.dockerVolumes
+              ++ map (v: "--docker-extra-hosts ${escapeShellArg v}") service.dockerExtraHosts
+              ++ map (v: "--docker-allowed-images ${escapeShellArg v}") service.dockerAllowedImages
+              ++ map (v: "--docker-allowed-services ${escapeShellArg v}") service.dockerAllowedServices
+            )
+          ))} && sleep 1
+        fi
+      '') hashedServices)}
+
+      # unregister old services
+      for NAME in $(echo "$OLD_SERVICES")
+      do
+        [ ! -z "$NAME" ] && gitlab-runner unregister \
+          --name "$NAME" && sleep 1
+      done
+
+      # update global options
+      remarshal --if toml --of json ${configPath} \
+        | jq -cM '.check_interval = ${toString cfg.checkInterval} |
+                  .concurrent = ${toString cfg.concurrent}' \
+        | remarshal --if json --of toml \
+        | sponge ${configPath}
+
+      # make config file readable by service
+      chown -R --reference=$HOME $(dirname ${configPath})
+    '');
+  startScript = pkgs.writeShellScriptBin "gitlab-runner-start" ''
+    export CONFIG_FILE=${configPath}
+    exec gitlab-runner run --working-directory $HOME
+  '';
+in
+{
+  options.services.gitlab-runner = {
+    enable = mkEnableOption "Gitlab Runner";
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Configuration file for gitlab-runner.
+
+        <option>configFile</option> takes precedence over <option>services</option>.
+        <option>checkInterval</option> and <option>concurrent</option> will be ignored too.
+
+        This option is deprecated, please use <option>services</option> instead.
+        You can use <option>registrationConfigFile</option> and
+        <option>registrationFlags</option>
+        for settings not covered by this module.
+      '';
+    };
+    checkInterval = mkOption {
+      type = types.int;
+      default = 0;
+      example = literalExample "with lib; (length (attrNames config.services.gitlab-runner.services)) * 3";
+      description = ''
+        Defines the interval length, in seconds, between new jobs check.
+        The default value is 3;
+        if set to 0 or lower, the default value will be used.
+        See <link xlink:href="https://docs.gitlab.com/runner/configuration/advanced-configuration.html#how-check_interval-works">runner documentation</link> for more information.
+      '';
+    };
+    concurrent = mkOption {
+      type = types.int;
+      default = 1;
+      example = literalExample "config.nix.maxJobs";
+      description = ''
+        Limits how many jobs globally can be run concurrently.
+        The most upper limit of jobs using all defined runners.
+        0 does not mean unlimited.
+      '';
+    };
+    gracefulTermination = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Finish all remaining jobs before stopping.
+        If not set gitlab-runner will stop immediatly without waiting
+        for jobs to finish, which will lead to failed builds.
+      '';
+    };
+    gracefulTimeout = mkOption {
+      type = types.str;
+      default = "infinity";
+      example = "5min 20s";
+      description = ''
+        Time to wait until a graceful shutdown is turned into a forceful one.
+      '';
+    };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.gitlab-runner;
+      defaultText = "pkgs.gitlab-runner";
+      example = literalExample "pkgs.gitlab-runner_1_11";
+      description = "Gitlab Runner package to use.";
+    };
+    extraPackages = mkOption {
+      type = types.listOf types.package;
+      default = [ ];
+      description = ''
+        Extra packages to add to PATH for the gitlab-runner process.
+      '';
+    };
+    services = mkOption {
+      description = "GitLab Runner services.";
+      default = { };
+      example = literalExample ''
+        {
+          # runner for building in docker via host's nix-daemon
+          # nix store will be readable in runner, might be insecure
+          nix = {
+            # File should contain at least these two variables:
+            # `CI_SERVER_URL`
+            # `REGISTRATION_TOKEN`
+            registrationConfigFile = "/run/secrets/gitlab-runner-registration";
+            dockerImage = "alpine";
+            dockerVolumes = [
+              "/nix/store:/nix/store:ro"
+              "/nix/var/nix/db:/nix/var/nix/db:ro"
+              "/nix/var/nix/daemon-socket:/nix/var/nix/daemon-socket:ro"
+            ];
+            dockerDisableCache = true;
+            preBuildScript = pkgs.writeScript "setup-container" '''
+              mkdir -p -m 0755 /nix/var/log/nix/drvs
+              mkdir -p -m 0755 /nix/var/nix/gcroots
+              mkdir -p -m 0755 /nix/var/nix/profiles
+              mkdir -p -m 0755 /nix/var/nix/temproots
+              mkdir -p -m 0755 /nix/var/nix/userpool
+              mkdir -p -m 1777 /nix/var/nix/gcroots/per-user
+              mkdir -p -m 1777 /nix/var/nix/profiles/per-user
+              mkdir -p -m 0755 /nix/var/nix/profiles/per-user/root
+              mkdir -p -m 0700 "$HOME/.nix-defexpr"
+
+              . ''${pkgs.nix}/etc/profile.d/nix.sh
+
+              ''${pkgs.nix}/bin/nix-env -i ''${concatStringsSep " " (with pkgs; [ nix cacert git openssh ])}
+
+              ''${pkgs.nix}/bin/nix-channel --add https://nixos.org/channels/nixpkgs-unstable
+              ''${pkgs.nix}/bin/nix-channel --update nixpkgs
+            ''';
+            environmentVariables = {
+              ENV = "/etc/profile";
+              USER = "root";
+              NIX_REMOTE = "daemon";
+              PATH = "/nix/var/nix/profiles/default/bin:/nix/var/nix/profiles/default/sbin:/bin:/sbin:/usr/bin:/usr/sbin";
+              NIX_SSL_CERT_FILE = "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt";
+            };
+            tagList = [ "nix" ];
+          };
+          # runner for building docker images
+          docker-images = {
+            # File should contain at least these two variables:
+            # `CI_SERVER_URL`
+            # `REGISTRATION_TOKEN`
+            registrationConfigFile = "/run/secrets/gitlab-runner-registration";
+            dockerImage = "docker:stable";
+            dockerVolumes = [
+              "/var/run/docker.sock:/var/run/docker.sock"
+            ];
+            tagList = [ "docker-images" ];
+          };
+          # runner for executing stuff on host system (very insecure!)
+          # make sure to add required packages (including git!)
+          # to `environment.systemPackages`
+          shell = {
+            # File should contain at least these two variables:
+            # `CI_SERVER_URL`
+            # `REGISTRATION_TOKEN`
+            registrationConfigFile = "/run/secrets/gitlab-runner-registration";
+            executor = "shell";
+            tagList = [ "shell" ];
+          };
+          # runner for everything else
+          default = {
+            # File should contain at least these two variables:
+            # `CI_SERVER_URL`
+            # `REGISTRATION_TOKEN`
+            registrationConfigFile = "/run/secrets/gitlab-runner-registration";
+            dockerImage = "debian:stable";
+          };
+        }
+      '';
+      type = types.attrsOf (types.submodule {
+        options = {
+          registrationConfigFile = mkOption {
+            type = types.path;
+            description = ''
+              Absolute path to a file with environment variables
+              used for gitlab-runner registration.
+              A list of all supported environment variables can be found in
+              <literal>gitlab-runner register --help</literal>.
+
+              Ones that you probably want to set is
+
+              <literal>CI_SERVER_URL=&lt;CI server URL&gt;</literal>
+
+              <literal>REGISTRATION_TOKEN=&lt;registration secret&gt;</literal>
+            '';
+          };
+          registrationFlags = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "--docker-helper-image my/gitlab-runner-helper" ];
+            description = ''
+              Extra command-line flags passed to
+              <literal>gitlab-runner register</literal>.
+              Execute <literal>gitlab-runner register --help</literal>
+              for a list of supported flags.
+            '';
+          };
+          environmentVariables = mkOption {
+            type = types.attrsOf types.str;
+            default = { };
+            example = { NAME = "value"; };
+            description = ''
+              Custom environment variables injected to build environment.
+              For secrets you can use <option>registrationConfigFile</option>
+              with <literal>RUNNER_ENV</literal> variable set.
+            '';
+          };
+          executor = mkOption {
+            type = types.str;
+            default = "docker";
+            description = ''
+              Select executor, eg. shell, docker, etc.
+              See <link xlink:href="https://docs.gitlab.com/runner/executors/README.html">runner documentation</link> for more information.
+            '';
+          };
+          buildsDir = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            example = "/var/lib/gitlab-runner/builds";
+            description = ''
+              Absolute path to a directory where builds will be stored
+              in context of selected executor (Locally, Docker, SSH).
+            '';
+          };
+          dockerImage = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = ''
+              Docker image to be used.
+            '';
+          };
+          dockerVolumes = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "/var/run/docker.sock:/var/run/docker.sock" ];
+            description = ''
+              Bind-mount a volume and create it
+              if it doesn't exist prior to mounting.
+            '';
+          };
+          dockerDisableCache = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Disable all container caching.
+            '';
+          };
+          dockerPrivileged = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Give extended privileges to container.
+            '';
+          };
+          dockerExtraHosts = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "other-host:127.0.0.1" ];
+            description = ''
+              Add a custom host-to-IP mapping.
+            '';
+          };
+          dockerAllowedImages = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "ruby:*" "python:*" "php:*" "my.registry.tld:5000/*:*" ];
+            description = ''
+              Whitelist allowed images.
+            '';
+          };
+          dockerAllowedServices = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "postgres:9" "redis:*" "mysql:*" ];
+            description = ''
+              Whitelist allowed services.
+            '';
+          };
+          preCloneScript = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = ''
+              Runner-specific command script executed before code is pulled.
+            '';
+          };
+          preBuildScript = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = ''
+              Runner-specific command script executed after code is pulled,
+              just before build executes.
+            '';
+          };
+          postBuildScript = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = ''
+              Runner-specific command script executed after code is pulled
+              and just after build executes.
+            '';
+          };
+          tagList = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            description = ''
+              Tag list.
+            '';
+          };
+          runUntagged = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Register to run untagged builds; defaults to
+              <literal>true</literal> when <option>tagList</option> is empty.
+            '';
+          };
+          limit = mkOption {
+            type = types.int;
+            default = 0;
+            description = ''
+              Limit how many jobs can be handled concurrently by this service.
+              0 (default) simply means don't limit.
+            '';
+          };
+          requestConcurrency = mkOption {
+            type = types.int;
+            default = 0;
+            description = ''
+              Limit number of concurrent requests for new jobs from GitLab.
+            '';
+          };
+          maximumTimeout = mkOption {
+            type = types.int;
+            default = 0;
+            description = ''
+              What is the maximum timeout (in seconds) that will be set for
+              job when using this Runner. 0 (default) simply means don't limit.
+            '';
+          };
+          protected = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              When set to true Runner will only run on pipelines
+              triggered on protected branches.
+            '';
+          };
+          debugTraceDisabled = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              When set to true Runner will disable the possibility of
+              using the <literal>CI_DEBUG_TRACE</literal> feature.
+            '';
+          };
+        };
+      });
+    };
+  };
+  config = mkIf cfg.enable {
+    warnings = optional (cfg.configFile != null) "services.gitlab-runner.`configFile` is deprecated, please use services.gitlab-runner.`services`.";
+    environment.systemPackages = [ cfg.package ];
+    systemd.services.gitlab-runner = {
+      description = "Gitlab Runner";
+      documentation = [ "https://docs.gitlab.com/runner/" ];
+      after = [ "network.target" ]
+        ++ optional hasDocker "docker.service";
+      requires = optional hasDocker "docker.service";
+      wantedBy = [ "multi-user.target" ];
+      environment = config.networking.proxy.envVars // {
+        HOME = "/var/lib/gitlab-runner";
+      };
+      path = with pkgs; [
+        bash
+        gawk
+        jq
+        moreutils
+        remarshal
+        utillinux
+        cfg.package
+      ] ++ cfg.extraPackages;
+      reloadIfChanged = true;
+      serviceConfig = {
+        # Set `DynamicUser` under `systemd.services.gitlab-runner.serviceConfig`
+        # to `lib.mkForce false` in your configuration to run this service as root.
+        # You can also set `User` and `Group` options to run this service as desired user.
+        # Make sure to restart service or changes won't apply.
+        DynamicUser = true;
+        StateDirectory = "gitlab-runner";
+        SupplementaryGroups = optional hasDocker "docker";
+        ExecStartPre = "!${configureScript}/bin/gitlab-runner-configure";
+        ExecStart = "${startScript}/bin/gitlab-runner-start";
+        ExecReload = "!${configureScript}/bin/gitlab-runner-configure";
+      } // optionalAttrs (cfg.gracefulTermination) {
+        TimeoutStopSec = "${cfg.gracefulTimeout}";
+        KillSignal = "SIGQUIT";
+        KillMode = "process";
+      };
+    };
+    # Enable docker if `docker` executor is used in any service
+    virtualisation.docker.enable = mkIf (
+      any (s: s.executor == "docker") (attrValues cfg.services)
+    ) (mkDefault true);
+  };
+  imports = [
+    (mkRenamedOptionModule [ "services" "gitlab-runner" "packages" ] [ "services" "gitlab-runner" "extraPackages" ] )
+    (mkRemovedOptionModule [ "services" "gitlab-runner" "configOptions" ] "Use services.gitlab-runner.services option instead" )
+    (mkRemovedOptionModule [ "services" "gitlab-runner" "workDir" ] "You should move contents of workDir (if any) to /var/lib/gitlab-runner" )
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix
new file mode 100644
index 000000000000..2e9e1c94857a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gocd-agent/default.nix
@@ -0,0 +1,206 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gocd-agent;
+in {
+  options = {
+    services.gocd-agent = {
+      enable = mkEnableOption "gocd-agent";
+
+      user = mkOption {
+        default = "gocd-agent";
+        type = types.str;
+        description = ''
+          User the Go.CD agent should execute under.
+        '';
+      };
+
+      group = mkOption {
+        default = "gocd-agent";
+        type = types.str;
+        description = ''
+          If the default user "gocd-agent" is configured then this is the primary
+          group of that user.
+        '';
+      };
+
+      extraGroups = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "wheel" "docker" ];
+        description = ''
+          List of extra groups that the "gocd-agent" user should be a part of.
+        '';
+      };
+
+      packages = mkOption {
+        default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
+        defaultText = "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
+        type = types.listOf types.package;
+        description = ''
+          Packages to add to PATH for the Go.CD agent process.
+        '';
+      };
+
+      agentConfig = mkOption {
+        default = "";
+        type = types.str;
+        example = ''
+          agent.auto.register.resources=ant,java
+          agent.auto.register.environments=QA,Performance
+          agent.auto.register.hostname=Agent01
+        '';
+        description = ''
+          Agent registration configuration.
+        '';
+      };
+
+      goServer = mkOption {
+        default = "https://127.0.0.1:8154/go";
+        type = types.str;
+        description = ''
+          URL of the GoCD Server to attach the Go.CD Agent to.
+        '';
+      };
+
+      workDir = mkOption {
+        default = "/var/lib/go-agent";
+        type = types.str;
+        description = ''
+          Specifies the working directory in which the Go.CD agent java archive resides.
+        '';
+      };
+
+      initialJavaHeapSize = mkOption {
+        default = "128m";
+        type = types.str;
+        description = ''
+          Specifies the initial java heap memory size for the Go.CD agent java process.
+        '';
+      };
+
+      maxJavaHeapMemory = mkOption {
+        default = "256m";
+        type = types.str;
+        description = ''
+          Specifies the java maximum heap memory size for the Go.CD agent java process.
+        '';
+      };
+
+      startupOptions = mkOption {
+        default = [
+          "-Xms${cfg.initialJavaHeapSize}"
+          "-Xmx${cfg.maxJavaHeapMemory}"
+          "-Djava.io.tmpdir=/tmp"
+          "-Dcruise.console.publish.interval=10"
+          "-Djava.security.egd=file:/dev/./urandom"
+        ];
+        description = ''
+          Specifies startup command line arguments to pass to Go.CD agent
+          java process.
+        '';
+      };
+
+      extraOptions = mkOption {
+        default = [ ];
+        example = [
+          "-X debug"
+          "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5006"
+          "-verbose:gc"
+          "-Xloggc:go-agent-gc.log"
+          "-XX:+PrintGCTimeStamps"
+          "-XX:+PrintTenuringDistribution"
+          "-XX:+PrintGCDetails"
+          "-XX:+PrintGC"
+        ];
+        description = ''
+          Specifies additional command line arguments to pass to Go.CD agent
+          java process.  Example contains debug and gcLog arguments.
+        '';
+      };
+
+      environment = mkOption {
+        default = { };
+        type = with types; attrsOf str;
+        description = ''
+          Additional environment variables to be passed to the Go.CD agent process.
+          As a base environment, Go.CD agent receives NIX_PATH from
+          <option>environment.sessionVariables</option>, NIX_REMOTE is set to
+          "daemon".
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups = optionalAttrs (cfg.group == "gocd-agent") {
+      gocd-agent.gid = config.ids.gids.gocd-agent;
+    };
+
+    users.users = optionalAttrs (cfg.user == "gocd-agent") {
+      gocd-agent = {
+        description = "gocd-agent user";
+        createHome = true;
+        home = cfg.workDir;
+        group = cfg.group;
+        extraGroups = cfg.extraGroups;
+        useDefaultShell = true;
+        uid = config.ids.uids.gocd-agent;
+      };
+    };
+
+    systemd.services.gocd-agent = {
+      description = "GoCD Agent";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment =
+        let
+          selectedSessionVars =
+            lib.filterAttrs (n: v: builtins.elem n [ "NIX_PATH" ])
+              config.environment.sessionVariables;
+        in
+          selectedSessionVars //
+            {
+              NIX_REMOTE = "daemon";
+              AGENT_WORK_DIR = cfg.workDir;
+              AGENT_STARTUP_ARGS = ''${concatStringsSep " "  cfg.startupOptions}'';
+              LOG_DIR = cfg.workDir;
+              LOG_FILE = "${cfg.workDir}/go-agent-start.log";
+            } //
+            cfg.environment;
+
+      path = cfg.packages;
+
+      script = ''
+        MPATH="''${PATH}";
+        source /etc/profile
+        export PATH="''${MPATH}:''${PATH}";
+
+        if ! test -f ~/.nixpkgs/config.nix; then
+          mkdir -p ~/.nixpkgs/
+          echo "{ allowUnfree = true; }" > ~/.nixpkgs/config.nix
+        fi
+
+        mkdir -p config
+        rm -f config/autoregister.properties
+        ln -s "${pkgs.writeText "autoregister.properties" cfg.agentConfig}" config/autoregister.properties
+
+        ${pkgs.git}/bin/git config --global --add http.sslCAinfo /etc/ssl/certs/ca-certificates.crt
+        ${pkgs.jre}/bin/java ${concatStringsSep " " cfg.startupOptions} \
+                        ${concatStringsSep " " cfg.extraOptions} \
+                              -jar ${pkgs.gocd-agent}/go-agent/agent-bootstrapper.jar \
+                              -serverUrl ${cfg.goServer}
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+        WorkingDirectory = cfg.workDir;
+        RestartSec = 30;
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix
new file mode 100644
index 000000000000..4fa41ac49edf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/gocd-server/default.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gocd-server;
+in {
+  options = {
+    services.gocd-server = {
+      enable = mkEnableOption "gocd-server";
+
+      user = mkOption {
+        default = "gocd-server";
+        type = types.str;
+        description = ''
+          User the Go.CD server should execute under.
+        '';
+      };
+
+      group = mkOption {
+        default = "gocd-server";
+        type = types.str;
+        description = ''
+          If the default user "gocd-server" is configured then this is the primary group of that user.
+        '';
+      };
+
+      extraGroups = mkOption {
+        default = [ ];
+        example = [ "wheel" "docker" ];
+        description = ''
+          List of extra groups that the "gocd-server" user should be a part of.
+        '';
+      };
+
+      listenAddress = mkOption {
+        default = "0.0.0.0";
+        example = "localhost";
+        type = types.str;
+        description = ''
+          Specifies the bind address on which the Go.CD server HTTP interface listens.
+        '';
+      };
+
+      port = mkOption {
+        default = 8153;
+        type = types.int;
+        description = ''
+          Specifies port number on which the Go.CD server HTTP interface listens.
+        '';
+      };
+
+      sslPort = mkOption {
+        default = 8154;
+        type = types.int;
+        description = ''
+          Specifies port number on which the Go.CD server HTTPS interface listens.
+        '';
+      };
+
+      workDir = mkOption {
+        default = "/var/lib/go-server";
+        type = types.str;
+        description = ''
+          Specifies the working directory in which the Go.CD server java archive resides.
+        '';
+      };
+
+      packages = mkOption {
+        default = [ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ];
+        defaultText = "[ pkgs.stdenv pkgs.jre pkgs.git config.programs.ssh.package pkgs.nix ]";
+        type = types.listOf types.package;
+        description = ''
+          Packages to add to PATH for the Go.CD server's process.
+        '';
+      };
+
+      initialJavaHeapSize = mkOption {
+        default = "512m";
+        type = types.str;
+        description = ''
+          Specifies the initial java heap memory size for the Go.CD server's java process.
+        '';
+      };
+
+      maxJavaHeapMemory = mkOption {
+        default = "1024m";
+        type = types.str;
+        description = ''
+          Specifies the java maximum heap memory size for the Go.CD server's java process.
+        '';
+      };
+
+      startupOptions = mkOption {
+        default = [
+          "-Xms${cfg.initialJavaHeapSize}"
+          "-Xmx${cfg.maxJavaHeapMemory}"
+          "-Dcruise.listen.host=${cfg.listenAddress}"
+          "-Duser.language=en"
+          "-Djruby.rack.request.size.threshold.bytes=30000000"
+          "-Duser.country=US"
+          "-Dcruise.config.dir=${cfg.workDir}/conf"
+          "-Dcruise.config.file=${cfg.workDir}/conf/cruise-config.xml"
+          "-Dcruise.server.port=${toString cfg.port}"
+          "-Dcruise.server.ssl.port=${toString cfg.sslPort}"
+        ];
+
+        description = ''
+          Specifies startup command line arguments to pass to Go.CD server
+          java process.
+        '';
+      };
+
+      extraOptions = mkOption {
+        default = [ ];
+        example = [
+          "-X debug"
+          "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"
+          "-verbose:gc"
+          "-Xloggc:go-server-gc.log"
+          "-XX:+PrintGCTimeStamps"
+          "-XX:+PrintTenuringDistribution"
+          "-XX:+PrintGCDetails"
+          "-XX:+PrintGC"
+        ];
+        description = ''
+          Specifies additional command line arguments to pass to Go.CD server's
+          java process.  Example contains debug and gcLog arguments.
+        '';
+      };
+
+      environment = mkOption {
+        default = { };
+        type = with types; attrsOf str;
+        description = ''
+          Additional environment variables to be passed to the gocd-server process.
+          As a base environment, gocd-server receives NIX_PATH from
+          <option>environment.sessionVariables</option>, NIX_REMOTE is set to
+          "daemon".
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups = optionalAttrs (cfg.group == "gocd-server") {
+      gocd-server.gid = config.ids.gids.gocd-server;
+    };
+
+    users.users = optionalAttrs (cfg.user == "gocd-server") {
+      gocd-server = {
+        description = "gocd-server user";
+        createHome = true;
+        home = cfg.workDir;
+        group = cfg.group;
+        extraGroups = cfg.extraGroups;
+        useDefaultShell = true;
+        uid = config.ids.uids.gocd-server;
+      };
+    };
+
+    systemd.services.gocd-server = {
+      description = "GoCD Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment =
+        let
+          selectedSessionVars =
+            lib.filterAttrs (n: v: builtins.elem n [ "NIX_PATH" ])
+              config.environment.sessionVariables;
+        in
+          selectedSessionVars //
+            { NIX_REMOTE = "daemon";
+            } //
+            cfg.environment;
+
+      path = cfg.packages;
+
+      script = ''
+        ${pkgs.git}/bin/git config --global --add http.sslCAinfo /etc/ssl/certs/ca-certificates.crt
+        ${pkgs.jre}/bin/java -server ${concatStringsSep " " cfg.startupOptions} \
+                               ${concatStringsSep " " cfg.extraOptions}  \
+                              -jar ${pkgs.gocd-server}/go-server/go.jar
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.workDir;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hail.nix b/nixpkgs/nixos/modules/services/continuous-integration/hail.nix
new file mode 100644
index 000000000000..5d0c3f7b4ab3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/hail.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfg = config.services.hail;
+in {
+
+
+  ###### interface
+
+  options.services.hail = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enables the Hail Auto Update Service. Hail can automatically deploy artifacts
+        built by a Hydra Continous Integration server. A common use case is to provide
+        continous deployment for single services or a full NixOS configuration.'';
+    };
+    profile = mkOption {
+      type = types.str;
+      default = "hail-profile";
+      description = "The name of the Nix profile used by Hail.";
+    };
+    hydraJobUri = mkOption {
+      type = types.str;
+      description = "The URI of the Hydra Job.";
+    };
+    netrc = mkOption {
+      type = types.nullOr types.path;
+      description = "The netrc file to use when fetching data from Hydra.";
+      default = null;
+    };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.haskellPackages.hail;
+      defaultText = "pkgs.haskellPackages.hail";
+      description = "Hail package to use.";
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.hail = {
+      description = "Hail Auto Update Service";
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [ nix ];
+      environment = {
+        HOME = "/var/lib/empty";
+      };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/hail --profile ${cfg.profile} --job-uri ${cfg.hydraJobUri}"
+          + lib.optionalString (cfg.netrc != null) " --netrc-file ${cfg.netrc}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
new file mode 100644
index 000000000000..502a5898a5de
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -0,0 +1,507 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.hydra;
+
+  baseDir = "/var/lib/hydra";
+
+  hydraConf = pkgs.writeScript "hydra.conf" cfg.extraConfig;
+
+  hydraEnv =
+    { HYDRA_DBI = cfg.dbi;
+      HYDRA_CONFIG = "${baseDir}/hydra.conf";
+      HYDRA_DATA = "${baseDir}";
+    };
+
+  env =
+    { NIX_REMOTE = "daemon";
+      SSL_CERT_FILE = "/etc/ssl/certs/ca-certificates.crt"; # Remove in 16.03
+      PGPASSFILE = "${baseDir}/pgpass";
+      NIX_REMOTE_SYSTEMS = concatStringsSep ":" cfg.buildMachinesFiles;
+    } // optionalAttrs (cfg.smtpHost != null) {
+      EMAIL_SENDER_TRANSPORT = "SMTP";
+      EMAIL_SENDER_TRANSPORT_host = cfg.smtpHost;
+    } // hydraEnv // cfg.extraEnv;
+
+  serverEnv = env //
+    { HYDRA_TRACKER = cfg.tracker;
+      XDG_CACHE_HOME = "${baseDir}/www/.cache";
+      COLUMNS = "80";
+      PGPASSFILE = "${baseDir}/pgpass-www"; # grrr
+    } // (optionalAttrs cfg.debugServer { DBIC_TRACE = "1"; });
+
+  localDB = "dbi:Pg:dbname=hydra;user=hydra;";
+
+  haveLocalDB = cfg.dbi == localDB;
+
+  inherit (config.system) stateVersion;
+
+  hydra-package =
+  let
+    makeWrapperArgs = concatStringsSep " " (mapAttrsToList (key: value: "--set \"${key}\" \"${value}\"") hydraEnv);
+  in pkgs.buildEnv rec {
+    name = "hydra-env";
+    buildInputs = [ pkgs.makeWrapper ];
+    paths = [ cfg.package ];
+
+    postBuild = ''
+      if [ -L "$out/bin" ]; then
+          unlink "$out/bin"
+      fi
+      mkdir -p "$out/bin"
+
+      for path in ${concatStringsSep " " paths}; do
+        if [ -d "$path/bin" ]; then
+          cd "$path/bin"
+          for prg in *; do
+            if [ -f "$prg" ]; then
+              rm -f "$out/bin/$prg"
+              if [ -x "$prg" ]; then
+                makeWrapper "$path/bin/$prg" "$out/bin/$prg" ${makeWrapperArgs}
+              fi
+            fi
+          done
+        fi
+      done
+   '';
+  };
+
+in
+
+{
+  ###### interface
+  options = {
+
+    services.hydra = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run Hydra services.
+        '';
+      };
+
+      dbi = mkOption {
+        type = types.str;
+        default = localDB;
+        example = "dbi:Pg:dbname=hydra;host=postgres.example.org;user=foo;";
+        description = ''
+          The DBI string for Hydra database connection.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        defaultText = "pkgs.hydra";
+        description = "The Hydra package.";
+      };
+
+      hydraURL = mkOption {
+        type = types.str;
+        description = ''
+          The base URL for the Hydra webserver instance. Used for links in emails.
+        '';
+      };
+
+      listenHost = mkOption {
+        type = types.str;
+        default = "*";
+        example = "localhost";
+        description = ''
+          The hostname or address to listen on or <literal>*</literal> to listen
+          on all interfaces.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 3000;
+        description = ''
+          TCP port the web server should listen to.
+        '';
+      };
+
+      minimumDiskFree = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Threshold of minimum disk space (GiB) to determine if the queue runner should run or not.
+        '';
+      };
+
+      minimumDiskFreeEvaluator = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Threshold of minimum disk space (GiB) to determine if the evaluator should run or not.
+        '';
+      };
+
+      notificationSender = mkOption {
+        type = types.str;
+        description = ''
+          Sender email address used for email notifications.
+        '';
+      };
+
+      smtpHost = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = ["localhost"];
+        description = ''
+          Hostname of the SMTP server to use to send email.
+        '';
+      };
+
+      tracker = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Piece of HTML that is included on all pages.
+        '';
+      };
+
+      logo = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Path to a file containing the logo of your Hydra instance.
+        '';
+      };
+
+      debugServer = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to run the server in debug mode.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        description = "Extra lines for the Hydra configuration.";
+      };
+
+      extraEnv = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        description = "Extra environment variables for Hydra.";
+      };
+
+      gcRootsDir = mkOption {
+        type = types.path;
+        default = "/nix/var/nix/gcroots/hydra";
+        description = "Directory that holds Hydra garbage collector roots.";
+      };
+
+      buildMachinesFiles = mkOption {
+        type = types.listOf types.path;
+        default = optional (config.nix.buildMachines != []) "/etc/nix/machines";
+        example = [ "/etc/nix/machines" "/var/lib/hydra/provisioner/machines" ];
+        description = "List of files containing build machines.";
+      };
+
+      useSubstitutes = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use binary caches for downloading store paths. Note that
+          binary substitutions trigger (a potentially large number of) additional
+          HTTP requests that slow down the queue monitor thread significantly.
+          Also, this Hydra instance will serve those downloaded store paths to
+          its users with its own signature attached as if it had built them
+          itself, so don't enable this feature unless your active binary caches
+          are absolute trustworthy.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    warnings = optional (cfg.package.migration or false) ''
+      You're currently deploying an older version of Hydra which is needed to
+      make some required database changes[1]. As soon as this is done, it's recommended
+      to run `hydra-backfill-ids` and set `services.hydra.package` to `pkgs.hydra-unstable`
+      after that.
+
+      [1] https://github.com/NixOS/hydra/pull/711
+    '';
+
+    services.hydra.package = with pkgs;
+      mkDefault (
+        if pkgs ? hydra
+          then throw ''
+            The Hydra package doesn't exist anymore in `nixpkgs`! It probably exists
+            due to an overlay. To upgrade Hydra, you need to take two steps as some
+            bigger changes in the database schema were implemented recently[1]. You first
+            need to deploy `pkgs.hydra-migration`, run `hydra-backfill-ids` on the server
+            and then deploy `pkgs.hydra-unstable`.
+
+            If you want to use `pkgs.hydra` from your overlay, please set `services.hydra.package`
+            explicitly to `pkgs.hydra` and make sure you know what you're doing.
+
+            [1] https://github.com/NixOS/hydra/pull/711
+          ''
+        else if versionOlder stateVersion "20.03" then hydra-migration
+        else hydra-unstable
+      );
+
+    users.groups.hydra = {
+      gid = config.ids.gids.hydra;
+    };
+
+    users.users.hydra =
+      { description = "Hydra";
+        group = "hydra";
+        createHome = true;
+        home = baseDir;
+        useDefaultShell = true;
+        uid = config.ids.uids.hydra;
+      };
+
+    users.users.hydra-queue-runner =
+      { description = "Hydra queue runner";
+        group = "hydra";
+        useDefaultShell = true;
+        home = "${baseDir}/queue-runner"; # really only to keep SSH happy
+        uid = config.ids.uids.hydra-queue-runner;
+      };
+
+    users.users.hydra-www =
+      { description = "Hydra web server";
+        group = "hydra";
+        useDefaultShell = true;
+        uid = config.ids.uids.hydra-www;
+      };
+
+    nix.trustedUsers = [ "hydra-queue-runner" ];
+
+    services.hydra.extraConfig =
+      ''
+        using_frontend_proxy = 1
+        base_uri = ${cfg.hydraURL}
+        notification_sender = ${cfg.notificationSender}
+        max_servers = 25
+        ${optionalString (cfg.logo != null) ''
+          hydra_logo = ${cfg.logo}
+        ''}
+        gc_roots_dir = ${cfg.gcRootsDir}
+        use-substitutes = ${if cfg.useSubstitutes then "1" else "0"}
+      '';
+
+    environment.systemPackages = [ hydra-package ];
+
+    environment.variables = hydraEnv;
+
+    nix.extraOptions = ''
+      keep-outputs = true
+      keep-derivations = true
+
+      # The default (`true') slows Nix down a lot since the build farm
+      # has so many GC roots.
+      gc-check-reachability = false
+    '';
+
+    systemd.services.hydra-init =
+      { wantedBy = [ "multi-user.target" ];
+        requires = optional haveLocalDB "postgresql.service";
+        after = optional haveLocalDB "postgresql.service";
+        environment = env;
+        preStart = ''
+          mkdir -p ${baseDir}
+          chown hydra.hydra ${baseDir}
+          chmod 0750 ${baseDir}
+
+          ln -sf ${hydraConf} ${baseDir}/hydra.conf
+
+          mkdir -m 0700 -p ${baseDir}/www
+          chown hydra-www.hydra ${baseDir}/www
+
+          mkdir -m 0700 -p ${baseDir}/queue-runner
+          mkdir -m 0750 -p ${baseDir}/build-logs
+          chown hydra-queue-runner.hydra ${baseDir}/queue-runner ${baseDir}/build-logs
+
+          ${optionalString haveLocalDB ''
+            if ! [ -e ${baseDir}/.db-created ]; then
+              ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createuser hydra
+              ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createdb -O hydra hydra
+              touch ${baseDir}/.db-created
+            fi
+            echo "create extension if not exists pg_trgm" | ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql hydra
+          ''}
+
+          if [ ! -e ${cfg.gcRootsDir} ]; then
+
+            # Move legacy roots directory.
+            if [ -e /nix/var/nix/gcroots/per-user/hydra/hydra-roots ]; then
+              mv /nix/var/nix/gcroots/per-user/hydra/hydra-roots ${cfg.gcRootsDir}
+            fi
+
+            mkdir -p ${cfg.gcRootsDir}
+          fi
+
+          # Move legacy hydra-www roots.
+          if [ -e /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots ]; then
+            find /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots/ -type f \
+              | xargs -r mv -f -t ${cfg.gcRootsDir}/
+            rmdir /nix/var/nix/gcroots/per-user/hydra-www/hydra-roots
+          fi
+
+          chown hydra.hydra ${cfg.gcRootsDir}
+          chmod 2775 ${cfg.gcRootsDir}
+        '';
+        serviceConfig.ExecStart = "${hydra-package}/bin/hydra-init";
+        serviceConfig.PermissionsStartOnly = true;
+        serviceConfig.User = "hydra";
+        serviceConfig.Type = "oneshot";
+        serviceConfig.RemainAfterExit = true;
+      };
+
+    systemd.services.hydra-server =
+      { wantedBy = [ "multi-user.target" ];
+        requires = [ "hydra-init.service" ];
+        after = [ "hydra-init.service" ];
+        environment = serverEnv;
+        restartTriggers = [ hydraConf ];
+        serviceConfig =
+          { ExecStart =
+              "@${hydra-package}/bin/hydra-server hydra-server -f -h '${cfg.listenHost}' "
+              + "-p ${toString cfg.port} --max_spare_servers 5 --max_servers 25 "
+              + "--max_requests 100 ${optionalString cfg.debugServer "-d"}";
+            User = "hydra-www";
+            PermissionsStartOnly = true;
+            Restart = "always";
+          };
+      };
+
+    systemd.services.hydra-queue-runner =
+      { wantedBy = [ "multi-user.target" ];
+        requires = [ "hydra-init.service" ];
+        after = [ "hydra-init.service" "network.target" ];
+        path = [ hydra-package pkgs.nettools pkgs.openssh pkgs.bzip2 config.nix.package ];
+        restartTriggers = [ hydraConf ];
+        environment = env // {
+          PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr
+          IN_SYSTEMD = "1"; # to get log severity levels
+        };
+        serviceConfig =
+          { ExecStart = "@${hydra-package}/bin/hydra-queue-runner hydra-queue-runner -v";
+            ExecStopPost = "${hydra-package}/bin/hydra-queue-runner --unlock";
+            User = "hydra-queue-runner";
+            Restart = "always";
+
+            # Ensure we can get core dumps.
+            LimitCORE = "infinity";
+            WorkingDirectory = "${baseDir}/queue-runner";
+          };
+      };
+
+    systemd.services.hydra-evaluator =
+      { wantedBy = [ "multi-user.target" ];
+        requires = [ "hydra-init.service" ];
+        after = [ "hydra-init.service" "network.target" ];
+        path = with pkgs; [ hydra-package nettools jq ];
+        restartTriggers = [ hydraConf ];
+        environment = env;
+        serviceConfig =
+          { ExecStart = "@${hydra-package}/bin/hydra-evaluator hydra-evaluator";
+            User = "hydra";
+            Restart = "always";
+            WorkingDirectory = baseDir;
+          };
+      };
+
+    systemd.services.hydra-update-gc-roots =
+      { requires = [ "hydra-init.service" ];
+        after = [ "hydra-init.service" ];
+        environment = env;
+        serviceConfig =
+          { ExecStart = "@${hydra-package}/bin/hydra-update-gc-roots hydra-update-gc-roots";
+            User = "hydra";
+          };
+        startAt = "2,14:15";
+      };
+
+    systemd.services.hydra-send-stats =
+      { wantedBy = [ "multi-user.target" ];
+        after = [ "hydra-init.service" ];
+        environment = env;
+        serviceConfig =
+          { ExecStart = "@${hydra-package}/bin/hydra-send-stats hydra-send-stats";
+            User = "hydra";
+          };
+      };
+
+    systemd.services.hydra-notify =
+      { wantedBy = [ "multi-user.target" ];
+        requires = [ "hydra-init.service" ];
+        after = [ "hydra-init.service" ];
+        restartTriggers = [ hydraConf ];
+        environment = env // {
+          PGPASSFILE = "${baseDir}/pgpass-queue-runner";
+        };
+        serviceConfig =
+          { ExecStart = "@${hydra-package}/bin/hydra-notify hydra-notify";
+            # FIXME: run this under a less privileged user?
+            User = "hydra-queue-runner";
+            Restart = "always";
+            RestartSec = 5;
+          };
+      };
+
+    # If there is less than a certain amount of free disk space, stop
+    # the queue/evaluator to prevent builds from failing or aborting.
+    systemd.services.hydra-check-space =
+      { script =
+          ''
+            if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFree} * 1024**3)) ]; then
+                echo "stopping Hydra queue runner due to lack of free space..."
+                systemctl stop hydra-queue-runner
+            fi
+            if [ $(($(stat -f -c '%a' /nix/store) * $(stat -f -c '%S' /nix/store))) -lt $((${toString cfg.minimumDiskFreeEvaluator} * 1024**3)) ]; then
+                echo "stopping Hydra evaluator due to lack of free space..."
+                systemctl stop hydra-evaluator
+            fi
+          '';
+        startAt = "*:0/5";
+      };
+
+    # Periodically compress build logs. The queue runner compresses
+    # logs automatically after a step finishes, but this doesn't work
+    # if the queue runner is stopped prematurely.
+    systemd.services.hydra-compress-logs =
+      { path = [ pkgs.bzip2 ];
+        script =
+          ''
+            find /var/lib/hydra/build-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f
+          '';
+        startAt = "Sun 01:45";
+      };
+
+    services.postgresql.enable = mkIf haveLocalDB true;
+
+    services.postgresql.identMap = optionalString haveLocalDB
+      ''
+        hydra-users hydra hydra
+        hydra-users hydra-queue-runner hydra
+        hydra-users hydra-www hydra
+        hydra-users root hydra
+        # The postgres user is used to create the pg_trgm extension for the hydra database
+        hydra-users postgres postgres
+      '';
+
+    services.postgresql.authentication = optionalString haveLocalDB
+      ''
+        local hydra all ident map=hydra-users
+      '';
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix
new file mode 100644
index 000000000000..1477c471f8ab
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/default.nix
@@ -0,0 +1,228 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.jenkins;
+in {
+  options = {
+    services.jenkins = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the jenkins continuous integration server.
+        '';
+      };
+
+      user = mkOption {
+        default = "jenkins";
+        type = types.str;
+        description = ''
+          User the jenkins server should execute under.
+        '';
+      };
+
+      group = mkOption {
+        default = "jenkins";
+        type = types.str;
+        description = ''
+          If the default user "jenkins" is configured then this is the primary
+          group of that user.
+        '';
+      };
+
+      extraGroups = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "wheel" "dialout" ];
+        description = ''
+          List of extra groups that the "jenkins" user should be a part of.
+        '';
+      };
+
+      home = mkOption {
+        default = "/var/lib/jenkins";
+        type = types.path;
+        description = ''
+          The path to use as JENKINS_HOME. If the default user "jenkins" is configured then
+          this is the home of the "jenkins" user.
+        '';
+      };
+
+      listenAddress = mkOption {
+        default = "0.0.0.0";
+        example = "localhost";
+        type = types.str;
+        description = ''
+          Specifies the bind address on which the jenkins HTTP interface listens.
+          The default is the wildcard address.
+        '';
+      };
+
+      port = mkOption {
+        default = 8080;
+        type = types.int;
+        description = ''
+          Specifies port number on which the jenkins HTTP interface listens.
+          The default is 8080.
+        '';
+      };
+
+      prefix = mkOption {
+        default = "";
+        example = "/jenkins";
+        type = types.str;
+        description = ''
+          Specifies a urlPrefix to use with jenkins.
+          If the example /jenkins is given, the jenkins server will be
+          accessible using localhost:8080/jenkins.
+        '';
+      };
+
+      package = mkOption {
+        default = pkgs.jenkins;
+        defaultText = "pkgs.jenkins";
+        type = types.package;
+        description = "Jenkins package to use.";
+      };
+
+      packages = mkOption {
+        default = [ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ];
+        defaultText = "[ pkgs.stdenv pkgs.git pkgs.jdk config.programs.ssh.package pkgs.nix ]";
+        type = types.listOf types.package;
+        description = ''
+          Packages to add to PATH for the jenkins process.
+        '';
+      };
+
+      environment = mkOption {
+        default = { };
+        type = with types; attrsOf str;
+        description = ''
+          Additional environment variables to be passed to the jenkins process.
+          As a base environment, jenkins receives NIX_PATH from
+          <option>environment.sessionVariables</option>, NIX_REMOTE is set to
+          "daemon" and JENKINS_HOME is set to the value of
+          <option>services.jenkins.home</option>.
+          This option has precedence and can be used to override those
+          mentioned variables.
+        '';
+      };
+
+      plugins = mkOption {
+        default = null;
+        type = types.nullOr (types.attrsOf types.package);
+        description = ''
+          A set of plugins to activate. Note that this will completely
+          remove and replace any previously installed plugins. If you
+          have manually-installed plugins that you want to keep while
+          using this module, set this option to
+          <literal>null</literal>. You can generate this set with a
+          tool such as <literal>jenkinsPlugins2nix</literal>.
+        '';
+        example = literalExample ''
+          import path/to/jenkinsPlugins2nix-generated-plugins.nix { inherit (pkgs) fetchurl stdenv; }
+        '';
+      };
+
+      extraOptions = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "--debug=9" ];
+        description = ''
+          Additional command line arguments to pass to Jenkins.
+        '';
+      };
+
+      extraJavaOptions = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "-Xmx80m" ];
+        description = ''
+          Additional command line arguments to pass to the Java run time (as opposed to Jenkins).
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # server references the dejavu fonts
+    environment.systemPackages = [
+      pkgs.dejavu_fonts
+    ];
+
+    users.groups = optionalAttrs (cfg.group == "jenkins") {
+      jenkins.gid = config.ids.gids.jenkins;
+    };
+
+    users.users = optionalAttrs (cfg.user == "jenkins") {
+      jenkins = {
+        description = "jenkins user";
+        createHome = true;
+        home = cfg.home;
+        group = cfg.group;
+        extraGroups = cfg.extraGroups;
+        useDefaultShell = true;
+        uid = config.ids.uids.jenkins;
+      };
+    };
+
+    systemd.services.jenkins = {
+      description = "Jenkins Continuous Integration Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment =
+        let
+          selectedSessionVars =
+            lib.filterAttrs (n: v: builtins.elem n [ "NIX_PATH" ])
+              config.environment.sessionVariables;
+        in
+          selectedSessionVars //
+          { JENKINS_HOME = cfg.home;
+            NIX_REMOTE = "daemon";
+          } //
+          cfg.environment;
+
+      path = cfg.packages;
+
+      # Force .war (re)extraction, or else we might run stale Jenkins.
+
+      preStart =
+        let replacePlugins =
+              if cfg.plugins == null
+              then ""
+              else
+                let pluginCmds = lib.attrsets.mapAttrsToList
+                      (n: v: "cp ${v} ${cfg.home}/plugins/${n}.jpi")
+                      cfg.plugins;
+                in ''
+                  rm -r ${cfg.home}/plugins || true
+                  mkdir -p ${cfg.home}/plugins
+                  ${lib.strings.concatStringsSep "\n" pluginCmds}
+                '';
+        in ''
+          rm -rf ${cfg.home}/war
+          ${replacePlugins}
+        '';
+
+      # For reference: https://wiki.jenkins.io/display/JENKINS/JenkinsLinuxStartupScript
+      script = ''
+        ${pkgs.jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOptions} -jar ${cfg.package}/webapps/jenkins.war --httpListenAddress=${cfg.listenAddress} \
+                                                  --httpPort=${toString cfg.port} \
+                                                  --prefix=${cfg.prefix} \
+                                                  -Djava.awt.headless=true \
+                                                  ${concatStringsSep " " cfg.extraOptions}
+      '';
+
+      postStart = ''
+        until [[ $(${pkgs.curl.bin}/bin/curl -L -s --head -w '\n%{http_code}' http://${cfg.listenAddress}:${toString cfg.port}${cfg.prefix} | tail -n1) =~ ^(200|403)$ ]]; do
+          sleep 1
+        done
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
new file mode 100644
index 000000000000..5d1bfe4ec407
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
@@ -0,0 +1,205 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  jenkinsCfg = config.services.jenkins;
+  cfg = config.services.jenkins.jobBuilder;
+
+in {
+  options = {
+    services.jenkins.jobBuilder = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not to enable the Jenkins Job Builder (JJB) service. It
+          allows defining jobs for Jenkins in a declarative manner.
+
+          Jobs managed through the Jenkins WebUI (or by other means) are left
+          unchanged.
+
+          Note that it really is declarative configuration; if you remove a
+          previously defined job, the corresponding job directory will be
+          deleted.
+
+          Please see the Jenkins Job Builder documentation for more info:
+          <link xlink:href="http://docs.openstack.org/infra/jenkins-job-builder/">
+          http://docs.openstack.org/infra/jenkins-job-builder/</link>
+        '';
+      };
+
+      accessUser = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          User id in Jenkins used to reload config.
+        '';
+      };
+
+      accessToken = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          User token in Jenkins used to reload config.
+          WARNING: This token will be world readable in the Nix store. To keep
+          it secret, use the <option>accessTokenFile</option> option instead.
+        '';
+      };
+
+      accessTokenFile = mkOption {
+        default = "";
+        type = types.str;
+        example = "/run/keys/jenkins-job-builder-access-token";
+        description = ''
+          File containing the API token for the <option>accessUser</option>
+          user.
+        '';
+      };
+
+      yamlJobs = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''
+          - job:
+              name: jenkins-job-test-1
+              builders:
+                - shell: echo 'Hello world!'
+        '';
+        description = ''
+          Job descriptions for Jenkins Job Builder in YAML format.
+        '';
+      };
+
+      jsonJobs = mkOption {
+        default = [ ];
+        type = types.listOf types.str;
+        example = literalExample ''
+          [
+            '''
+              [ { "job":
+                  { "name": "jenkins-job-test-2",
+                    "builders": [ "shell": "echo 'Hello world!'" ]
+                  }
+                }
+              ]
+            '''
+          ]
+        '';
+        description = ''
+          Job descriptions for Jenkins Job Builder in JSON format.
+        '';
+      };
+
+      nixJobs = mkOption {
+        default = [ ];
+        type = types.listOf types.attrs;
+        example = literalExample ''
+          [ { job =
+              { name = "jenkins-job-test-3";
+                builders = [
+                  { shell = "echo 'Hello world!'"; }
+                ];
+              };
+            }
+          ]
+        '';
+        description = ''
+          Job descriptions for Jenkins Job Builder in Nix format.
+
+          This is a trivial wrapper around jsonJobs, using builtins.toJSON
+          behind the scene.
+        '';
+      };
+    };
+  };
+
+  config = mkIf (jenkinsCfg.enable && cfg.enable) {
+    assertions = [
+      { assertion =
+          if cfg.accessUser != ""
+          then (cfg.accessToken != "" && cfg.accessTokenFile == "") ||
+               (cfg.accessToken == "" && cfg.accessTokenFile != "")
+          else true;
+        message = ''
+          One of accessToken and accessTokenFile options must be non-empty
+          strings, but not both. Current values:
+            services.jenkins.jobBuilder.accessToken = "${cfg.accessToken}"
+            services.jenkins.jobBuilder.accessTokenFile = "${cfg.accessTokenFile}"
+        '';
+      }
+    ];
+
+    systemd.services.jenkins-job-builder = {
+      description = "Jenkins Job Builder Service";
+      # JJB can run either before or after jenkins. We chose after, so we can
+      # always use curl to notify (running) jenkins to reload its config.
+      after = [ "jenkins.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      path = with pkgs; [ jenkins-job-builder curl ];
+
+      # Q: Why manipulate files directly instead of using "jenkins-jobs upload [...]"?
+      # A: Because this module is for administering a local jenkins install,
+      #    and using local file copy allows us to not worry about
+      #    authentication.
+      script =
+        let
+          yamlJobsFile = builtins.toFile "jobs.yaml" cfg.yamlJobs;
+          jsonJobsFiles =
+            map (x: (builtins.toFile "jobs.json" x))
+              (cfg.jsonJobs ++ [(builtins.toJSON cfg.nixJobs)]);
+          jobBuilderOutputDir = "/run/jenkins-job-builder/output";
+          # Stamp file is placed in $JENKINS_HOME/jobs/$JOB_NAME/ to indicate
+          # ownership. Enables tracking and removal of stale jobs.
+          ownerStamp = ".config-xml-managed-by-nixos-jenkins-job-builder";
+          reloadScript = ''
+            echo "Asking Jenkins to reload config"
+            curl_opts="--silent --fail --show-error"
+            access_token=${if cfg.accessTokenFile != ""
+                           then "$(cat '${cfg.accessTokenFile}')"
+                           else cfg.accessToken}
+            jenkins_url="http://${cfg.accessUser}:$access_token@${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}"
+            crumb=$(curl $curl_opts "$jenkins_url"'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')
+            curl $curl_opts -X POST -H "$crumb" "$jenkins_url"/reload
+          '';
+        in
+          ''
+            rm -rf ${jobBuilderOutputDir}
+            cur_decl_jobs=/run/jenkins-job-builder/declarative-jobs
+            rm -f "$cur_decl_jobs"
+
+            # Create / update jobs
+            mkdir -p ${jobBuilderOutputDir}
+            for inputFile in ${yamlJobsFile} ${concatStringsSep " " jsonJobsFiles}; do
+                HOME="${jenkinsCfg.home}" "${pkgs.jenkins-job-builder}/bin/jenkins-jobs" --ignore-cache test -o "${jobBuilderOutputDir}" "$inputFile"
+            done
+
+            for file in "${jobBuilderOutputDir}/"*; do
+                test -f "$file" || continue
+                jobname="$(basename $file)"
+                jobdir="${jenkinsCfg.home}/jobs/$jobname"
+                echo "Creating / updating job \"$jobname\""
+                mkdir -p "$jobdir"
+                touch "$jobdir/${ownerStamp}"
+                cp "$file" "$jobdir/config.xml"
+                echo "$jobname" >> "$cur_decl_jobs"
+            done
+
+            # Remove stale jobs
+            for file in "${jenkinsCfg.home}"/jobs/*/${ownerStamp}; do
+                test -f "$file" || continue
+                jobdir="$(dirname $file)"
+                jobname="$(basename "$jobdir")"
+                grep --quiet --line-regexp "$jobname" "$cur_decl_jobs" 2>/dev/null && continue
+                echo "Deleting stale job \"$jobname\""
+                rm -rf "$jobdir"
+            done
+          '' + (if cfg.accessUser != "" then reloadScript else "");
+      serviceConfig = {
+        User = jenkinsCfg.user;
+        RuntimeDirectory = "jenkins-job-builder";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix
new file mode 100644
index 000000000000..3c0e6f78e74c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/continuous-integration/jenkins/slave.nix
@@ -0,0 +1,68 @@
+{ config, lib, ... }:
+with lib;
+let
+  cfg = config.services.jenkinsSlave;
+  masterCfg = config.services.jenkins;
+in {
+  options = {
+    services.jenkinsSlave = {
+      # todo:
+      # * assure the profile of the jenkins user has a JRE and any specified packages. This would
+      # enable ssh slaves.
+      # * Optionally configure the node as a jenkins ad-hoc slave. This would imply configuration
+      # properties for the master node.
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If true the system will be configured to work as a jenkins slave.
+          If the system is also configured to work as a jenkins master then this has no effect.
+          In progress: Currently only assures the jenkins user is configured.
+        '';
+      };
+
+      user = mkOption {
+        default = "jenkins";
+        type = types.str;
+        description = ''
+          User the jenkins slave agent should execute under.
+        '';
+      };
+
+      group = mkOption {
+        default = "jenkins";
+        type = types.str;
+        description = ''
+          If the default slave agent user "jenkins" is configured then this is
+          the primary group of that user.
+        '';
+      };
+
+      home = mkOption {
+        default = "/var/lib/jenkins";
+        type = types.path;
+        description = ''
+          The path to use as JENKINS_HOME. If the default user "jenkins" is configured then
+          this is the home of the "jenkins" user.
+        '';
+      };
+    };
+  };
+
+  config = mkIf (cfg.enable && !masterCfg.enable) {
+    users.groups = optionalAttrs (cfg.group == "jenkins") {
+      jenkins.gid = config.ids.gids.jenkins;
+    };
+
+    users.users = optionalAttrs (cfg.user == "jenkins") {
+      jenkins = {
+        description = "jenkins user";
+        createHome = true;
+        home = cfg.home;
+        group = cfg.group;
+        useDefaultShell = true;
+        uid = config.ids.uids.jenkins;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/aerospike.nix b/nixpkgs/nixos/modules/services/databases/aerospike.nix
new file mode 100644
index 000000000000..4b905f90529d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/aerospike.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.aerospike;
+
+  aerospikeConf = pkgs.writeText "aerospike.conf" ''
+    # This stanza must come first.
+    service {
+      user aerospike
+      group aerospike
+      paxos-single-replica-limit 1 # Number of nodes where the replica count is automatically reduced to 1.
+      proto-fd-max 15000
+      work-directory ${cfg.workDir}
+    }
+    logging {
+      console {
+        context any info
+      }
+    }
+    mod-lua {
+      system-path ${cfg.package}/share/udf/lua
+      user-path ${cfg.workDir}/udf/lua
+    }
+    network {
+      ${cfg.networkConfig}
+    }
+    ${cfg.extraConfig}
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.aerospike = {
+      enable = mkEnableOption "Aerospike server";
+
+      package = mkOption {
+        default = pkgs.aerospike;
+        defaultText = "pkgs.aerospike";
+        type = types.package;
+        description = "Which Aerospike derivation to use";
+      };
+
+      workDir = mkOption {
+        type = types.str;
+        default = "/var/lib/aerospike";
+        description = "Location where Aerospike stores its files";
+      };
+
+      networkConfig = mkOption {
+        type = types.lines;
+        default = ''
+          service {
+            address any
+            port 3000
+          }
+
+          heartbeat {
+            address any
+            mode mesh
+            port 3002
+            interval 150
+            timeout 10
+          }
+
+          fabric {
+            address any
+            port 3001
+          }
+
+          info {
+            address any
+            port 3003
+          }
+        '';
+        description = "network section of configuration file";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          namespace test {
+            replication-factor 2
+            memory-size 4G
+            default-ttl 30d
+            storage-engine memory
+          }
+        '';
+        description = "Extra configuration";
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.aerospike.enable {
+
+    users.users.aerospike = {
+      name = "aerospike";
+      group = "aerospike";
+      uid = config.ids.uids.aerospike;
+      description = "Aerospike server user";
+    };
+    users.groups.aerospike.gid = config.ids.gids.aerospike;
+
+    systemd.services.aerospike = rec {
+      description = "Aerospike server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/asd --fgdaemon --config-file ${aerospikeConf}";
+        User = "aerospike";
+        Group = "aerospike";
+        LimitNOFILE = 100000;
+        PermissionsStartOnly = true;
+      };
+
+      preStart = ''
+        if [ $(echo "$(${pkgs.procps}/bin/sysctl -n kernel.shmall) < 4294967296" | ${pkgs.bc}/bin/bc) == "1"  ]; then
+          echo "kernel.shmall too low, setting to 4G pages"
+          ${pkgs.procps}/bin/sysctl -w kernel.shmall=4294967296
+        fi
+        if [ $(echo "$(${pkgs.procps}/bin/sysctl -n kernel.shmmax) < 1073741824" | ${pkgs.bc}/bin/bc) == "1"  ]; then
+          echo "kernel.shmmax too low, setting to 1GB"
+          ${pkgs.procps}/bin/sysctl -w kernel.shmmax=1073741824
+        fi
+        if [ $(echo "$(cat /proc/sys/net/core/rmem_max) < 15728640" | ${pkgs.bc}/bin/bc) == "1" ]; then
+          echo "increasing socket buffer limit (/proc/sys/net/core/rmem_max): $(cat /proc/sys/net/core/rmem_max) -> 15728640"
+          echo 15728640 > /proc/sys/net/core/rmem_max
+        fi
+        if [ $(echo "$(cat /proc/sys/net/core/wmem_max) <  5242880" | ${pkgs.bc}/bin/bc) == "1"  ]; then
+          echo "increasing socket buffer limit (/proc/sys/net/core/wmem_max): $(cat /proc/sys/net/core/wmem_max) -> 5242880"
+          echo  5242880 > /proc/sys/net/core/wmem_max
+        fi
+        install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}"
+        install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/smd"
+        install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/udf"
+        install -d -m0700 -o ${serviceConfig.User} -g ${serviceConfig.Group} "${cfg.workDir}/udf/lua"
+      '';
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/cassandra.nix b/nixpkgs/nixos/modules/services/databases/cassandra.nix
new file mode 100644
index 000000000000..90c094f68b61
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/cassandra.nix
@@ -0,0 +1,479 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cassandra;
+  defaultUser = "cassandra";
+  cassandraConfig = flip recursiveUpdate cfg.extraConfig
+    ({ commitlog_sync = "batch";
+       commitlog_sync_batch_window_in_ms = 2;
+       start_native_transport = cfg.allowClients;
+       cluster_name = cfg.clusterName;
+       partitioner = "org.apache.cassandra.dht.Murmur3Partitioner";
+       endpoint_snitch = "SimpleSnitch";
+       data_file_directories = [ "${cfg.homeDir}/data" ];
+       commitlog_directory = "${cfg.homeDir}/commitlog";
+       saved_caches_directory = "${cfg.homeDir}/saved_caches";
+     } // (lib.optionalAttrs (cfg.seedAddresses != []) {
+       seed_provider = [{
+         class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
+         parameters = [ { seeds = concatStringsSep "," cfg.seedAddresses; } ];
+       }];
+     }) // (lib.optionalAttrs (lib.versionAtLeast cfg.package.version "3") {
+       hints_directory = "${cfg.homeDir}/hints";
+     })
+    );
+  cassandraConfigWithAddresses = cassandraConfig //
+    ( if cfg.listenAddress == null
+        then { listen_interface = cfg.listenInterface; }
+        else { listen_address = cfg.listenAddress; }
+    ) // (
+      if cfg.rpcAddress == null
+        then { rpc_interface = cfg.rpcInterface; }
+        else { rpc_address = cfg.rpcAddress; }
+    );
+  cassandraEtc = pkgs.stdenv.mkDerivation
+    { name = "cassandra-etc";
+      cassandraYaml = builtins.toJSON cassandraConfigWithAddresses;
+      cassandraEnvPkg = "${cfg.package}/conf/cassandra-env.sh";
+      cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig;
+      buildCommand = ''
+        mkdir -p "$out"
+
+        echo "$cassandraYaml" > "$out/cassandra.yaml"
+        ln -s "$cassandraLogbackConfig" "$out/logback.xml"
+
+        cp "$cassandraEnvPkg" "$out/cassandra-env.sh"
+
+        # Delete default JMX Port, otherwise we can't set it using env variable
+        sed -i '/JMX_PORT="7199"/d' "$out/cassandra-env.sh"
+
+        # Delete default password file
+        sed -i '/-Dcom.sun.management.jmxremote.password.file=\/etc\/cassandra\/jmxremote.password/d' "$out/cassandra-env.sh"
+      '';
+    };
+  defaultJmxRolesFile = builtins.foldl'
+     (left: right: left + right) ""
+     (map (role: "${role.username} ${role.password}") cfg.jmxRoles);
+  fullJvmOptions = cfg.jvmOpts
+    ++ lib.optionals (cfg.jmxRoles != []) [
+      "-Dcom.sun.management.jmxremote.authenticate=true"
+      "-Dcom.sun.management.jmxremote.password.file=${cfg.jmxRolesFile}"
+    ]
+    ++ lib.optionals cfg.remoteJmx [
+      "-Djava.rmi.server.hostname=${cfg.rpcAddress}"
+    ];
+in {
+  options.services.cassandra = {
+    enable = mkEnableOption ''
+      Apache Cassandra – Scalable and highly available database.
+    '';
+    clusterName = mkOption {
+      type = types.str;
+      default = "Test Cluster";
+      description = ''
+        The name of the cluster.
+        This setting prevents nodes in one logical cluster from joining
+        another. All nodes in a cluster must have the same value.
+      '';
+    };
+    user = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = "Run Apache Cassandra under this user.";
+    };
+    group = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = "Run Apache Cassandra under this group.";
+    };
+    homeDir = mkOption {
+      type = types.path;
+      default = "/var/lib/cassandra";
+      description = ''
+        Home directory for Apache Cassandra.
+      '';
+    };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.cassandra;
+      defaultText = "pkgs.cassandra";
+      example = literalExample "pkgs.cassandra_3_11";
+      description = ''
+        The Apache Cassandra package to use.
+      '';
+    };
+    jvmOpts = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Populate the JVM_OPT environment variable.
+      '';
+    };
+    listenAddress = mkOption {
+      type = types.nullOr types.str;
+      default = "127.0.0.1";
+      example = literalExample "null";
+      description = ''
+        Address or interface to bind to and tell other Cassandra nodes
+        to connect to. You _must_ change this if you want multiple
+        nodes to be able to communicate!
+
+        Set listenAddress OR listenInterface, not both.
+
+        Leaving it blank leaves it up to
+        InetAddress.getLocalHost(). This will always do the Right
+        Thing _if_ the node is properly configured (hostname, name
+        resolution, etc), and the Right Thing is to use the address
+        associated with the hostname (it might not be).
+
+        Setting listen_address to 0.0.0.0 is always wrong.
+      '';
+    };
+    listenInterface = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "eth1";
+      description = ''
+        Set listenAddress OR listenInterface, not both. Interfaces
+        must correspond to a single address, IP aliasing is not
+        supported.
+      '';
+    };
+    rpcAddress = mkOption {
+      type = types.nullOr types.str;
+      default = "127.0.0.1";
+      example = literalExample "null";
+      description = ''
+        The address or interface to bind the native transport server to.
+
+        Set rpcAddress OR rpcInterface, not both.
+
+        Leaving rpcAddress blank has the same effect as on
+        listenAddress (i.e. it will be based on the configured hostname
+        of the node).
+
+        Note that unlike listenAddress, you can specify 0.0.0.0, but you
+        must also set extraConfig.broadcast_rpc_address to a value other
+        than 0.0.0.0.
+
+        For security reasons, you should not expose this port to the
+        internet. Firewall it if needed.
+      '';
+    };
+    rpcInterface = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "eth1";
+      description = ''
+        Set rpcAddress OR rpcInterface, not both. Interfaces must
+        correspond to a single address, IP aliasing is not supported.
+      '';
+    };
+    logbackConfig = mkOption {
+      type = types.lines;
+      default = ''
+        <configuration scan="false">
+          <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+            <encoder>
+              <pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern>
+            </encoder>
+          </appender>
+
+          <root level="INFO">
+            <appender-ref ref="STDOUT" />
+          </root>
+
+          <logger name="com.thinkaurelius.thrift" level="ERROR"/>
+        </configuration>
+      '';
+      description = ''
+        XML logback configuration for cassandra
+      '';
+    };
+    seedAddresses = mkOption {
+      type = types.listOf types.str;
+      default = [ "127.0.0.1" ];
+      description = ''
+        The addresses of hosts designated as contact points in the cluster. A
+        joining node contacts one of the nodes in the seeds list to learn the
+        topology of the ring.
+        Set to 127.0.0.1 for a single node cluster.
+      '';
+    };
+    allowClients = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Enables or disables the native transport server (CQL binary protocol).
+        This server uses the same address as the <literal>rpcAddress</literal>,
+        but the port it uses is not <literal>rpc_port</literal> but
+        <literal>native_transport_port</literal>. See the official Cassandra
+        docs for more information on these variables and set them using
+        <literal>extraConfig</literal>.
+      '';
+    };
+    extraConfig = mkOption {
+      type = types.attrs;
+      default = {};
+      example =
+        { commitlog_sync_batch_window_in_ms = 3;
+        };
+      description = ''
+        Extra options to be merged into cassandra.yaml as nix attribute set.
+      '';
+    };
+    fullRepairInterval = mkOption {
+      type = types.nullOr types.str;
+      default = "3w";
+      example = literalExample "null";
+      description = ''
+          Set the interval how often full repairs are run, i.e.
+          <literal>nodetool repair --full</literal> is executed. See
+          https://cassandra.apache.org/doc/latest/operating/repair.html
+          for more information.
+
+          Set to <literal>null</literal> to disable full repairs.
+        '';
+    };
+    fullRepairOptions = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "--partitioner-range" ];
+      description = ''
+          Options passed through to the full repair command.
+        '';
+    };
+    incrementalRepairInterval = mkOption {
+      type = types.nullOr types.str;
+      default = "3d";
+      example = literalExample "null";
+      description = ''
+          Set the interval how often incremental repairs are run, i.e.
+          <literal>nodetool repair</literal> is executed. See
+          https://cassandra.apache.org/doc/latest/operating/repair.html
+          for more information.
+
+          Set to <literal>null</literal> to disable incremental repairs.
+        '';
+    };
+    incrementalRepairOptions = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "--partitioner-range" ];
+      description = ''
+          Options passed through to the incremental repair command.
+        '';
+    };
+    maxHeapSize = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "4G";
+      description = ''
+        Must be left blank or set together with heapNewSize.
+        If left blank a sensible value for the available amount of RAM and CPU
+        cores is calculated.
+
+        Override to set the amount of memory to allocate to the JVM at
+        start-up. For production use you may wish to adjust this for your
+        environment. MAX_HEAP_SIZE is the total amount of memory dedicated
+        to the Java heap. HEAP_NEWSIZE refers to the size of the young
+        generation.
+
+        The main trade-off for the young generation is that the larger it
+        is, the longer GC pause times will be. The shorter it is, the more
+        expensive GC will be (usually).
+      '';
+    };
+    heapNewSize = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "800M";
+      description = ''
+        Must be left blank or set together with heapNewSize.
+        If left blank a sensible value for the available amount of RAM and CPU
+        cores is calculated.
+
+        Override to set the amount of memory to allocate to the JVM at
+        start-up. For production use you may wish to adjust this for your
+        environment. HEAP_NEWSIZE refers to the size of the young
+        generation.
+
+        The main trade-off for the young generation is that the larger it
+        is, the longer GC pause times will be. The shorter it is, the more
+        expensive GC will be (usually).
+
+        The example HEAP_NEWSIZE assumes a modern 8-core+ machine for decent pause
+        times. If in doubt, and if you do not particularly want to tweak, go with
+        100 MB per physical CPU core.
+      '';
+    };
+    mallocArenaMax = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      example = 4;
+      description = ''
+        Set this to control the amount of arenas per-thread in glibc.
+      '';
+    };
+    remoteJmx = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Cassandra ships with JMX accessible *only* from localhost.
+        To enable remote JMX connections set to true.
+
+        Be sure to also enable authentication and/or TLS.
+        See: https://wiki.apache.org/cassandra/JmxSecurity
+      '';
+    };
+    jmxPort = mkOption {
+      type = types.int;
+      default = 7199;
+      description = ''
+        Specifies the default port over which Cassandra will be available for
+        JMX connections.
+        For security reasons, you should not expose this port to the internet.
+        Firewall it if needed.
+      '';
+    };
+    jmxRoles = mkOption {
+      default = [];
+      description = ''
+        Roles that are allowed to access the JMX (e.g. nodetool)
+        BEWARE: The passwords will be stored world readable in the nix-store.
+                It's recommended to use your own protected file using
+                <literal>jmxRolesFile</literal>
+
+        Doesn't work in versions older than 3.11 because they don't like that
+        it's world readable.
+      '';
+      type = types.listOf (types.submodule {
+        options = {
+          username = mkOption {
+            type = types.str;
+            description = "Username for JMX";
+          };
+          password = mkOption {
+            type = types.str;
+            description = "Password for JMX";
+          };
+        };
+      });
+    };
+    jmxRolesFile = mkOption {
+      type = types.nullOr types.path;
+      default = if (lib.versionAtLeast cfg.package.version "3.11")
+                then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile
+                else null;
+      example = "/var/lib/cassandra/jmx.password";
+      description = ''
+        Specify your own jmx roles file.
+
+        Make sure the permissions forbid "others" from reading the file if
+        you're using Cassandra below version 3.11.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions =
+      [ { assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null);
+          message = "You have to set either listenAddress or listenInterface";
+        }
+        { assertion = (cfg.rpcAddress == null) != (cfg.rpcInterface == null);
+          message = "You have to set either rpcAddress or rpcInterface";
+        }
+        { assertion = (cfg.maxHeapSize == null) == (cfg.heapNewSize == null);
+          message = "If you set either of maxHeapSize or heapNewSize you have to set both";
+        }
+        { assertion = cfg.remoteJmx -> cfg.jmxRolesFile != null;
+          message = ''
+            If you want JMX available remotely you need to set a password using
+            <literal>jmxRoles</literal> or <literal>jmxRolesFile</literal> if
+            using Cassandra older than v3.11.
+          '';
+        }
+      ];
+    users = mkIf (cfg.user == defaultUser) {
+      extraUsers.${defaultUser} =
+        {  group = cfg.group;
+           home = cfg.homeDir;
+           createHome = true;
+           uid = config.ids.uids.cassandra;
+           description = "Cassandra service user";
+        };
+      extraGroups.${defaultUser}.gid = config.ids.gids.cassandra;
+    };
+
+    systemd.services.cassandra =
+      { description = "Apache Cassandra service";
+        after = [ "network.target" ];
+        environment =
+          { CASSANDRA_CONF = "${cassandraEtc}";
+            JVM_OPTS = builtins.concatStringsSep " " fullJvmOptions;
+            MAX_HEAP_SIZE = toString cfg.maxHeapSize;
+            HEAP_NEWSIZE = toString cfg.heapNewSize;
+            MALLOC_ARENA_MAX = toString cfg.mallocArenaMax;
+            LOCAL_JMX = if cfg.remoteJmx then "no" else "yes";
+            JMX_PORT = toString cfg.jmxPort;
+          };
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig =
+          { User = cfg.user;
+            Group = cfg.group;
+            ExecStart = "${cfg.package}/bin/cassandra -f";
+            SuccessExitStatus = 143;
+          };
+      };
+
+    systemd.services.cassandra-full-repair =
+      { description = "Perform a full repair on this Cassandra node";
+        after = [ "cassandra.service" ];
+        requires = [ "cassandra.service" ];
+        serviceConfig =
+          { User = cfg.user;
+            Group = cfg.group;
+            ExecStart =
+              lib.concatStringsSep " "
+                ([ "${cfg.package}/bin/nodetool" "repair" "--full"
+                 ] ++ cfg.fullRepairOptions);
+          };
+      };
+    systemd.timers.cassandra-full-repair =
+      mkIf (cfg.fullRepairInterval != null) {
+        description = "Schedule full repairs on Cassandra";
+        wantedBy = [ "timers.target" ];
+        timerConfig =
+          { OnBootSec = cfg.fullRepairInterval;
+            OnUnitActiveSec = cfg.fullRepairInterval;
+            Persistent = true;
+          };
+      };
+
+    systemd.services.cassandra-incremental-repair =
+      { description = "Perform an incremental repair on this cassandra node.";
+        after = [ "cassandra.service" ];
+        requires = [ "cassandra.service" ];
+        serviceConfig =
+          { User = cfg.user;
+            Group = cfg.group;
+            ExecStart =
+              lib.concatStringsSep " "
+                ([ "${cfg.package}/bin/nodetool" "repair"
+                 ] ++ cfg.incrementalRepairOptions);
+          };
+      };
+    systemd.timers.cassandra-incremental-repair =
+      mkIf (cfg.incrementalRepairInterval != null) {
+        description = "Schedule incremental repairs on Cassandra";
+        wantedBy = [ "timers.target" ];
+        timerConfig =
+          { OnBootSec = cfg.incrementalRepairInterval;
+            OnUnitActiveSec = cfg.incrementalRepairInterval;
+            Persistent = true;
+          };
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/clickhouse.nix b/nixpkgs/nixos/modules/services/databases/clickhouse.nix
new file mode 100644
index 000000000000..27440fec4e10
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/clickhouse.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.clickhouse;
+in
+with lib;
+{
+
+  ###### interface
+
+  options = {
+
+    services.clickhouse = {
+
+      enable = mkEnableOption "ClickHouse database server";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.clickhouse = {
+      name = "clickhouse";
+      uid = config.ids.uids.clickhouse;
+      group = "clickhouse";
+      description = "ClickHouse server user";
+    };
+
+    users.groups.clickhouse.gid = config.ids.gids.clickhouse;
+
+    systemd.services.clickhouse = {
+      description = "ClickHouse server";
+
+      wantedBy = [ "multi-user.target" ];
+
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        User = "clickhouse";
+        Group = "clickhouse";
+        ConfigurationDirectory = "clickhouse-server";
+        StateDirectory = "clickhouse";
+        LogsDirectory = "clickhouse";
+        ExecStart = "${pkgs.clickhouse}/bin/clickhouse-server --config-file=${pkgs.clickhouse}/etc/clickhouse-server/config.xml";
+      };
+    };
+
+    environment.etc = {
+      "clickhouse-server/config.xml" = {
+        source = "${pkgs.clickhouse}/etc/clickhouse-server/config.xml";
+      };
+
+      "clickhouse-server/users.xml" = {
+        source = "${pkgs.clickhouse}/etc/clickhouse-server/users.xml";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.clickhouse ];
+
+    # startup requires a `/etc/localtime` which only if exists if `time.timeZone != null`
+    time.timeZone = mkDefault "UTC";
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/cockroachdb.nix b/nixpkgs/nixos/modules/services/databases/cockroachdb.nix
new file mode 100644
index 000000000000..35fb46d69d8e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/cockroachdb.nix
@@ -0,0 +1,217 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cockroachdb;
+  crdb = cfg.package;
+
+  escape    = builtins.replaceStrings ["%"] ["%%"];
+  ifNotNull = v: s: optionalString (v != null) s;
+
+  startupCommand = lib.concatStringsSep " "
+    [ # Basic startup
+      "${crdb}/bin/cockroach start"
+      "--logtostderr"
+      "--store=/var/lib/cockroachdb"
+      (ifNotNull cfg.locality "--locality='${cfg.locality}'")
+
+      # WebUI settings
+      "--http-addr='${cfg.http.address}:${toString cfg.http.port}'"
+
+      # Cluster listen address
+      "--listen-addr='${cfg.listen.address}:${toString cfg.listen.port}'"
+
+      # Cluster configuration
+      (ifNotNull cfg.join "--join=${cfg.join}")
+
+      # Cache and memory settings. Must be escaped.
+      "--cache='${escape cfg.cache}'"
+      "--max-sql-memory='${escape cfg.maxSqlMemory}'"
+
+      # Certificate/security settings.
+      (if cfg.insecure then "--insecure" else "--certs-dir=${cfg.certsDir}")
+    ];
+
+    addressOption = descr: defaultPort: {
+      address = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Address to bind to for ${descr}";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = defaultPort;
+        description = "Port to bind to for ${descr}";
+      };
+    };
+in
+
+{
+  options = {
+    services.cockroachdb = {
+      enable = mkEnableOption "CockroachDB Server";
+
+      listen = addressOption "intra-cluster communication" 26257;
+
+      http = addressOption "http-based Admin UI" 8080;
+
+      locality = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          An ordered, comma-separated list of key-value pairs that describe the
+          topography of the machine. Topography might include country,
+          datacenter or rack designations. Data is automatically replicated to
+          maximize diversities of each tier. The order of tiers is used to
+          determine the priority of the diversity, so the more inclusive
+          localities like country should come before less inclusive localities
+          like datacenter.  The tiers and order must be the same on all nodes.
+          Including more tiers is better than including fewer. For example:
+
+          <literal>
+              country=us,region=us-west,datacenter=us-west-1b,rack=12
+              country=ca,region=ca-east,datacenter=ca-east-2,rack=4
+
+              planet=earth,province=manitoba,colo=secondary,power=3
+          </literal>
+        '';
+      };
+
+      join = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "The addresses for connecting the node to a cluster.";
+      };
+
+      insecure = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Run in insecure mode.";
+      };
+
+      certsDir = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "The path to the certificate directory.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "cockroachdb";
+        description = "User account under which CockroachDB runs";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "cockroachdb";
+        description = "User account under which CockroachDB runs";
+      };
+
+      openPorts = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open firewall ports for cluster communication by default";
+      };
+
+      cache = mkOption {
+        type = types.str;
+        default = "25%";
+        description = ''
+          The total size for caches.
+
+          This can be a percentage, expressed with a fraction sign or as a
+          decimal-point number, or any bytes-based unit. For example,
+          <literal>"25%"</literal>, <literal>"0.25"</literal> both represent
+          25% of the available system memory. The values
+          <literal>"1000000000"</literal> and <literal>"1GB"</literal> both
+          represent 1 gigabyte of memory.
+
+        '';
+      };
+
+      maxSqlMemory = mkOption {
+        type = types.str;
+        default = "25%";
+        description = ''
+          The maximum in-memory storage capacity available to store temporary
+          data for SQL queries.
+
+          This can be a percentage, expressed with a fraction sign or as a
+          decimal-point number, or any bytes-based unit. For example,
+          <literal>"25%"</literal>, <literal>"0.25"</literal> both represent
+          25% of the available system memory. The values
+          <literal>"1000000000"</literal> and <literal>"1GB"</literal> both
+          represent 1 gigabyte of memory.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.cockroachdb;
+        defaultText = "pkgs.cockroachdb";
+        description = ''
+          The CockroachDB derivation to use for running the service.
+
+          This would primarily be useful to enable Enterprise Edition features
+          in your own custom CockroachDB build (Nixpkgs CockroachDB binaries
+          only contain open source features and open source code).
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.cockroachdb.enable {
+    assertions = [
+      { assertion = !cfg.insecure -> cfg.certsDir != null;
+        message = "CockroachDB must have a set of SSL certificates (.certsDir), or run in Insecure Mode (.insecure = true)";
+      }
+    ];
+
+    environment.systemPackages = [ crdb ];
+
+    users.users = optionalAttrs (cfg.user == "cockroachdb") {
+      cockroachdb = {
+        description = "CockroachDB Server User";
+        uid         = config.ids.uids.cockroachdb;
+        group       = cfg.group;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "cockroachdb") {
+      cockroachdb.gid = config.ids.gids.cockroachdb;
+    };
+
+    networking.firewall.allowedTCPPorts = lib.optionals cfg.openPorts
+      [ cfg.http.port cfg.listen.port ];
+
+    systemd.services.cockroachdb =
+      { description   = "CockroachDB Server";
+        documentation = [ "man:cockroach(1)" "https://www.cockroachlabs.com" ];
+
+        after    = [ "network.target" "time-sync.target" ];
+        requires = [ "time-sync.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        unitConfig.RequiresMountsFor = "/var/lib/cockroachdb";
+
+        serviceConfig =
+          { ExecStart = startupCommand;
+            Type = "notify";
+            User = cfg.user;
+            StateDirectory = "cockroachdb";
+            StateDirectoryMode = "0700";
+
+            Restart = "always";
+
+            # A conservative-ish timeout is alright here, because for Type=notify
+            # cockroach will send systemd pings during startup to keep it alive
+            TimeoutStopSec = 60;
+            RestartSec = 10;
+          };
+      };
+  };
+
+  meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+}
diff --git a/nixpkgs/nixos/modules/services/databases/couchdb.nix b/nixpkgs/nixos/modules/services/databases/couchdb.nix
new file mode 100644
index 000000000000..53224db1d896
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/couchdb.nix
@@ -0,0 +1,201 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.couchdb;
+  useVersion2 = strings.versionAtLeast (strings.getVersion cfg.package) "2.0";
+  configFile = pkgs.writeText "couchdb.ini" (
+    ''
+      [couchdb]
+      database_dir = ${cfg.databaseDir}
+      uri_file = ${cfg.uriFile}
+      view_index_dir = ${cfg.viewIndexDir}
+    '' + (if useVersion2 then
+    ''
+      [chttpd]
+    '' else
+    ''
+      [httpd]
+    '') +
+    ''
+      port = ${toString cfg.port}
+      bind_address = ${cfg.bindAddress}
+
+      [log]
+      file = ${cfg.logFile}
+    '');
+  executable = if useVersion2 then "${cfg.package}/bin/couchdb"
+    else ''${cfg.package}/bin/couchdb -a ${configFile} -a ${pkgs.writeText "couchdb-extra.ini" cfg.extraConfig} -a ${cfg.configFile}'';
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.couchdb = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run CouchDB Server.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.couchdb;
+        defaultText = "pkgs.couchdb";
+        example = literalExample "pkgs.couchdb";
+        description = ''
+          CouchDB package to use.
+        '';
+      };
+
+
+      user = mkOption {
+        type = types.str;
+        default = "couchdb";
+        description = ''
+          User account under which couchdb runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "couchdb";
+        description = ''
+          Group account under which couchdb runs.
+        '';
+      };
+
+      # couchdb options: http://docs.couchdb.org/en/latest/config/index.html
+
+      databaseDir = mkOption {
+        type = types.path;
+        default = "/var/lib/couchdb";
+        description = ''
+          Specifies location of CouchDB database files (*.couch named). This
+          location should be writable and readable for the user the CouchDB
+          service runs as (couchdb by default).
+        '';
+      };
+
+      uriFile = mkOption {
+        type = types.path;
+        default = "/run/couchdb/couchdb.uri";
+        description = ''
+          This file contains the full URI that can be used to access this
+          instance of CouchDB. It is used to help discover the port CouchDB is
+          running on (if it was set to 0 (e.g. automatically assigned any free
+          one). This file should be writable and readable for the user that
+          runs the CouchDB service (couchdb by default).
+        '';
+      };
+
+      viewIndexDir = mkOption {
+        type = types.path;
+        default = "/var/lib/couchdb";
+        description = ''
+          Specifies location of CouchDB view index files. This location should
+          be writable and readable for the user that runs the CouchDB service
+          (couchdb by default).
+        '';
+      };
+
+      bindAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = ''
+          Defines the IP address by which CouchDB will be accessible.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 5984;
+        description = ''
+          Defined the port number to listen.
+        '';
+      };
+
+      logFile = mkOption {
+        type = types.path;
+        default = "/var/log/couchdb.log";
+        description = ''
+          Specifies the location of file for logging output.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration. Overrides any other cofiguration.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        description = ''
+          Configuration file for persisting runtime changes. File
+          needs to be readable and writable from couchdb user/group.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.couchdb.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    services.couchdb.configFile = mkDefault
+      (if useVersion2 then "/var/lib/couchdb/local.ini" else "/var/lib/couchdb/couchdb.ini");
+
+    systemd.tmpfiles.rules = [
+      "d '${dirOf cfg.uriFile}' - ${cfg.user} ${cfg.group} - -"
+      "f '${cfg.logFile}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.databaseDir}' -  ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.viewIndexDir}' -  ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.couchdb = {
+      description = "CouchDB Server";
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        touch ${cfg.configFile}
+      '';
+
+      environment = mkIf useVersion2 {
+        # we are actually specifying 4 configuration files:
+        # 1. the preinstalled default.ini
+        # 2. the module configuration
+        # 3. the extraConfig from the module options
+        # 4. the locally writable config file, which couchdb itself writes to
+        ERL_FLAGS= ''-couch_ini ${cfg.package}/etc/default.ini ${configFile} ${pkgs.writeText "couchdb-extra.ini" cfg.extraConfig} ${cfg.configFile}'';
+      };
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = executable;
+      };
+    };
+
+    users.users.couchdb = {
+      description = "CouchDB Server user";
+      group = "couchdb";
+      uid = config.ids.uids.couchdb;
+    };
+
+    users.groups.couchdb.gid = config.ids.gids.couchdb;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/firebird.nix b/nixpkgs/nixos/modules/services/databases/firebird.nix
new file mode 100644
index 000000000000..95837aa1cea6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/firebird.nix
@@ -0,0 +1,161 @@
+{ config, lib, pkgs, ... }:
+
+# TODO: This may file may need additional review, eg which configuartions to
+# expose to the user.
+#
+# I only used it to access some simple databases.
+
+# test:
+# isql, then type the following commands:
+# CREATE DATABASE '/var/db/firebird/data/test.fdb' USER 'SYSDBA' PASSWORD 'masterkey';
+# CONNECT '/var/db/firebird/data/test.fdb' USER 'SYSDBA' PASSWORD 'masterkey';
+# CREATE TABLE test ( text varchar(100) );
+# DROP DATABASE;
+#
+# Be careful, virtuoso-opensource also provides a different isql command !
+
+# There are at least two ways to run firebird. superserver has been choosen
+# however there are no strong reasons to prefer this or the other one AFAIK
+# Eg superserver is said to be most efficiently using resources according to
+# http://www.firebirdsql.org/manual/qsg25-classic-or-super.html
+
+with lib;
+
+let
+
+  cfg = config.services.firebird;
+
+  firebird = cfg.package;
+
+  dataDir = "${cfg.baseDir}/data";
+  systemDir = "${cfg.baseDir}/system";
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.firebird = {
+
+      enable = mkEnableOption "the Firebird super server";
+
+      package = mkOption {
+        default = pkgs.firebirdSuper;
+        defaultText = "pkgs.firebirdSuper";
+        type = types.package;
+        /*
+          Example: <code>package = pkgs.firebirdSuper.override { icu =
+            pkgs.icu; };</code> which is not recommended for compatibility
+            reasons. See comments at the firebirdSuper derivation
+        */
+
+        description = ''
+          Which firebird derivation to use.
+        '';
+      };
+
+      port = mkOption {
+        default = "3050";
+        description = ''
+          Port Firebird uses.
+        '';
+      };
+
+      user = mkOption {
+        default = "firebird";
+        description = ''
+          User account under which firebird runs.
+        '';
+      };
+
+      baseDir = mkOption {
+        default = "/var/db/firebird"; # ubuntu is using /var/lib/firebird/2.1/data/.. ?
+        description = ''
+          Location containing data/ and system/ directories.
+          data/ stores the databases, system/ stores the password database security2.fdb.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.firebird.enable {
+
+    environment.systemPackages = [cfg.package];
+
+    systemd.tmpfiles.rules = [
+      "d '${dataDir}' 0700 ${cfg.user} - - -"
+      "d '${systemDir}' 0700 ${cfg.user} - - -"
+    ];
+
+    systemd.services.firebird =
+      { description = "Firebird Super-Server";
+
+        wantedBy = [ "multi-user.target" ];
+
+        # TODO: moving security2.fdb into the data directory works, maybe there
+        # is a better way
+        preStart =
+          ''
+            if ! test -e "${systemDir}/security2.fdb"; then
+                cp ${firebird}/security2.fdb "${systemDir}"
+            fi
+
+            chmod -R 700         "${dataDir}" "${systemDir}" /var/log/firebird
+          '';
+
+        serviceConfig.User = cfg.user;
+        serviceConfig.LogsDirectory = "firebird";
+        serviceConfig.LogsDirectoryMode = "0700";
+        serviceConfig.ExecStart = ''${firebird}/bin/fbserver -d'';
+
+        # TODO think about shutdown
+      };
+
+    environment.etc."firebird/firebird.msg".source = "${firebird}/firebird.msg";
+
+    # think about this again - and eventually make it an option
+    environment.etc."firebird/firebird.conf".text = ''
+      # RootDirectory = Restrict ${dataDir}
+      DatabaseAccess = Restrict ${dataDir}
+      ExternalFileAccess = Restrict ${dataDir}
+      # what is this? is None allowed?
+      UdfAccess = None
+      # "Native" =  traditional interbase/firebird, "mixed" is windows only
+      Authentication = Native
+
+      # defaults to -1 on non Win32
+      #MaxUnflushedWrites = 100
+      #MaxUnflushedWriteTime = 100
+
+      # show trace if trouble occurs (does this require debug build?)
+      # BugcheckAbort = 0
+      # ConnectionTimeout = 180
+
+      #RemoteServiceName = gds_db
+      RemoteServicePort = ${cfg.port}
+
+      # randomly choose port for server Event Notification
+      #RemoteAuxPort = 0
+      # rsetrict connections to a network card:
+      #RemoteBindAddress =
+      # there are some additional settings which should be reviewed
+    '';
+
+    users.users.firebird = {
+      description = "Firebird server user";
+      group = "firebird";
+      uid = config.ids.uids.firebird;
+    };
+
+    users.groups.firebird.gid = config.ids.gids.firebird;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/foundationdb.nix b/nixpkgs/nixos/modules/services/databases/foundationdb.nix
new file mode 100644
index 000000000000..18727acc7c75
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/foundationdb.nix
@@ -0,0 +1,429 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.foundationdb;
+  pkg = cfg.package;
+
+  # used for initial cluster configuration
+  initialIpAddr = if (cfg.publicAddress != "auto") then cfg.publicAddress else "127.0.0.1";
+
+  fdbServers = n:
+    concatStringsSep "\n" (map (x: "[fdbserver.${toString (x+cfg.listenPortStart)}]") (range 0 (n - 1)));
+
+  backupAgents = n:
+    concatStringsSep "\n" (map (x: "[backup_agent.${toString x}]") (range 1 n));
+
+  configFile = pkgs.writeText "foundationdb.conf" ''
+    [general]
+    cluster_file  = /etc/foundationdb/fdb.cluster
+
+    [fdbmonitor]
+    restart_delay = ${toString cfg.restartDelay}
+    user          = ${cfg.user}
+    group         = ${cfg.group}
+
+    [fdbserver]
+    command        = ${pkg}/bin/fdbserver
+    public_address = ${cfg.publicAddress}:$ID
+    listen_address = ${cfg.listenAddress}
+    datadir        = ${cfg.dataDir}/$ID
+    logdir         = ${cfg.logDir}
+    logsize        = ${cfg.logSize}
+    maxlogssize    = ${cfg.maxLogSize}
+    ${optionalString (cfg.class != null) "class = ${cfg.class}"}
+    memory         = ${cfg.memory}
+    storage_memory = ${cfg.storageMemory}
+
+    ${optionalString (lib.versionAtLeast cfg.package.version "6.1") ''
+    trace_format   = ${cfg.traceFormat}
+    ''}
+
+    ${optionalString (cfg.tls != null) ''
+      tls_plugin           = ${pkg}/libexec/plugins/FDBLibTLS.so
+      tls_certificate_file = ${cfg.tls.certificate}
+      tls_key_file         = ${cfg.tls.key}
+      tls_verify_peers     = ${cfg.tls.allowedPeers}
+    ''}
+
+    ${optionalString (cfg.locality.machineId    != null) "locality_machineid=${cfg.locality.machineId}"}
+    ${optionalString (cfg.locality.zoneId       != null) "locality_zoneid=${cfg.locality.zoneId}"}
+    ${optionalString (cfg.locality.datacenterId != null) "locality_dcid=${cfg.locality.datacenterId}"}
+    ${optionalString (cfg.locality.dataHall     != null) "locality_data_hall=${cfg.locality.dataHall}"}
+
+    ${fdbServers cfg.serverProcesses}
+
+    [backup_agent]
+    command = ${pkg}/libexec/backup_agent
+    ${backupAgents cfg.backupProcesses}
+  '';
+in
+{
+  options.services.foundationdb = {
+
+    enable = mkEnableOption "FoundationDB Server";
+
+    package = mkOption {
+      type        = types.package;
+      description = ''
+        The FoundationDB package to use for this server. This must be specified by the user
+        in order to ensure migrations and upgrades are controlled appropriately.
+      '';
+    };
+
+    publicAddress = mkOption {
+      type        = types.str;
+      default     = "auto";
+      description = "Publicly visible IP address of the process. Port is determined by process ID";
+    };
+
+    listenAddress = mkOption {
+      type        = types.str;
+      default     = "public";
+      description = "Publicly visible IP address of the process. Port is determined by process ID";
+    };
+
+    listenPortStart = mkOption {
+      type          = types.int;
+      default       = 4500;
+      description   = ''
+        Starting port number for database listening sockets. Every FDB process binds to a
+        subsequent port, to this number reflects the start of the overall range. e.g. having
+        8 server processes will use all ports between 4500 and 4507.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = ''
+        Open the firewall ports corresponding to FoundationDB processes and coordinators
+        using <option>config.networking.firewall.*</option>.
+      '';
+    };
+
+    dataDir = mkOption {
+      type        = types.path;
+      default     = "/var/lib/foundationdb";
+      description = "Data directory. All cluster data will be put under here.";
+    };
+
+    logDir = mkOption {
+      type        = types.path;
+      default     = "/var/log/foundationdb";
+      description = "Log directory.";
+    };
+
+    user = mkOption {
+      type        = types.str;
+      default     = "foundationdb";
+      description = "User account under which FoundationDB runs.";
+    };
+
+    group = mkOption {
+      type        = types.str;
+      default     = "foundationdb";
+      description = "Group account under which FoundationDB runs.";
+    };
+
+    class = mkOption {
+      type        = types.nullOr (types.enum [ "storage" "transaction" "stateless" ]);
+      default     = null;
+      description = "Process class";
+    };
+
+    restartDelay = mkOption {
+      type = types.int;
+      default = 10;
+      description = "Number of seconds to wait before restarting servers.";
+    };
+
+    logSize = mkOption {
+      type        = types.str;
+      default     = "10MiB";
+      description = ''
+        Roll over to a new log file after the current log file
+        reaches the specified size.
+      '';
+    };
+
+    maxLogSize = mkOption {
+      type        = types.str;
+      default     = "100MiB";
+      description = ''
+        Delete the oldest log file when the total size of all log
+        files exceeds the specified size. If set to 0, old log files
+        will not be deleted.
+      '';
+    };
+
+    serverProcesses = mkOption {
+      type = types.int;
+      default = 1;
+      description = "Number of fdbserver processes to run.";
+    };
+
+    backupProcesses = mkOption {
+      type = types.int;
+      default = 1;
+      description = "Number of backup_agent processes to run for snapshots.";
+    };
+
+    memory = mkOption {
+      type        = types.str;
+      default     = "8GiB";
+      description = ''
+        Maximum memory used by the process. The default value is
+        <literal>8GiB</literal>. When specified without a unit,
+        <literal>MiB</literal> is assumed. This parameter does not
+        change the memory allocation of the program. Rather, it sets
+        a hard limit beyond which the process will kill itself and
+        be restarted. The default value of <literal>8GiB</literal>
+        is double the intended memory usage in the default
+        configuration (providing an emergency buffer to deal with
+        memory leaks or similar problems). It is not recommended to
+        decrease the value of this parameter below its default
+        value. It may be increased if you wish to allocate a very
+        large amount of storage engine memory or cache. In
+        particular, when the <literal>storageMemory</literal>
+        parameter is increased, the <literal>memory</literal>
+        parameter should be increased by an equal amount.
+      '';
+    };
+
+    storageMemory = mkOption {
+      type        = types.str;
+      default     = "1GiB";
+      description = ''
+        Maximum memory used for data storage. The default value is
+        <literal>1GiB</literal>. When specified without a unit,
+        <literal>MB</literal> is assumed. Clusters using the memory
+        storage engine will be restricted to using this amount of
+        memory per process for purposes of data storage. Memory
+        overhead associated with storing the data is counted against
+        this total. If you increase the
+        <literal>storageMemory</literal>, you should also increase
+        the <literal>memory</literal> parameter by the same amount.
+      '';
+    };
+
+    tls = mkOption {
+      default = null;
+      description = ''
+        FoundationDB Transport Security Layer (TLS) settings.
+      '';
+
+      type = types.nullOr (types.submodule ({
+        options = {
+          certificate = mkOption {
+            type = types.str;
+            description = ''
+              Path to the TLS certificate file. This certificate will
+              be offered to, and may be verified by, clients.
+            '';
+          };
+
+          key = mkOption {
+            type = types.str;
+            description = "Private key file for the certificate.";
+          };
+
+          allowedPeers = mkOption {
+            type = types.str;
+            default = "Check.Valid=1,Check.Unexpired=1";
+            description = ''
+	      "Peer verification string". This may be used to adjust which TLS
+              client certificates a server will accept, as a form of user
+              authorization; for example, it may only accept TLS clients who
+              offer a certificate abiding by some locality or organization name.
+
+              For more information, please see the FoundationDB documentation.
+            '';
+          };
+        };
+      }));
+    };
+
+    locality = mkOption {
+      default = {
+        machineId    = null;
+        zoneId       = null;
+        datacenterId = null;
+        dataHall     = null;
+      };
+
+      description = ''
+        FoundationDB locality settings.
+      '';
+
+      type = types.submodule ({
+        options = {
+          machineId = mkOption {
+            default = null;
+            type = types.nullOr types.str;
+            description = ''
+              Machine identifier key. All processes on a machine should share a
+              unique id. By default, processes on a machine determine a unique id to share.
+              This does not generally need to be set.
+            '';
+          };
+
+          zoneId = mkOption {
+            default = null;
+            type = types.nullOr types.str;
+            description = ''
+              Zone identifier key. Processes that share a zone id are
+              considered non-unique for the purposes of data replication.
+              If unset, defaults to machine id.
+            '';
+          };
+
+          datacenterId = mkOption {
+            default = null;
+            type = types.nullOr types.str;
+            description = ''
+              Data center identifier key. All processes physically located in a
+              data center should share the id. If you are depending on data
+              center based replication this must be set on all processes.
+            '';
+          };
+
+          dataHall = mkOption {
+            default = null;
+            type = types.nullOr types.str;
+            description = ''
+              Data hall identifier key. All processes physically located in a
+              data hall should share the id. If you are depending on data
+              hall based replication this must be set on all processes.
+            '';
+          };
+        };
+      });
+    };
+
+    extraReadWritePaths = mkOption {
+      default = [ ];
+      type = types.listOf types.path;
+      description = ''
+        An extra set of filesystem paths that FoundationDB can read to
+        and write from. By default, FoundationDB runs under a heavily
+        namespaced systemd environment without write access to most of
+        the filesystem outside of its data and log directories. By
+        adding paths to this list, the set of writeable paths will be
+        expanded. This is useful for allowing e.g. backups to local files,
+        which must be performed on behalf of the foundationdb service.
+      '';
+    };
+
+    pidfile = mkOption {
+      type        = types.path;
+      default     = "/run/foundationdb.pid";
+      description = "Path to pidfile for fdbmonitor.";
+    };
+
+    traceFormat = mkOption {
+      type = types.enum [ "xml" "json" ];
+      default = "xml";
+      description = "Trace logging format.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = lib.versionOlder cfg.package.version "6.1" -> cfg.traceFormat == "xml";
+        message = ''
+          Versions of FoundationDB before 6.1 do not support configurable trace formats (only XML is supported).
+          This option has no effect for version '' + cfg.package.version + '', and enabling it is an error.
+        '';
+      }
+    ];
+
+    environment.systemPackages = [ pkg ];
+
+    users.users = optionalAttrs (cfg.user == "foundationdb") {
+      foundationdb = {
+        description = "FoundationDB User";
+        uid         = config.ids.uids.foundationdb;
+        group       = cfg.group;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "foundationdb") {
+      foundationdb.gid = config.ids.gids.foundationdb;
+    };
+
+    networking.firewall.allowedTCPPortRanges = mkIf cfg.openFirewall
+      [ { from = cfg.listenPortStart;
+          to = (cfg.listenPortStart + cfg.serverProcesses) - 1;
+        }
+      ];
+
+    systemd.tmpfiles.rules = [
+      "d /etc/foundationdb 0755 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.logDir}' 0770 ${cfg.user} ${cfg.group} - -"
+      "F '${cfg.pidfile}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.foundationdb = {
+      description             = "FoundationDB Service";
+
+      after                   = [ "network.target" ];
+      wantedBy                = [ "multi-user.target" ];
+      unitConfig =
+        { RequiresMountsFor = "${cfg.dataDir} ${cfg.logDir}";
+        };
+
+      serviceConfig =
+        let rwpaths = [ cfg.dataDir cfg.logDir cfg.pidfile "/etc/foundationdb" ]
+                   ++ cfg.extraReadWritePaths;
+        in
+        { Type       = "simple";
+          Restart    = "always";
+          RestartSec = 5;
+          User       = cfg.user;
+          Group      = cfg.group;
+          PIDFile    = "${cfg.pidfile}";
+
+          PermissionsStartOnly = true;  # setup needs root perms
+          TimeoutSec           = 120;   # give reasonable time to shut down
+
+          # Security options
+          NoNewPrivileges       = true;
+          ProtectHome           = true;
+          ProtectSystem         = "strict";
+          ProtectKernelTunables = true;
+          ProtectControlGroups  = true;
+          PrivateTmp            = true;
+          PrivateDevices        = true;
+          ReadWritePaths        = lib.concatStringsSep " " (map (x: "-" + x) rwpaths);
+        };
+
+      path = [ pkg pkgs.coreutils ];
+
+      preStart = ''
+        if [ ! -f /etc/foundationdb/fdb.cluster ]; then
+            cf=/etc/foundationdb/fdb.cluster
+            desc=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
+            rand=$(tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c8)
+            echo ''${desc}:''${rand}@${initialIpAddr}:${builtins.toString cfg.listenPortStart} > $cf
+            chmod 0664 $cf
+            touch "${cfg.dataDir}/.first_startup"
+        fi
+      '';
+
+      script = "exec fdbmonitor --lockfile ${cfg.pidfile} --conffile ${configFile}";
+
+      postStart = ''
+        if [ -e "${cfg.dataDir}/.first_startup" ]; then
+          fdbcli --exec "configure new single ssd"
+          rm -f "${cfg.dataDir}/.first_startup";
+        fi
+      '';
+    };
+  };
+
+  meta.doc         = ./foundationdb.xml;
+  meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+}
diff --git a/nixpkgs/nixos/modules/services/databases/foundationdb.xml b/nixpkgs/nixos/modules/services/databases/foundationdb.xml
new file mode 100644
index 000000000000..b0b1ebeab45f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/foundationdb.xml
@@ -0,0 +1,443 @@
+<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-foundationdb">
+ <title>FoundationDB</title>
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/services/databases/foundationdb.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://apple.github.io/foundationdb/"/>
+ </para>
+ <para>
+  <emphasis>Maintainer:</emphasis> Austin Seipp
+ </para>
+ <para>
+  <emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x
+ </para>
+ <para>
+  FoundationDB (or "FDB") is an open source, distributed, transactional
+  key-value store.
+ </para>
+ <section xml:id="module-services-foundationdb-configuring">
+  <title>Configuring and basic setup</title>
+
+  <para>
+   To enable FoundationDB, add the following to your
+   <filename>configuration.nix</filename>:
+<programlisting>
+services.foundationdb.enable = true;
+services.foundationdb.package = pkgs.foundationdb52; # FoundationDB 5.2.x
+</programlisting>
+  </para>
+
+  <para>
+   The <option>services.foundationdb.package</option> option is required, and
+   must always be specified. Due to the fact FoundationDB network protocols and
+   on-disk storage formats may change between (major) versions, and upgrades
+   must be explicitly handled by the user, you must always manually specify
+   this yourself so that the NixOS module will use the proper version. Note
+   that minor, bugfix releases are always compatible.
+  </para>
+
+  <para>
+   After running <command>nixos-rebuild</command>, you can verify whether
+   FoundationDB is running by executing <command>fdbcli</command> (which is
+   added to <option>environment.systemPackages</option>):
+<screen>
+<prompt>$ </prompt>sudo -u foundationdb fdbcli
+Using cluster file `/etc/foundationdb/fdb.cluster'.
+
+The database is available.
+
+Welcome to the fdbcli. For help, type `help'.
+<prompt>fdb> </prompt>status
+
+Using cluster file `/etc/foundationdb/fdb.cluster'.
+
+Configuration:
+  Redundancy mode        - single
+  Storage engine         - memory
+  Coordinators           - 1
+
+Cluster:
+  FoundationDB processes - 1
+  Machines               - 1
+  Memory availability    - 5.4 GB per process on machine with least available
+  Fault Tolerance        - 0 machines
+  Server time            - 04/20/18 15:21:14
+
+...
+
+<prompt>fdb></prompt>
+</screen>
+  </para>
+
+  <para>
+   You can also write programs using the available client libraries. For
+   example, the following Python program can be run in order to grab the
+   cluster status, as a quick example. (This example uses
+   <command>nix-shell</command> shebang support to automatically supply the
+   necessary Python modules).
+<screen>
+<prompt>a@link> </prompt>cat fdb-status.py
+#! /usr/bin/env nix-shell
+#! nix-shell -i python -p python pythonPackages.foundationdb52
+
+import fdb
+import json
+
+def main():
+    fdb.api_version(520)
+    db = fdb.open()
+
+    @fdb.transactional
+    def get_status(tr):
+        return str(tr['\xff\xff/status/json'])
+
+    obj = json.loads(get_status(db))
+    print('FoundationDB available: %s' % obj['client']['database_status']['available'])
+
+if __name__ == "__main__":
+    main()
+<prompt>a@link> </prompt>chmod +x fdb-status.py
+<prompt>a@link> </prompt>./fdb-status.py
+FoundationDB available: True
+<prompt>a@link></prompt>
+</screen>
+  </para>
+
+  <para>
+   FoundationDB is run under the <command>foundationdb</command> user and group
+   by default, but this may be changed in the NixOS configuration. The systemd
+   unit <command>foundationdb.service</command> controls the
+   <command>fdbmonitor</command> process.
+  </para>
+
+  <para>
+   By default, the NixOS module for FoundationDB creates a single SSD-storage
+   based database for development and basic usage. This storage engine is
+   designed for SSDs and will perform poorly on HDDs; however it can handle far
+   more data than the alternative "memory" engine and is a better default
+   choice for most deployments. (Note that you can change the storage backend
+   on-the-fly for a given FoundationDB cluster using
+   <command>fdbcli</command>.)
+  </para>
+
+  <para>
+   Furthermore, only 1 server process and 1 backup agent are started in the
+   default configuration. See below for more on scaling to increase this.
+  </para>
+
+  <para>
+   FoundationDB stores all data for all server processes under
+   <filename>/var/lib/foundationdb</filename>. You can override this using
+   <option>services.foundationdb.dataDir</option>, e.g.
+<programlisting>
+services.foundationdb.dataDir = "/data/fdb";
+</programlisting>
+  </para>
+
+  <para>
+   Similarly, logs are stored under <filename>/var/log/foundationdb</filename>
+   by default, and there is a corresponding
+   <option>services.foundationdb.logDir</option> as well.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-scaling">
+  <title>Scaling processes and backup agents</title>
+
+  <para>
+   Scaling the number of server processes is quite easy; simply specify
+   <option>services.foundationdb.serverProcesses</option> to be the number of
+   FoundationDB worker processes that should be started on the machine.
+  </para>
+
+  <para>
+   FoundationDB worker processes typically require 4GB of RAM per-process at
+   minimum for good performance, so this option is set to 1 by default since
+   the maximum amount of RAM is unknown. You're advised to abide by this
+   restriction, so pick a number of processes so that each has 4GB or more.
+  </para>
+
+  <para>
+   A similar option exists in order to scale backup agent processes,
+   <option>services.foundationdb.backupProcesses</option>. Backup agents are
+   not as performance/RAM sensitive, so feel free to experiment with the number
+   of available backup processes.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-clustering">
+  <title>Clustering</title>
+
+  <para>
+   FoundationDB on NixOS works similarly to other Linux systems, so this
+   section will be brief. Please refer to the full FoundationDB documentation
+   for more on clustering.
+  </para>
+
+  <para>
+   FoundationDB organizes clusters using a set of
+   <emphasis>coordinators</emphasis>, which are just specially-designated
+   worker processes. By default, every installation of FoundationDB on NixOS
+   will start as its own individual cluster, with a single coordinator: the
+   first worker process on <command>localhost</command>.
+  </para>
+
+  <para>
+   Coordinators are specified globally using the
+   <command>/etc/foundationdb/fdb.cluster</command> file, which all servers and
+   client applications will use to find and join coordinators. Note that this
+   file <emphasis>can not</emphasis> be managed by NixOS so easily:
+   FoundationDB is designed so that it will rewrite the file at runtime for all
+   clients and nodes when cluster coordinators change, with clients
+   transparently handling this without intervention. It is fundamentally a
+   mutable file, and you should not try to manage it in any way in NixOS.
+  </para>
+
+  <para>
+   When dealing with a cluster, there are two main things you want to do:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Add a node to the cluster for storage/compute.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Promote an ordinary worker to a coordinator.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   A node must already be a member of the cluster in order to properly be
+   promoted to a coordinator, so you must always add it first if you wish to
+   promote it.
+  </para>
+
+  <para>
+   To add a machine to a FoundationDB cluster:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Choose one of the servers to start as the initial coordinator.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Copy the <command>/etc/foundationdb/fdb.cluster</command> file from this
+     server to all the other servers. Restart FoundationDB on all of these
+     other servers, so they join the cluster.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     All of these servers are now connected and working together in the
+     cluster, under the chosen coordinator.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   At this point, you can add as many nodes as you want by just repeating the
+   above steps. By default there will still be a single coordinator: you can
+   use <command>fdbcli</command> to change this and add new coordinators.
+  </para>
+
+  <para>
+   As a convenience, FoundationDB can automatically assign coordinators based
+   on the redundancy mode you wish to achieve for the cluster. Once all the
+   nodes have been joined, simply set the replication policy, and then issue
+   the <command>coordinators auto</command> command
+  </para>
+
+  <para>
+   For example, assuming we have 3 nodes available, we can enable double
+   redundancy mode, then auto-select coordinators. For double redundancy, 3
+   coordinators is ideal: therefore FoundationDB will make
+   <emphasis>every</emphasis> node a coordinator automatically:
+  </para>
+
+<screen>
+<prompt>fdbcli> </prompt>configure double ssd
+<prompt>fdbcli> </prompt>coordinators auto
+</screen>
+
+  <para>
+   This will transparently update all the servers within seconds, and
+   appropriately rewrite the <command>fdb.cluster</command> file, as well as
+   informing all client processes to do the same.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-connectivity">
+  <title>Client connectivity</title>
+
+  <para>
+   By default, all clients must use the current <command>fdb.cluster</command>
+   file to access a given FoundationDB cluster. This file is located by default
+   in <command>/etc/foundationdb/fdb.cluster</command> on all machines with the
+   FoundationDB service enabled, so you may copy the active one from your
+   cluster to a new node in order to connect, if it is not part of the cluster.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-authorization">
+  <title>Client authorization and TLS</title>
+
+  <para>
+   By default, any user who can connect to a FoundationDB process with the
+   correct cluster configuration can access anything. FoundationDB uses a
+   pluggable design to transport security, and out of the box it supports a
+   LibreSSL-based plugin for TLS support. This plugin not only does in-flight
+   encryption, but also performs client authorization based on the given
+   endpoint's certificate chain. For example, a FoundationDB server may be
+   configured to only accept client connections over TLS, where the client TLS
+   certificate is from organization <emphasis>Acme Co</emphasis> in the
+   <emphasis>Research and Development</emphasis> unit.
+  </para>
+
+  <para>
+   Configuring TLS with FoundationDB is done using the
+   <option>services.foundationdb.tls</option> options in order to control the
+   peer verification string, as well as the certificate and its private key.
+  </para>
+
+  <para>
+   Note that the certificate and its private key must be accessible to the
+   FoundationDB user account that the server runs under. These files are also
+   NOT managed by NixOS, as putting them into the store may reveal private
+   information.
+  </para>
+
+  <para>
+   After you have a key and certificate file in place, it is not enough to
+   simply set the NixOS module options -- you must also configure the
+   <command>fdb.cluster</command> file to specify that a given set of
+   coordinators use TLS. This is as simple as adding the suffix
+   <command>:tls</command> to your cluster coordinator configuration, after the
+   port number. For example, assuming you have a coordinator on localhost with
+   the default configuration, simply specifying:
+  </para>
+
+<programlisting>
+XXXXXX:XXXXXX@127.0.0.1:4500:tls
+</programlisting>
+
+  <para>
+   will configure all clients and server processes to use TLS from now on.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-disaster-recovery">
+  <title>Backups and Disaster Recovery</title>
+
+  <para>
+   The usual rules for doing FoundationDB backups apply on NixOS as written in
+   the FoundationDB manual. However, one important difference is the security
+   profile for NixOS: by default, the <command>foundationdb</command> systemd
+   unit uses <emphasis>Linux namespaces</emphasis> to restrict write access to
+   the system, except for the log directory, data directory, and the
+   <command>/etc/foundationdb/</command> directory. This is enforced by default
+   and cannot be disabled.
+  </para>
+
+  <para>
+   However, a side effect of this is that the <command>fdbbackup</command>
+   command doesn't work properly for local filesystem backups: FoundationDB
+   uses a server process alongside the database processes to perform backups
+   and copy the backups to the filesystem. As a result, this process is put
+   under the restricted namespaces above: the backup process can only write to
+   a limited number of paths.
+  </para>
+
+  <para>
+   In order to allow flexible backup locations on local disks, the FoundationDB
+   NixOS module supports a
+   <option>services.foundationdb.extraReadWritePaths</option> option. This
+   option takes a list of paths, and adds them to the systemd unit, allowing
+   the processes inside the service to write (and read) the specified
+   directories.
+  </para>
+
+  <para>
+   For example, to create backups in <command>/opt/fdb-backups</command>, first
+   set up the paths in the module options:
+  </para>
+
+<programlisting>
+services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
+</programlisting>
+
+  <para>
+   Restart the FoundationDB service, and it will now be able to write to this
+   directory (even if it does not yet exist.) Note: this path
+   <emphasis>must</emphasis> exist before restarting the unit. Otherwise,
+   systemd will not include it in the private FoundationDB namespace (and it
+   will not add it dynamically at runtime).
+  </para>
+
+  <para>
+   You can now perform a backup:
+  </para>
+
+<screen>
+<prompt>$ </prompt>sudo -u foundationdb fdbbackup start  -t default -d file:///opt/fdb-backups
+<prompt>$ </prompt>sudo -u foundationdb fdbbackup status -t default
+</screen>
+ </section>
+ <section xml:id="module-services-foundationdb-limitations">
+  <title>Known limitations</title>
+
+  <para>
+   The FoundationDB setup for NixOS should currently be considered beta.
+   FoundationDB is not new software, but the NixOS compilation and integration
+   has only undergone fairly basic testing of all the available functionality.
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     There is no way to specify individual parameters for individual
+     <command>fdbserver</command> processes. Currently, all server processes
+     inherit all the global <command>fdbmonitor</command> settings.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Ruby bindings are not currently installed.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Go bindings are not currently installed.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+ <section xml:id="module-services-foundationdb-options">
+  <title>Options</title>
+
+  <para>
+   NixOS's FoundationDB module allows you to configure all of the most relevant
+   configuration options for <command>fdbmonitor</command>, matching it quite
+   closely. A complete list of options for the FoundationDB module may be found
+   <link linkend="opt-services.foundationdb.enable">here</link>. You should
+   also read the FoundationDB documentation as well.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-full-docs">
+  <title>Full documentation</title>
+
+  <para>
+   FoundationDB is a complex piece of software, and requires careful
+   administration to properly use. Full documentation for administration can be
+   found here: <link xlink:href="https://apple.github.io/foundationdb/"/>.
+  </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/databases/hbase.nix b/nixpkgs/nixos/modules/services/databases/hbase.nix
new file mode 100644
index 000000000000..2d1a47bbaa31
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/hbase.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.hbase;
+
+  configFile = pkgs.writeText "hbase-site.xml" ''
+    <configuration>
+      <property>
+        <name>hbase.rootdir</name>
+        <value>file://${cfg.dataDir}/hbase</value>
+      </property>
+      <property>
+        <name>hbase.zookeeper.property.dataDir</name>
+        <value>${cfg.dataDir}/zookeeper</value>
+      </property>
+    </configuration>
+  '';
+
+  configDir = pkgs.runCommand "hbase-config-dir" { preferLocalBuild = true; } ''
+    mkdir -p $out
+    cp ${cfg.package}/conf/* $out/
+    rm $out/hbase-site.xml
+    ln -s ${configFile} $out/hbase-site.xml
+  '' ;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.hbase = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run HBase.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.hbase;
+        defaultText = "pkgs.hbase";
+        example = literalExample "pkgs.hbase";
+        description = ''
+          HBase package to use.
+        '';
+      };
+
+
+      user = mkOption {
+        type = types.str;
+        default = "hbase";
+        description = ''
+          User account under which HBase runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "hbase";
+        description = ''
+          Group account under which HBase runs.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/hbase";
+        description = ''
+          Specifies location of HBase database files. This location should be
+          writable and readable for the user the HBase service runs as
+          (hbase by default).
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        default = "/var/log/hbase";
+        description = ''
+          Specifies the location of HBase log files.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.hbase.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.logDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.hbase = {
+      description = "HBase Server";
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        JAVA_HOME = "${pkgs.jre}";
+        HBASE_LOG_DIR = cfg.logDir;
+      };
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/hbase --config ${configDir} master start";
+      };
+    };
+
+    users.users.hbase = {
+      description = "HBase Server user";
+      group = "hbase";
+      uid = config.ids.uids.hbase;
+    };
+
+    users.groups.hbase.gid = config.ids.gids.hbase;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/influxdb.nix b/nixpkgs/nixos/modules/services/databases/influxdb.nix
new file mode 100644
index 000000000000..dd5d69b1147a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/influxdb.nix
@@ -0,0 +1,197 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.influxdb;
+
+  configOptions = recursiveUpdate {
+    meta = {
+      bind-address = ":8088";
+      commit-timeout = "50ms";
+      dir = "${cfg.dataDir}/meta";
+      election-timeout = "1s";
+      heartbeat-timeout = "1s";
+      hostname = "localhost";
+      leader-lease-timeout = "500ms";
+      retention-autocreate = true;
+    };
+
+    data = {
+      dir = "${cfg.dataDir}/data";
+      wal-dir = "${cfg.dataDir}/wal";
+      max-wal-size = 104857600;
+      wal-enable-logging = true;
+      wal-flush-interval = "10m";
+      wal-partition-flush-delay = "2s";
+    };
+
+    cluster = {
+      shard-writer-timeout = "5s";
+      write-timeout = "5s";
+    };
+
+    retention = {
+      enabled = true;
+      check-interval = "30m";
+    };
+
+    http = {
+      enabled = true;
+      auth-enabled = false;
+      bind-address = ":8086";
+      https-enabled = false;
+      log-enabled = true;
+      pprof-enabled = false;
+      write-tracing = false;
+    };
+
+    monitor = {
+      store-enabled = false;
+      store-database = "_internal";
+      store-interval = "10s";
+    };
+
+    admin = {
+      enabled = true;
+      bind-address = ":8083";
+      https-enabled = false;
+    };
+
+    graphite = [{
+      enabled = false;
+    }];
+
+    udp = [{
+      enabled = false;
+    }];
+
+    collectd = [{
+      enabled = false;
+      typesdb = "${pkgs.collectd-data}/share/collectd/types.db";
+      database = "collectd_db";
+      bind-address = ":25826";
+    }];
+
+    opentsdb = [{
+      enabled = false;
+    }];
+
+    continuous_queries = {
+      enabled = true;
+      log-enabled = true;
+      recompute-previous-n = 2;
+      recompute-no-older-than = "10m";
+      compute-runs-per-interval = 10;
+      compute-no-more-than = "2m";
+    };
+
+    hinted-handoff = {
+      enabled = true;
+      dir = "${cfg.dataDir}/hh";
+      max-size = 1073741824;
+      max-age = "168h";
+      retry-rate-limit = 0;
+      retry-interval = "1s";
+    };
+  } cfg.extraConfig;
+
+  configFile = pkgs.runCommand "config.toml" {
+    buildInputs = [ pkgs.remarshal ];
+    preferLocalBuild = true;
+  } ''
+    remarshal -if json -of toml \
+      < ${pkgs.writeText "config.json" (builtins.toJSON configOptions)} \
+      > $out
+  '';
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.influxdb = {
+
+      enable = mkOption {
+        default = false;
+        description = "Whether to enable the influxdb server";
+        type = types.bool;
+      };
+
+      package = mkOption {
+        default = pkgs.influxdb;
+        defaultText = "pkgs.influxdb";
+        description = "Which influxdb derivation to use";
+        type = types.package;
+      };
+
+      user = mkOption {
+        default = "influxdb";
+        description = "User account under which influxdb runs";
+        type = types.str;
+      };
+
+      group = mkOption {
+        default = "influxdb";
+        description = "Group under which influxdb runs";
+        type = types.str;
+      };
+
+      dataDir = mkOption {
+        default = "/var/db/influxdb";
+        description = "Data directory for influxd data files.";
+        type = types.path;
+      };
+
+      extraConfig = mkOption {
+        default = {};
+        description = "Extra configuration options for influxdb";
+        type = types.attrs;
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.influxdb.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.influxdb = {
+      description = "InfluxDB Server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = ''${cfg.package}/bin/influxd -config "${configFile}"'';
+        User = cfg.user;
+        Group = cfg.group;
+      };
+      postStart =
+        let
+          scheme = if configOptions.http.https-enabled then "-k https" else "http";
+          bindAddr = (ba: if hasPrefix ":" ba then "127.0.0.1${ba}" else "${ba}")(toString configOptions.http.bind-address);
+        in
+        mkBefore ''
+          until ${pkgs.curl.bin}/bin/curl -s -o /dev/null ${scheme}://${bindAddr}/ping; do
+            sleep 1;
+          done
+        '';
+    };
+
+    users.users = optionalAttrs (cfg.user == "influxdb") {
+      influxdb = {
+        uid = config.ids.uids.influxdb;
+        description = "Influxdb daemon user";
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "influxdb") {
+      influxdb.gid = config.ids.gids.influxdb;
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/memcached.nix b/nixpkgs/nixos/modules/services/databases/memcached.nix
new file mode 100644
index 000000000000..f54bb6cc9b18
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/memcached.nix
@@ -0,0 +1,111 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.memcached;
+
+  memcached = pkgs.memcached;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.memcached = {
+
+      enable = mkEnableOption "Memcached";
+
+      user = mkOption {
+        default = "memcached";
+        description = "The user to run Memcached as";
+      };
+
+      listen = mkOption {
+        default = "127.0.0.1";
+        description = "The IP address to bind to";
+      };
+
+      port = mkOption {
+        default = 11211;
+        description = "The port to bind to";
+      };
+
+      enableUnixSocket = mkEnableOption "unix socket at /run/memcached/memcached.sock";
+
+      maxMemory = mkOption {
+        default = 64;
+        description = "The maximum amount of memory to use for storage, in megabytes.";
+      };
+
+      maxConnections = mkOption {
+        default = 1024;
+        description = "The maximum number of simultaneous connections";
+      };
+
+      extraOptions = mkOption {
+        default = [];
+        description = "A list of extra options that will be added as a suffix when running memcached";
+      };
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.memcached.enable {
+
+    users.users = optionalAttrs (cfg.user == "memcached") {
+      memcached.description = "Memcached server user";
+      memcached.isSystemUser = true;
+    };
+
+    environment.systemPackages = [ memcached ];
+
+    systemd.services.memcached = {
+      description = "Memcached server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart =
+        let
+          networking = if cfg.enableUnixSocket
+          then "-s /run/memcached/memcached.sock"
+          else "-l ${cfg.listen} -p ${toString cfg.port}";
+        in "${memcached}/bin/memcached ${networking} -m ${toString cfg.maxMemory} -c ${toString cfg.maxConnections} ${concatStringsSep " " cfg.extraOptions}";
+
+        User = cfg.user;
+
+        # Filesystem access
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RuntimeDirectory = "memcached";
+        # Caps
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        # Misc.
+        LockPersonality = true;
+        RestrictRealtime = true;
+        PrivateMounts = true;
+        MemoryDenyWriteExecute = true;
+      };
+    };
+  };
+  imports = [
+    (mkRemovedOptionModule ["services" "memcached" "socket"] ''
+      This option was replaced by a fixed unix socket path at /run/memcached/memcached.sock enabled using services.memcached.enableUnixSocket.
+    '')
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/monetdb.nix b/nixpkgs/nixos/modules/services/databases/monetdb.nix
new file mode 100644
index 000000000000..5c66fc7b2e36
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/monetdb.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.monetdb;
+
+in {
+  meta.maintainers = with maintainers; [ StillerHarpo primeos ];
+
+  ###### interface
+  options = {
+    services.monetdb = {
+
+      enable = mkEnableOption "the MonetDB database server";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.monetdb;
+        defaultText = "pkgs.monetdb";
+        description = "MonetDB package to use.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "monetdb";
+        description = "User account under which MonetDB runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "monetdb";
+        description = "Group under which MonetDB runs.";
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/monetdb";
+        description = "Data directory for the dbfarm.";
+      };
+
+      port = mkOption {
+        type = types.ints.u16;
+        default = 50000;
+        description = "Port to listen on.";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        example = "0.0.0.0";
+        description = "Address to listen on.";
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+
+    users.users.monetdb = mkIf (cfg.user == "monetdb") {
+      uid = config.ids.uids.monetdb;
+      group = cfg.group;
+      description = "MonetDB user";
+      home = cfg.dataDir;
+      createHome = true;
+    };
+
+    users.groups.monetdb = mkIf (cfg.group == "monetdb") {
+      gid = config.ids.gids.monetdb;
+      members = [ cfg.user ];
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.monetdb = {
+      description = "MonetDB database server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ cfg.package ];
+      unitConfig.RequiresMountsFor = "${cfg.dataDir}";
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/monetdbd start -n ${cfg.dataDir}";
+        ExecStop = "${cfg.package}/bin/monetdbd stop ${cfg.dataDir}";
+      };
+      preStart = ''
+        if [ ! -e ${cfg.dataDir}/.merovingian_properties ]; then
+          # Create the dbfarm (as cfg.user)
+          ${cfg.package}/bin/monetdbd create ${cfg.dataDir}
+        fi
+
+        # Update the properties
+        ${cfg.package}/bin/monetdbd set port=${toString cfg.port} ${cfg.dataDir}
+        ${cfg.package}/bin/monetdbd set listenaddr=${cfg.listenAddress} ${cfg.dataDir}
+      '';
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/mongodb.nix b/nixpkgs/nixos/modules/services/databases/mongodb.nix
new file mode 100644
index 000000000000..4453a182990d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/mongodb.nix
@@ -0,0 +1,188 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.mongodb;
+
+  mongodb = cfg.package;
+
+  mongoCnf = cfg: pkgs.writeText "mongodb.conf"
+  ''
+    net.bindIp: ${cfg.bind_ip}
+    ${optionalString cfg.quiet "systemLog.quiet: true"}
+    systemLog.destination: syslog
+    storage.dbPath: ${cfg.dbpath}
+    ${optionalString cfg.enableAuth "security.authorization: enabled"}
+    ${optionalString (cfg.replSetName != "") "replication.replSetName: ${cfg.replSetName}"}
+    ${cfg.extraConfig}
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.mongodb = {
+
+      enable = mkEnableOption "the MongoDB server";
+
+      package = mkOption {
+        default = pkgs.mongodb;
+        defaultText = "pkgs.mongodb";
+        type = types.package;
+        description = "
+          Which MongoDB derivation to use.
+        ";
+      };
+
+      user = mkOption {
+        default = "mongodb";
+        description = "User account under which MongoDB runs";
+      };
+
+      bind_ip = mkOption {
+        default = "127.0.0.1";
+        description = "IP to bind to";
+      };
+
+      quiet = mkOption {
+        default = false;
+        description = "quieter output";
+      };
+
+      enableAuth = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable client authentication. Creates a default superuser with username root!";
+      };
+
+      initialRootPassword = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Password for the root user if auth is enabled.";
+      };
+
+      dbpath = mkOption {
+        default = "/var/db/mongodb";
+        description = "Location where MongoDB stores its files";
+      };
+
+      pidFile = mkOption {
+        default = "/run/mongodb.pid";
+        description = "Location of MongoDB pid file";
+      };
+
+      replSetName = mkOption {
+        default = "";
+        description = ''
+          If this instance is part of a replica set, set its name here.
+          Otherwise, leave empty to run as single node.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        example = ''
+          storage.journal.enabled: false
+        '';
+        description = "MongoDB extra configuration in YAML format";
+      };
+
+      initialScript = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          A file containing MongoDB statements to execute on first startup.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.mongodb.enable {
+    assertions = [
+      { assertion = !cfg.enableAuth || cfg.initialRootPassword != null;
+        message = "`enableAuth` requires `initialRootPassword` to be set.";
+      }
+    ];
+
+    users.users.mongodb = mkIf (cfg.user == "mongodb")
+      { name = "mongodb";
+        uid = config.ids.uids.mongodb;
+        description = "MongoDB server user";
+      };
+
+    environment.systemPackages = [ mongodb ];
+
+    systemd.services.mongodb =
+      { description = "MongoDB server";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        serviceConfig = {
+          ExecStart = "${mongodb}/bin/mongod --config ${mongoCnf cfg} --fork --pidfilepath ${cfg.pidFile}";
+          User = cfg.user;
+          PIDFile = cfg.pidFile;
+          Type = "forking";
+          TimeoutStartSec=120; # intial creating of journal can take some time
+          PermissionsStartOnly = true;
+        };
+
+        preStart = let
+          cfg_ = cfg // { enableAuth = false; bind_ip = "127.0.0.1"; };
+        in ''
+          rm ${cfg.dbpath}/mongod.lock || true
+          if ! test -e ${cfg.dbpath}; then
+              install -d -m0700 -o ${cfg.user} ${cfg.dbpath}
+              # See postStart!
+              touch ${cfg.dbpath}/.first_startup
+          fi
+          if ! test -e ${cfg.pidFile}; then
+              install -D -o ${cfg.user} /dev/null ${cfg.pidFile}
+          fi '' + lib.optionalString cfg.enableAuth ''
+
+          if ! test -e "${cfg.dbpath}/.auth_setup_complete"; then
+            systemd-run --unit=mongodb-for-setup --uid=${cfg.user} ${mongodb}/bin/mongod --config ${mongoCnf cfg_}
+            # wait for mongodb
+            while ! ${mongodb}/bin/mongo --eval "db.version()" > /dev/null 2>&1; do sleep 0.1; done
+
+          ${mongodb}/bin/mongo <<EOF
+            use admin
+            db.createUser(
+              {
+                user: "root",
+                pwd: "${cfg.initialRootPassword}",
+                roles: [
+                  { role: "userAdminAnyDatabase", db: "admin" },
+                  { role: "dbAdminAnyDatabase", db: "admin" },
+                  { role: "readWriteAnyDatabase", db: "admin" }
+                ]
+              }
+            )
+          EOF
+            touch "${cfg.dbpath}/.auth_setup_complete"
+            systemctl stop mongodb-for-setup
+          fi
+        '';
+        postStart = ''
+            if test -e "${cfg.dbpath}/.first_startup"; then
+              ${optionalString (cfg.initialScript != null) ''
+                ${mongodb}/bin/mongo -u root -p ${cfg.initialRootPassword} admin "${cfg.initialScript}"
+              ''}
+              rm -f "${cfg.dbpath}/.first_startup"
+            fi
+        '';
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/mysql.nix b/nixpkgs/nixos/modules/services/databases/mysql.nix
new file mode 100644
index 000000000000..2e8c5b7640b2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/mysql.nix
@@ -0,0 +1,510 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.mysql;
+
+  mysql = cfg.package;
+
+  isMariaDB = lib.getName mysql == lib.getName pkgs.mariadb;
+
+  mysqldOptions =
+    "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
+
+  settingsFile = pkgs.writeText "my.cnf" (
+    generators.toINI { listsAsDuplicateKeys = true; } cfg.settings +
+    optionalString (cfg.extraOptions != null) "[mysqld]\n${cfg.extraOptions}"
+  );
+
+in
+
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "mysql" "pidDir" ] "Don't wait for pidfiles, describe dependencies through systemd")
+    (mkRemovedOptionModule [ "services" "mysql" "rootPassword" ] "Use socket authentication or set the password outside of the nix store.")
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.mysql = {
+
+      enable = mkEnableOption "MySQL server";
+
+      package = mkOption {
+        type = types.package;
+        example = literalExample "pkgs.mysql";
+        description = "
+          Which MySQL derivation to use. MariaDB packages are supported too.
+        ";
+      };
+
+      bind = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = literalExample "0.0.0.0";
+        description = "Address to bind to. The default is to bind to all addresses";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 3306;
+        description = "Port of MySQL";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "mysql";
+        description = "User account under which MySQL runs";
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        example = "/var/lib/mysql";
+        description = "Location where MySQL stores its table files";
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        default = settingsFile;
+        defaultText = "settingsFile";
+        description = ''
+          Override the configuration file used by MySQL. By default,
+          NixOS generates one automatically from <option>services.mysql.settings</option>.
+        '';
+        example = literalExample ''
+          pkgs.writeText "my.cnf" '''
+            [mysqld]
+            datadir = /var/lib/mysql
+            bind-address = 127.0.0.1
+            port = 3336
+
+            !includedir /etc/mysql/conf.d/
+          ''';
+        '';
+      };
+
+      settings = mkOption {
+        type = with types; attrsOf (attrsOf (oneOf [ bool int str (listOf str) ]));
+        default = {};
+        description = ''
+          MySQL configuration. Refer to
+          <link xlink:href="https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html"/>,
+          <link xlink:href="https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html"/>,
+          and <link xlink:href="https://mariadb.com/kb/en/server-system-variables/"/>
+          for details on supported values.
+
+          <note>
+            <para>
+              MySQL configuration options such as <literal>--quick</literal> should be treated as
+              boolean options and provided values such as <literal>true</literal>, <literal>false</literal>,
+              <literal>1</literal>, or <literal>0</literal>. See the provided example below.
+            </para>
+          </note>
+        '';
+        example = literalExample ''
+          {
+            mysqld = {
+              key_buffer_size = "6G";
+              table_cache = 1600;
+              log-error = "/var/log/mysql_err.log";
+              plugin-load-add = [ "server_audit" "ed25519=auth_ed25519" ];
+            };
+            mysqldump = {
+              quick = true;
+              max_allowed_packet = "16M";
+            };
+          }
+        '';
+      };
+
+      extraOptions = mkOption {
+        type = with types; nullOr lines;
+        default = null;
+        example = ''
+          key_buffer_size = 6G
+          table_cache = 1600
+          log-error = /var/log/mysql_err.log
+        '';
+        description = ''
+          Provide extra options to the MySQL configuration file.
+
+          Please note, that these options are added to the
+          <literal>[mysqld]</literal> section so you don't need to explicitly
+          state it again.
+        '';
+      };
+
+      initialDatabases = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = types.str;
+              description = ''
+                The name of the database to create.
+              '';
+            };
+            schema = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              description = ''
+                The initial schema of the database; if null (the default),
+                an empty database is created.
+              '';
+            };
+          };
+        });
+        default = [];
+        description = ''
+          List of database names and their initial schemas that should be used to create databases on the first startup
+          of MySQL. The schema attribute is optional: If not specified, an empty database is created.
+        '';
+        example = [
+          { name = "foodatabase"; schema = literalExample "./foodatabase.sql"; }
+          { name = "bardatabase"; }
+        ];
+      };
+
+      initialScript = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "A file containing SQL statements to be executed on the first startup. Can be used for granting certain permissions on the database";
+      };
+
+      ensureDatabases = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Ensures that the specified databases exist.
+          This option will never delete existing databases, especially not when the value of this
+          option is changed. This means that databases created once through this option or
+          otherwise have to be removed manually.
+        '';
+        example = [
+          "nextcloud"
+          "matomo"
+        ];
+      };
+
+      ensureUsers = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = types.str;
+              description = ''
+                Name of the user to ensure.
+              '';
+            };
+            ensurePermissions = mkOption {
+              type = types.attrsOf types.str;
+              default = {};
+              description = ''
+                Permissions to ensure for the user, specified as attribute set.
+                The attribute names specify the database and tables to grant the permissions for,
+                separated by a dot. You may use wildcards here.
+                The attribute values specfiy the permissions to grant.
+                You may specify one or multiple comma-separated SQL privileges here.
+
+                For more information on how to specify the target
+                and on which privileges exist, see the
+                <link xlink:href="https://mariadb.com/kb/en/library/grant/">GRANT syntax</link>.
+                The attributes are used as <code>GRANT ''${attrName} ON ''${attrValue}</code>.
+              '';
+              example = literalExample ''
+                {
+                  "database.*" = "ALL PRIVILEGES";
+                  "*.*" = "SELECT, LOCK TABLES";
+                }
+              '';
+            };
+          };
+        });
+        default = [];
+        description = ''
+          Ensures that the specified users exist and have at least the ensured permissions.
+          The MySQL users will be identified using Unix socket authentication. This authenticates the Unix user with the
+          same name only, and that without the need for a password.
+          This option will never delete existing users or remove permissions, especially not when the value of this
+          option is changed. This means that users created and permissions assigned once through this option or
+          otherwise have to be removed manually.
+        '';
+        example = literalExample ''
+          [
+            {
+              name = "nextcloud";
+              ensurePermissions = {
+                "nextcloud.*" = "ALL PRIVILEGES";
+              };
+            }
+            {
+              name = "backup";
+              ensurePermissions = {
+                "*.*" = "SELECT, LOCK TABLES";
+              };
+            }
+          ]
+        '';
+      };
+
+      replication = {
+        role = mkOption {
+          type = types.enum [ "master" "slave" "none" ];
+          default = "none";
+          description = "Role of the MySQL server instance.";
+        };
+
+        serverId = mkOption {
+          type = types.int;
+          default = 1;
+          description = "Id of the MySQL server instance. This number must be unique for each instance";
+        };
+
+        masterHost = mkOption {
+          type = types.str;
+          description = "Hostname of the MySQL master server";
+        };
+
+        slaveHost = mkOption {
+          type = types.str;
+          description = "Hostname of the MySQL slave server";
+        };
+
+        masterUser = mkOption {
+          type = types.str;
+          description = "Username of the MySQL replication user";
+        };
+
+        masterPassword = mkOption {
+          type = types.str;
+          description = "Password of the MySQL replication user";
+        };
+
+        masterPort = mkOption {
+          type = types.int;
+          default = 3306;
+          description = "Port number on which the MySQL master server runs";
+        };
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.mysql.enable {
+
+    warnings = optional (cfg.extraOptions != null) "services.mysql.`extraOptions` is deprecated, please use services.mysql.`settings`.";
+
+    services.mysql.dataDir =
+      mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/mysql"
+                 else "/var/mysql");
+
+    services.mysql.settings.mysqld = mkMerge [
+      {
+        datadir = cfg.dataDir;
+        bind-address = mkIf (cfg.bind != null) cfg.bind;
+        port = cfg.port;
+      }
+      (mkIf (cfg.replication.role == "master" || cfg.replication.role == "slave") {
+        log-bin = "mysql-bin-${toString cfg.replication.serverId}";
+        log-bin-index = "mysql-bin-${toString cfg.replication.serverId}.index";
+        relay-log = "mysql-relay-bin";
+        server-id = cfg.replication.serverId;
+        binlog-ignore-db = [ "information_schema" "performance_schema" "mysql" ];
+      })
+      (mkIf (!isMariaDB) {
+        plugin-load-add = optional (cfg.ensureUsers != []) "auth_socket.so";
+      })
+    ];
+
+    users.users.mysql = {
+      description = "MySQL server user";
+      group = "mysql";
+      uid = config.ids.uids.mysql;
+    };
+
+    users.groups.mysql.gid = config.ids.gids.mysql;
+
+    environment.systemPackages = [mysql];
+
+    environment.etc."my.cnf".source = cfg.configFile;
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} mysql - -"
+      "z '${cfg.dataDir}' 0700 ${cfg.user} mysql - -"
+    ];
+
+    systemd.services.mysql = let
+      hasNotify = (cfg.package == pkgs.mariadb);
+    in {
+        description = "MySQL Server";
+
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        restartTriggers = [ cfg.configFile ];
+
+        unitConfig.RequiresMountsFor = "${cfg.dataDir}";
+
+        path = [
+          # Needed for the mysql_install_db command in the preStart script
+          # which calls the hostname command.
+          pkgs.nettools
+        ];
+
+        preStart = if isMariaDB then ''
+          if ! test -e ${cfg.dataDir}/mysql; then
+            ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${mysqldOptions}
+            touch ${cfg.dataDir}/mysql_init
+          fi
+        '' else ''
+          if ! test -e ${cfg.dataDir}/mysql; then
+            ${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} --initialize-insecure
+            touch ${cfg.dataDir}/mysql_init
+          fi
+        '';
+
+        serviceConfig = {
+          Type = if hasNotify then "notify" else "simple";
+          Restart = "on-abort";
+          RestartSec = "5s";
+          # The last two environment variables are used for starting Galera clusters
+          ExecStart = "${mysql}/bin/mysqld --defaults-file=/etc/my.cnf ${mysqldOptions} $_WSREP_NEW_CLUSTER $_WSREP_START_POSITION";
+          ExecStartPost =
+            let
+              setupScript = pkgs.writeScript "mysql-setup" ''
+                #!${pkgs.runtimeShell} -e
+
+                ${optionalString (!hasNotify) ''
+                  # Wait until the MySQL server is available for use
+                  count=0
+                  while [ ! -e /run/mysqld/mysqld.sock ]
+                  do
+                      if [ $count -eq 30 ]
+                      then
+                          echo "Tried 30 times, giving up..."
+                          exit 1
+                      fi
+
+                      echo "MySQL daemon not yet started. Waiting for 1 second..."
+                      count=$((count++))
+                      sleep 1
+                  done
+                ''}
+
+                if [ -f ${cfg.dataDir}/mysql_init ]
+                then
+                    ${concatMapStrings (database: ''
+                      # Create initial databases
+                      if ! test -e "${cfg.dataDir}/${database.name}"; then
+                          echo "Creating initial database: ${database.name}"
+                          ( echo 'create database `${database.name}`;'
+
+                            ${optionalString (database.schema != null) ''
+                            echo 'use `${database.name}`;'
+
+                            # TODO: this silently falls through if database.schema does not exist,
+                            # we should catch this somehow and exit, but can't do it here because we're in a subshell.
+                            if [ -f "${database.schema}" ]
+                            then
+                                cat ${database.schema}
+                            elif [ -d "${database.schema}" ]
+                            then
+                                cat ${database.schema}/mysql-databases/*.sql
+                            fi
+                            ''}
+                          ) | ${mysql}/bin/mysql -u root -N
+                      fi
+                    '') cfg.initialDatabases}
+
+                    ${optionalString (cfg.replication.role == "master")
+                      ''
+                        # Set up the replication master
+
+                        ( echo "use mysql;"
+                          echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
+                          echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
+                          echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
+                        ) | ${mysql}/bin/mysql -u root -N
+                      ''}
+
+                    ${optionalString (cfg.replication.role == "slave")
+                      ''
+                        # Set up the replication slave
+
+                        ( echo "stop slave;"
+                          echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
+                          echo "start slave;"
+                        ) | ${mysql}/bin/mysql -u root -N
+                      ''}
+
+                    ${optionalString (cfg.initialScript != null)
+                      ''
+                        # Execute initial script
+                        # using toString to avoid copying the file to nix store if given as path instead of string,
+                        # as it might contain credentials
+                        cat ${toString cfg.initialScript} | ${mysql}/bin/mysql -u root -N
+                      ''}
+
+                    rm ${cfg.dataDir}/mysql_init
+                fi
+
+                ${optionalString (cfg.ensureDatabases != []) ''
+                  (
+                  ${concatMapStrings (database: ''
+                    echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
+                  '') cfg.ensureDatabases}
+                  ) | ${mysql}/bin/mysql -u root -N
+                ''}
+
+                ${concatMapStrings (user:
+                  ''
+                    ( echo "CREATE USER IF NOT EXISTS '${user.name}'@'localhost' IDENTIFIED WITH ${if isMariaDB then "unix_socket" else "auth_socket"};"
+                      ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+                        echo "GRANT ${permission} ON ${database} TO '${user.name}'@'localhost';"
+                      '') user.ensurePermissions)}
+                    ) | ${mysql}/bin/mysql -u root -N
+                  '') cfg.ensureUsers}
+              '';
+            in
+              # ensureDatbases & ensureUsers depends on this script being run as root
+              # when the user has secured their mysql install
+              "+${setupScript}";
+          # User and group
+          User = cfg.user;
+          Group = "mysql";
+          # Runtime directory and mode
+          RuntimeDirectory = "mysqld";
+          RuntimeDirectoryMode = "0755";
+          # Access write directories
+          ReadWritePaths = [ cfg.dataDir ];
+          # Capabilities
+          CapabilityBoundingSet = "";
+          # Security
+          NoNewPrivileges = true;
+          # Sandboxing
+          ProtectSystem = "strict";
+          ProtectHome = true;
+          PrivateTmp = true;
+          PrivateDevices = true;
+          ProtectHostname = true;
+          ProtectKernelTunables = true;
+          ProtectKernelModules = true;
+          ProtectControlGroups = true;
+          RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          PrivateMounts = true;
+          # System Call Filtering
+          SystemCallArchitectures = "native";
+        };
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/neo4j.nix b/nixpkgs/nixos/modules/services/databases/neo4j.nix
new file mode 100644
index 000000000000..09b453e75845
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/neo4j.nix
@@ -0,0 +1,663 @@
+{ config, options, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.neo4j;
+  certDirOpt = options.services.neo4j.directories.certificates;
+  isDefaultPathOption = opt: isOption opt && opt.type == types.path && opt.highestPrio >= 1500;
+
+  sslPolicies = mapAttrsToList (
+    name: conf: ''
+      dbms.ssl.policy.${name}.allow_key_generation=${boolToString conf.allowKeyGeneration}
+      dbms.ssl.policy.${name}.base_directory=${conf.baseDirectory}
+      ${optionalString (conf.ciphers != null) ''
+        dbms.ssl.policy.${name}.ciphers=${concatStringsSep "," conf.ciphers}
+      ''}
+      dbms.ssl.policy.${name}.client_auth=${conf.clientAuth}
+      ${if length (splitString "/" conf.privateKey) > 1 then
+        ''dbms.ssl.policy.${name}.private_key=${conf.privateKey}''
+      else
+        ''dbms.ssl.policy.${name}.private_key=${conf.baseDirectory}/${conf.privateKey}''
+      }
+      ${if length (splitString "/" conf.privateKey) > 1 then
+        ''dbms.ssl.policy.${name}.public_certificate=${conf.publicCertificate}''
+      else
+        ''dbms.ssl.policy.${name}.public_certificate=${conf.baseDirectory}/${conf.publicCertificate}''
+      }
+      dbms.ssl.policy.${name}.revoked_dir=${conf.revokedDir}
+      dbms.ssl.policy.${name}.tls_versions=${concatStringsSep "," conf.tlsVersions}
+      dbms.ssl.policy.${name}.trust_all=${boolToString conf.trustAll}
+      dbms.ssl.policy.${name}.trusted_dir=${conf.trustedDir}
+    ''
+  ) cfg.ssl.policies;
+
+  serverConfig = pkgs.writeText "neo4j.conf" ''
+    # General
+    dbms.allow_upgrade=${boolToString cfg.allowUpgrade}
+    dbms.connectors.default_listen_address=${cfg.defaultListenAddress}
+    dbms.read_only=${boolToString cfg.readOnly}
+    ${optionalString (cfg.workerCount > 0) ''
+      dbms.threads.worker_count=${toString cfg.workerCount}
+    ''}
+
+    # Directories
+    dbms.directories.certificates=${cfg.directories.certificates}
+    dbms.directories.data=${cfg.directories.data}
+    dbms.directories.logs=${cfg.directories.home}/logs
+    dbms.directories.plugins=${cfg.directories.plugins}
+    ${optionalString (cfg.constrainLoadCsv) ''
+      dbms.directories.import=${cfg.directories.imports}
+    ''}
+
+    # HTTP Connector
+    ${optionalString (cfg.http.enable) ''
+      dbms.connector.http.enabled=${boolToString cfg.http.enable}
+      dbms.connector.http.listen_address=${cfg.http.listenAddress}
+    ''}
+    ${optionalString (!cfg.http.enable) ''
+      # It is not possible to disable the HTTP connector. To fully prevent
+      # clients from connecting to HTTP, block the HTTP port (7474 by default)
+      # via firewall. listen_address is set to the loopback interface to
+      # prevent remote clients from connecting.
+      dbms.connector.http.listen_address=127.0.0.1
+    ''}
+
+    # HTTPS Connector
+    dbms.connector.https.enabled=${boolToString cfg.https.enable}
+    dbms.connector.https.listen_address=${cfg.https.listenAddress}
+    https.ssl_policy=${cfg.https.sslPolicy}
+
+    # BOLT Connector
+    dbms.connector.bolt.enabled=${boolToString cfg.bolt.enable}
+    dbms.connector.bolt.listen_address=${cfg.bolt.listenAddress}
+    bolt.ssl_policy=${cfg.bolt.sslPolicy}
+    dbms.connector.bolt.tls_level=${cfg.bolt.tlsLevel}
+
+    # neo4j-shell
+    dbms.shell.enabled=${boolToString cfg.shell.enable}
+
+    # SSL Policies
+    ${concatStringsSep "\n" sslPolicies}
+
+    # Default retention policy from neo4j.conf
+    dbms.tx_log.rotation.retention_policy=1 days
+
+    # Default JVM parameters from neo4j.conf
+    dbms.jvm.additional=-XX:+UseG1GC
+    dbms.jvm.additional=-XX:-OmitStackTraceInFastThrow
+    dbms.jvm.additional=-XX:+AlwaysPreTouch
+    dbms.jvm.additional=-XX:+UnlockExperimentalVMOptions
+    dbms.jvm.additional=-XX:+TrustFinalNonStaticFields
+    dbms.jvm.additional=-XX:+DisableExplicitGC
+    dbms.jvm.additional=-Djdk.tls.ephemeralDHKeySize=2048
+    dbms.jvm.additional=-Djdk.tls.rejectClientInitiatedRenegotiation=true
+    dbms.jvm.additional=-Dunsupported.dbms.udc.source=tarball
+
+    # Usage Data Collector
+    dbms.udc.enabled=${boolToString cfg.udc.enable}
+
+    # Extra Configuration
+    ${cfg.extraServerConfig}
+  '';
+
+in {
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "neo4j" "host" ] [ "services" "neo4j" "defaultListenAddress" ])
+    (mkRenamedOptionModule [ "services" "neo4j" "listenAddress" ] [ "services" "neo4j" "defaultListenAddress" ])
+    (mkRenamedOptionModule [ "services" "neo4j" "enableBolt" ] [ "services" "neo4j" "bolt" "enable" ])
+    (mkRenamedOptionModule [ "services" "neo4j" "enableHttps" ] [ "services" "neo4j" "https" "enable" ])
+    (mkRenamedOptionModule [ "services" "neo4j" "certDir" ] [ "services" "neo4j" "directories" "certificates" ])
+    (mkRenamedOptionModule [ "services" "neo4j" "dataDir" ] [ "services" "neo4j" "directories" "home" ])
+    (mkRemovedOptionModule [ "services" "neo4j" "port" ] "Use services.neo4j.http.listenAddress instead.")
+    (mkRemovedOptionModule [ "services" "neo4j" "boltPort" ] "Use services.neo4j.bolt.listenAddress instead.")
+    (mkRemovedOptionModule [ "services" "neo4j" "httpsPort" ] "Use services.neo4j.https.listenAddress instead.")
+  ];
+
+  ###### interface
+
+  options.services.neo4j = {
+
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable Neo4j Community Edition.
+      '';
+    };
+
+    allowUpgrade = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Allow upgrade of Neo4j database files from an older version.
+      '';
+    };
+
+    constrainLoadCsv = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Sets the root directory for file URLs used with the Cypher
+        <literal>LOAD CSV</literal> clause to be that defined by
+        <option>directories.imports</option>. It restricts
+        access to only those files within that directory and its
+        subdirectories.
+        </para>
+        <para>
+        Setting this option to <literal>false</literal> introduces
+        possible security problems.
+      '';
+    };
+
+    defaultListenAddress = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = ''
+        Default network interface to listen for incoming connections. To
+        listen for connections on all interfaces, use "0.0.0.0".
+        </para>
+        <para>
+        Specifies the default IP address and address part of connector
+        specific <option>listenAddress</option> options. To bind specific
+        connectors to a specific network interfaces, specify the entire
+        <option>listenAddress</option> option for that connector.
+      '';
+    };
+
+    extraServerConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Extra configuration for Neo4j Community server. Refer to the
+        <link xlink:href="https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/">complete reference</link>
+        of Neo4j configuration settings.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.neo4j;
+      defaultText = "pkgs.neo4j";
+      description = ''
+        Neo4j package to use.
+      '';
+    };
+
+    readOnly = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Only allow read operations from this Neo4j instance.
+      '';
+    };
+
+    workerCount = mkOption {
+      type = types.ints.between 0 44738;
+      default = 0;
+      description = ''
+        Number of Neo4j worker threads, where the default of
+        <literal>0</literal> indicates a worker count equal to the number of
+        available processors.
+      '';
+    };
+
+    bolt = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enable the BOLT connector for Neo4j. Setting this option to
+          <literal>false</literal> will stop Neo4j from listening for incoming
+          connections on the BOLT port (7687 by default).
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = ":7687";
+        description = ''
+          Neo4j listen address for BOLT traffic. The listen address is
+          expressed in the format <literal>&lt;ip-address&gt;:&lt;port-number&gt;</literal>.
+        '';
+      };
+
+      sslPolicy = mkOption {
+        type = types.str;
+        default = "legacy";
+        description = ''
+          Neo4j SSL policy for BOLT traffic.
+          </para>
+          <para>
+          The legacy policy is a special policy which is not defined in
+          the policy configuration section, but rather derives from
+          <option>directories.certificates</option> and
+          associated files (by default: <filename>neo4j.key</filename> and
+          <filename>neo4j.cert</filename>). Its use will be deprecated.
+          </para>
+          <para>
+          Note: This connector must be configured to support/require
+          SSL/TLS for the legacy policy to actually be utilized. See
+          <option>bolt.tlsLevel</option>.
+        '';
+      };
+
+      tlsLevel = mkOption {
+        type = types.enum [ "REQUIRED" "OPTIONAL" "DISABLED" ];
+        default = "OPTIONAL";
+        description = ''
+          SSL/TSL requirement level for BOLT traffic.
+        '';
+      };
+    };
+
+    directories = {
+      certificates = mkOption {
+        type = types.path;
+        default = "${cfg.directories.home}/certificates";
+        description = ''
+          Directory for storing certificates to be used by Neo4j for
+          TLS connections.
+          </para>
+          <para>
+          When setting this directory to something other than its default,
+          ensure the directory's existence, and that read/write permissions are
+          given to the Neo4j daemon user <literal>neo4j</literal>.
+          </para>
+          <para>
+          Note that changing this directory from its default will prevent
+          the directory structure required for each SSL policy from being
+          automatically generated. A policy's directory structure as defined by
+          its <option>baseDirectory</option>,<option>revokedDir</option> and
+          <option>trustedDir</option> must then be setup manually. The
+          existence of these directories is mandatory, as well as the presence
+          of the certificate file and the private key. Ensure the correct
+          permissions are set on these directories and files.
+        '';
+      };
+
+      data = mkOption {
+        type = types.path;
+        default = "${cfg.directories.home}/data";
+        description = ''
+          Path of the data directory. You must not configure more than one
+          Neo4j installation to use the same data directory.
+          </para>
+          <para>
+          When setting this directory to something other than its default,
+          ensure the directory's existence, and that read/write permissions are
+          given to the Neo4j daemon user <literal>neo4j</literal>.
+        '';
+      };
+
+      home = mkOption {
+        type = types.path;
+        default = "/var/lib/neo4j";
+        description = ''
+          Path of the Neo4j home directory. Other default directories are
+          subdirectories of this path. This directory will be created if
+          non-existent, and its ownership will be <command>chown</command> to
+          the Neo4j daemon user <literal>neo4j</literal>.
+        '';
+      };
+
+      imports = mkOption {
+        type = types.path;
+        default = "${cfg.directories.home}/import";
+        description = ''
+          The root directory for file URLs used with the Cypher
+          <literal>LOAD CSV</literal> clause. Only meaningful when
+          <option>constrainLoadCvs</option> is set to
+          <literal>true</literal>.
+          </para>
+          <para>
+          When setting this directory to something other than its default,
+          ensure the directory's existence, and that read permission is
+          given to the Neo4j daemon user <literal>neo4j</literal>.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.path;
+        default = "${cfg.directories.home}/plugins";
+        description = ''
+          Path of the database plugin directory. Compiled Java JAR files that
+          contain database procedures will be loaded if they are placed in
+          this directory.
+          </para>
+          <para>
+          When setting this directory to something other than its default,
+          ensure the directory's existence, and that read permission is
+          given to the Neo4j daemon user <literal>neo4j</literal>.
+        '';
+      };
+    };
+
+    http = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          The HTTP connector is required for Neo4j, and cannot be disabled.
+          Setting this option to <literal>false</literal> will force the HTTP
+          connector's <option>listenAddress</option> to the loopback
+          interface to prevent connection of remote clients. To prevent all
+          clients from connecting, block the HTTP port (7474 by default) by
+          firewall.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = ":7474";
+        description = ''
+          Neo4j listen address for HTTP traffic. The listen address is
+          expressed in the format <literal>&lt;ip-address&gt;:&lt;port-number&gt;</literal>.
+        '';
+      };
+    };
+
+    https = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enable the HTTPS connector for Neo4j. Setting this option to
+          <literal>false</literal> will stop Neo4j from listening for incoming
+          connections on the HTTPS port (7473 by default).
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = ":7473";
+        description = ''
+          Neo4j listen address for HTTPS traffic. The listen address is
+          expressed in the format <literal>&lt;ip-address&gt;:&lt;port-number&gt;</literal>.
+        '';
+      };
+
+      sslPolicy = mkOption {
+        type = types.str;
+        default = "legacy";
+        description = ''
+          Neo4j SSL policy for HTTPS traffic.
+          </para>
+          <para>
+          The legacy policy is a special policy which is not defined in the
+          policy configuration section, but rather derives from
+          <option>directories.certificates</option> and
+          associated files (by default: <filename>neo4j.key</filename> and
+          <filename>neo4j.cert</filename>). Its use will be deprecated.
+        '';
+      };
+    };
+
+    shell = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable a remote shell server which Neo4j Shell clients can log in to.
+          Only applicable to <command>neo4j-shell</command>.
+        '';
+      };
+    };
+
+    ssl.policies = mkOption {
+      type = with types; attrsOf (submodule ({ name, config, options, ... }: {
+        options = {
+
+          allowKeyGeneration = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Allows the generation of a private key and associated self-signed
+              certificate. Only performed when both objects cannot be found for
+              this policy. It is recommended to turn this off again after keys
+              have been generated.
+              </para>
+              <para>
+              The public certificate is required to be duplicated to the
+              directory holding trusted certificates as defined by the
+              <option>trustedDir</option> option.
+              </para>
+              <para>
+              Keys should in general be generated and distributed offline by a
+              trusted certificate authority and not by utilizing this mode.
+            '';
+          };
+
+          baseDirectory = mkOption {
+            type = types.path;
+            default = "${cfg.directories.certificates}/${name}";
+            description = ''
+              The mandatory base directory for cryptographic objects of this
+              policy. This path is only automatically generated when this
+              option as well as <option>directories.certificates</option> are
+              left at their default. Ensure read/write permissions are given
+              to the Neo4j daemon user <literal>neo4j</literal>.
+              </para>
+              <para>
+              It is also possible to override each individual
+              configuration with absolute paths. See the
+              <option>privateKey</option> and <option>publicCertificate</option>
+              policy options.
+            '';
+          };
+
+          ciphers = mkOption {
+            type = types.nullOr (types.listOf types.str);
+            default = null;
+            description = ''
+              Restrict the allowed ciphers of this policy to those defined
+              here. The default ciphers are those of the JVM platform.
+            '';
+          };
+
+          clientAuth = mkOption {
+            type = types.enum [ "NONE" "OPTIONAL" "REQUIRE" ];
+            default = "REQUIRE";
+            description = ''
+              The client authentication stance for this policy.
+            '';
+          };
+
+          privateKey = mkOption {
+            type = types.str;
+            default = "private.key";
+            description = ''
+              The name of private PKCS #8 key file for this policy to be found
+              in the <option>baseDirectory</option>, or the absolute path to
+              the key file. It is mandatory that a key can be found or generated.
+            '';
+          };
+
+          publicCertificate = mkOption {
+            type = types.str;
+            default = "public.crt";
+            description = ''
+              The name of public X.509 certificate (chain) file in PEM format
+              for this policy to be found in the <option>baseDirectory</option>,
+              or the absolute path to the certificate file. It is mandatory
+              that a certificate can be found or generated.
+              </para>
+              <para>
+              The public certificate is required to be duplicated to the
+              directory holding trusted certificates as defined by the
+              <option>trustedDir</option> option.
+            '';
+          };
+
+          revokedDir = mkOption {
+            type = types.path;
+            default = "${config.baseDirectory}/revoked";
+            description = ''
+              Path to directory of CRLs (Certificate Revocation Lists) in
+              PEM format. Must be an absolute path. The existence of this
+              directory is mandatory and will need to be created manually when:
+              setting this option to something other than its default; setting
+              either this policy's <option>baseDirectory</option> or
+              <option>directories.certificates</option> to something other than
+              their default. Ensure read/write permissions are given to the
+              Neo4j daemon user <literal>neo4j</literal>.
+            '';
+          };
+
+          tlsVersions = mkOption {
+            type = types.listOf types.str;
+            default = [ "TLSv1.2" ];
+            description = ''
+              Restrict the TLS protocol versions of this policy to those
+              defined here.
+            '';
+          };
+
+          trustAll = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Makes this policy trust all remote parties. Enabling this is not
+              recommended and the policy's trusted directory will be ignored.
+              Use of this mode is discouraged. It would offer encryption but
+              no security.
+            '';
+          };
+
+          trustedDir = mkOption {
+            type = types.path;
+            default = "${config.baseDirectory}/trusted";
+            description = ''
+              Path to directory of X.509 certificates in PEM format for
+              trusted parties. Must be an absolute path. The existence of this
+              directory is mandatory and will need to be created manually when:
+              setting this option to something other than its default; setting
+              either this policy's <option>baseDirectory</option> or
+              <option>directories.certificates</option> to something other than
+              their default. Ensure read/write permissions are given to the
+              Neo4j daemon user <literal>neo4j</literal>.
+              </para>
+              <para>
+              The public certificate as defined by
+              <option>publicCertificate</option> is required to be duplicated
+              to this directory.
+            '';
+          };
+
+          directoriesToCreate = mkOption {
+            type = types.listOf types.path;
+            internal = true;
+            readOnly = true;
+            description = ''
+              Directories of this policy that will be created automatically
+              when the certificates directory is left at its default value.
+              This includes all options of type path that are left at their
+              default value.
+            '';
+          };
+
+        };
+
+        config.directoriesToCreate = optionals
+          (certDirOpt.highestPrio >= 1500 && options.baseDirectory.highestPrio >= 1500)
+          (map (opt: opt.value) (filter isDefaultPathOption (attrValues options)));
+
+      }));
+      default = {};
+      description = ''
+        Defines the SSL policies for use with Neo4j connectors. Each attribute
+        of this set defines a policy, with the attribute name defining the name
+        of the policy and its namespace. Refer to the operations manual section
+        on Neo4j's
+        <link xlink:href="https://neo4j.com/docs/operations-manual/current/security/ssl-framework/">SSL Framework</link>
+        for further details.
+      '';
+    };
+
+    udc = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the Usage Data Collector which Neo4j uses to collect usage
+          data. Refer to the operations manual section on the
+          <link xlink:href="https://neo4j.com/docs/operations-manual/current/configuration/usage-data-collector/">Usage Data Collector</link>
+          for more information.
+        '';
+      };
+    };
+
+  };
+
+  ###### implementation
+
+  config =
+    let
+      # Assertion helpers
+      policyNameList = attrNames cfg.ssl.policies;
+      validPolicyNameList = [ "legacy" ] ++ policyNameList;
+      validPolicyNameString = concatStringsSep ", " validPolicyNameList;
+
+      # Capture various directories left at their default so they can be created.
+      defaultDirectoriesToCreate = map (opt: opt.value) (filter isDefaultPathOption (attrValues options.services.neo4j.directories));
+      policyDirectoriesToCreate = concatMap (pol: pol.directoriesToCreate) (attrValues cfg.ssl.policies);
+    in
+
+    mkIf cfg.enable {
+      assertions = [
+        { assertion = !elem "legacy" policyNameList;
+          message = "The policy 'legacy' is special to Neo4j, and its name is reserved."; }
+        { assertion = elem cfg.bolt.sslPolicy validPolicyNameList;
+          message = "Invalid policy assigned: `services.neo4j.bolt.sslPolicy = \"${cfg.bolt.sslPolicy}\"`, defined policies are: ${validPolicyNameString}"; }
+        { assertion = elem cfg.https.sslPolicy validPolicyNameList;
+          message = "Invalid policy assigned: `services.neo4j.https.sslPolicy = \"${cfg.https.sslPolicy}\"`, defined policies are: ${validPolicyNameString}"; }
+      ];
+
+      systemd.services.neo4j = {
+        description = "Neo4j Daemon";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        environment = {
+          NEO4J_HOME = "${cfg.package}/share/neo4j";
+          NEO4J_CONF = "${cfg.directories.home}/conf";
+        };
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/neo4j console";
+          User = "neo4j";
+          PermissionsStartOnly = true;
+          LimitNOFILE = 40000;
+        };
+
+        preStart = ''
+          # Directories Setup
+          #   Always ensure home exists with nested conf, logs directories.
+          mkdir -m 0700 -p ${cfg.directories.home}/{conf,logs}
+
+          #   Create other sub-directories and policy directories that have been left at their default.
+          ${concatMapStringsSep "\n" (
+            dir: ''
+              mkdir -m 0700 -p ${dir}
+          '') (defaultDirectoriesToCreate ++ policyDirectoriesToCreate)}
+
+          # Place the configuration where Neo4j can find it.
+          ln -fs ${serverConfig} ${cfg.directories.home}/conf/neo4j.conf
+
+          # Ensure neo4j user ownership
+          chown -R neo4j ${cfg.directories.home}
+        '';
+      };
+
+      environment.systemPackages = [ cfg.package ];
+
+      users.users.neo4j = {
+        uid = config.ids.uids.neo4j;
+        description = "Neo4j daemon user";
+        home = cfg.directories.home;
+      };
+    };
+
+  meta = {
+    maintainers = with lib.maintainers; [ patternspandemic ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/openldap.nix b/nixpkgs/nixos/modules/services/databases/openldap.nix
new file mode 100644
index 000000000000..8c2851c37ac2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/openldap.nix
@@ -0,0 +1,288 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.openldap;
+  openldap = pkgs.openldap;
+
+  dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents;
+  configFile = pkgs.writeText "slapd.conf" ((optionalString cfg.defaultSchemas ''
+    include ${pkgs.openldap.out}/etc/schema/core.schema
+    include ${pkgs.openldap.out}/etc/schema/cosine.schema
+    include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
+    include ${pkgs.openldap.out}/etc/schema/nis.schema
+  '') + ''
+    ${cfg.extraConfig}
+    database ${cfg.database}
+    suffix ${cfg.suffix}
+    rootdn ${cfg.rootdn}
+    ${if (cfg.rootpw != null) then ''
+      rootpw ${cfg.rootpw}
+    '' else ''
+      include ${cfg.rootpwFile}
+    ''}
+    directory ${cfg.dataDir}
+    ${cfg.extraDatabaseConfig}
+  '');
+  configOpts = if cfg.configDir == null then "-f ${configFile}"
+               else "-F ${cfg.configDir}";
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.openldap = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "
+          Whether to enable the ldap server.
+        ";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "openldap";
+        description = "User account under which slapd runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "openldap";
+        description = "Group account under which slapd runs.";
+      };
+
+      urlList = mkOption {
+        type = types.listOf types.str;
+        default = [ "ldap:///" ];
+        description = "URL list slapd should listen on.";
+        example = [ "ldaps:///" ];
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/db/openldap";
+        description = "The database directory.";
+      };
+
+      defaultSchemas = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Include the default schemas core, cosine, inetorgperson and nis.
+          This setting will be ignored if configDir is set.
+        '';
+      };
+
+      database = mkOption {
+        type = types.str;
+        default = "mdb";
+        description = ''
+          Database type to use for the LDAP.
+          This setting will be ignored if configDir is set.
+        '';
+      };
+
+      suffix = mkOption {
+        type = types.str;
+        example = "dc=example,dc=org";
+        description = ''
+          Specify the DN suffix of queries that will be passed to this backend
+          database.
+          This setting will be ignored if configDir is set.
+        '';
+      };
+
+      rootdn = mkOption {
+        type = types.str;
+        example = "cn=admin,dc=example,dc=org";
+        description = ''
+          Specify the distinguished name that is not subject to access control
+          or administrative limit restrictions for operations on this database.
+          This setting will be ignored if configDir is set.
+        '';
+      };
+
+      rootpw = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Password for the root user.
+          This setting will be ignored if configDir is set.
+          Using this option will store the root password in plain text in the
+          world-readable nix store. To avoid this the <literal>rootpwFile</literal> can be used.
+        '';
+      };
+
+      rootpwFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Password file for the root user.
+          The file should contain the string <literal>rootpw</literal> followed by the password.
+          e.g.: <literal>rootpw mysecurepassword</literal>
+        '';
+      };
+
+      logLevel = mkOption {
+        type = types.str;
+        default = "0";
+        example = "acl trace";
+        description = "The log level selector of slapd.";
+      };
+
+      configDir = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "Use this optional config directory instead of using slapd.conf";
+        example = "/var/db/slapd.d";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          slapd.conf configuration
+        ";
+        example = literalExample ''
+            '''
+            include ${pkgs.openldap.out}/etc/schema/core.schema
+            include ${pkgs.openldap.out}/etc/schema/cosine.schema
+            include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
+            include ${pkgs.openldap.out}/etc/schema/nis.schema
+
+            database bdb
+            suffix dc=example,dc=org
+            rootdn cn=admin,dc=example,dc=org
+            # NOTE: change after first start
+            rootpw secret
+            directory /var/db/openldap
+            '''
+          '';
+      };
+
+      declarativeContents = mkOption {
+        type = with types; nullOr lines;
+        default = null;
+        description = ''
+          Declarative contents for the LDAP database, in LDIF format.
+
+          Note a few facts when using it. First, the database
+          <emphasis>must</emphasis> be stored in the directory defined by
+          <code>dataDir</code>. Second, all <code>dataDir</code> will be erased
+          when starting the LDAP server. Third, modifications to the database
+          are not prevented, they are just dropped on the next reboot of the
+          server. Finally, performance-wise the database and indexes are rebuilt
+          on each server startup, so this will slow down server startup,
+          especially with large databases.
+        '';
+        example = ''
+          dn: dc=example,dc=org
+          objectClass: domain
+          dc: example
+
+          dn: ou=users,dc=example,dc=org
+          objectClass = organizationalUnit
+          ou: users
+
+          # ...
+        '';
+      };
+
+      extraDatabaseConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          slapd.conf configuration after the database option.
+          This setting will be ignored if configDir is set.
+        '';
+        example = ''
+          # Indices to maintain for this directory
+          # unique id so equality match only
+          index uid eq
+          # allows general searching on commonname, givenname and email
+          index cn,gn,mail eq,sub
+          # allows multiple variants on surname searching
+          index sn eq,sub
+          # sub above includes subintial,subany,subfinal
+          # optimise department searches
+          index ou eq
+          # if searches will include objectClass uncomment following
+          # index objectClass eq
+          # shows use of default index parameter
+          index default eq,sub
+          # indices missing - uses default eq,sub
+          index telephonenumber
+
+          # other database parameters
+          # read more in slapd.conf reference section
+          cachesize 10000
+          checkpoint 128 15
+        '';
+      };
+
+    };
+
+  };
+
+  meta = {
+    maintainers = lib.maintainers.mic92;
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.configDir != null || cfg.rootpwFile != null || cfg.rootpw != null;
+        message = "services.openldap: Unless configDir is set, either rootpw or rootpwFile must be set";
+      }
+    ];
+
+    environment.systemPackages = [ openldap ];
+
+    systemd.services.openldap = {
+      description = "LDAP server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      preStart = ''
+        mkdir -p /run/slapd
+        chown -R "${cfg.user}:${cfg.group}" /run/slapd
+        ${optionalString (cfg.declarativeContents != null) ''
+          rm -Rf "${cfg.dataDir}"
+        ''}
+        mkdir -p "${cfg.dataDir}"
+        ${optionalString (cfg.declarativeContents != null) ''
+          ${openldap.out}/bin/slapadd ${configOpts} -l ${dataFile}
+        ''}
+        chown -R "${cfg.user}:${cfg.group}" "${cfg.dataDir}"
+
+        ${openldap}/bin/slaptest ${configOpts}
+      '';
+      serviceConfig.ExecStart =
+        "${openldap.out}/libexec/slapd -d '${cfg.logLevel}' " +
+          "-u '${cfg.user}' -g '${cfg.group}' " +
+          "-h '${concatStringsSep " " cfg.urlList}' " +
+          "${configOpts}";
+    };
+
+    users.users.openldap =
+      { name = cfg.user;
+        group = cfg.group;
+        uid = config.ids.uids.openldap;
+      };
+
+    users.groups.openldap =
+      { name = cfg.group;
+        gid = config.ids.gids.openldap;
+      };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/opentsdb.nix b/nixpkgs/nixos/modules/services/databases/opentsdb.nix
new file mode 100644
index 000000000000..c4bd71f3d60e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/opentsdb.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.opentsdb;
+
+  configFile = pkgs.writeText "opentsdb.conf" cfg.config;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.opentsdb = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run OpenTSDB.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.opentsdb;
+        defaultText = "pkgs.opentsdb";
+        example = literalExample "pkgs.opentsdb";
+        description = ''
+          OpenTSDB package to use.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "opentsdb";
+        description = ''
+          User account under which OpenTSDB runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "opentsdb";
+        description = ''
+          Group account under which OpenTSDB runs.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 4242;
+        description = ''
+          Which port OpenTSDB listens on.
+        '';
+      };
+
+      config = mkOption {
+        type = types.lines;
+        default = ''
+          tsd.core.auto_create_metrics = true
+          tsd.http.request.enable_chunked  = true
+        '';
+        description = ''
+          The contents of OpenTSDB's configuration file
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.opentsdb.enable {
+
+    systemd.services.opentsdb = {
+      description = "OpenTSDB Server";
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "hbase.service" ];
+
+      environment.JAVA_HOME = "${pkgs.jre}";
+      path = [ pkgs.gnuplot ];
+
+      preStart =
+        ''
+        COMPRESSION=NONE HBASE_HOME=${config.services.hbase.package} ${cfg.package}/share/opentsdb/tools/create_table.sh
+        '';
+
+      serviceConfig = {
+        PermissionsStartOnly = true;
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/tsdb tsd --staticroot=${cfg.package}/share/opentsdb/static --cachedir=/tmp/opentsdb --port=${toString cfg.port} --config=${configFile}";
+      };
+    };
+
+    users.users.opentsdb = {
+      description = "OpenTSDB Server user";
+      group = "opentsdb";
+      uid = config.ids.uids.opentsdb;
+    };
+
+    users.groups.opentsdb.gid = config.ids.gids.opentsdb;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/pgmanage.nix b/nixpkgs/nixos/modules/services/databases/pgmanage.nix
new file mode 100644
index 000000000000..0f8634dab319
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/pgmanage.nix
@@ -0,0 +1,206 @@
+{ lib, pkgs, config, ... } :
+
+with lib;
+
+let
+  cfg = config.services.pgmanage;
+
+  confFile = pkgs.writeTextFile {
+    name = "pgmanage.conf";
+    text =  ''
+      connection_file = ${pgmanageConnectionsFile}
+
+      allow_custom_connections = ${builtins.toJSON cfg.allowCustomConnections}
+
+      pgmanage_port = ${toString cfg.port}
+
+      super_only = ${builtins.toJSON cfg.superOnly}
+
+      ${optionalString (cfg.loginGroup != null) "login_group = ${cfg.loginGroup}"}
+
+      login_timeout = ${toString cfg.loginTimeout}
+
+      web_root = ${cfg.package}/etc/pgmanage/web_root
+
+      sql_root = ${cfg.sqlRoot}
+
+      ${optionalString (cfg.tls != null) ''
+      tls_cert = ${cfg.tls.cert}
+      tls_key = ${cfg.tls.key}
+      ''}
+
+      log_level = ${cfg.logLevel}
+    '';
+  };
+
+  pgmanageConnectionsFile = pkgs.writeTextFile {
+    name = "pgmanage-connections.conf";
+    text = concatStringsSep "\n"
+      (mapAttrsToList (name : conn : "${name}: ${conn}") cfg.connections);
+  };
+
+  pgmanage = "pgmanage";
+
+in {
+
+  options.services.pgmanage = {
+    enable = mkEnableOption "PostgreSQL Administration for the web";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.pgmanage;
+      defaultText = "pkgs.pgmanage";
+      description = ''
+        The pgmanage package to use.
+      '';
+    };
+
+    connections = mkOption {
+      type = types.attrsOf types.str;
+      default = {};
+      example = {
+        nuc-server  = "hostaddr=192.168.0.100 port=5432 dbname=postgres";
+        mini-server = "hostaddr=127.0.0.1 port=5432 dbname=postgres sslmode=require";
+      };
+      description = ''
+        pgmanage requires at least one PostgreSQL server be defined.
+        </para><para>
+        Detailed information about PostgreSQL connection strings is available at:
+        <link xlink:href="http://www.postgresql.org/docs/current/static/libpq-connect.html"/>
+        </para><para>
+        Note that you should not specify your user name or password. That
+        information will be entered on the login screen. If you specify a
+        username or password, it will be removed by pgmanage before attempting to
+        connect to a database.
+      '';
+    };
+
+    allowCustomConnections = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        This tells pgmanage whether or not to allow anyone to use a custom
+        connection from the login screen.
+      '';
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 8080;
+      description = ''
+        This tells pgmanage what port to listen on for browser requests.
+      '';
+    };
+
+    localOnly = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        This tells pgmanage whether or not to set the listening socket to local
+        addresses only.
+      '';
+    };
+
+    superOnly = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        This tells pgmanage whether or not to only allow super users to
+        login. The recommended value is true and will restrict users who are not
+        super users from logging in to any PostgreSQL instance through
+        pgmanage. Note that a connection will be made to PostgreSQL in order to
+        test if the user is a superuser.
+      '';
+    };
+
+    loginGroup = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        This tells pgmanage to only allow users in a certain PostgreSQL group to
+        login to pgmanage. Note that a connection will be made to PostgreSQL in
+        order to test if the user is a member of the login group.
+      '';
+    };
+
+    loginTimeout = mkOption {
+      type = types.int;
+      default = 3600;
+      description = ''
+        Number of seconds of inactivity before user is automatically logged
+        out.
+      '';
+    };
+
+    sqlRoot = mkOption {
+      type = types.str;
+      default = "/var/lib/pgmanage";
+      description = ''
+        This tells pgmanage where to put the SQL file history. All tabs are saved
+        to this location so that if you get disconnected from pgmanage you
+        don't lose your work.
+      '';
+    };
+
+    tls = mkOption {
+      type = types.nullOr (types.submodule {
+        options = {
+          cert = mkOption {
+            type = types.str;
+            description = "TLS certificate";
+          };
+          key = mkOption {
+            type = types.str;
+            description = "TLS key";
+          };
+        };
+      });
+      default = null;
+      description = ''
+        These options tell pgmanage where the TLS Certificate and Key files
+        reside. If you use these options then you'll only be able to access
+        pgmanage through a secure TLS connection. These options are only
+        necessary if you wish to connect directly to pgmanage using a secure TLS
+        connection. As an alternative, you can set up pgmanage in a reverse proxy
+        configuration. This allows your web server to terminate the secure
+        connection and pass on the request to pgmanage. You can find help to set
+        up this configuration in:
+        <link xlink:href="https://github.com/pgManage/pgManage/blob/master/INSTALL_NGINX.md"/>
+      '';
+    };
+
+    logLevel = mkOption {
+      type = types.enum ["error" "warn" "notice" "info"];
+      default = "error";
+      description = ''
+        Verbosity of logs
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.pgmanage = {
+      description = "pgmanage - PostgreSQL Administration for the web";
+      wants    = [ "postgresql.service" ];
+      after    = [ "postgresql.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User         = pgmanage;
+        Group        = pgmanage;
+        ExecStart    = "${pkgs.pgmanage}/sbin/pgmanage -c ${confFile}" +
+                       optionalString cfg.localOnly " --local-only=true";
+      };
+    };
+    users = {
+      users.${pgmanage} = {
+        name  = pgmanage;
+        group = pgmanage;
+        home  = cfg.sqlRoot;
+        createHome = true;
+      };
+      groups.${pgmanage} = {
+        name = pgmanage;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.nix b/nixpkgs/nixos/modules/services/databases/postgresql.nix
new file mode 100644
index 000000000000..579b6a4d9c67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/postgresql.nix
@@ -0,0 +1,379 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.postgresql;
+
+  postgresql =
+    if cfg.extraPlugins == []
+      then cfg.package
+      else cfg.package.withPackages (_: cfg.extraPlugins);
+
+  # The main PostgreSQL configuration file.
+  configFile = pkgs.writeText "postgresql.conf"
+    ''
+      hba_file = '${pkgs.writeText "pg_hba.conf" cfg.authentication}'
+      ident_file = '${pkgs.writeText "pg_ident.conf" cfg.identMap}'
+      log_destination = 'stderr'
+      log_line_prefix = '${cfg.logLinePrefix}'
+      listen_addresses = '${if cfg.enableTCPIP then "*" else "localhost"}'
+      port = ${toString cfg.port}
+      ${cfg.extraConfig}
+    ''; 
+
+  groupAccessAvailable = versionAtLeast postgresql.version "11.0";
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.postgresql = {
+
+      enable = mkEnableOption "PostgreSQL Server";
+
+      package = mkOption {
+        type = types.package;
+        example = literalExample "pkgs.postgresql_11";
+        description = ''
+          PostgreSQL package to use.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 5432;
+        description = ''
+          The port on which PostgreSQL listens.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        example = "/var/lib/postgresql/11";
+        description = ''
+          Data directory for PostgreSQL.
+        '';
+      };
+
+      authentication = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Defines how users authenticate themselves to the server. By
+          default, "trust" access to local users will always be granted
+          along with any other custom options. If you do not want this,
+          set this option using "lib.mkForce" to override this
+          behaviour.
+        '';
+      };
+
+      identMap = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Defines the mapping from system users to database users.
+
+          The general form is:
+
+          map-name system-username database-username
+        '';
+      };
+
+      initdbArgs = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = [ "--data-checksums" "--allow-group-access" ];
+        description = ''
+          Additional arguments passed to <literal>initdb</literal> during data dir
+          initialisation.
+        '';
+      };
+
+      initialScript = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          A file containing SQL statements to execute on first startup.
+        '';
+      };
+
+      ensureDatabases = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Ensures that the specified databases exist.
+          This option will never delete existing databases, especially not when the value of this
+          option is changed. This means that databases created once through this option or
+          otherwise have to be removed manually.
+        '';
+        example = [
+          "gitea"
+          "nextcloud"
+        ];
+      };
+
+      ensureUsers = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = types.str;
+              description = ''
+                Name of the user to ensure.
+              '';
+            };
+            ensurePermissions = mkOption {
+              type = types.attrsOf types.str;
+              default = {};
+              description = ''
+                Permissions to ensure for the user, specified as an attribute set.
+                The attribute names specify the database and tables to grant the permissions for.
+                The attribute values specify the permissions to grant. You may specify one or
+                multiple comma-separated SQL privileges here.
+
+                For more information on how to specify the target
+                and on which privileges exist, see the
+                <link xlink:href="https://www.postgresql.org/docs/current/sql-grant.html">GRANT syntax</link>.
+                The attributes are used as <code>GRANT ''${attrName} ON ''${attrValue}</code>.
+              '';
+              example = literalExample ''
+                {
+                  "DATABASE nextcloud" = "ALL PRIVILEGES";
+                  "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
+                }
+              '';
+            };
+          };
+        });
+        default = [];
+        description = ''
+          Ensures that the specified users exist and have at least the ensured permissions.
+          The PostgreSQL users will be identified using peer authentication. This authenticates the Unix user with the
+          same name only, and that without the need for a password.
+          This option will never delete existing users or remove permissions, especially not when the value of this
+          option is changed. This means that users created and permissions assigned once through this option or
+          otherwise have to be removed manually.
+        '';
+        example = literalExample ''
+          [
+            {
+              name = "nextcloud";
+              ensurePermissions = {
+                "DATABASE nextcloud" = "ALL PRIVILEGES";
+              };
+            }
+            {
+              name = "superuser";
+              ensurePermissions = {
+                "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES";
+              };
+            }
+          ]
+        '';
+      };
+
+      enableTCPIP = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether PostgreSQL should listen on all network interfaces.
+          If disabled, the database can only be accessed via its Unix
+          domain socket or via TCP connections to localhost.
+        '';
+      };
+
+      logLinePrefix = mkOption {
+        type = types.str;
+        default = "[%p] ";
+        example = "%m [%p] ";
+        description = ''
+          A printf-style string that is output at the beginning of each log line.
+          Upstream default is <literal>'%m [%p] '</literal>, i.e. it includes the timestamp. We do
+          not include the timestamp, because journal has it anyway.
+        '';
+      };
+
+      extraPlugins = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        example = literalExample "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]";
+        description = ''
+          List of PostgreSQL plugins. PostgreSQL version for each plugin should
+          match version for <literal>services.postgresql.package</literal> value.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional text to be appended to <filename>postgresql.conf</filename>.";
+      };
+
+      recoveryConfig = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = ''
+          Contents of the <filename>recovery.conf</filename> file.
+        '';
+      };
+      superUser = mkOption {
+        type = types.str;
+        default= if versionAtLeast config.system.stateVersion "17.09" then "postgres" else "root";
+        internal = true;
+        description = ''
+          NixOS traditionally used 'root' as superuser, most other distros use 'postgres'.
+          From 17.09 we also try to follow this standard. Internal since changing this value
+          would lead to breakage while setting up databases.
+        '';
+        };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.postgresql.package =
+      # Note: when changing the default, make it conditional on
+      # ‘system.stateVersion’ to maintain compatibility with existing
+      # systems!
+      mkDefault (if versionAtLeast config.system.stateVersion "20.03" then pkgs.postgresql_11
+            else if versionAtLeast config.system.stateVersion "17.09" then pkgs.postgresql_9_6
+            else if versionAtLeast config.system.stateVersion "16.03" then pkgs.postgresql_9_5
+            else throw "postgresql_9_4 was removed, please upgrade your postgresql version.");
+
+    services.postgresql.dataDir =
+      mkDefault (if versionAtLeast config.system.stateVersion "17.09"
+                  then "/var/lib/postgresql/${cfg.package.psqlSchema}"
+                  else "/var/db/postgresql");
+
+    services.postgresql.authentication = mkAfter
+      ''
+        # Generated file; do not edit!
+        local all all              peer
+        host  all all 127.0.0.1/32 md5
+        host  all all ::1/128      md5
+      '';
+
+    users.users.postgres =
+      { name = "postgres";
+        uid = config.ids.uids.postgres;
+        group = "postgres";
+        description = "PostgreSQL server user";
+        home = "${cfg.dataDir}";
+        useDefaultShell = true;
+      };
+
+    users.groups.postgres.gid = config.ids.gids.postgres;
+
+    environment.systemPackages = [ postgresql ];
+
+    environment.pathsToLink = [
+     "/share/postgresql"
+    ];
+
+    systemd.services.postgresql =
+      { description = "PostgreSQL Server";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        environment.PGDATA = cfg.dataDir;
+
+        path = [ postgresql ];
+
+        preStart =
+          ''
+            # Create data directory.
+            if ! test -e ${cfg.dataDir}/PG_VERSION; then
+              mkdir -m 0700 -p ${cfg.dataDir}
+              rm -f ${cfg.dataDir}/*.conf
+              chown -R postgres:postgres ${cfg.dataDir}
+            fi
+          ''; # */
+
+        script =
+          ''
+            # Initialise the database.
+            if ! test -e ${cfg.dataDir}/PG_VERSION; then
+              initdb -U ${cfg.superUser} ${concatStringsSep " " cfg.initdbArgs}
+              # See postStart!
+              touch "${cfg.dataDir}/.first_startup"
+            fi
+            ln -sfn "${configFile}" "${cfg.dataDir}/postgresql.conf"
+            ${optionalString (cfg.recoveryConfig != null) ''
+              ln -sfn "${pkgs.writeText "recovery.conf" cfg.recoveryConfig}" \
+                "${cfg.dataDir}/recovery.conf"
+            ''}
+            ${optionalString (!groupAccessAvailable) ''
+              # postgresql pre 11.0 doesn't start if state directory mode is group accessible
+              chmod 0700 "${cfg.dataDir}"
+            ''}
+
+            exec postgres
+          '';
+
+        serviceConfig =
+          { ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+            User = "postgres";
+            Group = "postgres";
+            PermissionsStartOnly = true;
+            RuntimeDirectory = "postgresql";
+            Type = if versionAtLeast cfg.package.version "9.6"
+                   then "notify"
+                   else "simple";
+
+            # Shut down Postgres using SIGINT ("Fast Shutdown mode").  See
+            # http://www.postgresql.org/docs/current/static/server-shutdown.html
+            KillSignal = "SIGINT";
+            KillMode = "mixed";
+
+            # Give Postgres a decent amount of time to clean up after
+            # receiving systemd's SIGINT.
+            TimeoutSec = 120;
+          };
+
+        # Wait for PostgreSQL to be ready to accept connections.
+        postStart =
+          ''
+            PSQL="${pkgs.utillinux}/bin/runuser -u ${cfg.superUser} -- psql --port=${toString cfg.port}"
+
+            while ! $PSQL -d postgres -c "" 2> /dev/null; do
+                if ! kill -0 "$MAINPID"; then exit 1; fi
+                sleep 0.1
+            done
+
+            if test -e "${cfg.dataDir}/.first_startup"; then
+              ${optionalString (cfg.initialScript != null) ''
+                $PSQL -f "${cfg.initialScript}" -d postgres
+              ''}
+              rm -f "${cfg.dataDir}/.first_startup"
+            fi
+          '' + optionalString (cfg.ensureDatabases != []) ''
+            ${concatMapStrings (database: ''
+              $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${database}"'
+            '') cfg.ensureDatabases}
+          '' + ''
+            ${concatMapStrings (user: ''
+              $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc 'CREATE USER "${user.name}"'
+              ${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
+                $PSQL -tAc 'GRANT ${permission} ON ${database} TO "${user.name}"'
+              '') user.ensurePermissions)}
+            '') cfg.ensureUsers}
+          '';
+
+        unitConfig.RequiresMountsFor = "${cfg.dataDir}";
+      };
+
+  };
+
+  meta.doc = ./postgresql.xml;
+  meta.maintainers = with lib.maintainers; [ thoughtpolice danbst ];
+}
diff --git a/nixpkgs/nixos/modules/services/databases/postgresql.xml b/nixpkgs/nixos/modules/services/databases/postgresql.xml
new file mode 100644
index 000000000000..07af4c937f03
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/postgresql.xml
@@ -0,0 +1,192 @@
+<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-postgresql">
+ <title>PostgreSQL</title>
+<!-- FIXME: render nicely -->
+<!-- FIXME: source can be added automatically -->
+ <para>
+  <emphasis>Source:</emphasis> <filename>modules/services/databases/postgresql.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis> <link xlink:href="http://www.postgresql.org/docs/"/>
+ </para>
+<!-- FIXME: more stuff, like maintainer? -->
+ <para>
+  PostgreSQL is an advanced, free relational database.
+<!-- MORE -->
+ </para>
+ <section xml:id="module-services-postgres-configuring">
+  <title>Configuring</title>
+
+  <para>
+   To enable PostgreSQL, add the following to your <filename>configuration.nix</filename>:
+<programlisting>
+<xref linkend="opt-services.postgresql.enable"/> = true;
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11;
+</programlisting>
+   Note that you are required to specify the desired version of PostgreSQL (e.g. <literal>pkgs.postgresql_11</literal>). Since upgrading your PostgreSQL version requires a database dump and reload (see below), NixOS cannot provide a default value for <xref linkend="opt-services.postgresql.package"/> such as the most recent release of PostgreSQL.
+  </para>
+
+<!--
+<para>After running <command>nixos-rebuild</command>, you can verify
+whether PostgreSQL works by running <command>psql</command>:
+
+<screen>
+<prompt>$ </prompt>psql
+psql (9.2.9)
+Type "help" for help.
+
+<prompt>alice=></prompt>
+</screen>
+-->
+
+  <para>
+   By default, PostgreSQL stores its databases in <filename>/var/lib/postgresql/$psqlSchema</filename>. You can override this using <xref linkend="opt-services.postgresql.dataDir"/>, e.g.
+<programlisting>
+<xref linkend="opt-services.postgresql.dataDir"/> = "/data/postgresql";
+</programlisting>
+  </para>
+ </section>
+ <section xml:id="module-services-postgres-upgrading">
+  <title>Upgrading</title>
+
+  <para>
+   Major PostgreSQL upgrade requires PostgreSQL downtime and a few imperative steps to be called. To simplify this process, use the following NixOS module:
+<programlisting>
+  containers.temp-pg.config.services.postgresql = {
+    enable = true;
+    package = pkgs.postgresql_12;
+    ## set a custom new dataDir
+    # dataDir = "/some/data/dir";
+  };
+  environment.systemPackages =
+    let newpg = config.containers.temp-pg.config.services.postgresql;
+    in [
+      (pkgs.writeScriptBin "upgrade-pg-cluster" ''
+        set -x
+        export OLDDATA="${config.services.postgresql.dataDir}"
+        export NEWDATA="${newpg.dataDir}"
+        export OLDBIN="${config.services.postgresql.package}/bin"
+        export NEWBIN="${newpg.package}/bin"
+
+        install -d -m 0700 -o postgres -g postgres "$NEWDATA"
+        cd "$NEWDATA"
+        sudo -u postgres $NEWBIN/initdb -D "$NEWDATA"
+
+        systemctl stop postgresql    # old one
+
+        sudo -u postgres $NEWBIN/pg_upgrade \
+          --old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \
+          --old-bindir $OLDBIN --new-bindir $NEWBIN \
+          "$@"
+      '')
+    ];
+</programlisting>
+  </para>
+
+  <para>
+   The upgrade process is:
+  </para>
+
+  <orderedlist>
+   <listitem>
+    <para>
+     Rebuild nixos configuration with the configuration above added to your <filename>configuration.nix</filename>. Alternatively, add that into separate file and reference it in <literal>imports</literal> list.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Login as root (<literal>sudo su -</literal>)
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Run <literal>upgrade-pg-cluster</literal>. It will stop old postgresql, initialize new one and migrate old one to new one. You may supply arguments like <literal>--jobs 4</literal> and <literal>--link</literal> to speedup migration process. See <link xlink:href="https://www.postgresql.org/docs/current/pgupgrade.html" /> for details.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Change postgresql package in NixOS configuration to the one you were upgrading to, and change <literal>dataDir</literal> to the one you have migrated to. Rebuild NixOS. This should start new postgres using upgraded data directory.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     After upgrade you may want to <literal>ANALYZE</literal> new db.
+    </para>
+   </listitem>
+  </orderedlist>
+ </section>
+ <section xml:id="module-services-postgres-options">
+  <title>Options</title>
+
+  <para>
+   A complete list of options for the PostgreSQL module may be found <link linkend="opt-services.postgresql.enable">here</link>.
+  </para>
+ </section>
+ <section xml:id="module-services-postgres-plugins">
+  <title>Plugins</title>
+
+  <para>
+   Plugins collection for each PostgreSQL version can be accessed with <literal>.pkgs</literal>. For example, for <literal>pkgs.postgresql_11</literal> package, its plugin collection is accessed by <literal>pkgs.postgresql_11.pkgs</literal>:
+<screen>
+<prompt>$ </prompt>nix repl '&lt;nixpkgs&gt;'
+
+Loading '&lt;nixpkgs&gt;'...
+Added 10574 variables.
+
+<prompt>nix-repl&gt; </prompt>postgresql_11.pkgs.&lt;TAB&gt;&lt;TAB&gt;
+postgresql_11.pkgs.cstore_fdw        postgresql_11.pkgs.pg_repack
+postgresql_11.pkgs.pg_auto_failover  postgresql_11.pkgs.pg_safeupdate
+postgresql_11.pkgs.pg_bigm           postgresql_11.pkgs.pg_similarity
+postgresql_11.pkgs.pg_cron           postgresql_11.pkgs.pg_topn
+postgresql_11.pkgs.pg_hll            postgresql_11.pkgs.pgjwt
+postgresql_11.pkgs.pg_partman        postgresql_11.pkgs.pgroonga
+...
+</screen>
+  </para>
+
+  <para>
+   To add plugins via NixOS configuration, set <literal>services.postgresql.extraPlugins</literal>:
+<programlisting>
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_11;
+<xref linkend="opt-services.postgresql.extraPlugins"/> = with pkgs.postgresql_11.pkgs; [
+  pg_repack
+  postgis
+];
+</programlisting>
+  </para>
+
+  <para>
+   You can build custom PostgreSQL-with-plugins (to be used outside of NixOS) using function <literal>.withPackages</literal>. For example, creating a custom PostgreSQL package in an overlay can look like:
+<programlisting>
+self: super: {
+  postgresql_custom = self.postgresql_11.withPackages (ps: [
+    ps.pg_repack
+    ps.postgis
+  ]);
+}
+</programlisting>
+  </para>
+
+  <para>
+   Here's a recipe on how to override a particular plugin through an overlay:
+<programlisting>
+self: super: {
+  postgresql_11 = super.postgresql_11.override { this = self.postgresql_11; } // {
+    pkgs = super.postgresql_11.pkgs // {
+      pg_repack = super.postgresql_11.pkgs.pg_repack.overrideAttrs (_: {
+        name = "pg_repack-v20181024";
+        src = self.fetchzip {
+          url = "https://github.com/reorg/pg_repack/archive/923fa2f3c709a506e111cc963034bf2fd127aa00.tar.gz";
+          sha256 = "17k6hq9xaax87yz79j773qyigm4fwk8z4zh5cyp6z0sxnwfqxxw5";
+        };
+      });
+    };
+  };
+}
+</programlisting>
+  </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/databases/redis.nix b/nixpkgs/nixos/modules/services/databases/redis.nix
new file mode 100644
index 000000000000..f1777854e141
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/redis.nix
@@ -0,0 +1,248 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.redis;
+  redisBool = b: if b then "yes" else "no";
+  condOption = name: value: if value != null then "${name} ${toString value}" else "";
+
+  redisConfig = pkgs.writeText "redis.conf" ''
+    port ${toString cfg.port}
+    ${condOption "bind" cfg.bind}
+    ${condOption "unixsocket" cfg.unixSocket}
+    daemonize no
+    supervised systemd
+    loglevel ${cfg.logLevel}
+    logfile ${cfg.logfile}
+    syslog-enabled ${redisBool cfg.syslog}
+    databases ${toString cfg.databases}
+    ${concatMapStrings (d: "save ${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}\n") cfg.save}
+    dbfilename dump.rdb
+    dir /var/lib/redis
+    ${if cfg.slaveOf != null then "slaveof ${cfg.slaveOf.ip} ${toString cfg.slaveOf.port}" else ""}
+    ${condOption "masterauth" cfg.masterAuth}
+    ${condOption "requirepass" cfg.requirePass}
+    appendOnly ${redisBool cfg.appendOnly}
+    appendfsync ${cfg.appendFsync}
+    slowlog-log-slower-than ${toString cfg.slowLogLogSlowerThan}
+    slowlog-max-len ${toString cfg.slowLogMaxLen}
+    ${cfg.extraConfig}
+  '';
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "redis" "user" ] "The redis module now is hardcoded to the redis user.")
+    (mkRemovedOptionModule [ "services" "redis" "dbpath" ] "The redis module now uses /var/lib/redis as data directory.")
+    (mkRemovedOptionModule [ "services" "redis" "dbFilename" ] "The redis module now uses /var/lib/redis/dump.rdb as database dump location.")
+    (mkRemovedOptionModule [ "services" "redis" "appendOnlyFilename" ] "This option was never used.")
+    (mkRemovedOptionModule [ "services" "redis" "pidFile" ] "This option was removed.")
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.redis = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Redis server. Note that the NixOS module for
+          Redis disables kernel support for Transparent Huge Pages (THP),
+          because this features causes major performance problems for Redis,
+          e.g. (https://redis.io/topics/latency).
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.redis;
+        defaultText = "pkgs.redis";
+        description = "Which Redis derivation to use.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 6379;
+        description = "The port for Redis to listen to.";
+      };
+
+      vmOverCommit = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Set vm.overcommit_memory to 1 (Suggested for Background Saving: http://redis.io/topics/faq)
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open ports in the firewall for the server.
+        '';
+      };
+
+      bind = mkOption {
+        type = with types; nullOr str;
+        default = null; # All interfaces
+        description = "The IP interface to bind to.";
+        example = "127.0.0.1";
+      };
+
+      unixSocket = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = "The path to the socket to bind to.";
+        example = "/run/redis/redis.sock";
+      };
+
+      logLevel = mkOption {
+        type = types.str;
+        default = "notice"; # debug, verbose, notice, warning
+        example = "debug";
+        description = "Specify the server verbosity level, options: debug, verbose, notice, warning.";
+      };
+
+      logfile = mkOption {
+        type = types.str;
+        default = "/dev/null";
+        description = "Specify the log file name. Also 'stdout' can be used to force Redis to log on the standard output.";
+        example = "/var/log/redis.log";
+      };
+
+      syslog = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Enable logging to the system logger.";
+      };
+
+      databases = mkOption {
+        type = types.int;
+        default = 16;
+        description = "Set the number of databases.";
+      };
+
+      save = mkOption {
+        type = with types; listOf (listOf int);
+        default = [ [900 1] [300 10] [60 10000] ];
+        description = "The schedule in which data is persisted to disk, represented as a list of lists where the first element represent the amount of seconds and the second the number of changes.";
+        example = [ [900 1] [300 10] [60 10000] ];
+      };
+
+      slaveOf = mkOption {
+        default = null; # { ip, port }
+        description = "An attribute set with two attributes: ip and port to which this redis instance acts as a slave.";
+        example = { ip = "192.168.1.100"; port = 6379; };
+      };
+
+      masterAuth = mkOption {
+        default = null;
+        description = ''If the master is password protected (using the requirePass configuration)
+        it is possible to tell the slave to authenticate before starting the replication synchronization
+        process, otherwise the master will refuse the slave request.
+        (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE)'';
+      };
+
+      requirePass = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Password for database (STORED PLAIN TEXT, WORLD-READABLE IN NIX STORE).
+          Use requirePassFile to store it outside of the nix store in a dedicated file.
+        '';
+        example = "letmein!";
+      };
+
+      requirePassFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = "File with password for the database.";
+        example = "/run/keys/redis-password";
+      };
+
+      appendOnly = mkOption {
+        type = types.bool;
+        default = false;
+        description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
+      };
+
+      appendFsync = mkOption {
+        type = types.str;
+        default = "everysec"; # no, always, everysec
+        description = "How often to fsync the append-only log, options: no, always, everysec.";
+      };
+
+      slowLogLogSlowerThan = mkOption {
+        type = types.int;
+        default = 10000;
+        description = "Log queries whose execution take longer than X in milliseconds.";
+        example = 1000;
+      };
+
+      slowLogMaxLen = mkOption {
+        type = types.int;
+        default = 128;
+        description = "Maximum number of items to keep in slow log.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra configuration options for redis.conf.";
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.redis.enable {
+    assertions = [{
+      assertion = cfg.requirePass != null -> cfg.requirePassFile == null;
+      message = "You can only set one services.redis.requirePass or services.redis.requirePassFile";
+    }];
+    boot.kernel.sysctl = (mkMerge [
+      { "vm.nr_hugepages" = "0"; }
+      ( mkIf cfg.vmOverCommit { "vm.overcommit_memory" = "1"; } )
+    ]);
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+
+    users.users.redis = {
+      description = "Redis database user";
+      isSystemUser = true;
+    };
+    users.groups.redis = {};
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.redis = {
+      description = "Redis Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        install -m 600 ${redisConfig} /run/redis/redis.conf
+      '' + optionalString (cfg.requirePassFile != null) ''
+        password=$(cat ${escapeShellArg cfg.requirePassFile})
+        echo "requirePass $password" >> /run/redis/redis.conf
+      '';
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/redis-server /run/redis/redis.conf";
+        RuntimeDirectory = "redis";
+        StateDirectory = "redis";
+        Type = "notify";
+        User = "redis";
+        Group = "redis";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/rethinkdb.nix b/nixpkgs/nixos/modules/services/databases/rethinkdb.nix
new file mode 100644
index 000000000000..c764d6c21c6c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/rethinkdb.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.rethinkdb;
+  rethinkdb = cfg.package;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.rethinkdb = {
+
+      enable = mkEnableOption "RethinkDB server";
+
+      #package = mkOption {
+      #  default = pkgs.rethinkdb;
+      #  description = "Which RethinkDB derivation to use.";
+      #};
+
+      user = mkOption {
+        default = "rethinkdb";
+        description = "User account under which RethinkDB runs.";
+      };
+
+      group = mkOption {
+        default = "rethinkdb";
+        description = "Group which rethinkdb user belongs to.";
+      };
+
+      dbpath = mkOption {
+        default = "/var/db/rethinkdb";
+        description = "Location where RethinkDB stores its data, 1 data directory per instance.";
+      };
+
+      pidpath = mkOption {
+        default = "/run/rethinkdb";
+        description = "Location where each instance's pid file is located.";
+      };
+
+      #cfgpath = mkOption {
+      #  default = "/etc/rethinkdb/instances.d";
+      #  description = "Location where RethinkDB stores it config files, 1 config file per instance.";
+      #};
+
+      # TODO: currently not used by our implementation.
+      #instances = mkOption {
+      #  type = types.attrsOf types.str;
+      #  default = {};
+      #  description = "List of named RethinkDB instances in our cluster.";
+      #};
+
+    };
+
+  };
+
+  ###### implementation
+  config = mkIf config.services.rethinkdb.enable {
+
+    environment.systemPackages = [ rethinkdb ];
+
+    systemd.services.rethinkdb = {
+      description = "RethinkDB server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        # TODO: abstract away 'default', which is a per-instance directory name
+        #       allowing end user of this nix module to provide multiple instances,
+        #       and associated directory per instance
+        ExecStart = "${rethinkdb}/bin/rethinkdb -d ${cfg.dbpath}/default";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        User = cfg.user;
+        Group = cfg.group;
+        PIDFile = "${cfg.pidpath}/default.pid";
+        PermissionsStartOnly = true;
+      };
+
+      preStart = ''
+        if ! test -e ${cfg.dbpath}; then
+            install -d -m0755 -o ${cfg.user} -g ${cfg.group} ${cfg.dbpath}
+            install -d -m0755 -o ${cfg.user} -g ${cfg.group} ${cfg.dbpath}/default
+            chown -R ${cfg.user}:${cfg.group} ${cfg.dbpath}
+        fi
+        if ! test -e "${cfg.pidpath}/default.pid"; then
+            install -D -o ${cfg.user} -g ${cfg.group} /dev/null "${cfg.pidpath}/default.pid"
+        fi
+      '';
+    };
+
+    users.users.rethinkdb = mkIf (cfg.user == "rethinkdb")
+      { name = "rethinkdb";
+        description = "RethinkDB server user";
+        isSystemUser = true;
+      };
+
+    users.groups = optionalAttrs (cfg.group == "rethinkdb") (singleton
+      { name = "rethinkdb";
+      });
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/riak-cs.nix b/nixpkgs/nixos/modules/services/databases/riak-cs.nix
new file mode 100644
index 000000000000..2cb204f729a7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/riak-cs.nix
@@ -0,0 +1,202 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.riak-cs;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.riak-cs = {
+
+      enable = mkEnableOption "riak-cs";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.riak-cs;
+        defaultText = "pkgs.riak-cs";
+        example = literalExample "pkgs.riak-cs";
+        description = ''
+          Riak package to use.
+        '';
+      };
+
+      nodeName = mkOption {
+        type = types.str;
+        default = "riak-cs@127.0.0.1";
+        description = ''
+          Name of the Erlang node.
+        '';
+      };
+      
+      anonymousUserCreation = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Anonymous user creation.
+        '';
+      };
+
+      riakHost = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8087";
+        description = ''
+          Name of riak hosting service.
+        '';
+      };
+
+      listener = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8080";
+        description = ''
+          Name of Riak CS listening service.
+        '';
+      };
+
+      stanchionHost = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8085";
+        description = ''
+          Name of stanchion hosting service.
+        '';
+      };
+
+      stanchionSsl = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Tell stanchion to use SSL.
+        '';
+      };
+
+      distributedCookie = mkOption {
+        type = types.str;
+        default = "riak";
+        description = ''
+          Cookie for distributed node communication.  All nodes in the
+          same cluster should use the same cookie or they will not be able to
+          communicate.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/db/riak-cs";
+        description = ''
+          Data directory for Riak CS.
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        default = "/var/log/riak-cs";
+        description = ''
+          Log directory for Riak CS.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be appended to <filename>riak-cs.conf</filename>.
+        '';
+      };
+
+      extraAdvancedConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be appended to <filename>advanced.config</filename>.
+        '';
+      };
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+    environment.etc."riak-cs/riak-cs.conf".text = ''
+      nodename = ${cfg.nodeName}
+      distributed_cookie = ${cfg.distributedCookie}
+
+      platform_log_dir = ${cfg.logDir}
+
+      riak_host = ${cfg.riakHost}
+      listener = ${cfg.listener}
+      stanchion_host = ${cfg.stanchionHost}
+
+      anonymous_user_creation = ${if cfg.anonymousUserCreation then "on" else "off"}
+
+      ${cfg.extraConfig}
+    '';
+
+    environment.etc."riak-cs/advanced.config".text = ''
+      ${cfg.extraAdvancedConfig}
+    '';
+
+    users.users.riak-cs = {
+      name = "riak-cs";
+      uid = config.ids.uids.riak-cs;
+      group = "riak";
+      description = "Riak CS server user";
+    };
+
+  systemd.services.riak-cs = {
+      description = "Riak CS Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = [
+        pkgs.utillinux # for `logger`
+        pkgs.bash
+      ];
+
+      environment.HOME = "${cfg.dataDir}";
+      environment.RIAK_CS_DATA_DIR = "${cfg.dataDir}";
+      environment.RIAK_CS_LOG_DIR = "${cfg.logDir}";
+      environment.RIAK_CS_ETC_DIR = "/etc/riak";
+
+      preStart = ''
+        if ! test -e ${cfg.logDir}; then
+          mkdir -m 0755 -p ${cfg.logDir}
+          chown -R riak-cs ${cfg.logDir}
+        fi
+
+        if ! test -e ${cfg.dataDir}; then
+          mkdir -m 0700 -p ${cfg.dataDir}
+          chown -R riak-cs ${cfg.dataDir}
+        fi
+      '';
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/riak-cs console";
+        ExecStop = "${cfg.package}/bin/riak-cs stop";
+        StandardInput = "tty";
+        User = "riak-cs";
+        Group = "riak-cs";
+        PermissionsStartOnly = true;
+        # Give Riak a decent amount of time to clean up.
+        TimeoutStopSec = 120;
+        LimitNOFILE = 65536;
+      };
+
+      unitConfig.RequiresMountsFor = [
+        "${cfg.dataDir}"
+        "${cfg.logDir}"
+        "/etc/riak"
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/riak.nix b/nixpkgs/nixos/modules/services/databases/riak.nix
new file mode 100644
index 000000000000..885215209bdf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/riak.nix
@@ -0,0 +1,163 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.riak;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.riak = {
+
+      enable = mkEnableOption "riak";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.riak;
+        defaultText = "pkgs.riak";
+        example = literalExample "pkgs.riak";
+        description = ''
+          Riak package to use.
+        '';
+      };
+
+      nodeName = mkOption {
+        type = types.str;
+        default = "riak@127.0.0.1";
+        description = ''
+          Name of the Erlang node.
+        '';
+      };
+
+      distributedCookie = mkOption {
+        type = types.str;
+        default = "riak";
+        description = ''
+          Cookie for distributed node communication.  All nodes in the
+          same cluster should use the same cookie or they will not be able to
+          communicate.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/db/riak";
+        description = ''
+          Data directory for Riak.
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        default = "/var/log/riak";
+        description = ''
+          Log directory for Riak.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be appended to <filename>riak.conf</filename>.
+        '';
+      };
+
+      extraAdvancedConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be appended to <filename>advanced.config</filename>.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+    environment.etc."riak/riak.conf".text = ''
+      nodename = ${cfg.nodeName}
+      distributed_cookie = ${cfg.distributedCookie}
+
+      platform_log_dir = ${cfg.logDir}
+      platform_etc_dir = /etc/riak
+      platform_data_dir = ${cfg.dataDir}
+
+      ${cfg.extraConfig}
+    '';
+
+    environment.etc."riak/advanced.config".text = ''
+      ${cfg.extraAdvancedConfig}
+    '';
+
+    users.users.riak = {
+      name = "riak";
+      uid = config.ids.uids.riak;
+      group = "riak";
+      description = "Riak server user";
+    };
+
+    users.groups.riak.gid = config.ids.gids.riak;
+
+    systemd.services.riak = {
+      description = "Riak Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = [
+        pkgs.utillinux # for `logger`
+        pkgs.bash
+      ];
+
+      environment.HOME = "${cfg.dataDir}";
+      environment.RIAK_DATA_DIR = "${cfg.dataDir}";
+      environment.RIAK_LOG_DIR = "${cfg.logDir}";
+      environment.RIAK_ETC_DIR = "/etc/riak";
+
+      preStart = ''
+        if ! test -e ${cfg.logDir}; then
+          mkdir -m 0755 -p ${cfg.logDir}
+          chown -R riak ${cfg.logDir}
+        fi
+
+        if ! test -e ${cfg.dataDir}; then
+          mkdir -m 0700 -p ${cfg.dataDir}
+          chown -R riak ${cfg.dataDir}
+        fi
+      '';
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/riak console";
+        ExecStop = "${cfg.package}/bin/riak stop";
+        StandardInput = "tty";
+        User = "riak";
+        Group = "riak";
+        PermissionsStartOnly = true;
+        # Give Riak a decent amount of time to clean up.
+        TimeoutStopSec = 120;
+        LimitNOFILE = 65536;
+      };
+
+      unitConfig.RequiresMountsFor = [
+        "${cfg.dataDir}"
+        "${cfg.logDir}"
+        "/etc/riak"
+      ];
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/databases/stanchion.nix b/nixpkgs/nixos/modules/services/databases/stanchion.nix
new file mode 100644
index 000000000000..97e55bc70c47
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/stanchion.nix
@@ -0,0 +1,194 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.stanchion;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.stanchion = {
+
+      enable = mkEnableOption "stanchion";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.stanchion;
+        defaultText = "pkgs.stanchion";
+        example = literalExample "pkgs.stanchion";
+        description = ''
+          Stanchion package to use.
+        '';
+      };
+
+      nodeName = mkOption {
+        type = types.str;
+        default = "stanchion@127.0.0.1";
+        description = ''
+          Name of the Erlang node.
+        '';
+      };
+
+      adminKey = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Name of admin user.
+        '';
+      };
+
+      adminSecret = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Name of admin secret
+        '';
+      };
+
+      riakHost = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8087";
+        description = ''
+          Name of riak hosting service.
+        '';
+      };
+
+      listener = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8085";
+        description = ''
+          Name of Riak CS listening service.
+        '';
+      };
+
+      stanchionHost = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8085";
+        description = ''
+          Name of stanchion hosting service.
+        '';
+      };
+
+      distributedCookie = mkOption {
+        type = types.str;
+        default = "riak";
+        description = ''
+          Cookie for distributed node communication.  All nodes in the
+          same cluster should use the same cookie or they will not be able to
+          communicate.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/db/stanchion";
+        description = ''
+          Data directory for Stanchion.
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        default = "/var/log/stanchion";
+        description = ''
+          Log directory for Stanchion.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be appended to <filename>stanchion.conf</filename>.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    environment.etc."stanchion/advanced.config".text = ''
+      [{stanchion, []}].
+    '';
+
+    environment.etc."stanchion/stanchion.conf".text = ''
+      listener = ${cfg.listener}
+
+      riak_host = ${cfg.riakHost}
+
+      ${optionalString (cfg.adminKey == "") "#"} admin.key=${optionalString (cfg.adminKey != "") cfg.adminKey}
+      ${optionalString (cfg.adminSecret == "") "#"} admin.secret=${optionalString (cfg.adminSecret != "") cfg.adminSecret}
+
+      platform_bin_dir = ${pkgs.stanchion}/bin
+      platform_data_dir = ${cfg.dataDir}
+      platform_etc_dir = /etc/stanchion
+      platform_lib_dir = ${pkgs.stanchion}/lib
+      platform_log_dir = ${cfg.logDir}
+
+      nodename = ${cfg.nodeName}
+
+      distributed_cookie = ${cfg.distributedCookie}
+
+      ${cfg.extraConfig}
+    '';
+
+    users.users.stanchion = {
+      name = "stanchion";
+      uid = config.ids.uids.stanchion;
+      group = "stanchion";
+      description = "Stanchion server user";
+    };
+
+    users.groups.stanchion.gid = config.ids.gids.stanchion;
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logDir}' - stanchion stanchion --"
+      "d '${cfg.dataDir}' 0700 stanchion stanchion --"
+    ];
+
+    systemd.services.stanchion = {
+      description = "Stanchion Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = [
+        pkgs.utillinux # for `logger`
+        pkgs.bash
+      ];
+
+      environment.HOME = "${cfg.dataDir}";
+      environment.STANCHION_DATA_DIR = "${cfg.dataDir}";
+      environment.STANCHION_LOG_DIR = "${cfg.logDir}";
+      environment.STANCHION_ETC_DIR = "/etc/stanchion";
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/stanchion console";
+        ExecStop = "${cfg.package}/bin/stanchion stop";
+        StandardInput = "tty";
+        User = "stanchion";
+        Group = "stanchion";
+        # Give Stanchion a decent amount of time to clean up.
+        TimeoutStopSec = 120;
+        LimitNOFILE = 65536;
+      };
+
+      unitConfig.RequiresMountsFor = [
+        "${cfg.dataDir}"
+        "${cfg.logDir}"
+        "/etc/stanchion"
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/victoriametrics.nix b/nixpkgs/nixos/modules/services/databases/victoriametrics.nix
new file mode 100644
index 000000000000..cb6bf8508fb6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/victoriametrics.nix
@@ -0,0 +1,70 @@
+{ config, pkgs, lib, ... }:
+let cfg = config.services.victoriametrics; in
+{
+  options.services.victoriametrics = with lib; {
+    enable = mkEnableOption "victoriametrics";
+    package = mkOption {
+      type = types.package;
+      default = pkgs.victoriametrics;
+      defaultText = "pkgs.victoriametrics";
+      description = ''
+        The VictoriaMetrics distribution to use.
+      '';
+    };
+    listenAddress = mkOption {
+      default = ":8428";
+      type = types.str;
+      description = ''
+        The listen address for the http interface.
+      '';
+    };
+    retentionPeriod = mkOption {
+      type = types.int;
+      default = 1;
+      description = ''
+        Retention period in months.
+      '';
+    };
+    extraOptions = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra options to pass to VictoriaMetrics. See the README: <link
+        xlink:href="https://github.com/VictoriaMetrics/VictoriaMetrics/blob/master/README.md" />
+        or <command>victoriametrics -help</command> for more
+        information.
+      '';
+    };
+  };
+  config = lib.mkIf cfg.enable {
+    systemd.services.victoriametrics = {
+      description = "VictoriaMetrics time series database";
+      after = [ "network.target" ];
+      serviceConfig = {
+        Restart = "on-failure";
+        RestartSec = 1;
+        StartLimitBurst = 5;
+        StateDirectory = "victoriametrics";
+        DynamicUser = true;
+        ExecStart = ''
+          ${cfg.package}/bin/victoria-metrics \
+              -storageDataPath=/var/lib/victoriametrics \
+              -httpListenAddr ${cfg.listenAddress}
+              -retentionPeriod ${toString cfg.retentionPeriod}
+              ${lib.escapeShellArgs cfg.extraOptions}
+        '';
+      };
+      wantedBy = [ "multi-user.target" ];
+
+      postStart =
+        let
+          bindAddr = (lib.optionalString (lib.hasPrefix ":" cfg.listenAddress) "127.0.0.1") + cfg.listenAddress;
+        in
+        lib.mkBefore ''
+          until ${lib.getBin pkgs.curl}/bin/curl -s -o /dev/null http://${bindAddr}/ping; do
+            sleep 1;
+          done
+        '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/databases/virtuoso.nix b/nixpkgs/nixos/modules/services/databases/virtuoso.nix
new file mode 100644
index 000000000000..6eb09e0a58fc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/databases/virtuoso.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.virtuoso;
+  virtuosoUser = "virtuoso";
+  stateDir = "/var/lib/virtuoso";
+in
+with lib;
+{
+
+  ###### interface
+
+  options = {
+
+    services.virtuoso = {
+
+      enable = mkEnableOption "Virtuoso Opensource database server";
+
+      config = mkOption {
+        default = "";
+        description = "Extra options to put into Virtuoso configuration file.";
+      };
+
+      parameters = mkOption {
+        default = "";
+        description = "Extra options to put into [Parameters] section of Virtuoso configuration file.";
+      };
+
+      listenAddress = mkOption {
+        default = "1111";
+        example = "myserver:1323";
+        description = "ip:port or port to listen on.";
+      };
+
+      httpListenAddress = mkOption {
+        default = null;
+        example = "myserver:8080";
+        description = "ip:port or port for Virtuoso HTTP server to listen on.";
+      };
+
+      dirsAllowed = mkOption {
+        default = null;
+        example = "/www, /home/";
+        description = "A list of directories Virtuoso is allowed to access";
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.${virtuosoUser} =
+      { uid = config.ids.uids.virtuoso;
+        description = "virtuoso user";
+        home = stateDir;
+      };
+
+    systemd.services.virtuoso = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        mkdir -p ${stateDir}
+        chown ${virtuosoUser} ${stateDir}
+      '';
+
+      script = ''
+        cd ${stateDir}
+        ${pkgs.virtuoso}/bin/virtuoso-t +foreground +configfile ${pkgs.writeText "virtuoso.ini" cfg.config}
+      '';
+    };
+
+    services.virtuoso.config = ''
+      [Database]
+      DatabaseFile=${stateDir}/x-virtuoso.db
+      TransactionFile=${stateDir}/x-virtuoso.trx
+      ErrorLogFile=${stateDir}/x-virtuoso.log
+      xa_persistent_file=${stateDir}/x-virtuoso.pxa
+
+      [Parameters]
+      ServerPort=${cfg.listenAddress}
+      RunAs=${virtuosoUser}
+      ${optionalString (cfg.dirsAllowed != null) "DirsAllowed=${cfg.dirsAllowed}"}
+      ${cfg.parameters}
+
+      [HTTPServer]
+      ${optionalString (cfg.httpListenAddress != null) "ServerPort=${cfg.httpListenAddress}"}
+    '';
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/accountsservice.nix b/nixpkgs/nixos/modules/services/desktops/accountsservice.nix
new file mode 100644
index 000000000000..ae2ecb5ffeb7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/accountsservice.nix
@@ -0,0 +1,58 @@
+# AccountsService daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.freedesktop.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.accounts-daemon = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable AccountsService, a DBus service for accessing
+          the list of user accounts and information attached to those accounts.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.accounts-daemon.enable {
+
+    environment.systemPackages = [ pkgs.accountsservice ];
+
+    # Accounts daemon looks for dbus interfaces in $XDG_DATA_DIRS/accountsservice
+    environment.pathsToLink = [ "/share/accountsservice" ];
+
+    services.dbus.packages = [ pkgs.accountsservice ];
+
+    systemd.packages = [ pkgs.accountsservice ];
+
+    systemd.services.accounts-daemon = recursiveUpdate {
+
+      wantedBy = [ "graphical.target" ];
+
+      # Accounts daemon looks for dbus interfaces in $XDG_DATA_DIRS/accountsservice
+      environment.XDG_DATA_DIRS = "${config.system.path}/share";
+
+    } (optionalAttrs (!config.users.mutableUsers) {
+      environment.NIXOS_USERS_PURE = "true";
+    });
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/bamf.nix b/nixpkgs/nixos/modules/services/desktops/bamf.nix
new file mode 100644
index 000000000000..4b35146d0844
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/bamf.nix
@@ -0,0 +1,27 @@
+# Bamf
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  meta = {
+    maintainers = with maintainers; [ worldofpeace ];
+  };
+
+  ###### interface
+
+  options = {
+    services.bamf = {
+      enable = mkEnableOption "bamf";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.bamf.enable {
+    services.dbus.packages = [ pkgs.bamf ];
+
+    systemd.packages = [ pkgs.bamf ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/blueman.nix b/nixpkgs/nixos/modules/services/desktops/blueman.nix
new file mode 100644
index 000000000000..18ad610247ed
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/blueman.nix
@@ -0,0 +1,25 @@
+# blueman service
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.blueman;
+in {
+  ###### interface
+  options = {
+    services.blueman = {
+      enable = mkEnableOption "blueman";
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.blueman ];
+
+    services.dbus.packages = [ pkgs.blueman ];
+
+    systemd.packages = [ pkgs.blueman ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/deepin/deepin.nix b/nixpkgs/nixos/modules/services/desktops/deepin/deepin.nix
new file mode 100644
index 000000000000..f8fb73701af6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/deepin/deepin.nix
@@ -0,0 +1,123 @@
+# deepin
+
+{ config, pkgs, lib, ... }:
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.deepin.core.enable = lib.mkEnableOption "
+      Basic dbus and systemd services, groups and users needed by the
+      Deepin Desktop Environment.
+    ";
+
+    services.deepin.deepin-menu.enable = lib.mkEnableOption "
+      DBus service for unified menus in Deepin Desktop Environment.
+    ";
+
+    services.deepin.deepin-turbo.enable = lib.mkEnableOption "
+      Turbo service for the Deepin Desktop Environment. It is a daemon
+      that helps to launch applications faster.
+    ";
+
+  };
+
+
+  ###### implementation
+
+  config = lib.mkMerge [
+
+    (lib.mkIf config.services.deepin.core.enable {
+      environment.systemPackages = [
+        pkgs.deepin.dde-api
+        pkgs.deepin.dde-calendar
+        pkgs.deepin.dde-control-center
+        pkgs.deepin.dde-daemon
+        pkgs.deepin.dde-dock
+        pkgs.deepin.dde-launcher
+        pkgs.deepin.dde-file-manager
+        pkgs.deepin.dde-session-ui
+        pkgs.deepin.deepin-anything
+        pkgs.deepin.deepin-image-viewer
+      ];
+
+      services.dbus.packages = [
+        pkgs.deepin.dde-api
+        pkgs.deepin.dde-calendar
+        pkgs.deepin.dde-control-center
+        pkgs.deepin.dde-daemon
+        pkgs.deepin.dde-dock
+        pkgs.deepin.dde-launcher
+        pkgs.deepin.dde-file-manager
+        pkgs.deepin.dde-session-ui
+        pkgs.deepin.deepin-anything
+        pkgs.deepin.deepin-image-viewer
+      ];
+
+      systemd.packages = [
+        pkgs.deepin.dde-api
+        pkgs.deepin.dde-daemon
+        pkgs.deepin.dde-file-manager
+        pkgs.deepin.deepin-anything
+      ];
+
+      boot.extraModulePackages = [ config.boot.kernelPackages.deepin-anything ];
+
+      boot.kernelModules = [ "vfs_monitor" ];
+
+      users.groups.deepin-sound-player = { };
+
+      users.users.deepin-sound-player = {
+        description = "Deepin sound player";
+        group = "deepin-sound-player";
+        isSystemUser = true;
+      };
+
+      users.groups.deepin-daemon = { };
+
+      users.users.deepin-daemon = {
+        description = "Deepin daemon user";
+        group = "deepin-daemon";
+        isSystemUser = true;
+      };
+
+      users.groups.deepin_anything_server = { };
+
+      users.users.deepin_anything_server = {
+        description = "Deepin Anything Server";
+        group = "deepin_anything_server";
+        isSystemUser = true;
+      };
+
+      security.pam.services.deepin-auth-keyboard.text = ''
+        # original at ${pkgs.deepin.dde-daemon}/etc/pam.d/deepin-auth-keyboard
+        auth	[success=2 default=ignore]	pam_lsass.so
+        auth	[success=1 default=ignore]	pam_unix.so nullok_secure try_first_pass
+        auth	requisite	pam_deny.so
+        auth	required	pam_permit.so
+      '';
+
+      environment.etc = {
+        "polkit-1/localauthority/10-vendor.d/com.deepin.api.device.pkla".source = "${pkgs.deepin.dde-api}/etc/polkit-1/localauthority/10-vendor.d/com.deepin.api.device.pkla";
+        "polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Accounts.pkla".source = "${pkgs.deepin.dde-daemon}/etc/polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Accounts.pkla";
+        "polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Grub2.pkla".source = "${pkgs.deepin.dde-daemon}/etc/polkit-1/localauthority/10-vendor.d/com.deepin.daemon.Grub2.pkla";
+      };
+
+      services.deepin.deepin-menu.enable = true;
+      services.deepin.deepin-turbo.enable = true;
+    })
+
+    (lib.mkIf config.services.deepin.deepin-menu.enable {
+      services.dbus.packages = [ pkgs.deepin.deepin-menu ];
+    })
+
+    (lib.mkIf config.services.deepin.deepin-turbo.enable {
+      environment.systemPackages = [ pkgs.deepin.deepin-turbo ];
+      systemd.packages = [ pkgs.deepin.deepin-turbo ];
+    })
+
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/dleyna-renderer.nix b/nixpkgs/nixos/modules/services/desktops/dleyna-renderer.nix
new file mode 100644
index 000000000000..7f88605f627c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/dleyna-renderer.nix
@@ -0,0 +1,28 @@
+# dleyna-renderer service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  ###### interface
+  options = {
+    services.dleyna-renderer = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable dleyna-renderer service, a DBus service
+          for handling DLNA renderers.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+  config = mkIf config.services.dleyna-renderer.enable {
+    environment.systemPackages = [ pkgs.dleyna-renderer ];
+
+    services.dbus.packages = [ pkgs.dleyna-renderer ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/dleyna-server.nix b/nixpkgs/nixos/modules/services/desktops/dleyna-server.nix
new file mode 100644
index 000000000000..9a131a5e700f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/dleyna-server.nix
@@ -0,0 +1,28 @@
+# dleyna-server service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  ###### interface
+  options = {
+    services.dleyna-server = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable dleyna-server service, a DBus service
+          for handling DLNA servers.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+  config = mkIf config.services.dleyna-server.enable {
+    environment.systemPackages = [ pkgs.dleyna-server ];
+
+    services.dbus.packages = [ pkgs.dleyna-server ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/flatpak.nix b/nixpkgs/nixos/modules/services/desktops/flatpak.nix
new file mode 100644
index 000000000000..7fb0024f37dc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/flatpak.nix
@@ -0,0 +1,53 @@
+# flatpak service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.flatpak;
+in {
+  meta = {
+    doc = ./flatpak.xml;
+    maintainers = pkgs.flatpak.meta.maintainers;
+  };
+
+  ###### interface
+  options = {
+    services.flatpak = {
+      enable = mkEnableOption "flatpak";
+    };
+  };
+
+
+  ###### implementation
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = (config.xdg.portal.enable == true);
+        message = "To use Flatpak you must enable XDG Desktop Portals with xdg.portal.enable.";
+      }
+    ];
+
+    environment.systemPackages = [ pkgs.flatpak ];
+
+    services.dbus.packages = [ pkgs.flatpak ];
+
+    systemd.packages = [ pkgs.flatpak ];
+
+    environment.profiles = [
+      "$HOME/.local/share/flatpak/exports"
+      "/var/lib/flatpak/exports"
+    ];
+
+    # It has been possible since https://github.com/flatpak/flatpak/releases/tag/1.3.2
+    # to build a SELinux policy module.
+
+    users.users.flatpak = {
+      description = "Flatpak system helper";
+      group = "flatpak";
+      isSystemUser = true;
+    };
+
+    users.groups.flatpak = { };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/flatpak.xml b/nixpkgs/nixos/modules/services/desktops/flatpak.xml
new file mode 100644
index 000000000000..8f080b250228
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/flatpak.xml
@@ -0,0 +1,56 @@
+<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-flatpak">
+ <title>Flatpak</title>
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/services/desktop/flatpak.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://github.com/flatpak/flatpak/wiki"/>
+ </para>
+ <para>
+  Flatpak is a system for building, distributing, and running sandboxed desktop
+  applications on Linux.
+ </para>
+ <para>
+  To enable Flatpak, add the following to your
+  <filename>configuration.nix</filename>:
+<programlisting>
+  <xref linkend="opt-services.flatpak.enable"/> = true;
+</programlisting>
+ </para>
+ <para>
+  For the sandboxed apps to work correctly, desktop integration portals need to
+  be installed. If you run GNOME, this will be handled automatically for you;
+  in other cases, you will need to add something like the following to your
+  <filename>configuration.nix</filename>:
+<programlisting>
+  <xref linkend="opt-xdg.portal.extraPortals"/> = [ pkgs.xdg-desktop-portal-gtk ];
+</programlisting>
+ </para>
+ <para>
+  Then, you will need to add a repository, for example,
+  <link xlink:href="https://github.com/flatpak/flatpak/wiki">Flathub</link>,
+  either using the following commands:
+<screen>
+<prompt>$ </prompt>flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
+<prompt>$ </prompt>flatpak update
+</screen>
+  or by opening the
+  <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository
+  file</link> in GNOME Software.
+ </para>
+ <para>
+  Finally, you can search and install programs:
+<screen>
+<prompt>$ </prompt>flatpak search bustle
+<prompt>$ </prompt>flatpak install flathub org.freedesktop.Bustle
+<prompt>$ </prompt>flatpak run org.freedesktop.Bustle
+</screen>
+  Again, GNOME Software offers graphical interface for these tasks.
+ </para>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/desktops/geoclue2.nix b/nixpkgs/nixos/modules/services/desktops/geoclue2.nix
new file mode 100644
index 000000000000..542b2ead4104
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/geoclue2.nix
@@ -0,0 +1,268 @@
+# GeoClue 2 daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  # the demo agent isn't built by default, but we need it here
+  package = pkgs.geoclue2.override { withDemoAgent = config.services.geoclue2.enableDemoAgent; };
+
+  cfg = config.services.geoclue2;
+
+  defaultWhitelist = [ "gnome-shell" "io.elementary.desktop.agent-geoclue2" ];
+
+  appConfigModule = types.submodule ({ name, ... }: {
+    options = {
+      desktopID = mkOption {
+        type = types.str;
+        description = "Desktop ID of the application.";
+      };
+
+      isAllowed = mkOption {
+        type = types.bool;
+        default = null;
+        description = ''
+          Whether the application will be allowed access to location information.
+        '';
+      };
+
+      isSystem = mkOption {
+        type = types.bool;
+        default = null;
+        description = ''
+          Whether the application is a system component or not.
+        '';
+      };
+
+      users = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          List of UIDs of all users for which this application is allowed location
+          info access, Defaults to an empty string to allow it for all users.
+        '';
+      };
+    };
+
+    config.desktopID = mkDefault name;
+  });
+
+  appConfigToINICompatible = _: { desktopID, isAllowed, isSystem, users, ... }: {
+    name = desktopID;
+    value = {
+      allowed = isAllowed;
+      system = isSystem;
+      users = concatStringsSep ";" users;
+    };
+  };
+
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.geoclue2 = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable GeoClue 2 daemon, a DBus service
+          that provides location information for accessing.
+        '';
+      };
+
+      enableDemoAgent = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to use the GeoClue demo agent. This should be
+          overridden by desktop environments that provide their own
+          agent.
+        '';
+      };
+
+      enableNmea = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to fetch location from NMEA sources on local network.
+        '';
+      };
+
+      enable3G = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable 3G source.
+        '';
+      };
+
+      enableCDMA = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable CDMA source.
+        '';
+      };
+
+      enableModemGPS = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable Modem-GPS source.
+        '';
+      };
+
+      enableWifi = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable WiFi source.
+        '';
+      };
+
+      geoProviderUrl = mkOption {
+        type = types.str;
+        default = "https://location.services.mozilla.com/v1/geolocate?key=geoclue";
+        example = "https://www.googleapis.com/geolocation/v1/geolocate?key=YOUR_KEY";
+        description = ''
+          The url to the wifi GeoLocation Service.
+        '';
+      };
+
+      submitData = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to submit data to a GeoLocation Service.
+        '';
+      };
+
+      submissionUrl = mkOption {
+        type = types.str;
+        default = "https://location.services.mozilla.com/v1/submit?key=geoclue";
+        description = ''
+          The url to submit data to a GeoLocation Service.
+        '';
+      };
+
+      submissionNick = mkOption {
+        type = types.str;
+        default = "geoclue";
+        description = ''
+          A nickname to submit network data with.
+          Must be 2-32 characters long.
+        '';
+      };
+
+      appConfig = mkOption {
+        type = types.loaOf appConfigModule;
+        default = {};
+        example = literalExample ''
+          "com.github.app" = {
+            isAllowed = true;
+            isSystem = true;
+            users = [ "300" ];
+          };
+        '';
+        description = ''
+          Specify extra settings per application.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ package ];
+
+    services.dbus.packages = [ package ];
+
+    systemd.packages = [ package ];
+
+    # we cannot use DynamicUser as we need the the geoclue user to exist for the dbus policy to work
+    users = {
+      users.geoclue = {
+        isSystemUser = true;
+        home = "/var/lib/geoclue";
+        group = "geoclue";
+        description = "Geoinformation service";
+      };
+
+      groups.geoclue = {};
+    };
+
+    systemd.services.geoclue = {
+      # restart geoclue service when the configuration changes
+      restartTriggers = [
+        config.environment.etc."geoclue/geoclue.conf".source
+      ];
+      serviceConfig.StateDirectory = "geoclue";
+    };
+
+    # this needs to run as a user service, since it's associated with the
+    # user who is making the requests
+    systemd.user.services = mkIf cfg.enableDemoAgent {
+      geoclue-agent = {
+        description = "Geoclue agent";
+        # this should really be `partOf = [ "geoclue.service" ]`, but
+        # we can't be part of a system service, and the agent should
+        # be okay with the main service coming and going
+        wantedBy = [ "default.target" ];
+        serviceConfig = {
+          Type = "exec";
+          ExecStart = "${package}/libexec/geoclue-2.0/demos/agent";
+          Restart = "on-failure";
+          PrivateTmp = true;
+        };
+      };
+    };
+
+    services.geoclue2.appConfig.epiphany = {
+      isAllowed = true;
+      isSystem = false;
+    };
+
+    services.geoclue2.appConfig.firefox = {
+      isAllowed = true;
+      isSystem = false;
+    };
+
+    environment.etc."geoclue/geoclue.conf".text =
+      generators.toINI {} ({
+        agent = {
+          whitelist = concatStringsSep ";"
+            (optional cfg.enableDemoAgent "geoclue-demo-agent" ++ defaultWhitelist);
+        };
+        network-nmea = {
+          enable = cfg.enableNmea;
+        };
+        "3g" = {
+          enable = cfg.enable3G;
+        };
+        cdma = {
+          enable = cfg.enableCDMA;
+        };
+        modem-gps = {
+          enable = cfg.enableModemGPS;
+        };
+        wifi = {
+          enable = cfg.enableWifi;
+          url = cfg.geoProviderUrl;
+          submit-data = boolToString cfg.submitData;
+          submission-url = cfg.submissionUrl;
+          submission-nick = cfg.submissionNick;
+        };
+      } // mapAttrs' appConfigToINICompatible cfg.appConfig);
+  };
+
+  meta.maintainers = with lib.maintainers; [ worldofpeace ];
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/at-spi2-core.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/at-spi2-core.nix
new file mode 100644
index 000000000000..492242e3296d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/at-spi2-core.nix
@@ -0,0 +1,49 @@
+# at-spi2-core daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.at-spi2-core = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable at-spi2-core, a service for the Assistive Technologies
+          available on the GNOME platform.
+
+          Enable this if you get the error or warning
+          <literal>The name org.a11y.Bus was not provided by any .service files</literal>.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkMerge [
+    (mkIf config.services.gnome3.at-spi2-core.enable {
+      environment.systemPackages = [ pkgs.at-spi2-core ];
+      services.dbus.packages = [ pkgs.at-spi2-core ];
+      systemd.packages = [ pkgs.at-spi2-core ];
+    })
+
+    (mkIf (!config.services.gnome3.at-spi2-core.enable) {
+      environment.variables.NO_AT_BRIDGE = "1";
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix
new file mode 100644
index 000000000000..3c7f217b18df
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix
@@ -0,0 +1,33 @@
+# Chrome GNOME Shell native host connector.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+  options = {
+    services.gnome3.chrome-gnome-shell.enable = mkEnableOption ''
+      Chrome GNOME Shell native host connector, a DBus service
+      allowing to install GNOME Shell extensions from a web browser.
+    '';
+  };
+
+
+  ###### implementation
+  config = mkIf config.services.gnome3.chrome-gnome-shell.enable {
+    environment.etc = {
+      "chromium/native-messaging-hosts/org.gnome.chrome_gnome_shell.json".source = "${pkgs.chrome-gnome-shell}/etc/chromium/native-messaging-hosts/org.gnome.chrome_gnome_shell.json";
+      "opt/chrome/native-messaging-hosts/org.gnome.chrome_gnome_shell.json".source = "${pkgs.chrome-gnome-shell}/etc/opt/chrome/native-messaging-hosts/org.gnome.chrome_gnome_shell.json";
+    };
+
+    environment.systemPackages = [ pkgs.chrome-gnome-shell ];
+
+    services.dbus.packages = [ pkgs.chrome-gnome-shell ];
+
+    nixpkgs.config.firefox.enableGnomeExtensions = true;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/evolution-data-server.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/evolution-data-server.nix
new file mode 100644
index 000000000000..bd62d16f61ce
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/evolution-data-server.nix
@@ -0,0 +1,45 @@
+# Evolution Data Server daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.evolution-data-server = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Evolution Data Server, a collection of services for
+          storing addressbooks and calendars.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.evolution-data-server.enable {
+
+    environment.systemPackages = [ pkgs.gnome3.evolution-data-server ];
+
+    services.dbus.packages = [ pkgs.gnome3.evolution-data-server ];
+
+    systemd.packages = [ pkgs.gnome3.evolution-data-server ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/glib-networking.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/glib-networking.nix
new file mode 100644
index 000000000000..7e667b6b1f04
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/glib-networking.nix
@@ -0,0 +1,37 @@
+# GLib Networking
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.glib-networking = {
+
+      enable = mkEnableOption "network extensions for GLib";
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.glib-networking.enable {
+
+    services.dbus.packages = [ pkgs.glib-networking ];
+
+    systemd.packages = [ pkgs.glib-networking ];
+
+    environment.variables.GIO_EXTRA_MODULES = [ "${pkgs.glib-networking.out}/lib/gio/modules" ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix
new file mode 100644
index 000000000000..c391ad9694c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix
@@ -0,0 +1,90 @@
+# GNOME Initial Setup.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  # GNOME initial setup's run is conditioned on whether
+  # the gnome-initial-setup-done file exists in XDG_CONFIG_HOME
+  # Because of this, every existing user will have initial setup
+  # running because they never ran it before.
+  #
+  # To prevent this we create the file if the users stateVersion
+  # is older than 20.03 (the release we added this module).
+
+  script = pkgs.writeScript "create-gis-stamp-files" ''
+    #!${pkgs.runtimeShell}
+    setup_done=$HOME/.config/gnome-initial-setup-done
+
+    echo "Creating g-i-s stamp file $setup_done ..."
+    cat - > $setup_done <<- EOF
+    yes
+    EOF
+  '';
+
+  createGisStampFilesAutostart = pkgs.writeTextFile rec {
+    name = "create-g-i-s-stamp-files";
+    destination = "/etc/xdg/autostart/${name}.desktop";
+    text = ''
+      [Desktop Entry]
+      Type=Application
+      Name=Create GNOME Initial Setup stamp files
+      Exec=${script}
+      StartupNotify=false
+      NoDisplay=true
+      OnlyShowIn=GNOME;
+      AutostartCondition=unless-exists gnome-initial-setup-done
+      X-GNOME-Autostart-Phase=EarlyInitialization
+    '';
+  };
+
+in
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.gnome-initial-setup = {
+
+      enable = mkEnableOption "GNOME Initial Setup, a Simple, easy, and safe way to prepare a new system";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.gnome-initial-setup.enable {
+
+    environment.systemPackages = [
+      pkgs.gnome3.gnome-initial-setup
+    ]
+    ++ optional (versionOlder config.system.stateVersion "20.03") createGisStampFilesAutostart
+    ;
+
+    systemd.packages = [
+      pkgs.gnome3.gnome-initial-setup
+    ];
+
+    systemd.user.targets."gnome-session".wants = [
+      "gnome-initial-setup-copy-worker.service"
+      "gnome-initial-setup-first-login.service"
+      "gnome-welcome-tour.service"
+    ];
+
+    systemd.user.targets."gnome-session@gnome-initial-setup".wants = [
+      "gnome-initial-setup.service"
+    ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-keyring.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
new file mode 100644
index 000000000000..2916a3c82b34
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
@@ -0,0 +1,53 @@
+# GNOME Keyring daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.gnome-keyring = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable GNOME Keyring daemon, a service designed to
+          take care of the user's security credentials,
+          such as user names and passwords.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.gnome-keyring.enable {
+
+    environment.systemPackages = [ pkgs.gnome3.gnome-keyring ];
+
+    services.dbus.packages = [ pkgs.gnome3.gnome-keyring pkgs.gcr ];
+
+    xdg.portal.extraPortals = [ pkgs.gnome3.gnome-keyring ];
+
+    security.pam.services.login.enableGnomeKeyring = true;
+
+    security.wrappers.gnome-keyring-daemon = {
+      source = "${pkgs.gnome3.gnome-keyring}/bin/gnome-keyring-daemon";
+      capabilities = "cap_ipc_lock=ep";
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
new file mode 100644
index 000000000000..3f9ced5e86b1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
@@ -0,0 +1,43 @@
+# GNOME Online Accounts daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.gnome-online-accounts = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable GNOME Online Accounts daemon, a service that provides
+          a single sign-on framework for the GNOME desktop.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.gnome-online-accounts.enable {
+
+    environment.systemPackages = [ pkgs.gnome-online-accounts ];
+
+    services.dbus.packages = [ pkgs.gnome-online-accounts ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix
new file mode 100644
index 000000000000..39d669e8b30f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-online-miners.nix
@@ -0,0 +1,43 @@
+# GNOME Online Miners daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.gnome-online-miners = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable GNOME Online Miners, a service that
+          crawls through your online content.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.gnome-online-miners.enable {
+
+    environment.systemPackages = [ pkgs.gnome3.gnome-online-miners ];
+
+    services.dbus.packages = [ pkgs.gnome3.gnome-online-miners ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix
new file mode 100644
index 000000000000..164a0a44f8c8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix
@@ -0,0 +1,24 @@
+# Remote desktop daemon using Pipewire.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+  options = {
+    services.gnome3.gnome-remote-desktop = {
+      enable = mkEnableOption "Remote Desktop support using Pipewire";
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.services.gnome3.gnome-remote-desktop.enable {
+    services.pipewire.enable = true;
+
+    systemd.packages = [ pkgs.gnome3.gnome-remote-desktop ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
new file mode 100644
index 000000000000..1c33ed064a19
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
@@ -0,0 +1,78 @@
+# GNOME Settings Daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.gnome3.gnome-settings-daemon;
+
+in
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  imports = [
+    (mkRemovedOptionModule
+      ["services" "gnome3" "gnome-settings-daemon" "package"]
+      "")
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.gnome-settings-daemon = {
+
+      enable = mkEnableOption "GNOME Settings Daemon";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [
+      pkgs.gnome3.gnome-settings-daemon
+    ];
+
+    services.udev.packages = [
+      pkgs.gnome3.gnome-settings-daemon
+    ];
+
+    systemd.packages = [
+      pkgs.gnome3.gnome-settings-daemon
+    ];
+
+    systemd.user.targets."gnome-session-initialized".wants = [
+      "gsd-color.target"
+      "gsd-datetime.target"
+      "gsd-keyboard.target"
+      "gsd-media-keys.target"
+      "gsd-print-notifications.target"
+      "gsd-rfkill.target"
+      "gsd-screensaver-proxy.target"
+      "gsd-sharing.target"
+      "gsd-smartcard.target"
+      "gsd-sound.target"
+      "gsd-wacom.target"
+      "gsd-wwan.target"
+      "gsd-a11y-settings.target"
+      "gsd-housekeeping.target"
+      "gsd-power.target"
+    ];
+
+    systemd.user.targets."gnome-session-x11-services".wants = [
+      "gsd-xsettings.target"
+    ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-user-share.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
new file mode 100644
index 000000000000..f2fe8b41a9e2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
@@ -0,0 +1,40 @@
+# GNOME User Share daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.gnome-user-share = {
+
+      enable = mkEnableOption "GNOME User Share, a user-level file sharing service for GNOME";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.gnome-user-share.enable {
+
+    environment.systemPackages = [
+      pkgs.gnome3.gnome-user-share
+    ];
+
+    systemd.packages = [
+      pkgs.gnome3.gnome-user-share
+    ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/rygel.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/rygel.nix
new file mode 100644
index 000000000000..917a1d6541e0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/rygel.nix
@@ -0,0 +1,36 @@
+# rygel service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+  options = {
+    services.gnome3.rygel = {
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable Rygel UPnP Mediaserver.
+
+          You will need to also allow UPnP connections in firewall, see the following <link xlink:href="https://github.com/NixOS/nixpkgs/pull/45045#issuecomment-416030795">comment</link>.
+        '';
+        type = types.bool;
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.services.gnome3.rygel.enable {
+    environment.systemPackages = [ pkgs.gnome3.rygel ];
+
+    services.dbus.packages = [ pkgs.gnome3.rygel ];
+
+    systemd.packages = [ pkgs.gnome3.rygel ];
+
+    environment.etc."rygel.conf".source = "${pkgs.gnome3.rygel}/etc/rygel.conf";
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/sushi.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/sushi.nix
new file mode 100644
index 000000000000..83b17365d5dd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/sushi.nix
@@ -0,0 +1,42 @@
+# GNOME Sushi daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.sushi = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Sushi, a quick previewer for nautilus.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.sushi.enable {
+
+    environment.systemPackages = [ pkgs.gnome3.sushi ];
+
+    services.dbus.packages = [ pkgs.gnome3.sushi ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/tracker-miners.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/tracker-miners.nix
new file mode 100644
index 000000000000..f2af40249271
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/tracker-miners.nix
@@ -0,0 +1,44 @@
+# Tracker Miners daemons.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.tracker-miners = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Tracker miners, indexing services for Tracker
+          search engine and metadata storage system.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.tracker-miners.enable {
+
+    environment.systemPackages = [ pkgs.tracker-miners ];
+
+    services.dbus.packages = [ pkgs.tracker-miners ];
+
+    systemd.packages = [ pkgs.tracker-miners ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gnome3/tracker.nix b/nixpkgs/nixos/modules/services/desktops/gnome3/tracker.nix
new file mode 100644
index 000000000000..cd196e385539
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gnome3/tracker.nix
@@ -0,0 +1,45 @@
+# Tracker daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.tracker = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Tracker services, a search engine,
+          search tool and metadata storage system.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.tracker.enable {
+
+    environment.systemPackages = [ pkgs.tracker ];
+
+    services.dbus.packages = [ pkgs.tracker ];
+
+    systemd.packages = [ pkgs.tracker ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gsignond.nix b/nixpkgs/nixos/modules/services/desktops/gsignond.nix
new file mode 100644
index 000000000000..5ab9add9f32d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gsignond.nix
@@ -0,0 +1,45 @@
+# Accounts-SSO gSignOn daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  package = pkgs.gsignond.override { plugins = config.services.gsignond.plugins; };
+in
+{
+
+  meta.maintainers = pkgs.pantheon.maintainers;
+
+  ###### interface
+
+  options = {
+
+    services.gsignond = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable gSignOn daemon, a DBus service
+          which performs user authentication on behalf of its clients.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = ''
+          What plugins to use with the gSignOn daemon.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.services.gsignond.enable {
+    environment.etc."gsignond.conf".source = "${package}/etc/gsignond.conf";
+    services.dbus.packages = [ package ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/gvfs.nix b/nixpkgs/nixos/modules/services/desktops/gvfs.nix
new file mode 100644
index 000000000000..250ea6d4575f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/gvfs.nix
@@ -0,0 +1,63 @@
+# GVfs
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.gvfs;
+
+in
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  # Added 2019-08-19
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "gnome3" "gvfs" "enable" ]
+      [ "services" "gvfs" "enable" ])
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.gvfs = {
+
+      enable = mkEnableOption "GVfs, a userspace virtual filesystem";
+
+      # gvfs can be built with multiple configurations
+      package = mkOption {
+        type = types.package;
+        default = pkgs.gnome3.gvfs;
+        description = "Which GVfs package to use.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    services.dbus.packages = [ cfg.package ];
+
+    systemd.packages = [ cfg.package ];
+
+    services.udev.packages = [ pkgs.libmtp.bin ];
+
+    # Needed for unwrapped applications
+    environment.variables.GIO_EXTRA_MODULES = [ "${cfg.package}/lib/gio/modules" ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/malcontent.nix b/nixpkgs/nixos/modules/services/desktops/malcontent.nix
new file mode 100644
index 000000000000..5d6912595b52
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/malcontent.nix
@@ -0,0 +1,37 @@
+# Malcontent daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.malcontent = {
+
+      enable = mkEnableOption "Malcontent, parental control support for applications";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.malcontent.enable {
+
+    environment.systemPackages = with pkgs; [
+      malcontent
+      malcontent-ui
+    ];
+
+    services.dbus.packages = [ pkgs.malcontent ];
+
+    services.accounts-daemon.enable = true;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/neard.nix b/nixpkgs/nixos/modules/services/desktops/neard.nix
new file mode 100644
index 000000000000..9b0f8d1b3a77
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/neard.nix
@@ -0,0 +1,23 @@
+# neard service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  ###### interface
+  options = {
+    services.neard = {
+      enable = mkEnableOption "neard, NFC daemon";
+    };
+  };
+
+
+  ###### implementation
+  config = mkIf config.services.neard.enable {
+    environment.systemPackages = [ pkgs.neard ];
+
+    services.dbus.packages = [ pkgs.neard ];
+
+    systemd.packages = [ pkgs.neard ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/pantheon/files.nix b/nixpkgs/nixos/modules/services/desktops/pantheon/files.nix
new file mode 100644
index 000000000000..8cee9f42b62f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/pantheon/files.nix
@@ -0,0 +1,13 @@
+# pantheon files daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "pantheon" "files" "enable" ] "Use `environment.systemPackages [ pkgs.pantheon.elementary-files ];`")
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/pipewire.nix b/nixpkgs/nixos/modules/services/desktops/pipewire.nix
new file mode 100644
index 000000000000..5aee59cfdcce
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/pipewire.nix
@@ -0,0 +1,41 @@
+# pipewire service.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pipewire;
+  packages = with pkgs; [ pipewire ];
+
+in {
+
+  meta = {
+    maintainers = teams.freedesktop.members;
+  };
+
+  ###### interface
+  options = {
+    services.pipewire = {
+      enable = mkEnableOption "pipewire service";
+
+      socketActivation = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Automatically run pipewire when connections are made to the pipewire socket.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    environment.systemPackages = packages;
+
+    systemd.packages = packages;
+
+    systemd.user.sockets.pipewire.wantedBy = lib.mkIf cfg.socketActivation [ "sockets.target" ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/profile-sync-daemon.nix b/nixpkgs/nixos/modules/services/desktops/profile-sync-daemon.nix
new file mode 100644
index 000000000000..a8ac22ac1276
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/profile-sync-daemon.nix
@@ -0,0 +1,77 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.psd;
+in {
+  options.services.psd = with types; {
+    enable = mkOption {
+      type = bool;
+      default = false;
+      description = ''
+        Whether to enable the Profile Sync daemon.
+      '';
+    };
+    resyncTimer = mkOption {
+      type = str;
+      default = "1h";
+      example = "1h 30min";
+      description = ''
+        The amount of time to wait before syncing browser profiles back to the
+        disk.
+
+        Takes a systemd.unit time span. The time unit defaults to seconds if
+        omitted.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd = {
+      user = {
+        services = {
+          psd = {
+            enable = true;
+            description = "Profile Sync daemon";
+            wants = [ "psd-resync.service" ];
+            wantedBy = [ "default.target" ];
+            path = with pkgs; [ rsync kmod gawk nettools utillinux profile-sync-daemon ];
+            unitConfig = {
+              RequiresMountsFor = [ "/home/" ];
+            };
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = "yes";
+              ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon sync";
+              ExecStop = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon unsync";
+            };
+          };
+
+          psd-resync = {
+            enable = true;
+            description = "Timed profile resync";
+            after = [ "psd.service" ];
+            wants = [ "psd-resync.timer" ];
+            partOf = [ "psd.service" ];
+            wantedBy = [ "default.target" ];
+            path = with pkgs; [ rsync kmod gawk nettools utillinux profile-sync-daemon ];
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon resync";
+            };
+          };
+        };
+
+        timers.psd-resync = {
+          description = "Timer for profile sync daemon - ${cfg.resyncTimer}";
+          partOf = [ "psd-resync.service" "psd.service" ];
+
+          timerConfig = {
+            OnUnitActiveSec = "${cfg.resyncTimer}";
+          };
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix b/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix
new file mode 100644
index 000000000000..09c68c587b43
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/system-config-printer.nix
@@ -0,0 +1,41 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.system-config-printer = {
+
+      enable = mkEnableOption "system-config-printer, a service for CUPS administration used by printing interfaces";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.system-config-printer.enable {
+
+    services.dbus.packages = [
+      pkgs.system-config-printer
+    ];
+
+    systemd.packages = [
+      pkgs.system-config-printer
+    ];
+
+    services.udev.packages = [
+      pkgs.system-config-printer
+    ];
+
+    # for $out/bin/install-printer-driver
+    services.packagekit.enable = true;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/telepathy.nix b/nixpkgs/nixos/modules/services/desktops/telepathy.nix
new file mode 100644
index 000000000000..34596bf78184
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/telepathy.nix
@@ -0,0 +1,43 @@
+# Telepathy daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.telepathy = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Telepathy service, a communications framework
+          that enables real-time communication via pluggable protocol backends.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.telepathy.enable {
+
+    environment.systemPackages = [ pkgs.telepathy-mission-control ];
+
+    services.dbus.packages = [ pkgs.telepathy-mission-control ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/tumbler.nix b/nixpkgs/nixos/modules/services/desktops/tumbler.nix
new file mode 100644
index 000000000000..a09079517f04
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/tumbler.nix
@@ -0,0 +1,52 @@
+# Tumbler
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.tumbler;
+
+in
+
+{
+
+  imports = [
+    (mkRemovedOptionModule
+      [ "services" "tumbler" "package" ]
+      "")
+  ];
+
+  meta = {
+    maintainers = with maintainers; [ worldofpeace ];
+  };
+
+  ###### interface
+
+  options = {
+
+    services.tumbler = {
+
+      enable = mkEnableOption "Tumbler, A D-Bus thumbnailer service";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = with pkgs.xfce; [
+      tumbler
+    ];
+
+    services.dbus.packages = with pkgs.xfce; [
+      tumbler
+    ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix b/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix
new file mode 100644
index 000000000000..cf7dd5fe3a13
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/desktops/zeitgeist.nix
@@ -0,0 +1,31 @@
+# Zeitgeist
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  meta = {
+    maintainers = with maintainers; [ worldofpeace ];
+  };
+
+  ###### interface
+
+  options = {
+    services.zeitgeist = {
+      enable = mkEnableOption "zeitgeist";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.zeitgeist.enable {
+
+    environment.systemPackages = [ pkgs.zeitgeist ];
+
+    services.dbus.packages = [ pkgs.zeitgeist ];
+
+    systemd.packages = [ pkgs.zeitgeist ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/development/bloop.nix b/nixpkgs/nixos/modules/services/development/bloop.nix
new file mode 100644
index 000000000000..226718a9e80a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/bloop.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.bloop;
+
+in {
+
+  options.services.bloop = {
+    extraOptions = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [
+        "-J-Xmx2G"
+        "-J-XX:MaxInlineLevel=20"
+        "-J-XX:+UseParallelGC"
+      ];
+      description = ''
+        Specifies additional command line argument to pass to bloop
+        java process.
+      '';
+    };
+
+    install = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to install a user service for the Bloop server.
+
+        The service must be manually started for each user with
+        "systemctl --user start bloop".
+      '';
+    };
+  };
+
+  config = mkIf (cfg.install) {
+    systemd.user.services.bloop = {
+      description = "Bloop Scala build server";
+
+      environment = {
+        PATH = mkForce "${makeBinPath [ config.programs.java.package ]}";
+      };
+      serviceConfig = {
+        Type        = "simple";
+        ExecStart   = ''${pkgs.bloop}/bin/bloop server'';
+        Restart     = "always";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.bloop ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/development/hoogle.nix b/nixpkgs/nixos/modules/services/development/hoogle.nix
new file mode 100644
index 000000000000..1a98f005602a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/hoogle.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.hoogle;
+
+  hoogleEnv = pkgs.buildEnv {
+    name = "hoogle";
+    paths = [ (cfg.haskellPackages.ghcWithHoogle cfg.packages) ];
+  };
+
+in {
+
+  options.services.hoogle = {
+    enable = mkEnableOption "Haskell documentation server";
+
+    port = mkOption {
+      type = types.int;
+      default = 8080;
+      description = ''
+        Port number Hoogle will be listening to.
+      '';
+    };
+
+    packages = mkOption {
+      default = hp: [];
+      defaultText = "hp: []";
+      example = "hp: with hp; [ text lens ]";
+      description = ''
+        The Haskell packages to generate documentation for.
+
+        The option value is a function that takes the package set specified in
+        the <varname>haskellPackages</varname> option as its sole parameter and
+        returns a list of packages.
+      '';
+    };
+
+    haskellPackages = mkOption {
+      description = "Which haskell package set to use.";
+      default = pkgs.haskellPackages;
+      defaultText = "pkgs.haskellPackages";
+    };
+
+    home = mkOption {
+      type = types.str;
+      description = "Url for hoogle logo";
+      default = "https://hoogle.haskell.org";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.hoogle = {
+      description = "Haskell documentation server";
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Restart = "always";
+        ExecStart = ''${hoogleEnv}/bin/hoogle server --local --port ${toString cfg.port} --home ${cfg.home}'';
+
+        User = "nobody";
+        Group = "nogroup";
+
+        PrivateTmp = true;
+        ProtectHome = true;
+
+        RuntimeDirectory = "hoogle";
+        WorkingDirectory = "%t/hoogle";
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/development/jupyter/default.nix b/nixpkgs/nixos/modules/services/development/jupyter/default.nix
new file mode 100644
index 000000000000..e598b0186450
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/jupyter/default.nix
@@ -0,0 +1,185 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.jupyter;
+
+  # NOTE: We don't use top-level jupyter because we don't
+  # want to pass in JUPYTER_PATH but use .environment instead,
+  # saving a rebuild.
+  package = pkgs.python3.pkgs.notebook;
+
+  kernels = (pkgs.jupyter-kernel.create  {
+    definitions = if cfg.kernels != null
+      then cfg.kernels
+      else  pkgs.jupyter-kernel.default;
+  });
+
+  notebookConfig = pkgs.writeText "jupyter_config.py" ''
+    ${cfg.notebookConfig}
+
+    c.NotebookApp.password = ${cfg.password}
+  '';
+
+in {
+  meta.maintainers = with maintainers; [ aborsu ];
+
+  options.services.jupyter = {
+    enable = mkEnableOption "Jupyter development server";
+
+    ip = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = ''
+        IP address Jupyter will be listening on.
+      '';
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 8888;
+      description = ''
+        Port number Jupyter will be listening on.
+      '';
+    };
+
+    notebookDir = mkOption {
+      type = types.str;
+      default = "~/";
+      description = ''
+        Root directory for notebooks.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "jupyter";
+      description = ''
+        Name of the user used to run the jupyter service.
+        For security reason, jupyter should really not be run as root.
+        If not set (jupyter), the service will create a jupyter user with appropriate settings.
+      '';
+      example = "aborsu";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "jupyter";
+      description = ''
+        Name of the group used to run the jupyter service.
+        Use this if you want to create a group of users that are able to view the notebook directory's content.
+      '';
+      example = "users";
+    };
+
+    password = mkOption {
+      type = types.str;
+      description = ''
+        Password to use with notebook.
+        Can be generated using:
+          In [1]: from notebook.auth import passwd
+          In [2]: passwd('test')
+          Out[2]: 'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'
+          NOTE: you need to keep the single quote inside the nix string.
+        Or you can use a python oneliner:
+          "open('/path/secret_file', 'r', encoding='utf8').read().strip()"
+        It will be interpreted at the end of the notebookConfig.
+      '';
+      example = [
+        "'sha1:1b961dc713fb:88483270a63e57d18d43cf337e629539de1436ba'"
+        "open('/path/secret_file', 'r', encoding='utf8').read().strip()"
+      ];
+    };
+
+    notebookConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Raw jupyter config.
+      '';
+    };
+
+    kernels = mkOption {
+      type = types.nullOr (types.attrsOf(types.submodule (import ./kernel-options.nix {
+        inherit lib;
+      })));
+
+      default = null;
+      example = literalExample ''
+        {
+          python3 = let
+            env = (pkgs.python3.withPackages (pythonPackages: with pythonPackages; [
+                    ipykernel
+                    pandas
+                    scikitlearn
+                  ]));
+          in {
+            displayName = "Python 3 for machine learning";
+            argv = [
+              "''${env.interpreter}"
+              "-m"
+              "ipykernel_launcher"
+              "-f"
+              "{connection_file}"
+            ];
+            language = "python";
+            logo32 = "''${env.sitePackages}/ipykernel/resources/logo-32x32.png";
+            logo64 = "''${env.sitePackages}/ipykernel/resources/logo-64x64.png";
+          };
+        }
+      '';
+      description = "Declarative kernel config
+
+      Kernels can be declared in any language that supports and has the required
+      dependencies to communicate with a jupyter server.
+      In python's case, it means that ipykernel package must always be included in
+      the list of packages of the targeted environment.
+      ";
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.enable  {
+      systemd.services.jupyter = {
+        description = "Jupyter development server";
+
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        # TODO: Patch notebook so we can explicitly pass in a shell
+        path = [ pkgs.bash ]; # needed for sh in cell magic to work
+
+        environment = {
+          JUPYTER_PATH = toString kernels;
+        };
+
+        serviceConfig = {
+          Restart = "always";
+          ExecStart = ''${package}/bin/jupyter-notebook \
+            --no-browser \
+            --ip=${cfg.ip} \
+            --port=${toString cfg.port} --port-retries 0 \
+            --notebook-dir=${cfg.notebookDir} \
+            --NotebookApp.config_file=${notebookConfig}
+          '';
+          User = cfg.user;
+          Group = cfg.group;
+          WorkingDirectory = "~";
+        };
+      };
+    })
+    (mkIf (cfg.enable && (cfg.group == "jupyter")) {
+      users.groups.jupyter = {};
+    })
+    (mkIf (cfg.enable && (cfg.user == "jupyter")) {
+      users.extraUsers.jupyter = {
+        extraGroups = [ cfg.group ];
+        home = "/var/lib/jupyter";
+        createHome = true;
+        useDefaultShell = true; # needed so that the user can start a terminal.
+      };
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix b/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix
new file mode 100644
index 000000000000..03547637449a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/jupyter/kernel-options.nix
@@ -0,0 +1,60 @@
+# Options that can be used for creating a jupyter kernel.
+{lib }:
+
+with lib;
+
+{
+  options = {
+
+    displayName = mkOption {
+      type = types.str;
+      default = "";
+      example = [
+        "Python 3"
+        "Python 3 for Data Science"
+      ];
+      description = ''
+        Name that will be shown to the user.
+      '';
+    };
+
+    argv = mkOption {
+      type = types.listOf types.str;
+      example = [
+        "{customEnv.interpreter}"
+        "-m"
+        "ipykernel_launcher"
+        "-f"
+        "{connection_file}"
+      ];
+      description = ''
+        Command and arguments to start the kernel.
+      '';
+    };
+
+    language = mkOption {
+      type = types.str;
+      example = "python";
+      description = ''
+        Language of the environment. Typically the name of the binary.
+      '';
+    };
+
+    logo32 = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "{env.sitePackages}/ipykernel/resources/logo-32x32.png";
+      description = ''
+        Path to 32x32 logo png.
+      '';
+    };
+    logo64 = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "{env.sitePackages}/ipykernel/resources/logo-64x64.png";
+      description = ''
+        Path to 64x64 logo png.
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/development/lorri.nix b/nixpkgs/nixos/modules/services/development/lorri.nix
new file mode 100644
index 000000000000..c843aa56d133
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/development/lorri.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.lorri;
+  socketPath = "lorri/daemon.socket";
+in {
+  options = {
+    services.lorri = {
+      enable = lib.mkOption {
+        default = false;
+        type = lib.types.bool;
+        description = ''
+          Enables the daemon for `lorri`, a nix-shell replacement for project
+          development. The socket-activated daemon starts on the first request
+          issued by the `lorri` command.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.user.sockets.lorri = {
+      description = "Socket for Lorri Daemon";
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        ListenStream = "%t/${socketPath}";
+        RuntimeDirectory = "lorri";
+      };
+    };
+
+    systemd.user.services.lorri = {
+      description = "Lorri Daemon";
+      requires = [ "lorri.socket" ];
+      after = [ "lorri.socket" ];
+      path = with pkgs; [ config.nix.package git gnutar gzip ];
+      serviceConfig = {
+        ExecStart = "${pkgs.lorri}/bin/lorri daemon";
+        PrivateTmp = true;
+        ProtectSystem = "strict";
+        ProtectHome = "read-only";
+        Restart = "on-failure";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.lorri ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/editors/emacs.nix b/nixpkgs/nixos/modules/services/editors/emacs.nix
new file mode 100644
index 000000000000..d791b387665f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/editors/emacs.nix
@@ -0,0 +1,102 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.emacs;
+
+  editorScript = pkgs.writeScriptBin "emacseditor" ''
+    #!${pkgs.runtimeShell}
+    if [ -z "$1" ]; then
+      exec ${cfg.package}/bin/emacsclient --create-frame --alternate-editor ${cfg.package}/bin/emacs
+    else
+      exec ${cfg.package}/bin/emacsclient --alternate-editor ${cfg.package}/bin/emacs "$@"
+    fi
+  '';
+
+desktopApplicationFile = pkgs.writeTextFile {
+  name = "emacsclient.desktop";
+  destination = "/share/applications/emacsclient.desktop";
+  text = ''
+[Desktop Entry]
+Name=Emacsclient
+GenericName=Text Editor
+Comment=Edit text
+MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
+Exec=emacseditor %F
+Icon=emacs
+Type=Application
+Terminal=false
+Categories=Development;TextEditor;
+StartupWMClass=Emacs
+Keywords=Text;Editor;
+'';
+};
+
+in {
+
+  options.services.emacs = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable a user service for the Emacs daemon. Use <literal>emacsclient</literal> to connect to the
+        daemon. If <literal>true</literal>, <varname>services.emacs.install</varname> is
+        considered <literal>true</literal>, whatever its value.
+      '';
+    };
+
+    install = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to install a user service for the Emacs daemon. Once
+        the service is started, use emacsclient to connect to the
+        daemon.
+
+        The service must be manually started for each user with
+        "systemctl --user start emacs" or globally through
+        <varname>services.emacs.enable</varname>.
+      '';
+    };
+
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.emacs;
+      defaultText = "pkgs.emacs";
+      description = ''
+        emacs derivation to use.
+      '';
+    };
+
+    defaultEditor = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        When enabled, configures emacsclient to be the default editor
+        using the EDITOR environment variable.
+      '';
+    };
+  };
+
+  config = mkIf (cfg.enable || cfg.install) {
+    systemd.user.services.emacs = {
+      description = "Emacs: the extensible, self-documenting text editor";
+
+      serviceConfig = {
+        Type      = "forking";
+        ExecStart = "${pkgs.bash}/bin/bash -c 'source ${config.system.build.setEnvironment}; exec ${cfg.package}/bin/emacs --daemon'";
+        ExecStop  = "${cfg.package}/bin/emacsclient --eval (kill-emacs)";
+        Restart   = "always";
+      };
+    } // optionalAttrs cfg.enable { wantedBy = [ "default.target" ]; };
+
+    environment.systemPackages = [ cfg.package editorScript desktopApplicationFile ];
+
+    environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "${editorScript}/bin/emacseditor");
+  };
+
+  meta.doc = ./emacs.xml;
+}
diff --git a/nixpkgs/nixos/modules/services/editors/emacs.xml b/nixpkgs/nixos/modules/services/editors/emacs.xml
new file mode 100644
index 000000000000..74c60014dcea
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/editors/emacs.xml
@@ -0,0 +1,580 @@
+<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-emacs">
+ <title>Emacs</title>
+<!--
+    Documentation contributors:
+      Damien Cassou @DamienCassou
+      Thomas Tuegel @ttuegel
+      Rodney Lorrimar @rvl
+      Adam Hoese @adisbladis
+  -->
+ <para>
+  <link xlink:href="https://www.gnu.org/software/emacs/">Emacs</link> is an
+  extensible, customizable, self-documenting real-time display editor — and
+  more. At its core is an interpreter for Emacs Lisp, a dialect of the Lisp
+  programming language with extensions to support text editing.
+ </para>
+ <para>
+  Emacs runs within a graphical desktop environment using the X Window System,
+  but works equally well on a text terminal. Under
+  <productname>macOS</productname>, a "Mac port" edition is available, which
+  uses Apple's native GUI frameworks.
+ </para>
+ <para>
+  <productname>Nixpkgs</productname> provides a superior environment for
+  running <application>Emacs</application>. It's simple to create custom builds
+  by overriding the default packages. Chaotic collections of Emacs Lisp code
+  and extensions can be brought under control using declarative package
+  management. <productname>NixOS</productname> even provides a
+  <command>systemd</command> user service for automatically starting the Emacs
+  daemon.
+ </para>
+ <section xml:id="module-services-emacs-installing">
+  <title>Installing <application>Emacs</application></title>
+
+  <para>
+   Emacs can be installed in the normal way for Nix (see
+   <xref linkend="sec-package-management" />). In addition, a NixOS
+   <emphasis>service</emphasis> can be enabled.
+  </para>
+
+  <section xml:id="module-services-emacs-releases">
+   <title>The Different Releases of Emacs</title>
+
+   <para>
+    <productname>Nixpkgs</productname> defines several basic Emacs packages.
+    The following are attributes belonging to the <varname>pkgs</varname> set:
+    <variablelist>
+     <varlistentry>
+      <term>
+       <varname>emacs</varname>
+      </term>
+      <term>
+       <varname>emacs25</varname>
+      </term>
+      <listitem>
+       <para>
+        The latest stable version of Emacs 25 using the
+        <link
+                xlink:href="http://www.gtk.org">GTK 2</link>
+        widget toolkit.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term>
+       <varname>emacs25-nox</varname>
+      </term>
+      <listitem>
+       <para>
+        Emacs 25 built without any dependency on X11 libraries.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term>
+       <varname>emacsMacport</varname>
+      </term>
+      <term>
+       <varname>emacs25Macport</varname>
+      </term>
+      <listitem>
+       <para>
+        Emacs 25 with the "Mac port" patches, providing a more native look and
+        feel under macOS.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+
+   <para>
+    If those aren't suitable, then the following imitation Emacs editors are
+    also available in Nixpkgs:
+    <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>,
+    <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>,
+    <link xlink:href="http://yi-editor.github.io/">Yi</link>,
+    <link xlink:href="https://joe-editor.sourceforge.io/">jmacs</link>.
+   </para>
+  </section>
+
+  <section xml:id="module-services-emacs-adding-packages">
+   <title>Adding Packages to Emacs</title>
+
+   <para>
+    Emacs includes an entire ecosystem of functionality beyond text editing,
+    including a project planner, mail and news reader, debugger interface,
+    calendar, and more.
+   </para>
+
+   <para>
+    Most extensions are gotten with the Emacs packaging system
+    (<filename>package.el</filename>) from
+    <link
+        xlink:href="https://elpa.gnu.org/">Emacs Lisp Package Archive
+    (<acronym>ELPA</acronym>)</link>,
+    <link xlink:href="https://melpa.org/"><acronym>MELPA</acronym></link>,
+    <link xlink:href="https://stable.melpa.org/">MELPA Stable</link>, and
+    <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>. Nixpkgs is
+    regularly updated to mirror all these archives.
+   </para>
+
+   <para>
+    Under NixOS, you can continue to use
+    <function>package-list-packages</function> and
+    <function>package-install</function> to install packages. You can also
+    declare the set of Emacs packages you need using the derivations from
+    Nixpkgs. The rest of this section discusses declarative installation of
+    Emacs packages through nixpkgs.
+   </para>
+
+   <para>
+    The first step to declare the list of packages you want in your Emacs
+    installation is to create a dedicated derivation. This can be done in a
+    dedicated <filename>emacs.nix</filename> file such as:
+    <example xml:id="ex-emacsNix">
+     <title>Nix expression to build Emacs with packages (<filename>emacs.nix</filename>)</title>
+<programlisting language="nix">
+/*
+This is a nix expression to build Emacs and some Emacs packages I like
+from source on any distribution where Nix is installed. This will install
+all the dependencies from the nixpkgs repository and build the binary files
+without interfering with the host distribution.
+
+To build the project, type the following from the current directory:
+
+$ nix-build emacs.nix
+
+To run the newly compiled executable:
+
+$ ./result/bin/emacs
+*/
+{ pkgs ? import &lt;nixpkgs&gt; {} }: <co xml:id="ex-emacsNix-1" />
+
+let
+  myEmacs = pkgs.emacs; <co xml:id="ex-emacsNix-2" />
+  emacsWithPackages = (pkgs.emacsPackagesGen myEmacs).emacsWithPackages; <co xml:id="ex-emacsNix-3" />
+in
+  emacsWithPackages (epkgs: (with epkgs.melpaStablePackages; [ <co xml:id="ex-emacsNix-4" />
+    magit          # ; Integrate git &lt;C-x g&gt;
+    zerodark-theme # ; Nicolas' theme
+  ]) ++ (with epkgs.melpaPackages; [ <co xml:id="ex-emacsNix-5" />
+    undo-tree      # ; &lt;C-x u&gt; to show the undo tree
+    zoom-frm       # ; increase/decrease font size for all buffers %lt;C-x C-+&gt;
+  ]) ++ (with epkgs.elpaPackages; [ <co xml:id="ex-emacsNix-6" />
+    auctex         # ; LaTeX mode
+    beacon         # ; highlight my cursor when scrolling
+    nameless       # ; hide current package name everywhere in elisp code
+  ]) ++ [
+    pkgs.notmuch   # From main packages set <co xml:id="ex-emacsNix-7" />
+  ])
+</programlisting>
+    </example>
+    <calloutlist>
+     <callout arearefs="ex-emacsNix-1">
+      <para>
+       The first non-comment line in this file (<literal>{ pkgs ? ...
+       }</literal>) indicates that the whole file represents a function.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-2">
+      <para>
+       The <varname>let</varname> expression below defines a
+       <varname>myEmacs</varname> binding pointing to the current stable
+       version of Emacs. This binding is here to separate the choice of the
+       Emacs binary from the specification of the required packages.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-3">
+      <para>
+       This generates an <varname>emacsWithPackages</varname> function. It
+       takes a single argument: a function from a package set to a list of
+       packages (the packages that will be available in Emacs).
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-4">
+      <para>
+       The rest of the file specifies the list of packages to install. In the
+       example, two packages (<varname>magit</varname> and
+       <varname>zerodark-theme</varname>) are taken from MELPA stable.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-5">
+      <para>
+       Two packages (<varname>undo-tree</varname> and
+       <varname>zoom-frm</varname>) are taken from MELPA.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-6">
+      <para>
+       Three packages are taken from GNU ELPA.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-7">
+      <para>
+       <varname>notmuch</varname> is taken from a nixpkgs derivation which
+       contains an Emacs mode.
+      </para>
+     </callout>
+    </calloutlist>
+   </para>
+
+   <para>
+    The result of this configuration will be an <command>emacs</command>
+    command which launches Emacs with all of your chosen packages in the
+    <varname>load-path</varname>.
+   </para>
+
+   <para>
+    You can check that it works by executing this in a terminal:
+<screen>
+<prompt>$ </prompt>nix-build emacs.nix
+<prompt>$ </prompt>./result/bin/emacs -q
+</screen>
+    and then typing <literal>M-x package-initialize</literal>. Check that you
+    can use all the packages you want in this Emacs instance. For example, try
+    switching to the zerodark theme through <literal>M-x load-theme &lt;RET&gt;
+    zerodark &lt;RET&gt; y</literal>.
+   </para>
+
+   <tip>
+    <para>
+     A few popular extensions worth checking out are: auctex, company,
+     edit-server, flycheck, helm, iedit, magit, multiple-cursors, projectile,
+     and yasnippet.
+    </para>
+   </tip>
+
+   <para>
+    The list of available packages in the various ELPA repositories can be seen
+    with the following commands:
+    <example xml:id="module-services-emacs-querying-packages">
+     <title>Querying Emacs packages</title>
+<programlisting><![CDATA[
+nix-env -f "<nixpkgs>" -qaP -A emacsPackages.elpaPackages
+nix-env -f "<nixpkgs>" -qaP -A emacsPackages.melpaPackages
+nix-env -f "<nixpkgs>" -qaP -A emacsPackages.melpaStablePackages
+nix-env -f "<nixpkgs>" -qaP -A emacsPackages.orgPackages
+]]></programlisting>
+    </example>
+   </para>
+
+   <para>
+    If you are on NixOS, you can install this particular Emacs for all users by
+    adding it to the list of system packages (see
+    <xref linkend="sec-declarative-package-mgmt" />). Simply modify your file
+    <filename>configuration.nix</filename> to make it contain:
+    <example xml:id="module-services-emacs-configuration-nix">
+     <title>Custom Emacs in <filename>configuration.nix</filename></title>
+<programlisting><![CDATA[
+{
+ environment.systemPackages = [
+   # [...]
+   (import /path/to/emacs.nix { inherit pkgs; })
+  ];
+}
+]]></programlisting>
+    </example>
+   </para>
+
+   <para>
+    In this case, the next <command>nixos-rebuild switch</command> will take
+    care of adding your <command>emacs</command> to the <varname>PATH</varname>
+    environment variable (see <xref linkend="sec-changing-config" />).
+   </para>
+
+<!-- fixme: i think the following is better done with config.nix
+https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides
+-->
+
+   <para>
+    If you are not on NixOS or want to install this particular Emacs only for
+    yourself, you can do so by adding it to your
+    <filename>~/.config/nixpkgs/config.nix</filename> (see
+    <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs
+    manual</link>):
+    <example xml:id="module-services-emacs-config-nix">
+     <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title>
+<programlisting><![CDATA[
+{
+  packageOverrides = super: let self = super.pkgs; in {
+    myemacs = import /path/to/emacs.nix { pkgs = self; };
+  };
+}
+]]></programlisting>
+    </example>
+   </para>
+
+   <para>
+    In this case, the next <literal>nix-env -f '&lt;nixpkgs&gt;' -iA
+    myemacs</literal> will take care of adding your emacs to the
+    <varname>PATH</varname> environment variable.
+   </para>
+  </section>
+
+  <section xml:id="module-services-emacs-advanced">
+   <title>Advanced Emacs Configuration</title>
+
+   <para>
+    If you want, you can tweak the Emacs package itself from your
+    <filename>emacs.nix</filename>. For example, if you want to have a
+    GTK 3-based Emacs instead of the default GTK 2-based binary and remove the
+    automatically generated <filename>emacs.desktop</filename> (useful is you
+    only use <command>emacsclient</command>), you can change your file
+    <filename>emacs.nix</filename> in this way:
+   </para>
+
+   <example xml:id="ex-emacsGtk3Nix">
+    <title>Custom Emacs build</title>
+<programlisting><![CDATA[
+{ pkgs ? import <nixpkgs> {} }:
+let
+  myEmacs = (pkgs.emacs.override {
+    # Use gtk3 instead of the default gtk2
+    withGTK3 = true;
+    withGTK2 = false;
+  }).overrideAttrs (attrs: {
+    # I don't want emacs.desktop file because I only use
+    # emacsclient.
+    postInstall = (attrs.postInstall or "") + ''
+      rm $out/share/applications/emacs.desktop
+    '';
+  });
+in [...]
+]]></programlisting>
+   </example>
+
+   <para>
+    After building this file as shown in <xref linkend="ex-emacsNix" />, you
+    will get an GTK 3-based Emacs binary pre-loaded with your favorite packages.
+   </para>
+  </section>
+ </section>
+ <section xml:id="module-services-emacs-running">
+  <title>Running Emacs as a Service</title>
+
+  <para>
+   <productname>NixOS</productname> provides an optional
+   <command>systemd</command> service which launches
+   <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html">
+   Emacs daemon </link> with the user's login session.
+  </para>
+
+  <para>
+   <emphasis>Source:</emphasis>
+   <filename>modules/services/editors/emacs.nix</filename>
+  </para>
+
+  <section xml:id="module-services-emacs-enabling">
+   <title>Enabling the Service</title>
+
+   <para>
+    To install and enable the <command>systemd</command> user service for Emacs
+    daemon, add the following to your <filename>configuration.nix</filename>:
+<programlisting>
+<xref linkend="opt-services.emacs.enable"/> = true;
+<xref linkend="opt-services.emacs.package"/> = import /home/cassou/.emacs.d { pkgs = pkgs; };
+</programlisting>
+   </para>
+
+   <para>
+    The <varname>services.emacs.package</varname> option allows a custom
+    derivation to be used, for example, one created by
+    <function>emacsWithPackages</function>.
+   </para>
+
+   <para>
+    Ensure that the Emacs server is enabled for your user's Emacs
+    configuration, either by customizing the <varname>server-mode</varname>
+    variable, or by adding <literal>(server-start)</literal> to
+    <filename>~/.emacs.d/init.el</filename>.
+   </para>
+
+   <para>
+    To start the daemon, execute the following:
+<screen>
+<prompt>$ </prompt>nixos-rebuild switch  # to activate the new configuration.nix
+<prompt>$ </prompt>systemctl --user daemon-reload        # to force systemd reload
+<prompt>$ </prompt>systemctl --user start emacs.service  # to start the Emacs daemon
+</screen>
+    The server should now be ready to serve Emacs clients.
+   </para>
+  </section>
+
+  <section xml:id="module-services-emacs-starting-client">
+   <title>Starting the client</title>
+
+   <para>
+    Ensure that the emacs server is enabled, either by customizing the
+    <varname>server-mode</varname> variable, or by adding
+    <literal>(server-start)</literal> to <filename>~/.emacs</filename>.
+   </para>
+
+   <para>
+    To connect to the emacs daemon, run one of the following:
+<programlisting><![CDATA[
+emacsclient FILENAME
+emacsclient --create-frame  # opens a new frame (window)
+emacsclient --create-frame --tty  # opens a new frame on the current terminal
+]]></programlisting>
+   </para>
+  </section>
+
+  <section xml:id="module-services-emacs-editor-variable">
+   <title>Configuring the <varname>EDITOR</varname> variable</title>
+
+<!--<title><command>emacsclient</command> as the Default Editor</title>-->
+
+   <para>
+    If <xref linkend="opt-services.emacs.defaultEditor"/> is
+    <literal>true</literal>, the <varname>EDITOR</varname> variable will be set
+    to a wrapper script which launches <command>emacsclient</command>.
+   </para>
+
+   <para>
+    Any setting of <varname>EDITOR</varname> in the shell config files will
+    override <varname>services.emacs.defaultEditor</varname>. To make sure
+    <varname>EDITOR</varname> refers to the Emacs wrapper script, remove any
+    existing <varname>EDITOR</varname> assignment from
+    <filename>.profile</filename>, <filename>.bashrc</filename>,
+    <filename>.zshenv</filename> or any other shell config file.
+   </para>
+
+   <para>
+    If you have formed certain bad habits when editing files, these can be
+    corrected with a shell alias to the wrapper script:
+<programlisting>alias vi=$EDITOR</programlisting>
+   </para>
+  </section>
+
+  <section xml:id="module-services-emacs-per-user">
+   <title>Per-User Enabling of the Service</title>
+
+   <para>
+    In general, <command>systemd</command> user services are globally enabled
+    by symlinks in <filename>/etc/systemd/user</filename>. In the case where
+    Emacs daemon is not wanted for all users, it is possible to install the
+    service but not globally enable it:
+<programlisting>
+<xref linkend="opt-services.emacs.enable"/> = false;
+<xref linkend="opt-services.emacs.install"/> = true;
+</programlisting>
+   </para>
+
+   <para>
+    To enable the <command>systemd</command> user service for just the
+    currently logged in user, run:
+<programlisting>systemctl --user enable emacs</programlisting>
+    This will add the symlink
+    <filename>~/.config/systemd/user/emacs.service</filename>.
+   </para>
+  </section>
+ </section>
+ <section xml:id="module-services-emacs-configuring">
+  <title>Configuring Emacs</title>
+
+  <para>
+   The Emacs init file should be changed to load the extension packages at
+   startup:
+   <example xml:id="module-services-emacs-package-initialisation">
+    <title>Package initialization in <filename>.emacs</filename></title>
+<programlisting><![CDATA[
+(require 'package)
+
+;; optional. makes unpure packages archives unavailable
+(setq package-archives nil)
+
+(setq package-enable-at-startup nil)
+(package-initialize)
+]]></programlisting>
+   </example>
+  </para>
+
+  <para>
+   After the declarative emacs package configuration has been tested,
+   previously downloaded packages can be cleaned up by removing
+   <filename>~/.emacs.d/elpa</filename> (do make a backup first, in case you
+   forgot a package).
+  </para>
+
+<!--
+      todo: is it worth documenting customizations for
+      server-switch-hook, server-done-hook?
+  -->
+
+  <section xml:id="module-services-emacs-major-mode">
+   <title>A Major Mode for Nix Expressions</title>
+
+   <para>
+    Of interest may be <varname>melpaPackages.nix-mode</varname>, which
+    provides syntax highlighting for the Nix language. This is particularly
+    convenient if you regularly edit Nix files.
+   </para>
+  </section>
+
+  <section xml:id="module-services-emacs-man-pages">
+   <title>Accessing man pages</title>
+
+   <para>
+    You can use <function>woman</function> to get completion of all available
+    man pages. For example, type <literal>M-x woman &lt;RET&gt; nixos-rebuild
+    &lt;RET&gt;.</literal>
+   </para>
+  </section>
+
+  <section xml:id="sec-emacs-docbook-xml">
+   <title>Editing DocBook 5 XML Documents</title>
+
+   <para>
+    Emacs includes
+    <link
+      xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>,
+    a major-mode for validating and editing XML documents. When editing DocBook
+    5.0 documents, such as <link linkend="book-nixos-manual">this one</link>,
+    nXML needs to be configured with the relevant schema, which is not
+    included.
+   </para>
+
+   <para>
+    To install the DocBook 5.0 schemas, either add
+    <varname>pkgs.docbook5</varname> to
+    <xref linkend="opt-environment.systemPackages"/>
+    (<link
+      linkend="sec-declarative-package-mgmt">NixOS</link>), or run
+    <literal>nix-env -f '&lt;nixpkgs&gt;' -iA docbook5</literal>
+    (<link linkend="sec-ad-hoc-packages">Nix</link>).
+   </para>
+
+   <para>
+    Then customize the variable <varname>rng-schema-locating-files</varname> to
+    include <filename>~/.emacs.d/schemas.xml</filename> and put the following
+    text into that file:
+    <example xml:id="ex-emacs-docbook-xml">
+     <title>nXML Schema Configuration (<filename>~/.emacs.d/schemas.xml</filename>)</title>
+<programlisting language="xml"><![CDATA[
+<?xml version="1.0"?>
+<!--
+  To let emacs find this file, evaluate:
+  (add-to-list 'rng-schema-locating-files "~/.emacs.d/schemas.xml")
+-->
+<locatingRules xmlns="http://thaiopensource.com/ns/locating-rules/1.0">
+  <!--
+    Use this variation if pkgs.docbook5 is added to environment.systemPackages
+  -->
+  <namespace ns="http://docbook.org/ns/docbook"
+             uri="/run/current-system/sw/share/xml/docbook-5.0/rng/docbookxi.rnc"/>
+  <!--
+    Use this variation if installing schema with "nix-env -iA pkgs.docbook5".
+  <namespace ns="http://docbook.org/ns/docbook"
+             uri="../.nix-profile/share/xml/docbook-5.0/rng/docbookxi.rnc"/>
+  -->
+</locatingRules>
+]]></programlisting>
+    </example>
+   </para>
+  </section>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/editors/infinoted.nix b/nixpkgs/nixos/modules/services/editors/infinoted.nix
new file mode 100644
index 000000000000..8b997ccbf66e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/editors/infinoted.nix
@@ -0,0 +1,160 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.infinoted;
+in {
+  options.services.infinoted = {
+    enable = mkEnableOption "infinoted";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.libinfinity;
+      defaultText = "pkgs.libinfinity";
+      description = ''
+        Package providing infinoted
+      '';
+    };
+
+    keyFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Private key to use for TLS
+      '';
+    };
+
+    certificateFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Server certificate to use for TLS
+      '';
+    };
+
+    certificateChain = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Chain of CA-certificates to which our `certificateFile` is relative.
+        Optional for TLS.
+      '';
+    };
+
+    securityPolicy = mkOption {
+      type = types.enum ["no-tls" "allow-tls" "require-tls"];
+      default = "require-tls";
+      description = ''
+        How strictly to enforce clients connection with TLS.
+      '';
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 6523;
+      description = ''
+        Port to listen on
+      '';
+    };
+
+    rootDirectory = mkOption {
+      type = types.path;
+      default = "/var/lib/infinoted/documents/";
+      description = ''
+        Root of the directory structure to serve
+      '';
+    };
+
+    plugins = mkOption {
+      type = types.listOf types.str;
+      default = [ "note-text" "note-chat" "logging" "autosave" ];
+      description = ''
+        Plugins to enable
+      '';
+    };
+
+    passwordFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        File to read server-wide password from
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = ''
+        [autosave]
+        interval=10
+      '';
+      description = ''
+        Additional configuration to append to infinoted.conf
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "infinoted";
+      description = ''
+        What to call the dedicated user under which infinoted is run
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "infinoted";
+      description = ''
+        What to call the primary group of the dedicated user under which infinoted is run
+      '';
+    };
+  };
+
+  config = mkIf (cfg.enable) {
+    users.users = optionalAttrs (cfg.user == "infinoted")
+      { infinoted = {
+          description = "Infinoted user";
+          group = cfg.group;
+          isSystemUser = true;
+        };
+      };
+    users.groups = optionalAttrs (cfg.group == "infinoted")
+      { infinoted = { };
+      };
+
+    systemd.services.infinoted =
+      { description = "Gobby Dedicated Server";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        serviceConfig = {
+          Type = "simple";
+          Restart = "always";
+          ExecStart = "${cfg.package.infinoted} --config-file=/var/lib/infinoted/infinoted.conf";
+          User = cfg.user;
+          Group = cfg.group;
+          PermissionsStartOnly = true;
+        };
+        preStart = ''
+          mkdir -p /var/lib/infinoted
+          install -o ${cfg.user} -g ${cfg.group} -m 0600 /dev/null /var/lib/infinoted/infinoted.conf
+          cat >>/var/lib/infinoted/infinoted.conf <<EOF
+          [infinoted]
+          ${optionalString (cfg.keyFile != null) ''key-file=${cfg.keyFile}''}
+          ${optionalString (cfg.certificateFile != null) ''certificate-file=${cfg.certificateFile}''}
+          ${optionalString (cfg.certificateChain != null) ''certificate-chain=${cfg.certificateChain}''}
+          port=${toString cfg.port}
+          security-policy=${cfg.securityPolicy}
+          root-directory=${cfg.rootDirectory}
+          plugins=${concatStringsSep ";" cfg.plugins}
+          ${optionalString (cfg.passwordFile != null) ''password=$(head -n 1 ${cfg.passwordFile})''}
+
+          ${cfg.extraConfig}
+          EOF
+
+          install -o ${cfg.user} -g ${cfg.group} -m 0750 -d ${cfg.rootDirectory}
+        '';
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/games/factorio.nix b/nixpkgs/nixos/modules/services/games/factorio.nix
new file mode 100644
index 000000000000..4b2e1a3c07f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/factorio.nix
@@ -0,0 +1,242 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.factorio;
+  name = "Factorio";
+  stateDir = "/var/lib/${cfg.stateDirName}";
+  mkSavePath = name: "${stateDir}/saves/${name}.zip";
+  configFile = pkgs.writeText "factorio.conf" ''
+    use-system-read-write-data-directories=true
+    [path]
+    read-data=${cfg.package}/share/factorio/data
+    write-data=${stateDir}
+  '';
+  serverSettings = {
+    name = cfg.game-name;
+    description = cfg.description;
+    visibility = {
+      public = cfg.public;
+      lan = cfg.lan;
+    };
+    username = cfg.username;
+    password = cfg.password;
+    token = cfg.token;
+    game_password = cfg.game-password;
+    require_user_verification = cfg.requireUserVerification;
+    max_upload_in_kilobytes_per_second = 0;
+    minimum_latency_in_ticks = 0;
+    ignore_player_limit_for_returning_players = false;
+    allow_commands = "admins-only";
+    autosave_interval = cfg.autosave-interval;
+    autosave_slots = 5;
+    afk_autokick_interval = 0;
+    auto_pause = true;
+    only_admins_can_pause_the_game = true;
+    autosave_only_on_server = true;
+    admins = [];
+  } // cfg.extraSettings;
+  serverSettingsFile = pkgs.writeText "server-settings.json" (builtins.toJSON (filterAttrsRecursive (n: v: v != null) serverSettings));
+  modDir = pkgs.factorio-utils.mkModDirDrv cfg.mods;
+in
+{
+  options = {
+    services.factorio = {
+      enable = mkEnableOption name;
+      port = mkOption {
+        type = types.int;
+        default = 34197;
+        description = ''
+          The port to which the service should bind.
+
+          This option will also open up the UDP port in the firewall configuration.
+        '';
+      };
+      saveName = mkOption {
+        type = types.str;
+        default = "default";
+        description = ''
+          The name of the savegame that will be used by the server.
+
+          When not present in ${stateDir}/saves, a new map with default
+          settings will be generated before starting the service.
+        '';
+      };
+      # TODO Add more individual settings as nixos-options?
+      # TODO XXX The server tries to copy a newly created config file over the old one
+      #   on shutdown, but fails, because it's in the nix store. When is this needed?
+      #   Can an admin set options in-game and expect to have them persisted?
+      configFile = mkOption {
+        type = types.path;
+        default = configFile;
+        defaultText = "configFile";
+        description = ''
+          The server's configuration file.
+
+          The default file generated by this module contains lines essential to
+          the server's operation. Use its contents as a basis for any
+          customizations.
+        '';
+      };
+      stateDirName = mkOption {
+        type = types.str;
+        default = "factorio";
+        description = ''
+          Name of the directory under /var/lib holding the server's data.
+
+          The configuration and map will be stored here.
+        '';
+      };
+      mods = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = ''
+          Mods the server should install and activate.
+
+          The derivations in this list must "build" the mod by simply copying
+          the .zip, named correctly, into the output directory. Eventually,
+          there will be a way to pull in the most up-to-date list of
+          derivations via nixos-channel. Until then, this is for experts only.
+        '';
+      };
+      game-name = mkOption {
+        type = types.nullOr types.str;
+        default = "Factorio Game";
+        description = ''
+          Name of the game as it will appear in the game listing.
+        '';
+      };
+      description = mkOption {
+        type = types.nullOr types.str;
+        default = "";
+        description = ''
+          Description of the game that will appear in the listing.
+        '';
+      };
+      extraSettings = mkOption {
+        type = types.attrs;
+        default = {};
+        example = { admins = [ "username" ];};
+        description = ''
+          Extra game configuration that will go into server-settings.json
+        '';
+      };
+      public = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Game will be published on the official Factorio matching server.
+        '';
+      };
+      lan = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Game will be broadcast on LAN.
+        '';
+      };
+      username = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Your factorio.com login credentials. Required for games with visibility public.
+        '';
+      };
+      package = mkOption {
+        type = types.package;
+        default = pkgs.factorio-headless;
+        defaultText = "pkgs.factorio-headless";
+        example = "pkgs.factorio-headless-experimental";
+        description = ''
+          Factorio version to use. This defaults to the stable channel.
+        '';
+      };
+      password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Your factorio.com login credentials. Required for games with visibility public.
+        '';
+      };
+      token = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Authentication token. May be used instead of 'password' above.
+        '';
+      };
+      game-password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Game password.
+        '';
+      };
+      requireUserVerification = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          When set to true, the server will only allow clients that have a valid factorio.com account.
+        '';
+      };
+      autosave-interval = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 10;
+        description = ''
+          Autosave interval in minutes.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.factorio = {
+      description   = "Factorio headless server";
+      wantedBy      = [ "multi-user.target" ];
+      after         = [ "network.target" ];
+
+      preStart = toString [
+        "test -e ${stateDir}/saves/${cfg.saveName}.zip"
+        "||"
+        "${cfg.package}/bin/factorio"
+          "--config=${cfg.configFile}"
+          "--create=${mkSavePath cfg.saveName}"
+          (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
+      ];
+
+      serviceConfig = {
+        Restart = "always";
+        KillSignal = "SIGINT";
+        DynamicUser = true;
+        StateDirectory = cfg.stateDirName;
+        UMask = "0007";
+        ExecStart = toString [
+          "${cfg.package}/bin/factorio"
+          "--config=${cfg.configFile}"
+          "--port=${toString cfg.port}"
+          "--start-server=${mkSavePath cfg.saveName}"
+          "--server-settings=${serverSettingsFile}"
+          (optionalString (cfg.mods != []) "--mod-directory=${modDir}")
+        ];
+
+        # Sandboxing
+        NoNewPrivileges = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectControlGroups = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+        MemoryDenyWriteExecute = true;
+      };
+    };
+
+    networking.firewall.allowedUDPPorts = [ cfg.port ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/games/minecraft-server.nix b/nixpkgs/nixos/modules/services/games/minecraft-server.nix
new file mode 100644
index 000000000000..eb9288fca586
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/minecraft-server.nix
@@ -0,0 +1,234 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.minecraft-server;
+
+  # We don't allow eula=false anyways
+  eulaFile = builtins.toFile "eula.txt" ''
+    # eula.txt managed by NixOS Configuration
+    eula=true
+  '';
+
+  whitelistFile = pkgs.writeText "whitelist.json"
+    (builtins.toJSON
+      (mapAttrsToList (n: v: { name = n; uuid = v; }) cfg.whitelist));
+
+  cfgToString = v: if builtins.isBool v then boolToString v else toString v;
+
+  serverPropertiesFile = pkgs.writeText "server.properties" (''
+    # server.properties managed by NixOS configuration
+  '' + concatStringsSep "\n" (mapAttrsToList
+    (n: v: "${n}=${cfgToString v}") cfg.serverProperties));
+
+
+  # To be able to open the firewall, we need to read out port values in the
+  # server properties, but fall back to the defaults when those don't exist.
+  # These defaults are from https://minecraft.gamepedia.com/Server.properties#Java_Edition_3
+  defaultServerPort = 25565;
+
+  serverPort = cfg.serverProperties.server-port or defaultServerPort;
+
+  rconPort = if cfg.serverProperties.enable-rcon or false
+    then cfg.serverProperties."rcon.port" or 25575
+    else null;
+
+  queryPort = if cfg.serverProperties.enable-query or false
+    then cfg.serverProperties."query.port" or 25565
+    else null;
+
+in {
+  options = {
+    services.minecraft-server = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If enabled, start a Minecraft Server. The server
+          data will be loaded from and saved to
+          <option>services.minecraft-server.dataDir</option>.
+        '';
+      };
+
+      declarative = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use a declarative Minecraft server configuration.
+          Only if set to <literal>true</literal>, the options
+          <option>services.minecraft-server.whitelist</option> and
+          <option>services.minecraft-server.serverProperties</option> will be
+          applied.
+        '';
+      };
+
+      eula = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether you agree to
+          <link xlink:href="https://account.mojang.com/documents/minecraft_eula">
+          Mojangs EULA</link>. This option must be set to
+          <literal>true</literal> to run Minecraft server.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/minecraft";
+        description = ''
+          Directory to store Minecraft database and other state/data files.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open ports in the firewall for the server.
+        '';
+      };
+
+      whitelist = mkOption {
+        type = let
+          minecraftUUID = types.strMatching
+            "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" // {
+              description = "Minecraft UUID";
+            };
+          in types.attrsOf minecraftUUID;
+        default = {};
+        description = ''
+          Whitelisted players, only has an effect when
+          <option>services.minecraft-server.declarative</option> is
+          <literal>true</literal> and the whitelist is enabled
+          via <option>services.minecraft-server.serverProperties</option> by
+          setting <literal>white-list</literal> to <literal>true</literal>.
+          This is a mapping from Minecraft usernames to UUIDs.
+          You can use <link xlink:href="https://mcuuid.net/"/> to get a
+          Minecraft UUID for a username.
+        '';
+        example = literalExample ''
+          {
+            username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
+            username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
+          };
+        '';
+      };
+
+      serverProperties = mkOption {
+        type = with types; attrsOf (oneOf [ bool int str ]);
+        default = {};
+        example = literalExample ''
+          {
+            server-port = 43000;
+            difficulty = 3;
+            gamemode = 1;
+            max-players = 5;
+            motd = "NixOS Minecraft server!";
+            white-list = true;
+            enable-rcon = true;
+            "rcon.password" = "hunter2";
+          }
+        '';
+        description = ''
+          Minecraft server properties for the server.properties file. Only has
+          an effect when <option>services.minecraft-server.declarative</option>
+          is set to <literal>true</literal>. See
+          <link xlink:href="https://minecraft.gamepedia.com/Server.properties#Java_Edition_3"/>
+          for documentation on these values.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.minecraft-server;
+        defaultText = "pkgs.minecraft-server";
+        example = literalExample "pkgs.minecraft-server_1_12_2";
+        description = "Version of minecraft-server to run.";
+      };
+
+      jvmOpts = mkOption {
+        type = types.separatedString " ";
+        default = "-Xmx2048M -Xms2048M";
+        # Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script
+        example = "-Xmx2048M -Xms4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
+          + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
+          + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
+        description = "JVM options for the Minecraft server.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.minecraft = {
+      description     = "Minecraft server service user";
+      home            = cfg.dataDir;
+      createHome      = true;
+      uid             = config.ids.uids.minecraft;
+    };
+
+    systemd.services.minecraft-server = {
+      description   = "Minecraft Server Service";
+      wantedBy      = [ "multi-user.target" ];
+      after         = [ "network.target" ];
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}";
+        Restart = "always";
+        User = "minecraft";
+        WorkingDirectory = cfg.dataDir;
+      };
+
+      preStart = ''
+        ln -sf ${eulaFile} eula.txt
+      '' + (if cfg.declarative then ''
+
+        if [ -e .declarative ]; then
+
+          # Was declarative before, no need to back up anything
+          ln -sf ${whitelistFile} whitelist.json
+          cp -f ${serverPropertiesFile} server.properties
+
+        else
+
+          # Declarative for the first time, backup stateful files
+          ln -sb --suffix=.stateful ${whitelistFile} whitelist.json
+          cp -b --suffix=.stateful ${serverPropertiesFile} server.properties
+
+          # server.properties must have write permissions, because every time
+          # the server starts it first parses the file and then regenerates it..
+          chmod +w server.properties
+          echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
+            > .declarative
+
+        fi
+      '' else ''
+        if [ -e .declarative ]; then
+          rm .declarative
+        fi
+      '');
+    };
+
+    networking.firewall = mkIf cfg.openFirewall (if cfg.declarative then {
+      allowedUDPPorts = [ serverPort ];
+      allowedTCPPorts = [ serverPort ]
+        ++ optional (queryPort != null) queryPort
+        ++ optional (rconPort != null) rconPort;
+    } else {
+      allowedUDPPorts = [ defaultServerPort ];
+      allowedTCPPorts = [ defaultServerPort ];
+    });
+
+    assertions = [
+      { assertion = cfg.eula;
+        message = "You must agree to Mojangs EULA to run minecraft-server."
+          + " Read https://account.mojang.com/documents/minecraft_eula and"
+          + " set `services.minecraft-server.eula` to `true` if you agree.";
+      }
+    ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/games/minetest-server.nix b/nixpkgs/nixos/modules/services/games/minetest-server.nix
new file mode 100644
index 000000000000..98e69c6dc0ea
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/minetest-server.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg   = config.services.minetest-server;
+  flag  = val: name: if val != null then "--${name} ${val} " else "";
+  flags = [ 
+    (flag cfg.gameId "gameid") 
+    (flag cfg.world "world") 
+    (flag cfg.configPath "config") 
+    (flag cfg.logPath "logfile") 
+    (flag cfg.port "port") 
+  ];
+in
+{
+  options = {
+    services.minetest-server = {
+      enable = mkOption {
+        type        = types.bool;
+        default     = false;
+        description = "If enabled, starts a Minetest Server.";
+      };
+
+      gameId = mkOption {
+        type        = types.nullOr types.str;
+        default     = null;
+        description = ''
+          Id of the game to use. To list available games run 
+          `minetestserver --gameid list`.
+
+          If only one game exists, this option can be null.
+        '';
+      };
+
+      world = mkOption {
+        type        = types.nullOr types.path;
+        default     = null;
+        description = ''
+          Name of the world to use. To list available worlds run
+          `minetestserver --world list`.
+
+          If only one world exists, this option can be null.
+        '';
+      };
+
+      configPath = mkOption {
+        type        = types.nullOr types.path;
+        default     = null;
+        description = ''
+          Path to the config to use.
+
+          If set to null, the config of the running user will be used:
+          `~/.minetest/minetest.conf`.
+        '';
+      };
+
+      logPath = mkOption {
+        type        = types.nullOr types.path;
+        default     = null;
+        description = ''
+          Path to logfile for logging. 
+
+          If set to null, logging will be output to stdout which means
+          all output will be catched by systemd.
+        '';
+      };
+
+      port = mkOption {
+        type        = types.nullOr types.int;
+        default     = null;
+        description = ''
+          Port number to bind to.
+
+          If set to null, the default 30000 will be used.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.minetest = {
+      description     = "Minetest Server Service user";
+      home            = "/var/lib/minetest";
+      createHome      = true;
+      uid             = config.ids.uids.minetest;
+      group           = "minetest";
+    };
+    users.groups.minetest.gid = config.ids.gids.minetest;
+
+    systemd.services.minetest-server = {
+      description   = "Minetest Server Service";
+      wantedBy      = [ "multi-user.target" ];
+      after         = [ "network.target" ];
+
+      serviceConfig.Restart = "always";
+      serviceConfig.User    = "minetest";
+      serviceConfig.Group   = "minetest";
+
+      script = ''
+        cd /var/lib/minetest
+
+        exec ${pkgs.minetest}/bin/minetest --server ${concatStrings flags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/games/openarena.nix b/nixpkgs/nixos/modules/services/games/openarena.nix
new file mode 100644
index 000000000000..8c014d78809b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/openarena.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.openarena;
+in
+{
+  options = {
+    services.openarena = {
+      enable = mkEnableOption "OpenArena";
+
+      openPorts = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to open firewall ports for OpenArena";
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''Extra flags to pass to <command>oa_ded</command>'';
+        example = [
+          "+set dedicated 2"
+          "+set sv_hostname 'My NixOS OpenArena Server'"
+          # Load a map. Mandatory for clients to be able to connect.
+          "+map oa_dm1"
+        ];
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.firewall = mkIf cfg.openPorts {
+      allowedUDPPorts = [ 27960 ];
+    };
+
+    systemd.services.openarena = {
+      description = "OpenArena";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = "openarena";
+        ExecStart = "${pkgs.openarena}/bin/oa_ded +set fs_basepath ${pkgs.openarena}/openarena-0.8.8 +set fs_homepath /var/lib/openarena ${concatStringsSep " " cfg.extraFlags}";
+        Restart = "on-failure";
+
+        # Hardening
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/games/teeworlds.nix b/nixpkgs/nixos/modules/services/games/teeworlds.nix
new file mode 100644
index 000000000000..babf989c98ca
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/teeworlds.nix
@@ -0,0 +1,119 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.teeworlds;
+  register = cfg.register;
+
+  teeworldsConf = pkgs.writeText "teeworlds.cfg" ''
+    sv_port ${toString cfg.port}
+    sv_register ${if cfg.register then "1" else "0"}
+    ${optionalString (cfg.name != null) "sv_name ${cfg.name}"}
+    ${optionalString (cfg.motd != null) "sv_motd ${cfg.motd}"}
+    ${optionalString (cfg.password != null) "password ${cfg.password}"}
+    ${optionalString (cfg.rconPassword != null) "sv_rcon_password ${cfg.rconPassword}"}
+    ${concatStringsSep "\n" cfg.extraOptions}
+  '';
+
+in
+{
+  options = {
+    services.teeworlds = {
+      enable = mkEnableOption "Teeworlds Server";
+
+      openPorts = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to open firewall ports for Teeworlds";
+      };
+
+      name = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Name of the server. Defaults to 'unnamed server'.
+        '';
+      };
+
+      register = mkOption {
+        type = types.bool;
+        example = true;
+        default = false;
+        description = ''
+          Whether the server registers as public server in the global server list. This is disabled by default because of privacy.
+        '';
+      };
+
+      motd = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Set the server message of the day text.
+        '';
+      };
+
+      password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Password to connect to the server.
+        '';
+      };
+
+      rconPassword = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Password to access the remote console. If not set, a randomly generated one is displayed in the server log.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8303;
+        description = ''
+          Port the server will listen on.
+        '';
+      };
+
+      extraOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Extra configuration lines for the <filename>teeworlds.cfg</filename>. See <link xlink:href="https://www.teeworlds.com/?page=docs&amp;wiki=server_settings">Teeworlds Documentation</link>.
+        '';
+        example = [ "sv_map dm1" "sv_gametype dm" ];
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.firewall = mkIf cfg.openPorts {
+      allowedUDPPorts = [ cfg.port ];
+    };
+
+    systemd.services.teeworlds = {
+      description = "Teeworlds Server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.teeworlds}/bin/teeworlds_srv -f ${teeworldsConf}";
+
+        # Hardening
+        CapabilityBoundingSet = false;
+        PrivateDevices = true;
+        PrivateUsers = true;
+        ProtectHome = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        SystemCallArchitectures = "native";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/games/terraria.nix b/nixpkgs/nixos/modules/services/games/terraria.nix
new file mode 100644
index 000000000000..a59b74c0b4c4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/games/terraria.nix
@@ -0,0 +1,149 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg   = config.services.terraria;
+  worldSizeMap = { small = 1; medium = 2; large = 3; };
+  valFlag = name: val: optionalString (val != null) "-${name} \"${escape ["\\" "\""] (toString val)}\"";
+  boolFlag = name: val: optionalString val "-${name}";
+  flags = [ 
+    (valFlag "port" cfg.port)
+    (valFlag "maxPlayers" cfg.maxPlayers)
+    (valFlag "password" cfg.password)
+    (valFlag "motd" cfg.messageOfTheDay)
+    (valFlag "world" cfg.worldPath)
+    (valFlag "autocreate" (builtins.getAttr cfg.autoCreatedWorldSize worldSizeMap))
+    (valFlag "banlist" cfg.banListPath)
+    (boolFlag "secure" cfg.secure)
+    (boolFlag "noupnp" cfg.noUPnP)
+  ];
+  stopScript = pkgs.writeScript "terraria-stop" ''
+    #!${pkgs.runtimeShell}
+
+    if ! [ -d "/proc/$1" ]; then
+      exit 0
+    fi
+
+    ${getBin pkgs.tmux}/bin/tmux -S /var/lib/terraria/terraria.sock send-keys Enter exit Enter
+    ${getBin pkgs.coreutils}/bin/tail --pid="$1" -f /dev/null
+  '';
+in
+{
+  options = {
+    services.terraria = {
+      enable = mkOption {
+        type        = types.bool;
+        default     = false;
+        description = ''
+          If enabled, starts a Terraria server. The server can be connected to via <literal>tmux -S /var/lib/terraria/terraria.sock attach</literal>
+          for administration by users who are a part of the <literal>terraria</literal> group (use <literal>C-b d</literal> shortcut to detach again).
+        '';
+      };
+
+      port = mkOption {
+        type        = types.int;
+        default     = 7777;
+        description = ''
+          Specifies the port to listen on.
+        '';
+      };
+
+      maxPlayers = mkOption {
+        type        = types.int;
+        default     = 255;
+        description = ''
+          Sets the max number of players (between 1 and 255).
+        '';
+      };
+
+      password = mkOption {
+        type        = types.nullOr types.str;
+        default     = null;
+        description = ''
+          Sets the server password. Leave <literal>null</literal> for no password.
+        '';
+      };
+
+      messageOfTheDay = mkOption {
+        type        = types.nullOr types.str;
+        default     = null;
+        description = ''
+          Set the server message of the day text.
+        '';
+      };
+
+      worldPath = mkOption {
+        type        = types.nullOr types.path;
+        default     = null;
+        description = ''
+          The path to the world file (<literal>.wld</literal>) which should be loaded.
+          If no world exists at this path, one will be created with the size
+          specified by <literal>autoCreatedWorldSize</literal>.
+        '';
+      };
+
+      autoCreatedWorldSize = mkOption {
+        type        = types.enum [ "small" "medium" "large" ];
+        default     = "medium";
+        description = ''
+          Specifies the size of the auto-created world if <literal>worldPath</literal> does not
+          point to an existing world.
+        '';
+      };
+
+      banListPath = mkOption {
+        type        = types.nullOr types.path;
+        default     = null;
+        description = ''
+          The path to the ban list.
+        '';
+      };
+
+      secure = mkOption {
+        type        = types.bool;
+        default     = false;
+        description = "Adds additional cheat protection to the server.";
+      };
+
+      noUPnP = mkOption {
+        type        = types.bool;
+        default     = false;
+        description = "Disables automatic Universal Plug and Play.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.terraria = {
+      description = "Terraria server service user";
+      home        = "/var/lib/terraria";
+      createHome  = true;
+      uid         = config.ids.uids.terraria;
+    };
+
+    users.groups.terraria = {
+      gid = config.ids.gids.terraria;
+      members = [ "terraria" ];
+    };
+
+    systemd.services.terraria = {
+      description   = "Terraria Server Service";
+      wantedBy      = [ "multi-user.target" ];
+      after         = [ "network.target" ];
+
+      serviceConfig = {
+        User    = "terraria";
+        Type = "forking";
+        GuessMainPID = true;
+        ExecStart = "${getBin pkgs.tmux}/bin/tmux -S /var/lib/terraria/terraria.sock new -d ${pkgs.terraria-server}/bin/TerrariaServer ${concatStringsSep " " flags}";
+        ExecStop = "${stopScript} $MAINPID";
+      };
+
+      postStart = ''
+        ${pkgs.coreutils}/bin/chmod 660 /var/lib/terraria/terraria.sock
+        ${pkgs.coreutils}/bin/chgrp terraria /var/lib/terraria/terraria.sock
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/acpid.nix b/nixpkgs/nixos/modules/services/hardware/acpid.nix
new file mode 100644
index 000000000000..4c97485d9726
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/acpid.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  canonicalHandlers = {
+    powerEvent = {
+      event = "button/power.*";
+      action = config.services.acpid.powerEventCommands;
+    };
+
+    lidEvent = {
+      event = "button/lid.*";
+      action = config.services.acpid.lidEventCommands;
+    };
+
+    acEvent = {
+      event = "ac_adapter.*";
+      action = config.services.acpid.acEventCommands;
+    };
+  };
+
+  acpiConfDir = pkgs.runCommand "acpi-events" { preferLocalBuild = true; }
+    ''
+      mkdir -p $out
+      ${
+        # Generate a configuration file for each event. (You can't have
+        # multiple events in one config file...)
+        let f = name: handler:
+          ''
+            fn=$out/${name}
+            echo "event=${handler.event}" > $fn
+            echo "action=${pkgs.writeShellScriptBin "${name}.sh" handler.action }/bin/${name}.sh '%e'" >> $fn
+          '';
+        in concatStringsSep "\n" (mapAttrsToList f (canonicalHandlers // config.services.acpid.handlers))
+      }
+    '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.acpid = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the ACPI daemon.";
+      };
+
+      logEvents = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Log all event activity.";
+      };
+
+      handlers = mkOption {
+        type = types.attrsOf (types.submodule {
+          options = {
+            event = mkOption {
+              type = types.str;
+              example = [ "button/power.*" "button/lid.*" "ac_adapter.*" "button/mute.*" "button/volumedown.*" "cd/play.*" "cd/next.*" ];
+              description = "Event type.";
+            };
+
+            action = mkOption {
+              type = types.lines;
+              description = "Shell commands to execute when the event is triggered.";
+            };
+          };
+        });
+
+        description = ''
+          Event handlers.
+
+          <note><para>
+            Handler can be a single command.
+          </para></note>
+        '';
+        default = {};
+        example = {
+          ac-power = {
+            event = "ac_adapter/*";
+            action = ''
+              vals=($1)  # space separated string to array of multiple values
+              case ''${vals[3]} in
+                  00000000)
+                      echo unplugged >> /tmp/acpi.log
+                      ;;
+                  00000001)
+                      echo plugged in >> /tmp/acpi.log
+                      ;;
+                  *)
+                      echo unknown >> /tmp/acpi.log
+                      ;;
+              esac
+            '';
+          };
+        };
+      };
+
+      powerEventCommands = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Shell commands to execute on a button/power.* event.";
+      };
+
+      lidEventCommands = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Shell commands to execute on a button/lid.* event.";
+      };
+
+      acEventCommands = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Shell commands to execute on an ac_adapter.* event.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.acpid.enable {
+
+    systemd.services.acpid = {
+      description = "ACPI Daemon";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "systemd-udev-settle.service" ];
+
+      path = [ pkgs.acpid ];
+
+      serviceConfig = {
+        Type = "forking";
+      };
+
+      unitConfig = {
+        ConditionVirtualization = "!systemd-nspawn";
+        ConditionPathExists = [ "/proc/acpi" ];
+      };
+
+      script = "acpid ${optionalString config.services.acpid.logEvents "--logevents"} --confdir ${acpiConfDir}";
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/actkbd.nix b/nixpkgs/nixos/modules/services/hardware/actkbd.nix
new file mode 100644
index 000000000000..daa407ca1f0e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/actkbd.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.actkbd;
+
+  configFile = pkgs.writeText "actkbd.conf" ''
+    ${concatMapStringsSep "\n"
+      ({ keys, events, attributes, command, ... }:
+        ''${concatMapStringsSep "+" toString keys}:${concatStringsSep "," events}:${concatStringsSep "," attributes}:${command}''
+      )
+      cfg.bindings}
+    ${cfg.extraConfig}
+  '';
+
+  bindingCfg = { ... }: {
+    options = {
+
+      keys = mkOption {
+        type = types.listOf types.int;
+        description = "List of keycodes to match.";
+      };
+
+      events = mkOption {
+        type = types.listOf (types.enum ["key" "rep" "rel"]);
+        default = [ "key" ];
+        description = "List of events to match.";
+      };
+
+      attributes = mkOption {
+        type = types.listOf types.str;
+        default = [ "exec" ];
+        description = "List of attributes.";
+      };
+
+      command = mkOption {
+        type = types.str;
+        default = "";
+        description = "What to run.";
+      };
+
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.actkbd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the <command>actkbd</command> key mapping daemon.
+
+          Turning this on will start an <command>actkbd</command>
+          instance for every evdev input that has at least one key
+          (which is okay even for systems with tiny memory footprint,
+          since actkbd normally uses &lt;100 bytes of memory per
+          instance).
+
+          This allows binding keys globally without the need for e.g.
+          X11.
+        '';
+      };
+
+      bindings = mkOption {
+        type = types.listOf (types.submodule bindingCfg);
+        default = [];
+        example = lib.literalExample ''
+          [ { keys = [ 113 ]; events = [ "key" ]; command = "''${pkgs.alsaUtils}/bin/amixer -q set Master toggle"; }
+          ]
+        '';
+        description = ''
+          Key bindings for <command>actkbd</command>.
+
+          See <command>actkbd</command> <filename>README</filename> for documentation.
+
+          The example shows a piece of what <option>sound.mediaKeys.enable</option> does when enabled.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Literal contents to append to the end of actkbd configuration file.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.udev.packages = lib.singleton (pkgs.writeTextFile {
+      name = "actkbd-udev-rules";
+      destination = "/etc/udev/rules.d/61-actkbd.rules";
+      text = ''
+        ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ENV{ID_INPUT_KEY}=="1", TAG+="systemd", ENV{SYSTEMD_WANTS}+="actkbd@$env{DEVNAME}.service"
+      '';
+    });
+
+    systemd.services."actkbd@" = {
+      enable = true;
+      restartIfChanged = true;
+      unitConfig = {
+        Description = "actkbd on %I";
+        ConditionPathExists = "%I";
+      };
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.actkbd}/bin/actkbd -D -c ${configFile} -d %I";
+      };
+    };
+
+    # For testing
+    environment.systemPackages = [ pkgs.actkbd ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/bluetooth.nix b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
new file mode 100644
index 000000000000..dfa39e7f6024
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/bluetooth.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.bluetooth;
+  bluez-bluetooth = cfg.package;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    hardware.bluetooth = {
+      enable = mkEnableOption "support for Bluetooth";
+
+      powerOnBoot = mkOption {
+        type    = types.bool;
+        default = true;
+        description = "Whether to power up the default Bluetooth controller on boot.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.bluez;
+        defaultText = "pkgs.bluez";
+        example = "pkgs.bluezFull";
+        description = ''
+          Which BlueZ package to use.
+
+          <note><para>
+            Use the <literal>pkgs.bluezFull</literal> package to enable all
+            bluez plugins.
+          </para></note>
+        '';
+      };
+
+      config = mkOption {
+        type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
+        example = {
+          General = {
+            ControllerMode = "bredr";
+          };
+        };
+        description = "Set configuration for system-wide bluetooth (/etc/bluetooth/main.conf).";
+      };
+
+      extraConfig = mkOption {
+        type = with types; nullOr lines;
+        default = null;
+        example = ''
+          [General]
+          ControllerMode = bredr
+        '';
+        description = ''
+          Set additional configuration for system-wide bluetooth (/etc/bluetooth/main.conf).
+        '';
+      };
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    warnings = optional (cfg.extraConfig != null) "hardware.bluetooth.`extraConfig` is deprecated, please use hardware.bluetooth.`config`.";
+
+    hardware.bluetooth.config = {
+      Policy = {
+        AutoEnable = mkDefault cfg.powerOnBoot;
+      };
+    };
+
+    environment.systemPackages = [ bluez-bluetooth ];
+
+    environment.etc."bluetooth/main.conf"= {
+      source = pkgs.writeText "main.conf"
+        (generators.toINI { } cfg.config + optionalString (cfg.extraConfig != null) cfg.extraConfig);
+    };
+
+    services.udev.packages = [ bluez-bluetooth ];
+    services.dbus.packages = [ bluez-bluetooth ];
+    systemd.packages       = [ bluez-bluetooth ];
+
+    systemd.services = {
+      bluetooth = {
+        wantedBy = [ "bluetooth.target" ];
+        aliases  = [ "dbus-org.bluez.service" ];
+      };
+    };
+
+    systemd.user.services = {
+      obex.aliases = [ "dbus-org.bluez.obex.service" ];
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/bolt.nix b/nixpkgs/nixos/modules/services/hardware/bolt.nix
new file mode 100644
index 000000000000..32b60af06037
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/bolt.nix
@@ -0,0 +1,34 @@
+# Thunderbolt 3 device manager
+
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+{
+  options = {
+
+    services.hardware.bolt = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Bolt, a userspace daemon to enable
+          security levels for Thunderbolt 3 on GNU/Linux.
+
+          Bolt is used by GNOME 3 to handle Thunderbolt settings.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf config.services.hardware.bolt.enable {
+
+    environment.systemPackages = [ pkgs.bolt ];
+    services.udev.packages = [ pkgs.bolt ];
+    systemd.packages = [ pkgs.bolt ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/brltty.nix b/nixpkgs/nixos/modules/services/hardware/brltty.nix
new file mode 100644
index 000000000000..1266e8f81e5b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/brltty.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.brltty;
+
+in {
+
+  options = {
+
+    services.brltty.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Whether to enable the BRLTTY daemon.";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.brltty = {
+      description = "Braille Device Support";
+      unitConfig = {
+        Documentation = "http://mielke.cc/brltty/";
+        DefaultDependencies = "no";
+        RequiresMountsFor = "${pkgs.brltty}/var/lib/brltty";
+      };
+      serviceConfig = {
+        ExecStart = "${pkgs.brltty}/bin/brltty --no-daemon";
+        Type = "notify";
+        TimeoutStartSec = 5;
+        TimeoutStopSec = 10;
+        Restart = "always";
+        RestartSec = 30;
+        Nice = -10;
+        OOMScoreAdjust = -900;
+        ProtectHome = "read-only";
+        ProtectSystem = "full";
+        SystemCallArchitectures = "native";
+      };
+      wants = [ "systemd-udev-settle.service" ];
+      after = [ "local-fs.target" "systemd-udev-settle.service" ];
+      before = [ "sysinit.target" ];
+      wantedBy = [ "sysinit.target" ];
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/evscript.nix b/nixpkgs/nixos/modules/services/hardware/evscript.nix
new file mode 100644
index 000000000000..5f5f511013c7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/evscript.nix
@@ -0,0 +1,50 @@
+{ pkgs, lib, config, ... }:
+
+let
+  cfg = config.services.evscript;
+
+in
+{
+  options = with lib; {
+    services.evscript = {
+      enable = mkEnableOption "the evscript service";
+
+      package = mkOption {
+        description = "evscript package to use for the evscript service";
+        type = types.package;
+        default = pkgs.evscript;
+      };
+
+      devices = mkOption {
+        description = "evdev devices for evscript to listen to";
+        type = types.listOf types.path;
+        example = [ "/dev/input/by-path/pci-0000:00:1d.0-usb-0:1.1:1.0-event-kbd" ];
+      };
+
+      script = mkOption {
+        description = "Dyon script for evscript service to run";
+        type = types.path;
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    boot.kernelModules = [ "uinput" ];
+
+    services.udev.extraRules = ''
+      KERNEL=="uinput", MODE="0660", GROUP="input"
+    '';
+
+    systemd.services.evscript = {
+      after = [ "systemd-udevd" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.DynamicUser = true;
+      serviceConfig.SupplementaryGroups = [ "input" ];
+      script = ''
+        ${cfg.package}/bin/evscript \
+            ${lib.concatMapStringsSep " " (d: "-d ${d}") cfg.devices} \
+            -f ${cfg.script}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/fancontrol.nix b/nixpkgs/nixos/modules/services/hardware/fancontrol.nix
new file mode 100644
index 000000000000..bb4541a784da
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/fancontrol.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.fancontrol;
+  configFile = pkgs.writeText "fancontrol.conf" cfg.config;
+
+in{
+  options.hardware.fancontrol = {
+    enable = mkEnableOption "software fan control (requires fancontrol.config)";
+
+    config = mkOption {
+      default = null;
+      type = types.lines;
+      description = "Fancontrol configuration file content. See <citerefentry><refentrytitle>pwmconfig</refentrytitle><manvolnum>8</manvolnum></citerefentry> from the lm_sensors package.";
+      example = ''
+        # Configuration file generated by pwmconfig
+        INTERVAL=10
+        DEVPATH=hwmon3=devices/virtual/thermal/thermal_zone2 hwmon4=devices/platform/f71882fg.656
+        DEVNAME=hwmon3=soc_dts1 hwmon4=f71869a
+        FCTEMPS=hwmon4/device/pwm1=hwmon3/temp1_input
+        FCFANS= hwmon4/device/pwm1=hwmon4/device/fan1_input
+        MINTEMP=hwmon4/device/pwm1=35
+        MAXTEMP=hwmon4/device/pwm1=65
+        MINSTART=hwmon4/device/pwm1=150
+        MINSTOP=hwmon4/device/pwm1=0
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.fancontrol = {
+      unitConfig.Documentation = "man:fancontrol(8)";
+      description = "software fan control";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "lm_sensors.service" ];
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.lm_sensors}/sbin/fancontrol ${configFile}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/freefall.nix b/nixpkgs/nixos/modules/services/hardware/freefall.nix
new file mode 100644
index 000000000000..83f1e8c84f28
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/freefall.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.freefall;
+
+in {
+
+  options.services.freefall = {
+
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to protect HP/Dell laptop hard drives (not SSDs) in free fall.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.freefall;
+      defaultText = "pkgs.freefall";
+      description = ''
+        freefall derivation to use.
+      '';
+    };
+
+    devices = mkOption {
+      type = types.listOf types.str;
+      default = [ "/dev/sda" ];
+      description = ''
+        Device paths to all internal spinning hard drives.
+      '';
+    };
+
+  };
+
+  config = let
+
+    mkService = dev:
+      assert dev != "";
+      let dev' = utils.escapeSystemdPath dev; in
+      nameValuePair "freefall-${dev'}" {
+        description = "Free-fall protection for ${dev}";
+        after = [ "${dev'}.device" ];
+        wantedBy = [ "${dev'}.device" ];
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/freefall ${dev}";
+          Restart = "on-failure";
+          Type = "forking";
+        };
+      };
+
+  in mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services = builtins.listToAttrs (map mkService cfg.devices);
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/fwupd.nix b/nixpkgs/nixos/modules/services/hardware/fwupd.nix
new file mode 100644
index 000000000000..222ac8e487eb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/fwupd.nix
@@ -0,0 +1,129 @@
+# fwupd daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.fwupd;
+
+  customEtc = {
+    "fwupd/daemon.conf" = {
+      source = pkgs.writeText "daemon.conf" ''
+        [fwupd]
+        BlacklistDevices=${lib.concatStringsSep ";" cfg.blacklistDevices}
+        BlacklistPlugins=${lib.concatStringsSep ";" cfg.blacklistPlugins}
+      '';
+    };
+    "fwupd/uefi.conf" = {
+      source = pkgs.writeText "uefi.conf" ''
+        [uefi]
+        OverrideESPMountPoint=${config.boot.loader.efi.efiSysMountPoint}
+      '';
+    };
+  };
+
+  originalEtc =
+    let
+      mkEtcFile = n: nameValuePair n { source = "${cfg.package}/etc/${n}"; };
+    in listToAttrs (map mkEtcFile cfg.package.filesInstalledToEtc);
+  extraTrustedKeys =
+    let
+      mkName = p: "pki/fwupd/${baseNameOf (toString p)}";
+      mkEtcFile = p: nameValuePair (mkName p) { source = p; };
+    in listToAttrs (map mkEtcFile cfg.extraTrustedKeys);
+
+  # We cannot include the file in $out and rely on filesInstalledToEtc
+  # to install it because it would create a cyclic dependency between
+  # the outputs. We also need to enable the remote,
+  # which should not be done by default.
+  testRemote = if cfg.enableTestRemote then {
+    "fwupd/remotes.d/fwupd-tests.conf" = {
+      source = pkgs.runCommand "fwupd-tests-enabled.conf" {} ''
+        sed "s,^Enabled=false,Enabled=true," \
+        "${cfg.package.installedTests}/etc/fwupd/remotes.d/fwupd-tests.conf" > "$out"
+      '';
+    };
+  } else {};
+in {
+
+  ###### interface
+  options = {
+    services.fwupd = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable fwupd, a DBus service that allows
+          applications to update firmware.
+        '';
+      };
+
+      blacklistDevices = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ];
+        description = ''
+          Allow blacklisting specific devices by their GUID
+        '';
+      };
+
+      blacklistPlugins = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "udev" ];
+        description = ''
+          Allow blacklisting specific plugins
+        '';
+      };
+
+      extraTrustedKeys = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        example = literalExample "[ /etc/nixos/fwupd/myfirmware.pem ]";
+        description = ''
+          Installing a public key allows firmware signed with a matching private key to be recognized as trusted, which may require less authentication to install than for untrusted files. By default trusted firmware can be upgraded (but not downgraded) without the user or administrator password. Only very few keys are installed by default.
+        '';
+      };
+
+      enableTestRemote = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable test remote. This is used by
+          <link xlink:href="https://github.com/fwupd/fwupd/blob/master/data/installed-tests/README.md">installed tests</link>.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.fwupd;
+        description = ''
+          Which fwupd package to use.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    # Disable test related plug-ins implicitly so that users do not have to care about them.
+    services.fwupd.blacklistPlugins = cfg.package.defaultBlacklistedPlugins;
+
+    environment.systemPackages = [ cfg.package ];
+
+    # customEtc overrides some files from the package
+    environment.etc = originalEtc // customEtc // extraTrustedKeys // testRemote;
+
+    services.dbus.packages = [ cfg.package ];
+
+    services.udev.packages = [ cfg.package ];
+
+    systemd.packages = [ cfg.package ];
+  };
+
+  meta = {
+    maintainers = pkgs.fwupd.meta.maintainers;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/illum.nix b/nixpkgs/nixos/modules/services/hardware/illum.nix
new file mode 100644
index 000000000000..ff73c99a6537
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/illum.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.illum;
+in {
+
+  options = {
+
+    services.illum = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enable illum, a daemon for controlling screen brightness with brightness buttons.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.illum = {
+      description = "Backlight Adjustment Service";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${pkgs.illum}/bin/illum-d";
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/interception-tools.nix b/nixpkgs/nixos/modules/services/hardware/interception-tools.nix
new file mode 100644
index 000000000000..fadcb19a016f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/interception-tools.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.interception-tools;
+in {
+  options.services.interception-tools = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Whether to enable the interception tools service.";
+    };
+
+    plugins = mkOption {
+      type = types.listOf types.package;
+      default = [ pkgs.interception-tools-plugins.caps2esc ];
+      description = ''
+        A list of interception tools plugins that will be made available to use
+        inside the udevmon configuration.
+      '';
+    };
+
+    udevmonConfig = mkOption {
+      type = types.either types.str types.path;
+      default = ''
+        - JOB: "intercept -g $DEVNODE | caps2esc | uinput -d $DEVNODE"
+          DEVICE:
+            EVENTS:
+              EV_KEY: [KEY_CAPSLOCK, KEY_ESC]
+      '';
+      example = ''
+        - JOB: "intercept -g $DEVNODE | y2z | x2y | uinput -d $DEVNODE"
+          DEVICE:
+            EVENTS:
+              EV_KEY: [KEY_X, KEY_Y]
+      '';
+      description = ''
+        String of udevmon YAML configuration, or path to a udevmon YAML
+        configuration file.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.interception-tools = {
+      description = "Interception tools";
+      path = [ pkgs.bash pkgs.interception-tools ] ++ cfg.plugins;
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.interception-tools}/bin/udevmon -c \
+          ${if builtins.typeOf cfg.udevmonConfig == "path"
+          then cfg.udevmonConfig
+          else pkgs.writeText "udevmon.yaml" cfg.udevmonConfig}
+        '';
+        Nice = -20;
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/irqbalance.nix b/nixpkgs/nixos/modules/services/hardware/irqbalance.nix
new file mode 100644
index 000000000000..c79e0eb83ece
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/irqbalance.nix
@@ -0,0 +1,24 @@
+#
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.irqbalance;
+
+in
+{
+  options.services.irqbalance.enable = mkEnableOption "irqbalance daemon";
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.irqbalance ];
+
+    systemd.services.irqbalance.wantedBy = ["multi-user.target"];
+
+    systemd.packages = [ pkgs.irqbalance ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/lcd.nix b/nixpkgs/nixos/modules/services/hardware/lcd.nix
new file mode 100644
index 000000000000..d78d742cd318
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/lcd.nix
@@ -0,0 +1,172 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.hardware.lcd;
+  pkg = lib.getBin pkgs.lcdproc;
+
+  serverCfg = pkgs.writeText "lcdd.conf" ''
+    [server]
+    DriverPath=${pkg}/lib/lcdproc/
+    ReportToSyslog=false
+    Bind=${cfg.serverHost}
+    Port=${toString cfg.serverPort}
+    ${cfg.server.extraConfig}
+  '';
+
+  clientCfg = pkgs.writeText "lcdproc.conf" ''
+    [lcdproc]
+    Server=${cfg.serverHost}
+    Port=${toString cfg.serverPort}
+    ReportToSyslog=false
+    ${cfg.client.extraConfig}
+  '';
+
+  serviceCfg = {
+    DynamicUser = true;
+    Restart = "on-failure";
+    Slice = "lcd.slice";
+  };
+
+in with lib; {
+
+  meta.maintainers = with maintainers; [ peterhoeg ];
+
+  options = with types; {
+    services.hardware.lcd = {
+      serverHost = mkOption {
+        type = str;
+        default = "localhost";
+        description = "Host on which LCDd is listening.";
+      };
+
+      serverPort = mkOption {
+        type = int;
+        default = 13666;
+        description = "Port on which LCDd is listening.";
+      };
+
+      server = {
+        enable = mkOption {
+          type = bool;
+          default = false;
+          description = "Enable the LCD panel server (LCDd)";
+        };
+
+        openPorts = mkOption {
+          type = bool;
+          default = false;
+          description = "Open the ports in the firewall";
+        };
+
+        usbPermissions = mkOption {
+          type = bool;
+          default = false;
+          description = ''
+            Set group-write permissions on a USB device.
+            </para>
+            <para>
+            A USB connected LCD panel will most likely require having its
+            permissions modified for lcdd to write to it. Enabling this option
+            sets group-write permissions on the device identified by
+            <option>services.hardware.lcd.usbVid</option> and
+            <option>services.hardware.lcd.usbPid</option>. In order to find the
+            values, you can run the <command>lsusb</command> command. Example
+            output:
+            </para>
+            <para>
+            <literal>
+            Bus 005 Device 002: ID 0403:c630 Future Technology Devices International, Ltd lcd2usb interface
+            </literal>
+            </para>
+            <para>
+            In this case the vendor id is 0403 and the product id is c630.
+          '';
+        };
+
+        usbVid = mkOption {
+          type = str;
+          default = "";
+          description = "The vendor ID of the USB device to claim.";
+        };
+
+        usbPid = mkOption {
+          type = str;
+          default = "";
+          description = "The product ID of the USB device to claim.";
+        };
+
+        usbGroup = mkOption {
+          type = str;
+          default = "dialout";
+          description = "The group to use for settings permissions. This group must exist or you will have to create it.";
+        };
+
+        extraConfig = mkOption {
+          type = lines;
+          default = "";
+          description = "Additional configuration added verbatim to the server config.";
+        };
+      };
+
+      client = {
+        enable = mkOption {
+          type = bool;
+          default = false;
+          description = "Enable the LCD panel client (LCDproc)";
+        };
+
+        extraConfig = mkOption {
+          type = lines;
+          default = "";
+          description = "Additional configuration added verbatim to the client config.";
+        };
+
+        restartForever = mkOption {
+          type = bool;
+          default = true;
+          description = "Try restarting the client forever.";
+        };
+      };
+    };
+  };
+
+  config = mkIf (cfg.server.enable || cfg.client.enable) {
+    networking.firewall.allowedTCPPorts = mkIf (cfg.server.enable && cfg.server.openPorts) [ cfg.serverPort ];
+
+    services.udev.extraRules = mkIf (cfg.server.enable && cfg.server.usbPermissions) ''
+      ACTION=="add", SUBSYSTEMS=="usb", ATTRS{idVendor}=="${cfg.server.usbVid}", ATTRS{idProduct}=="${cfg.server.usbPid}", MODE="660", GROUP="${cfg.server.usbGroup}"
+    '';
+
+    systemd.services = {
+      lcdd = mkIf cfg.server.enable {
+        description = "LCDproc - server";
+        wantedBy = [ "lcd.target" ];
+        serviceConfig = serviceCfg // {
+          ExecStart = "${pkg}/bin/LCDd -f -c ${serverCfg}";
+          SupplementaryGroups = cfg.server.usbGroup;
+        };
+      };
+
+      lcdproc = mkIf cfg.client.enable {
+        description = "LCDproc - client";
+        after = [ "lcdd.service" ];
+        wantedBy = [ "lcd.target" ];
+        serviceConfig = serviceCfg // {
+          ExecStart = "${pkg}/bin/lcdproc -f -c ${clientCfg}";
+          # If the server is being restarted at the same time, the client will
+          # fail as it cannot connect, so space it out a bit.
+          RestartSec = "5";
+          # Allow restarting for eternity
+          StartLimitIntervalSec = lib.mkIf cfg.client.restartForever "0";
+          StartLimitBurst = lib.mkIf cfg.client.restartForever "0";
+        };
+      };
+    };
+
+    systemd.targets.lcd = {
+      description = "LCD client/server";
+      after = [ "lcdd.service" "lcdproc.service" ];
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/lirc.nix b/nixpkgs/nixos/modules/services/hardware/lirc.nix
new file mode 100644
index 000000000000..826e512c75d1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/lirc.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lirc;
+in {
+
+  ###### interface
+
+  options = {
+    services.lirc = {
+
+      enable = mkEnableOption "LIRC daemon";
+
+      options = mkOption {
+        type = types.lines;
+        example = ''
+          [lircd]
+          nodaemon = False
+        '';
+        description = "LIRC default options descriped in man:lircd(8) (<filename>lirc_options.conf</filename>)";
+      };
+
+      configs = mkOption {
+        type = types.listOf types.lines;
+        description = "Configurations for lircd to load, see man:lircd.conf(5) for details (<filename>lircd.conf</filename>)";
+      };
+
+      extraArguments = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Extra arguments to lircd.";
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    # Note: LIRC executables raises a warning, if lirc_options.conf do not exists
+    environment.etc."lirc/lirc_options.conf".text = cfg.options;
+
+    passthru.lirc.socket = "/run/lirc/lircd";
+
+    environment.systemPackages = [ pkgs.lirc ];
+
+    systemd.sockets.lircd = {
+      description = "LIRC daemon socket";
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        ListenStream = config.passthru.lirc.socket;
+        SocketUser = "lirc";
+        SocketMode = "0660";
+      };
+    };
+
+    systemd.services.lircd = let
+      configFile = pkgs.writeText "lircd.conf" (builtins.concatStringsSep "\n" cfg.configs);
+    in {
+      description = "LIRC daemon service";
+      after = [ "network.target" ];
+
+      unitConfig.Documentation = [ "man:lircd(8)" ];
+
+      serviceConfig = {
+        RuntimeDirectory = "lirc";
+
+        # Service runtime directory and socket share same folder.
+        # Following hacks are necessary to get everything right:
+
+        # 1. prevent socket deletion during stop and restart
+        RuntimeDirectoryPreserve = true;
+
+        # 2. fix runtime folder owner-ship, happens when socket activation
+        #    creates the folder
+        PermissionsStartOnly = true;
+        ExecStartPre = [
+          "${pkgs.coreutils}/bin/chown lirc /run/lirc/"
+        ];
+
+        ExecStart = ''
+          ${pkgs.lirc}/bin/lircd --nodaemon \
+            ${escapeShellArgs cfg.extraArguments} \
+            ${configFile}
+        '';
+        User = "lirc";
+      };
+    };
+
+    users.users.lirc = {
+      uid = config.ids.uids.lirc;
+      group = "lirc";
+      description = "LIRC user for lircd";
+    };
+
+    users.groups.lirc.gid = config.ids.gids.lirc;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/nvidia-optimus.nix b/nixpkgs/nixos/modules/services/hardware/nvidia-optimus.nix
new file mode 100644
index 000000000000..d53175052c74
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/nvidia-optimus.nix
@@ -0,0 +1,43 @@
+{ config, lib, ... }:
+
+let kernel = config.boot.kernelPackages; in
+
+{
+
+  ###### interface
+
+  options = {
+
+    hardware.nvidiaOptimus.disable = lib.mkOption {
+      default = false;
+      type = lib.types.bool;
+      description = ''
+        Completely disable the NVIDIA graphics card and use the
+        integrated graphics processor instead.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = lib.mkIf config.hardware.nvidiaOptimus.disable {
+    boot.blacklistedKernelModules = ["nouveau" "nvidia" "nvidiafb" "nvidia-drm"];
+    boot.kernelModules = [ "bbswitch" ];
+    boot.extraModulePackages = [ kernel.bbswitch ];
+
+    systemd.services.bbswitch = {
+      description = "Disable NVIDIA Card";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${kernel.bbswitch}/bin/discrete_vga_poweroff";
+        ExecStop = "${kernel.bbswitch}/bin/discrete_vga_poweron";
+      };
+      path = [ kernel.bbswitch ];
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/pcscd.nix b/nixpkgs/nixos/modules/services/hardware/pcscd.nix
new file mode 100644
index 000000000000..f3fc4c3cc79e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/pcscd.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfgFile = pkgs.writeText "reader.conf" config.services.pcscd.readerConfig;
+
+  pluginEnv = pkgs.buildEnv {
+    name = "pcscd-plugins";
+    paths = map (p: "${p}/pcsc/drivers") config.services.pcscd.plugins;
+  };
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.pcscd = {
+      enable = mkEnableOption "PCSC-Lite daemon";
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [ pkgs.ccid ];
+        defaultText = "[ pkgs.ccid ]";
+        example = literalExample "[ pkgs.pcsc-cyberjack ]";
+        description = "Plugin packages to be used for PCSC-Lite.";
+      };
+
+      readerConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          FRIENDLYNAME      "Some serial reader"
+          DEVICENAME        /dev/ttyS0
+          LIBPATH           /path/to/serial_reader.so
+          CHANNELID         1
+        '';
+        description = ''
+          Configuration for devices that aren't hotpluggable.
+
+          See <citerefentry><refentrytitle>reader.conf</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for valid options.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.pcscd.enable {
+
+    systemd.sockets.pcscd = {
+      description = "PCSC-Lite Socket";
+      wantedBy = [ "sockets.target" ];
+      before = [ "multi-user.target" ];
+      socketConfig.ListenStream = "/run/pcscd/pcscd.comm";
+    };
+
+    systemd.services.pcscd = {
+      description = "PCSC-Lite daemon";
+      environment.PCSCLITE_HP_DROPDIR = pluginEnv;
+      serviceConfig = {
+        ExecStart = "${getBin pkgs.pcsclite}/sbin/pcscd -f -x -c ${cfgFile}";
+        ExecReload = "${getBin pkgs.pcsclite}/sbin/pcscd -H";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/pommed.nix b/nixpkgs/nixos/modules/services/hardware/pommed.nix
new file mode 100644
index 000000000000..bf7d6a46a293
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/pommed.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.hardware.pommed;
+    defaultConf = "${pkgs.pommed_light}/etc/pommed.conf.mactel";
+in {
+
+  options = {
+
+    services.hardware.pommed = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use the pommed tool to handle Apple laptop
+          keyboard hotkeys.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the <filename>pommed.conf</filename> file. Leave
+          to null to use the default config file
+          (<filename>/etc/pommed.conf.mactel</filename>). See the
+          files <filename>/etc/pommed.conf.mactel</filename> and
+          <filename>/etc/pommed.conf.pmac</filename> for examples to
+          build on.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.polkit pkgs.pommed_light ];
+
+    environment.etc."pommed.conf".source =
+      if cfg.configFile == null then defaultConf else cfg.configFile;
+
+    systemd.services.pommed = {
+      description = "Pommed Apple Hotkeys Daemon";
+      wantedBy = [ "multi-user.target" ];
+      script = "${pkgs.pommed_light}/bin/pommed -f";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/ratbagd.nix b/nixpkgs/nixos/modules/services/hardware/ratbagd.nix
new file mode 100644
index 000000000000..01a8276750f2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/ratbagd.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ratbagd;
+in
+{
+  ###### interface
+
+  options = {
+    services.ratbagd = {
+      enable = mkEnableOption "ratbagd for configuring gaming mice";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    # Give users access to the "ratbagctl" tool
+    environment.systemPackages = [ pkgs.libratbag ];
+
+    services.dbus.packages = [ pkgs.libratbag ];
+
+    systemd.packages = [ pkgs.libratbag ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/sane.nix b/nixpkgs/nixos/modules/services/hardware/sane.nix
new file mode 100644
index 000000000000..b344dfc20610
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/sane.nix
@@ -0,0 +1,162 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  pkg = if config.hardware.sane.snapshot
+    then pkgs.sane-backends-git
+    else pkgs.sane-backends;
+
+  sanedConf = pkgs.writeTextFile {
+    name = "saned.conf";
+    destination = "/etc/sane.d/saned.conf";
+    text = ''
+      localhost
+      ${config.services.saned.extraConfig}
+    '';
+  };
+
+  netConf = pkgs.writeTextFile {
+    name = "net.conf";
+    destination = "/etc/sane.d/net.conf";
+    text = ''
+      ${lib.optionalString config.services.saned.enable "localhost"}
+      ${config.hardware.sane.netConf}
+    '';
+  };
+
+  env = {
+    SANE_CONFIG_DIR = config.hardware.sane.configDir;
+    LD_LIBRARY_PATH = [ "${saneConfig}/lib/sane" ];
+  };
+
+  backends = [ pkg netConf ] ++ optional config.services.saned.enable sanedConf ++ config.hardware.sane.extraBackends;
+  saneConfig = pkgs.mkSaneConfig { paths = backends; };
+
+  enabled = config.hardware.sane.enable || config.services.saned.enable;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    hardware.sane.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable support for SANE scanners.
+
+        <note><para>
+          Users in the "scanner" group will gain access to the scanner, or the "lp" group if it's also a printer.
+        </para></note>
+      '';
+    };
+
+    hardware.sane.snapshot = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Use a development snapshot of SANE scanner drivers.";
+    };
+
+    hardware.sane.extraBackends = mkOption {
+      type = types.listOf types.path;
+      default = [];
+      description = ''
+        Packages providing extra SANE backends to enable.
+
+        <note><para>
+          The example contains the package for HP scanners.
+        </para></note>
+      '';
+      example = literalExample "[ pkgs.hplipWithPlugin ]";
+    };
+
+    hardware.sane.configDir = mkOption {
+      type = types.str;
+      internal = true;
+      description = "The value of SANE_CONFIG_DIR.";
+    };
+
+    hardware.sane.netConf = mkOption {
+      type = types.lines;
+      default = "";
+      example = "192.168.0.16";
+      description = ''
+        Network hosts that should be probed for remote scanners.
+      '';
+    };
+
+    services.saned.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable saned network daemon for remote connection to scanners.
+
+        saned would be runned from <literal>scanner</literal> user; to allow
+        access to hardware that doesn't have <literal>scanner</literal> group
+        you should add needed groups to this user.
+      '';
+    };
+
+    services.saned.extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = "192.168.0.0/24";
+      description = ''
+        Extra saned configuration lines.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkMerge [
+    (mkIf enabled {
+      hardware.sane.configDir = mkDefault "${saneConfig}/etc/sane.d";
+
+      environment.systemPackages = backends;
+      environment.sessionVariables = env;
+      services.udev.packages = backends;
+
+      users.groups.scanner.gid = config.ids.gids.scanner;
+    })
+
+    (mkIf config.services.saned.enable {
+      networking.firewall.connectionTrackingModules = [ "sane" ];
+
+      systemd.services."saned@" = {
+        description = "Scanner Service";
+        environment = mapAttrs (name: val: toString val) env;
+        serviceConfig = {
+          User = "scanner";
+          Group = "scanner";
+          ExecStart = "${pkg}/bin/saned";
+        };
+      };
+
+      systemd.sockets.saned = {
+        description = "saned incoming socket";
+        wantedBy = [ "sockets.target" ];
+        listenStreams = [ "0.0.0.0:6566" "[::]:6566" ];
+        socketConfig = {
+          # saned needs to distinguish between IPv4 and IPv6 to open matching data sockets.
+          BindIPv6Only = "ipv6-only";
+          Accept = true;
+          MaxConnections = 1;
+        };
+      };
+
+      users.users.scanner = {
+        uid = config.ids.uids.scanner;
+        group = "scanner";
+      };
+    })
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
new file mode 100644
index 000000000000..6f49a1ab6d40
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.sane.brscan4;
+
+  netDeviceList = attrValues cfg.netDevices;
+
+  etcFiles = pkgs.callPackage ./brscan4_etc_files.nix { netDevices = netDeviceList; };
+
+  netDeviceOpts = { name, ... }: {
+
+    options = {
+
+      name = mkOption {
+        type = types.str;
+        description = ''
+          The friendly name you give to the network device. If undefined,
+          the name of attribute will be used.
+        '';
+
+        example = literalExample "office1";
+      };
+
+      model = mkOption {
+        type = types.str;
+        description = ''
+          The model of the network device.
+        '';
+
+        example = literalExample "MFC-7860DW";
+      };
+
+      ip = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          The ip address of the device. If undefined, you will have to
+          provide a nodename.
+        '';
+
+        example = literalExample "192.168.1.2";
+      };
+
+      nodename = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          The node name of the device. If undefined, you will have to
+          provide an ip.
+        '';
+
+        example = literalExample "BRW0080927AFBCE";
+      };
+
+    };
+
+
+    config =
+      { name = mkDefault name;
+      };
+  };
+
+in
+
+{
+  options = {
+
+    hardware.sane.brscan4.enable =
+      mkEnableOption "Brother's brscan4 scan backend" // {
+      description = ''
+        When enabled, will automatically register the "brscan4" sane
+        backend and bring configuration files to their expected location.
+      '';
+    };
+
+    hardware.sane.brscan4.netDevices = mkOption {
+      default = {};
+      example =
+        { office1 = { model = "MFC-7860DW"; ip = "192.168.1.2"; };
+          office2 = { model = "MFC-7860DW"; nodename = "BRW0080927AFBCE"; };
+        };
+      type = with types; loaOf (submodule netDeviceOpts);
+      description = ''
+        The list of network devices that will be registered against the brscan4
+        sane backend.
+      '';
+    };
+  };
+
+  config = mkIf (config.hardware.sane.enable && cfg.enable) {
+
+    hardware.sane.extraBackends = [
+      pkgs.brscan4
+    ];
+
+    environment.etc."opt/brother/scanner/brscan4" =
+      { source = "${etcFiles}/etc/opt/brother/scanner/brscan4"; };
+
+    assertions = [
+      { assertion = all (x: !(null != x.ip && null != x.nodename)) netDeviceList;
+        message = ''
+          When describing a network device as part of the attribute list
+          `hardware.sane.brscan4.netDevices`, only one of its `ip` or `nodename`
+          attribute should be specified, not both!
+        '';
+      }
+    ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
new file mode 100644
index 000000000000..ec0457bbd583
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
@@ -0,0 +1,71 @@
+{ stdenv, lib, brscan4, netDevices ? [] }:
+
+/*
+
+Testing
+-------
+
+No net devices:
+
+~~~
+nix-shell -E 'with import <nixpkgs> { }; brscan4-etc-files'
+~~~
+
+Two net devices:
+
+~~~
+nix-shell -E 'with import <nixpkgs> { }; brscan4-etc-files.override{netDevices=[{name="a"; model="MFC-7860DW"; nodename="BRW0080927AFBCE";} {name="b"; model="MFC-7860DW"; ip="192.168.1.2";}];}'
+~~~
+
+*/
+
+with lib;
+
+let
+
+  addNetDev = nd: ''
+    brsaneconfig4 -a \
+    name="${nd.name}" \
+    model="${nd.model}" \
+    ${if (hasAttr "nodename" nd && nd.nodename != null) then
+      ''nodename="${nd.nodename}"'' else
+      ''ip="${nd.ip}"''}'';
+  addAllNetDev = xs: concatStringsSep "\n" (map addNetDev xs);
+in
+
+stdenv.mkDerivation {
+
+  name = "brscan4-etc-files-0.4.3-3";
+  src = "${brscan4}/opt/brother/scanner/brscan4";
+
+  nativeBuildInputs = [ brscan4 ];
+
+  dontConfigure = true;
+
+  buildPhase = ''
+    TARGET_DIR="$out/etc/opt/brother/scanner/brscan4"
+    mkdir -p "$TARGET_DIR"
+    cp -rp "./models4" "$TARGET_DIR"
+    cp -rp "./Brsane4.ini" "$TARGET_DIR"
+    cp -rp "./brsanenetdevice4.cfg" "$TARGET_DIR"
+
+    export BRSANENETDEVICE4_CFG_FILENAME="$TARGET_DIR/brsanenetdevice4.cfg"
+
+    printf '${addAllNetDev netDevices}\n'
+
+    ${addAllNetDev netDevices}
+  '';
+
+  installPhase = ":";
+
+  dontStrip = true;
+  dontPatchELF = true;
+
+  meta = {
+    description = "Brother brscan4 sane backend driver etc files";
+    homepage = "http://www.brother.com";
+    platforms = stdenv.lib.platforms.linux;
+    license = stdenv.lib.licenses.unfree;
+    maintainers = with stdenv.lib.maintainers; [ jraygauthier ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix
new file mode 100644
index 000000000000..d71a17f5ea6b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/sane_extra_backends/dsseries.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  options = {
+
+    hardware.sane.dsseries.enable =
+      mkEnableOption "Brother DSSeries scan backend" // {
+      description = ''
+        When enabled, will automatically register the "dsseries" SANE backend.
+
+        This supports the Brother DSmobile scanner series, including the
+        DS-620, DS-720D, DS-820W, and DS-920DW scanners.
+      '';
+    };
+  };
+
+  config = mkIf (config.hardware.sane.enable && config.hardware.sane.dsseries.enable) {
+
+    hardware.sane.extraBackends = [ pkgs.dsseries ];
+    services.udev.packages = [ pkgs.dsseries ];
+    boot.kernelModules = [ "sg" ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/tcsd.nix b/nixpkgs/nixos/modules/services/hardware/tcsd.nix
new file mode 100644
index 000000000000..68cb5d791aa3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/tcsd.nix
@@ -0,0 +1,151 @@
+# tcsd daemon.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+
+  cfg = config.services.tcsd;
+
+  tcsdConf = pkgs.writeText "tcsd.conf" ''
+    port = 30003
+    num_threads = 10
+    system_ps_file = ${cfg.stateDir}/system.data
+    # This is the log of each individual measurement done by the system.
+    # By re-calculating the PCR registers based on this information, even
+    # finer details about the measured environment can be inferred than
+    # what is available directly from the PCR registers.
+    firmware_log_file = /sys/kernel/security/tpm0/binary_bios_measurements
+    kernel_log_file = /sys/kernel/security/ima/binary_runtime_measurements
+    firmware_pcrs = ${cfg.firmwarePCRs}
+    kernel_pcrs = ${cfg.kernelPCRs}
+    platform_cred = ${cfg.platformCred}
+    conformance_cred = ${cfg.conformanceCred}
+    endorsement_cred = ${cfg.endorsementCred}
+    #remote_ops = create_key,random
+    #host_platform_class = server_12
+    #all_platform_classes = pc_11,pc_12,mobile_12
+  '';
+
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.tcsd = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to enable tcsd, a Trusted Computing management service
+          that provides TCG Software Stack (TSS).  The tcsd daemon is
+          the only portal to the Trusted Platform Module (TPM), a hardware
+          chip on the motherboard.
+        '';
+      };
+
+      user = mkOption {
+        default = "tss";
+        type = types.str;
+        description = "User account under which tcsd runs.";
+      };
+
+      group = mkOption {
+        default = "tss";
+        type = types.str;
+        description = "Group account under which tcsd runs.";
+      };
+
+      stateDir = mkOption {
+        default = "/var/lib/tpm";
+        type = types.path;
+        description = ''
+          The location of the system persistent storage file.
+          The system persistent storage file holds keys and data across
+          restarts of the TCSD and system reboots.
+        '';
+      };
+
+      firmwarePCRs = mkOption {
+        default = "0,1,2,3,4,5,6,7";
+        type = types.str;
+        description = "PCR indices used in the TPM for firmware measurements.";
+      };
+
+      kernelPCRs = mkOption {
+        default = "8,9,10,11,12";
+        type = types.str;
+        description = "PCR indices used in the TPM for kernel measurements.";
+      };
+
+      platformCred = mkOption {
+        default = "${cfg.stateDir}/platform.cert";
+        type = types.path;
+        description = ''
+          Path to the platform credential for your TPM. Your TPM
+          manufacturer may have provided you with a set of credentials
+          (certificates) that should be used when creating identities
+          using your TPM. When a user of your TPM makes an identity,
+          this credential will be encrypted as part of that process.
+          See the 1.1b TPM Main specification section 9.3 for information
+          on this process. '';
+      };
+
+      conformanceCred = mkOption {
+        default = "${cfg.stateDir}/conformance.cert";
+        type = types.path;
+        description = ''
+          Path to the conformance credential for your TPM.
+          See also the platformCred option'';
+      };
+
+      endorsementCred = mkOption {
+        default = "${cfg.stateDir}/endorsement.cert";
+        type = types.path;
+        description = ''
+          Path to the endorsement credential for your TPM.
+          See also the platformCred option'';
+      };
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.trousers ];
+
+#    system.activationScripts.tcsd =
+#      ''
+#        chown ${cfg.user}:${cfg.group} ${tcsdConf}
+#      '';
+
+    systemd.services.tcsd = {
+      description = "TCSD";
+      after = [ "systemd-udev-settle.service" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.trousers ];
+      preStart =
+        ''
+        mkdir -m 0700 -p ${cfg.stateDir}
+        chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+        '';
+      serviceConfig.ExecStart = "${pkgs.trousers}/sbin/tcsd -f -c ${tcsdConf}";
+    };
+
+    users.users = optionalAttrs (cfg.user == "tss") {
+      tss = {
+        group = "tss";
+        uid = config.ids.uids.tss;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "tss") {
+      tss.gid = config.ids.gids.tss;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/thermald.nix b/nixpkgs/nixos/modules/services/hardware/thermald.nix
new file mode 100644
index 000000000000..ecb529e9bf01
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/thermald.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.thermald;
+in {
+  ###### interface
+  options = {
+    services.thermald = {
+      enable = mkEnableOption "thermald, the temperature management daemon";
+
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable debug logging.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "the thermald manual configuration file.";
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.dbus.packages = [ pkgs.thermald ];
+
+    systemd.services.thermald = {
+      description = "Thermal Daemon Service";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.thermald}/sbin/thermald \
+            --no-daemon \
+            ${optionalString cfg.debug "--loglevel=debug"} \
+            ${optionalString (cfg.configFile != null) "--config-file ${cfg.configFile}"} \
+            --dbus-enable
+        '';
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/thinkfan.nix b/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
new file mode 100644
index 000000000000..7c105e99ca54
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/thinkfan.nix
@@ -0,0 +1,152 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.thinkfan;
+  configFile = pkgs.writeText "thinkfan.conf" ''
+    # ATTENTION: There is only very basic sanity checking on the configuration.
+    # That means you can set your temperature limits as insane as you like. You
+    # can do anything stupid, e.g. turn off your fan when your CPU reaches 70°C.
+    #
+    # That's why this program is called THINKfan: You gotta think for yourself.
+    #
+    ######################################################################
+    #
+    # IBM/Lenovo Thinkpads (thinkpad_acpi, /proc/acpi/ibm)
+    # ====================================================
+    #
+    # IMPORTANT:
+    #
+    # To keep your HD from overheating, you have to specify a correction value for
+    # the sensor that has the HD's temperature. You need to do this because
+    # thinkfan uses only the highest temperature it can find in the system, and
+    # that'll most likely never be your HD, as most HDs are already out of spec
+    # when they reach 55 °C.
+    # Correction values are applied from left to right in the same order as the
+    # temperatures are read from the file.
+    #
+    # For example:
+    # tp_thermal /proc/acpi/ibm/thermal (0, 0, 10)
+    # will add a fixed value of 10 °C the 3rd value read from that file. Check out
+    # http://www.thinkwiki.org/wiki/Thermal_Sensors to find out how much you may
+    # want to add to certain temperatures.
+
+    ${cfg.fan}
+    ${cfg.sensors}
+
+    #  Syntax:
+    #  (LEVEL, LOW, HIGH)
+    #  LEVEL is the fan level to use (0-7 with thinkpad_acpi)
+    #  LOW is the temperature at which to step down to the previous level
+    #  HIGH is the temperature at which to step up to the next level
+    #  All numbers are integers.
+    #
+
+    ${cfg.levels}
+  '';
+
+  thinkfan = pkgs.thinkfan.override { smartSupport = cfg.smartSupport; };
+
+in {
+
+  options = {
+
+    services.thinkfan = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable thinkfan, fan controller for IBM/Lenovo ThinkPads.
+        '';
+      };
+
+      smartSupport = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to build thinkfan with SMART support to read temperatures 
+          directly from hard disks.
+        '';
+      };
+
+      sensors = mkOption {
+        type = types.lines;
+        default = ''
+          tp_thermal /proc/acpi/ibm/thermal (0,0,10)
+        '';
+        description =''
+          thinkfan can read temperatures from three possible sources:
+
+            /proc/acpi/ibm/thermal
+              Which is provided by the thinkpad_acpi kernel
+              module (keyword tp_thermal)
+
+            /sys/class/hwmon/*/temp*_input
+              Which may be provided by any hwmon drivers (keyword
+              hwmon)
+
+            S.M.A.R.T. (requires smartSupport to be enabled)
+              Which reads the temperature directly from the hard
+              disk using libatasmart (keyword atasmart)
+
+          Multiple sensors may be added, in which case they will be
+          numbered in their order of appearance.
+        '';
+      };
+
+      fan = mkOption {
+        type = types.str;
+        default = "tp_fan /proc/acpi/ibm/fan";
+        description =''
+          Specifies the fan we want to use.
+          On anything other than a Thinkpad you'll probably
+          use some PWM control file in /sys/class/hwmon.
+          A sysfs fan would be specified like this:
+            pwm_fan /sys/class/hwmon/hwmon2/device/pwm1
+        '';
+      };
+
+      levels = mkOption {
+        type = types.lines;
+        default = ''
+          (0,     0,      55)
+          (1,     48,     60)
+          (2,     50,     61)
+          (3,     52,     63)
+          (6,     56,     65)
+          (7,     60,     85)
+          (127,   80,     32767)
+        '';
+        description = ''
+          (LEVEL, LOW, HIGH)
+          LEVEL is the fan level to use (0-7 with thinkpad_acpi).
+          LOW is the temperature at which to step down to the previous level.
+          HIGH is the temperature at which to step up to the next level.
+          All numbers are integers.
+        '';
+      };
+
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ thinkfan ];
+
+    systemd.services.thinkfan = {
+      description = "Thinkfan";
+      after = [ "basic.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ thinkfan ];
+      serviceConfig.ExecStart = "${thinkfan}/bin/thinkfan -n -c ${configFile}";
+    };
+
+    boot.extraModprobeConfig = "options thinkpad_acpi experimental=1 fan_control=1";
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/throttled.nix b/nixpkgs/nixos/modules/services/hardware/throttled.nix
new file mode 100644
index 000000000000..7617c4492d7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/throttled.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.throttled;
+in {
+  options = {
+    services.throttled = {
+      enable = mkEnableOption "fix for Intel CPU throttling";
+
+      extraConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = "Alternative configuration";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.packages = [ pkgs.throttled ];
+    # The upstream package has this in Install, but that's not enough, see the NixOS manual
+    systemd.services.lenovo_fix.wantedBy = [ "multi-user.target" ];
+
+    environment.etc."lenovo_fix.conf".source =
+      if cfg.extraConfig != ""
+      then pkgs.writeText "lenovo_fix.conf" cfg.extraConfig
+      else "${pkgs.throttled}/etc/lenovo_fix.conf";
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/tlp.nix b/nixpkgs/nixos/modules/services/hardware/tlp.nix
new file mode 100644
index 000000000000..3962d7b15989
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/tlp.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.tlp;
+  enableRDW = config.networking.networkmanager.enable;
+  tlp = pkgs.tlp.override { inherit enableRDW; };
+  # TODO: Use this for having proper parameters in the future
+  mkTlpConfig = tlpConfig: generators.toKeyValue {
+    mkKeyValue = generators.mkKeyValueDefault {
+      mkValueString = val:
+        if isInt val then toString val
+        else if isString val then val
+        else if true == val then "1"
+        else if false == val then "0"
+        else if isList val then "\"" + (concatStringsSep " " val) + "\""
+        else err "invalid value provided to mkTlpConfig:" (toString val);
+    } "=";
+  } tlpConfig;
+in
+{
+  ###### interface
+  options = {
+    services.tlp = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the TLP power management daemon.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional configuration variables for TLP";
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "msr" ];
+
+    environment.etc = {
+      "tlp.conf".text = cfg.extraConfig;
+    } // optionalAttrs enableRDW {
+      "NetworkManager/dispatcher.d/99tlp-rdw-nm".source =
+        "${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm";
+    };
+
+    environment.systemPackages = [ tlp ];
+
+    # FIXME: When the config is parametrized we need to move these into a
+    # conditional on the relevant options being enabled.
+    powerManagement = {
+      scsiLinkPolicy = null;
+      cpuFreqGovernor = null;
+      cpufreq.max = null;
+      cpufreq.min = null;
+    };
+
+    services.udev.packages = [ tlp ];
+
+    systemd = {
+      packages = [ tlp ];
+      # XXX: These must always be disabled/masked according to [1].
+      #
+      # [1]: https://github.com/linrunner/TLP/blob/a9ada09e0821f275ce5f93dc80a4d81a7ff62ae4/tlp-stat.in#L319
+      sockets.systemd-rfkill.enable = false;
+      services.systemd-rfkill.enable = false;
+
+      services.tlp = {
+        # XXX: The service should reload whenever the configuration changes,
+        # otherwise newly set power options remain inactive until reboot (or
+        # manual unit restart.)
+        restartTriggers = [ config.environment.etc."tlp.conf".source ];
+        # XXX: When using systemd.packages (which we do above) the [Install]
+        # section of systemd units does not work (citation needed) so we manually
+        # enforce it here.
+        wantedBy = [ "multi-user.target" ];
+      };
+
+      services.tlp-sleep = {
+        # XXX: When using systemd.packages (which we do above) the [Install]
+        # section of systemd units does not work (citation needed) so we manually
+        # enforce it here.
+        before = [ "sleep.target" ];
+        wantedBy = [ "sleep.target" ];
+        # XXX: `tlp suspend` requires /var/lib/tlp to exist in order to save
+        # some stuff in there. There is no way, that I know of, to do this in
+        # the package itself, so we do it here instead making sure the unit
+        # won't fail due to the save dir not existing.
+        serviceConfig.StateDirectory = "tlp";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/trezord.nix b/nixpkgs/nixos/modules/services/hardware/trezord.nix
new file mode 100644
index 000000000000..c517e9fbb2bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/trezord.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.trezord;
+in {
+
+  ### docs
+
+  meta = {
+    doc = ./trezord.xml;
+  };
+  
+  ### interface
+
+  options = {
+    services.trezord = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable Trezor bridge daemon, for use with Trezor hardware bitcoin wallets.
+        '';
+      };
+
+      emulator.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable Trezor emulator support.
+          '';
+       };
+
+      emulator.port = mkOption {
+        type = types.port;
+        default = 21324;
+        description = ''
+          Listening port for the Trezor emulator.
+          '';
+      };
+    };
+  };
+  
+  ### implementation
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ pkgs.trezor-udev-rules ];
+
+    systemd.services.trezord = {
+      description = "TREZOR Bridge";
+      after = [ "systemd-udev-settle.service" "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [];
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.trezord}/bin/trezord-go ${optionalString cfg.emulator.enable "-e ${builtins.toString cfg.emulator.port}"}";
+        User = "trezord";
+      };
+    };
+
+    users.users.trezord = {
+      group = "trezord";
+      description = "Trezor bridge daemon user";
+      isSystemUser = true;
+    };
+
+    users.groups.trezord = {};
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/hardware/trezord.xml b/nixpkgs/nixos/modules/services/hardware/trezord.xml
new file mode 100644
index 000000000000..972d409d9d0e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/trezord.xml
@@ -0,0 +1,26 @@
+<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="trezor">
+ <title>Trezor</title>
+ <para>
+  Trezor is an open-source cryptocurrency hardware wallet and security token
+  allowing secure storage of private keys.
+ </para>
+ <para>
+  It offers advanced features such U2F two-factor authorization, SSH login
+  through
+  <link xlink:href="https://wiki.trezor.io/Apps:SSH_agent">Trezor SSH agent</link>,
+  <link xlink:href="https://wiki.trezor.io/GPG">GPG</link> and a
+  <link xlink:href="https://wiki.trezor.io/Trezor_Password_Manager">password manager</link>.
+  For more information, guides and documentation, see <link xlink:href="https://wiki.trezor.io"/>.
+ </para>
+ <para>
+  To enable Trezor support, add the following to your <filename>configuration.nix</filename>:
+<programlisting>
+<xref linkend="opt-services.trezord.enable"/> = true;
+</programlisting>
+  This will add all necessary udev rules and start Trezor Bridge.
+ </para>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/hardware/triggerhappy.nix b/nixpkgs/nixos/modules/services/hardware/triggerhappy.nix
new file mode 100644
index 000000000000..f9f5234bdc3f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/triggerhappy.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.triggerhappy;
+
+  socket = "/run/thd.socket";
+
+  configFile = pkgs.writeText "triggerhappy.conf" ''
+    ${concatMapStringsSep "\n"
+      ({ keys, event, cmd, ... }:
+        ''${concatMapStringsSep "+" (x: "KEY_" + x) keys} ${toString { press = 1; hold = 2; release = 0; }.${event}} ${cmd}''
+      )
+      cfg.bindings}
+    ${cfg.extraConfig}
+  '';
+
+  bindingCfg = { ... }: {
+    options = {
+
+      keys = mkOption {
+        type = types.listOf types.str;
+        description = "List of keys to match.  Key names as defined in linux/input-event-codes.h";
+      };
+
+      event = mkOption {
+        type = types.enum ["press" "hold" "release"];
+        default = "press";
+        description = "Event to match.";
+      };
+
+      cmd = mkOption {
+        type = types.str;
+        description = "What to run.";
+      };
+
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.triggerhappy = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the <command>triggerhappy</command> hotkey daemon.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "nobody";
+        example = "root";
+        description = ''
+          User account under which <command>triggerhappy</command> runs.
+        '';
+      };
+
+      bindings = mkOption {
+        type = types.listOf (types.submodule bindingCfg);
+        default = [];
+        example = lib.literalExample ''
+          [ { keys = ["PLAYPAUSE"];  cmd = "''${pkgs.mpc_cli}/bin/mpc -q toggle"; } ]
+        '';
+        description = ''
+          Key bindings for <command>triggerhappy</command>.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Literal contents to append to the end of <command>triggerhappy</command> configuration file.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.sockets.triggerhappy = {
+      description = "Triggerhappy Socket";
+      wantedBy = [ "sockets.target" ];
+      socketConfig.ListenDatagram = socket;
+    };
+
+    systemd.services.triggerhappy = {
+      wantedBy = [ "multi-user.target" ];
+      description = "Global hotkey daemon";
+      serviceConfig = {
+        ExecStart = "${pkgs.triggerhappy}/bin/thd ${optionalString (cfg.user != "root") "--user ${cfg.user}"} --socket ${socket} --triggers ${configFile} --deviceglob /dev/input/event*";
+      };
+    };
+
+    services.udev.packages = lib.singleton (pkgs.writeTextFile {
+      name = "triggerhappy-udev-rules";
+      destination = "/etc/udev/rules.d/61-triggerhappy.rules";
+      text = ''
+        ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ATTRS{name}!="triggerhappy", \
+          RUN+="${pkgs.triggerhappy}/bin/th-cmd --socket ${socket} --passfd --udev"
+      '';
+    });
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/udev.nix b/nixpkgs/nixos/modules/services/hardware/udev.nix
new file mode 100644
index 000000000000..587b9b0234aa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/udev.nix
@@ -0,0 +1,319 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  udev = config.systemd.package;
+
+  cfg = config.services.udev;
+
+  extraUdevRules = pkgs.writeTextFile {
+    name = "extra-udev-rules";
+    text = cfg.extraRules;
+    destination = "/etc/udev/rules.d/99-local.rules";
+  };
+
+  extraHwdbFile = pkgs.writeTextFile {
+    name = "extra-hwdb-file";
+    text = cfg.extraHwdb;
+    destination = "/etc/udev/hwdb.d/99-local.hwdb";
+  };
+
+  nixosRules = ''
+    # Miscellaneous devices.
+    KERNEL=="kvm",                  MODE="0666"
+    KERNEL=="kqemu",                MODE="0666"
+
+    # Needed for gpm.
+    SUBSYSTEM=="input", KERNEL=="mice", TAG+="systemd"
+  '';
+
+  # Perform substitutions in all udev rules files.
+  udevRules = pkgs.runCommand "udev-rules"
+    { preferLocalBuild = true;
+      allowSubstitutes = false;
+      packages = unique (map toString cfg.packages);
+    }
+    ''
+      mkdir -p $out
+      shopt -s nullglob
+      set +o pipefail
+
+      # Set a reasonable $PATH for programs called by udev rules.
+      echo 'ENV{PATH}="${udevPath}/bin:${udevPath}/sbin"' > $out/00-path.rules
+
+      # Add the udev rules from other packages.
+      for i in $packages; do
+        echo "Adding rules for package $i"
+        for j in $i/{etc,lib}/udev/rules.d/*; do
+          echo "Copying $j to $out/$(basename $j)"
+          cat $j > $out/$(basename $j)
+        done
+      done
+
+      # Fix some paths in the standard udev rules.  Hacky.
+      for i in $out/*.rules; do
+        substituteInPlace $i \
+          --replace \"/sbin/modprobe \"${pkgs.kmod}/bin/modprobe \
+          --replace \"/sbin/mdadm \"${pkgs.mdadm}/sbin/mdadm \
+          --replace \"/sbin/blkid \"${pkgs.utillinux}/sbin/blkid \
+          --replace \"/bin/mount \"${pkgs.utillinux}/bin/mount \
+          --replace /usr/bin/readlink ${pkgs.coreutils}/bin/readlink \
+          --replace /usr/bin/basename ${pkgs.coreutils}/bin/basename
+      done
+
+      echo -n "Checking that all programs called by relative paths in udev rules exist in ${udev}/lib/udev... "
+      import_progs=$(grep 'IMPORT{program}="[^/$]' $out/* |
+        sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
+      run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="[^/$]' |
+        sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
+      for i in $import_progs $run_progs; do
+        if [[ ! -x ${udev}/lib/udev/$i && ! $i =~ socket:.* ]]; then
+          echo "FAIL"
+          echo "$i is called in udev rules but not installed by udev"
+          exit 1
+        fi
+      done
+      echo "OK"
+
+      echo -n "Checking that all programs called by absolute paths in udev rules exist... "
+      import_progs=$(grep 'IMPORT{program}="\/' $out/* |
+        sed -e 's/.*IMPORT{program}="\([^ "]*\)[ "].*/\1/' | uniq)
+      run_progs=$(grep -v '^[[:space:]]*#' $out/* | grep 'RUN+="/' |
+        sed -e 's/.*RUN+="\([^ "]*\)[ "].*/\1/' | uniq)
+      for i in $import_progs $run_progs; do
+        # if the path refers to /run/current-system/systemd, replace with config.systemd.package
+        if [[ $i == /run/current-system/systemd* ]]; then
+          i="${config.systemd.package}/''${i#/run/current-system/systemd/}"
+        fi
+        if [[ ! -x $i ]]; then
+          echo "FAIL"
+          echo "$i is called in udev rules but is not executable or does not exist"
+          exit 1
+        fi
+      done
+      echo "OK"
+
+      filesToFixup="$(for i in "$out"/*; do
+        grep -l '\B\(/usr\)\?/s\?bin' "$i" || :
+      done)"
+
+      if [ -n "$filesToFixup" ]; then
+        echo "Consider fixing the following udev rules:"
+        echo "$filesToFixup" | while read localFile; do
+          remoteFile="origin unknown"
+          for i in ${toString cfg.packages}; do
+            for j in "$i"/*/udev/rules.d/*; do
+              [ -e "$out/$(basename "$j")" ] || continue
+              [ "$(basename "$j")" = "$(basename "$localFile")" ] || continue
+              remoteFile="originally from $j"
+              break 2
+            done
+          done
+          refs="$(
+            grep -o '\B\(/usr\)\?/s\?bin/[^ "]\+' "$localFile" \
+              | sed -e ':r;N;''${s/\n/ and /;br};s/\n/, /g;br'
+          )"
+          echo "$localFile ($remoteFile) contains references to $refs."
+        done
+        exit 1
+      fi
+
+      # If auto-configuration is disabled, then remove
+      # udev's 80-drivers.rules file, which contains rules for
+      # automatically calling modprobe.
+      ${optionalString (!config.boot.hardwareScan) ''
+        ln -s /dev/null $out/80-drivers.rules
+      ''}
+    ''; # */
+
+  hwdbBin = pkgs.runCommand "hwdb.bin"
+    { preferLocalBuild = true;
+      allowSubstitutes = false;
+      packages = unique (map toString ([udev] ++ cfg.packages));
+    }
+    ''
+      mkdir -p etc/udev/hwdb.d
+      for i in $packages; do
+        echo "Adding hwdb files for package $i"
+        for j in $i/{etc,lib}/udev/hwdb.d/*; do
+          ln -s $j etc/udev/hwdb.d/$(basename $j)
+        done
+      done
+
+      echo "Generating hwdb database..."
+      # hwdb --update doesn't return error code even on errors!
+      res="$(${pkgs.buildPackages.udev}/bin/udevadm hwdb --update --root=$(pwd) 2>&1)"
+      echo "$res"
+      [ -z "$(echo "$res" | egrep '^Error')" ]
+      mv etc/udev/hwdb.bin $out
+    '';
+
+  # Udev has a 512-character limit for ENV{PATH}, so create a symlink
+  # tree to work around this.
+  udevPath = pkgs.buildEnv {
+    name = "udev-path";
+    paths = cfg.path;
+    pathsToLink = [ "/bin" "/sbin" ];
+    ignoreCollisions = true;
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    boot.hardwareScan = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to try to load kernel modules for all detected hardware.
+        Usually this does a good job of providing you with the modules
+        you need, but sometimes it can crash the system or cause other
+        nasty effects.
+      '';
+    };
+
+    services.udev = {
+
+      packages = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          List of packages containing <command>udev</command> rules.
+          All files found in
+          <filename><replaceable>pkg</replaceable>/etc/udev/rules.d</filename> and
+          <filename><replaceable>pkg</replaceable>/lib/udev/rules.d</filename>
+          will be included.
+        '';
+        apply = map getBin;
+      };
+
+      path = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          Packages added to the <envar>PATH</envar> environment variable when
+          executing programs from Udev rules.
+        '';
+      };
+
+      extraRules = mkOption {
+        default = "";
+        example = ''
+          KERNEL=="eth*", ATTR{address}=="00:1D:60:B9:6D:4F", NAME="my_fast_network_card"
+        '';
+        type = types.lines;
+        description = ''
+          Additional <command>udev</command> rules. They'll be written
+          into file <filename>99-local.rules</filename>. Thus they are
+          read and applied after all other rules.
+        '';
+      };
+
+      extraHwdb = mkOption {
+        default = "";
+        example = ''
+          evdev:input:b0003v05AFp8277*
+            KEYBOARD_KEY_70039=leftalt
+            KEYBOARD_KEY_700e2=leftctrl
+        '';
+        type = types.lines;
+        description = ''
+          Additional <command>hwdb</command> files. They'll be written
+          into file <filename>99-local.hwdb</filename>. Thus they are
+          read after all other files.
+        '';
+      };
+
+    };
+
+    hardware.firmware = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      description = ''
+        List of packages containing firmware files.  Such files
+        will be loaded automatically if the kernel asks for them
+        (i.e., when it has detected specific hardware that requires
+        firmware to function).  If multiple packages contain firmware
+        files with the same name, the first package in the list takes
+        precedence.  Note that you must rebuild your system if you add
+        files to any of these directories.
+      '';
+      apply = list: pkgs.buildEnv {
+        name = "firmware";
+        paths = list;
+        pathsToLink = [ "/lib/firmware" ];
+        ignoreCollisions = true;
+      };
+    };
+
+    networking.usePredictableInterfaceNames = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether to assign <link
+        xlink:href='http://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames'>predictable
+        names to network interfaces</link>.  If enabled, interfaces
+        are assigned names that contain topology information
+        (e.g. <literal>wlp3s0</literal>) and thus should be stable
+        across reboots.  If disabled, names depend on the order in
+        which interfaces are discovered by the kernel, which may
+        change randomly across reboots; for instance, you may find
+        <literal>eth0</literal> and <literal>eth1</literal> flipping
+        unpredictably.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf (!config.boot.isContainer) {
+
+    services.udev.extraRules = nixosRules;
+
+    services.udev.packages = [ extraUdevRules extraHwdbFile ];
+
+    services.udev.path = [ pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.utillinux udev ];
+
+    boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
+
+    environment.etc =
+      {
+        "udev/rules.d".source = udevRules;
+        "udev/hwdb.bin".source = hwdbBin;
+      };
+
+    system.requiredKernelConfig = with config.lib.kernelConfig; [
+      (isEnabled "UNIX")
+      (isYes "INOTIFY_USER")
+      (isYes "NET")
+    ];
+
+    boot.extraModprobeConfig = "options firmware_class path=${config.hardware.firmware}/lib/firmware";
+
+    system.activationScripts.udevd =
+      ''
+        # The deprecated hotplug uevent helper is not used anymore
+        if [ -e /proc/sys/kernel/hotplug ]; then
+          echo "" > /proc/sys/kernel/hotplug
+        fi
+
+        # Allow the kernel to find our firmware.
+        if [ -e /sys/module/firmware_class/parameters/path ]; then
+          echo -n "${config.hardware.firmware}/lib/firmware" > /sys/module/firmware_class/parameters/path
+        fi
+      '';
+
+    systemd.services.systemd-udevd =
+      { restartTriggers = cfg.packages;
+      };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/udisks2.nix b/nixpkgs/nixos/modules/services/hardware/udisks2.nix
new file mode 100644
index 000000000000..e898f3260585
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/udisks2.nix
@@ -0,0 +1,44 @@
+# Udisks daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.udisks2 = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable Udisks, a DBus service that allows
+          applications to query and manipulate storage devices.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.udisks2.enable {
+
+    environment.systemPackages = [ pkgs.udisks2 ];
+
+    services.dbus.packages = [ pkgs.udisks2 ];
+
+    systemd.tmpfiles.rules = [ "d /var/lib/udisks2 0755 root root -" ];
+
+    services.udev.packages = [ pkgs.udisks2 ];
+
+    systemd.packages = [ pkgs.udisks2 ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/undervolt.nix b/nixpkgs/nixos/modules/services/hardware/undervolt.nix
new file mode 100644
index 000000000000..e5ef0601de3c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/undervolt.nix
@@ -0,0 +1,134 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.undervolt;
+in {
+  options.services.undervolt = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to undervolt intel cpus.
+      '';
+    };
+
+    verbose = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable verbose logging.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.undervolt;
+      defaultText = "pkgs.undervolt";
+      description = ''
+        undervolt derivation to use.
+      '';
+    };
+
+    coreOffset = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The amount of voltage to offset the CPU cores by. Accepts a floating point number.
+      '';
+    };
+
+    gpuOffset = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The amount of voltage to offset the GPU by. Accepts a floating point number.
+      '';
+    };
+
+    uncoreOffset = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The amount of voltage to offset uncore by. Accepts a floating point number.
+      '';
+    };
+
+    analogioOffset = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The amount of voltage to offset analogio by. Accepts a floating point number.
+      '';
+    };
+
+    temp = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The temperature target. Accepts a floating point number.
+      '';
+    };
+
+    tempAc = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The temperature target on AC power. Accepts a floating point number.
+      '';
+    };
+
+    tempBat = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The temperature target on battery power. Accepts a floating point number.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "msr" ];
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.undervolt = {
+      path = [ pkgs.undervolt ];
+
+      description = "Intel Undervolting Service";
+      serviceConfig = {
+        Type = "oneshot";
+        Restart = "no";
+
+        # `core` and `cache` are both intentionally set to `cfg.coreOffset` as according to the undervolt docs:
+        #
+        #     Core or Cache offsets have no effect. It is not possible to set different offsets for
+        #     CPU Core and Cache. The CPU will take the smaller of the two offsets, and apply that to
+        #     both CPU and Cache. A warning message will be displayed if you attempt to set different offsets.
+        ExecStart = ''
+          ${pkgs.undervolt}/bin/undervolt \
+            ${optionalString cfg.verbose "--verbose"} \
+            ${optionalString (cfg.coreOffset != null) "--core ${cfg.coreOffset}"} \
+            ${optionalString (cfg.coreOffset != null) "--cache ${cfg.coreOffset}"} \
+            ${optionalString (cfg.gpuOffset != null) "--gpu ${cfg.gpuOffset}"} \
+            ${optionalString (cfg.uncoreOffset != null) "--uncore ${cfg.uncoreOffset}"} \
+            ${optionalString (cfg.analogioOffset != null) "--analogio ${cfg.analogioOffset}"} \
+            ${optionalString (cfg.temp != null) "--temp ${cfg.temp}"} \
+            ${optionalString (cfg.tempAc != null) "--temp-ac ${cfg.tempAc}"} \
+            ${optionalString (cfg.tempBat != null) "--temp-bat ${cfg.tempBat}"}
+        '';
+      };
+    };
+
+    systemd.timers.undervolt = {
+      description = "Undervolt timer to ensure voltage settings are always applied";
+      partOf = [ "undervolt.service" ];
+      wantedBy = [ "multi-user.target" ];
+      timerConfig = {
+        OnBootSec = "2min";
+        OnUnitActiveSec = "30";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/upower.nix b/nixpkgs/nixos/modules/services/hardware/upower.nix
new file mode 100644
index 000000000000..449810b53150
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/upower.nix
@@ -0,0 +1,240 @@
+# Upower daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.upower;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.upower = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Upower, a DBus service that provides power
+          management support to applications.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.upower;
+        defaultText = "pkgs.upower";
+        example = lib.literalExample "pkgs.upower";
+        description = ''
+          Which upower package to use.
+        '';
+      };
+
+      enableWattsUpPro = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the Watts Up Pro device.
+
+          The Watts Up Pro contains a generic FTDI USB device without a specific
+          vendor and product ID. When we probe for WUP devices, we can cause
+          the user to get a perplexing "Device or resource busy" error when
+          attempting to use their non-WUP device.
+
+          The generic FTDI device is known to also be used on:
+
+          <itemizedlist>
+            <listitem><para>Sparkfun FT232 breakout board</para></listitem>
+            <listitem><para>Parallax Propeller</para></listitem>
+          </itemizedlist>
+        '';
+      };
+
+      noPollBatteries = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Don't poll the kernel for battery level changes.
+
+          Some hardware will send us battery level changes through
+          events, rather than us having to poll for it. This option
+          allows disabling polling for hardware that sends out events.
+        '';
+      };
+
+      ignoreLid = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Do we ignore the lid state
+
+          Some laptops are broken. The lid state is either inverted, or stuck
+          on or off. We can't do much to fix these problems, but this is a way
+          for users to make the laptop panel vanish, a state that might be used
+          by a couple of user-space daemons. On Linux systems, see also
+          logind.conf(5).
+        '';
+      };
+
+      usePercentageForPolicy = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Policy for warnings and action based on battery levels
+
+          Whether battery percentage based policy should be used. The default
+          is to use the percentage, which
+          should work around broken firmwares. It is also more reliable than
+          the time left (frantically saving all your files is going to use more
+          battery than letting it rest for example).
+        '';
+      };
+
+      percentageLow = mkOption {
+        type = types.ints.unsigned;
+        default = 10;
+        description = ''
+          When <literal>usePercentageForPolicy</literal> is
+          <literal>true</literal>, the levels at which UPower will consider the
+          battery low.
+
+          This will also be used for batteries which don't have time information
+          such as that of peripherals.
+
+          If any value (of <literal>percentageLow</literal>,
+          <literal>percentageCritical</literal> and
+          <literal>percentageAction</literal>) is invalid, or not in descending
+          order, the defaults will be used.
+        '';
+      };
+
+      percentageCritical = mkOption {
+        type = types.ints.unsigned;
+        default = 3;
+        description = ''
+          When <literal>usePercentageForPolicy</literal> is
+          <literal>true</literal>, the levels at which UPower will consider the
+          battery critical.
+
+          This will also be used for batteries which don't have time information
+          such as that of peripherals.
+
+          If any value (of <literal>percentageLow</literal>,
+          <literal>percentageCritical</literal> and
+          <literal>percentageAction</literal>) is invalid, or not in descending
+          order, the defaults will be used.
+        '';
+      };
+
+      percentageAction = mkOption {
+        type = types.ints.unsigned;
+        default = 2;
+        description = ''
+          When <literal>usePercentageForPolicy</literal> is
+          <literal>true</literal>, the levels at which UPower will take action
+          for the critical battery level.
+
+          This will also be used for batteries which don't have time information
+          such as that of peripherals.
+
+          If any value (of <literal>percentageLow</literal>,
+          <literal>percentageCritical</literal> and
+          <literal>percentageAction</literal>) is invalid, or not in descending
+          order, the defaults will be used.
+        '';
+      };
+
+      timeLow = mkOption {
+        type = types.ints.unsigned;
+        default = 1200;
+        description = ''
+          When <literal>usePercentageForPolicy</literal> is
+          <literal>false</literal>, the time remaining at which UPower will
+          consider the battery low.
+
+          If any value (of <literal>timeLow</literal>,
+          <literal>timeCritical</literal> and <literal>timeAction</literal>) is
+          invalid, or not in descending order, the defaults will be used.
+        '';
+      };
+
+      timeCritical = mkOption {
+        type = types.ints.unsigned;
+        default = 300;
+        description = ''
+          When <literal>usePercentageForPolicy</literal> is
+          <literal>false</literal>, the time remaining at which UPower will
+          consider the battery critical.
+
+          If any value (of <literal>timeLow</literal>,
+          <literal>timeCritical</literal> and <literal>timeAction</literal>) is
+          invalid, or not in descending order, the defaults will be used.
+        '';
+      };
+
+      timeAction = mkOption {
+        type = types.ints.unsigned;
+        default = 120;
+        description = ''
+          When <literal>usePercentageForPolicy</literal> is
+          <literal>false</literal>, the time remaining at which UPower will
+          take action for the critical battery level.
+
+          If any value (of <literal>timeLow</literal>,
+          <literal>timeCritical</literal> and <literal>timeAction</literal>) is
+          invalid, or not in descending order, the defaults will be used.
+        '';
+      };
+
+      criticalPowerAction = mkOption {
+        type = types.enum [ "PowerOff" "Hibernate" "HybridSleep" ];
+        default = "HybridSleep";
+        description = ''
+          The action to take when <literal>timeAction</literal> or
+          <literal>percentageAction</literal> has been reached for the batteries
+          (UPS or laptop batteries) supplying the computer
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    services.dbus.packages = [ cfg.package ];
+
+    services.udev.packages = [ cfg.package ];
+
+    systemd.packages = [ cfg.package ];
+
+    environment.etc."UPower/UPower.conf".text = generators.toINI {} {
+      UPower = {
+        EnableWattsUpPro = cfg.enableWattsUpPro;
+        NoPollBatteries = cfg.noPollBatteries;
+        IgnoreLid = cfg.ignoreLid;
+        UsePercentageForPolicy = cfg.usePercentageForPolicy;
+        PercentageLow = cfg.percentageLow;
+        PercentageCritical = cfg.percentageCritical;
+        PercentageAction = cfg.percentageAction;
+        TimeLow = cfg.timeLow;
+        TimeCritical = cfg.timeCritical;
+        TimeAction = cfg.timeAction;
+        CriticalPowerAction = cfg.criticalPowerAction;
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix b/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix
new file mode 100644
index 000000000000..11a4b0a858f9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/usbmuxd.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  defaultUserGroup = "usbmux";
+  apple = "05ac";
+
+  cfg = config.services.usbmuxd;
+
+in
+
+{
+  options.services.usbmuxd = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable the usbmuxd ("USB multiplexing daemon") service. This daemon is
+        in charge of multiplexing connections over USB to an iOS device. This is
+        needed for transferring data from and to iOS devices (see ifuse). Also
+        this may enable plug-n-play tethering for iPhones.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = defaultUserGroup;
+      description = ''
+        The user usbmuxd should use to run after startup.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = defaultUserGroup;
+      description = ''
+        The group usbmuxd should use to run after startup.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users = optionalAttrs (cfg.user == defaultUserGroup) {
+      ${cfg.user} = {
+        description = "usbmuxd user";
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == defaultUserGroup) {
+      ${cfg.group} = { };
+    };
+
+    # Give usbmuxd permission for Apple devices
+    services.udev.extraRules = ''
+      SUBSYSTEM=="usb", ATTR{idVendor}=="${apple}", GROUP="${cfg.group}"
+    '';
+
+    systemd.services.usbmuxd = {
+      description = "usbmuxd";
+      wantedBy = [ "multi-user.target" ];
+      unitConfig.Documentation = "man:usbmuxd(8)";
+      serviceConfig = {
+        # Trigger the udev rule manually. This doesn't require replugging the
+        # device when first enabling the option to get it to work
+        ExecStartPre = "${pkgs.udev}/bin/udevadm trigger -s usb -a idVendor=${apple}";
+        ExecStart = "${pkgs.usbmuxd}/bin/usbmuxd -U ${cfg.user} -f";
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/vdr.nix b/nixpkgs/nixos/modules/services/hardware/vdr.nix
new file mode 100644
index 000000000000..8a6cde51b06f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/vdr.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.vdr;
+  libDir = "/var/lib/vdr";
+in {
+
+  ###### interface
+
+  options = {
+
+    services.vdr = {
+      enable = mkEnableOption "VDR. Please put config into ${libDir}";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.vdr;
+        defaultText = "pkgs.vdr";
+        example = literalExample "pkgs.wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }";
+        description = "Package to use.";
+      };
+
+      videoDir = mkOption {
+        type = types.path;
+        default = "/srv/vdr/video";
+        description = "Recording directory";
+      };
+
+      extraArguments = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Additional command line arguments to pass to VDR.";
+      };
+
+      enableLirc = mkEnableOption "LIRC";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable (mkMerge [{
+    systemd.tmpfiles.rules = [
+      "d ${cfg.videoDir} 0755 vdr vdr -"
+      "Z ${cfg.videoDir} - vdr vdr -"
+    ];
+
+    systemd.services.vdr = {
+      description = "VDR";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/vdr \
+            --video="${cfg.videoDir}" \
+            --config="${libDir}" \
+            ${escapeShellArgs cfg.extraArguments}
+        '';
+        User = "vdr";
+        CacheDirectory = "vdr";
+        StateDirectory = "vdr";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users.vdr = {
+      group = "vdr";
+      home = libDir;
+      isSystemUser = true;
+    };
+
+    users.groups.vdr = {};
+  }
+
+  (mkIf cfg.enableLirc {
+    services.lirc.enable = true;
+    users.users.vdr.extraGroups = [ "lirc" ];
+    services.vdr.extraArguments = [
+      "--lirc=${config.passthru.lirc.socket}"
+    ];
+  })]);
+}
diff --git a/nixpkgs/nixos/modules/services/hardware/xow.nix b/nixpkgs/nixos/modules/services/hardware/xow.nix
new file mode 100644
index 000000000000..a18d60ad83be
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/hardware/xow.nix
@@ -0,0 +1,17 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.hardware.xow;
+in {
+  options.services.hardware.xow = {
+    enable = lib.mkEnableOption "xow as a systemd service";
+  };
+
+  config = lib.mkIf cfg.enable {
+    hardware.uinput.enable = true;
+
+    systemd.packages = [ pkgs.xow ];
+
+    services.udev.packages = [ pkgs.xow ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/SystemdJournal2Gelf.nix b/nixpkgs/nixos/modules/services/logging/SystemdJournal2Gelf.nix
new file mode 100644
index 000000000000..f26aef7262ba
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/SystemdJournal2Gelf.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.SystemdJournal2Gelf;
+in
+
+{ options = {
+    services.SystemdJournal2Gelf = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable SystemdJournal2Gelf.
+        '';
+      };
+
+      graylogServer = mkOption {
+        type = types.str;
+        example = "graylog2.example.com:11201";
+        description = ''
+          Host and port of your graylog2 input. This should be a GELF
+          UDP input.
+        '';
+      };
+
+      extraOptions = mkOption {
+        type = types.separatedString " ";
+        default = "";
+        description = ''
+          Any extra flags to pass to SystemdJournal2Gelf. Note that
+          these are basically <literal>journalctl</literal> flags.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.systemd-journal2gelf;
+        description = ''
+          SystemdJournal2Gelf package to use.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.SystemdJournal2Gelf = {
+      description = "SystemdJournal2Gelf";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/SystemdJournal2Gelf ${cfg.graylogServer} --follow ${cfg.extraOptions}";
+        Restart = "on-failure";
+        RestartSec = "30";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/awstats.nix b/nixpkgs/nixos/modules/services/logging/awstats.nix
new file mode 100644
index 000000000000..896f52302ff3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/awstats.nix
@@ -0,0 +1,257 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.awstats;
+  package = pkgs.awstats;
+  configOpts = {name, config, ...}: {
+    options = {
+      type = mkOption{
+        type = types.enum [ "mail" "web" ];
+        default = "web";
+        example = "mail";
+        description = ''
+          The type of log being collected.
+        '';
+      };
+      domain = mkOption {
+        type = types.str;
+        default = name;
+        description = "The domain name to collect stats for.";
+        example = "example.com";
+      };
+
+      logFile = mkOption {
+        type = types.str;
+        example = "/var/log/nginx/access.log";
+        description = ''
+          The log file to be scanned.
+
+          For mail, set this to
+          <literal>
+          journalctl $OLD_CURSOR -u postfix.service | ''${pkgs.perl}/bin/perl ''${pkgs.awstats.out}/share/awstats/tools/maillogconvert.pl standard |
+          </literal>
+        '';
+      };
+
+      logFormat = mkOption {
+        type = types.str;
+        default = "1";
+        description = ''
+          The log format being used.
+
+          For mail, set this to
+          <literal>
+          %time2 %email %email_r %host %host_r %method %url %code %bytesd
+          </literal>
+        '';
+      };
+
+      hostAliases = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = "[ \"www.example.org\" ]";
+        description = ''
+          List of aliases the site has.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        example = literalExample ''
+          {
+            "ValidHTTPCodes" = "404";
+          }
+        '';
+        description = "Extra configuration to be appendend to awstats.\${name}.conf.";
+      };
+
+      webService = {
+        enable = mkEnableOption "awstats web service";
+
+        hostname = mkOption {
+          type = types.str;
+          default = config.domain;
+          description = "The hostname the web service appears under.";
+        };
+
+        urlPrefix = mkOption {
+          type = types.str;
+          default = "/awstats";
+          description = "The URL prefix under which the awstats pages appear.";
+        };
+      };
+    };
+  };
+  webServices = filterAttrs (name: value: value.webService.enable) cfg.configs;
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "awstats" "service" "enable" ] "Please enable per domain with `services.awstats.configs.<name>.webService.enable`")
+    (mkRemovedOptionModule [ "services" "awstats" "service" "urlPrefix" ] "Please set per domain with `services.awstats.configs.<name>.webService.urlPrefix`")
+    (mkRenamedOptionModule [ "services" "awstats" "vardir" ] [ "services" "awstats" "dataDir" ])
+  ];
+
+  options.services.awstats = {
+    enable = mkEnableOption "awstats";
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/awstats";
+      description = "The directory where awstats data will be stored.";
+    };
+
+    configs = mkOption {
+      type = types.attrsOf (types.submodule configOpts);
+      default = {};
+      example = literalExample ''
+        {
+          "mysite" = {
+            domain = "example.com";
+            logFile = "/var/log/nginx/access.log";
+          };
+        }
+      '';
+      description = "Attribute set of domains to collect stats for.";
+    };
+
+    updateAt = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "hourly";
+      description = ''
+        Specification of the time at which awstats will get updated.
+        (in the format described by <citerefentry>
+          <refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>)
+      '';
+    };
+  };
+
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ package.bin ];
+
+    environment.etc = mapAttrs' (name: opts:
+    nameValuePair "awstats/awstats.${name}.conf" {
+      source = pkgs.runCommand "awstats.${name}.conf"
+      { preferLocalBuild = true; }
+      (''
+        sed \
+      ''
+      # set up mail stats
+      + optionalString (opts.type == "mail")
+      ''
+        -e 's|^\(LogType\)=.*$|\1=M|' \
+        -e 's|^\(LevelForBrowsersDetection\)=.*$|\1=0|' \
+        -e 's|^\(LevelForOSDetection\)=.*$|\1=0|' \
+        -e 's|^\(LevelForRefererAnalyze\)=.*$|\1=0|' \
+        -e 's|^\(LevelForRobotsDetection\)=.*$|\1=0|' \
+        -e 's|^\(LevelForSearchEnginesDetection\)=.*$|\1=0|' \
+        -e 's|^\(LevelForFileTypesDetection\)=.*$|\1=0|' \
+        -e 's|^\(LevelForWormsDetection\)=.*$|\1=0|' \
+        -e 's|^\(ShowMenu\)=.*$|\1=1|' \
+        -e 's|^\(ShowSummary\)=.*$|\1=HB|' \
+        -e 's|^\(ShowMonthStats\)=.*$|\1=HB|' \
+        -e 's|^\(ShowDaysOfMonthStats\)=.*$|\1=HB|' \
+        -e 's|^\(ShowDaysOfWeekStats\)=.*$|\1=HB|' \
+        -e 's|^\(ShowHoursStats\)=.*$|\1=HB|' \
+        -e 's|^\(ShowDomainsStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowHostsStats\)=.*$|\1=HB|' \
+        -e 's|^\(ShowAuthenticatedUsers\)=.*$|\1=0|' \
+        -e 's|^\(ShowRobotsStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowEMailSenders\)=.*$|\1=HBML|' \
+        -e 's|^\(ShowEMailReceivers\)=.*$|\1=HBML|' \
+        -e 's|^\(ShowSessionsStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowPagesStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowFileTypesStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowFileSizesStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowBrowsersStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowOSStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowOriginStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowKeyphrasesStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowKeywordsStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowMiscStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowHTTPErrorsStats\)=.*$|\1=0|' \
+        -e 's|^\(ShowSMTPErrorsStats\)=.*$|\1=1|' \
+      ''
+      +
+      # common options
+      ''
+        -e 's|^\(DirData\)=.*$|\1="${cfg.dataDir}/${name}"|' \
+        -e 's|^\(DirIcons\)=.*$|\1="icons"|' \
+        -e 's|^\(CreateDirDataIfNotExists\)=.*$|\1=1|' \
+        -e 's|^\(SiteDomain\)=.*$|\1="${name}"|' \
+        -e 's|^\(LogFile\)=.*$|\1="${opts.logFile}"|' \
+        -e 's|^\(LogFormat\)=.*$|\1="${opts.logFormat}"|' \
+      ''
+      +
+      # extra config
+      concatStringsSep "\n" (mapAttrsToList (n: v: ''
+        -e 's|^\(${n}\)=.*$|\1="${v}"|' \
+      '') opts.extraConfig)
+      +
+      ''
+        < '${package.out}/wwwroot/cgi-bin/awstats.model.conf' > "$out"
+      '');
+    }) cfg.configs;
+
+    # create data directory with the correct permissions
+    systemd.tmpfiles.rules =
+      [ "d '${cfg.dataDir}' 755 root root - -" ] ++
+      mapAttrsToList (name: opts: "d '${cfg.dataDir}/${name}' 755 root root - -") cfg.configs ++
+      [ "Z '${cfg.dataDir}' 755 root root - -" ];
+
+    # nginx options
+    services.nginx.virtualHosts = mapAttrs'(name: opts: {
+      name = opts.webService.hostname;
+      value = {
+        locations = {
+          "${opts.webService.urlPrefix}/css/" = {
+            alias = "${package.out}/wwwroot/css/";
+          };
+          "${opts.webService.urlPrefix}/icons/" = {
+            alias = "${package.out}/wwwroot/icon/";
+          };
+          "${opts.webService.urlPrefix}/" = {
+            alias = "${cfg.dataDir}/${name}/";
+            extraConfig = ''
+              autoindex on;
+            '';
+          };
+        };
+      };
+    }) webServices;
+
+    # update awstats
+    systemd.services = mkIf (cfg.updateAt != null) (mapAttrs' (name: opts:
+      nameValuePair "awstats-${name}-update" {
+        description = "update awstats for ${name}";
+        script = optionalString (opts.type == "mail")
+        ''
+          if [[ -f "${cfg.dataDir}/${name}-cursor" ]]; then
+            CURSOR="$(cat "${cfg.dataDir}/${name}-cursor" | tr -d '\n')"
+            if [[ -n "$CURSOR" ]]; then
+              echo "Using cursor: $CURSOR"
+              export OLD_CURSOR="--cursor $CURSOR"
+            fi
+          fi
+          NEW_CURSOR="$(journalctl $OLD_CURSOR -u postfix.service --show-cursor | tail -n 1 | tr -d '\n' | sed -e 's#^-- cursor: \(.*\)#\1#')"
+          echo "New cursor: $NEW_CURSOR"
+          ${package.bin}/bin/awstats -update -config=${name}
+          if [ -n "$NEW_CURSOR" ]; then
+            echo -n "$NEW_CURSOR" > ${cfg.dataDir}/${name}-cursor
+          fi
+        '' + ''
+          ${package.out}/share/awstats/tools/awstats_buildstaticpages.pl \
+            -config=${name} -update -dir=${cfg.dataDir}/${name} \
+            -awstatsprog=${package.bin}/bin/awstats
+        '';
+        startAt = cfg.updateAt;
+    }) cfg.configs);
+  };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/logging/fluentd.nix b/nixpkgs/nixos/modules/services/logging/fluentd.nix
new file mode 100644
index 000000000000..95825705d9d7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/fluentd.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.fluentd;
+
+  pluginArgs = concatStringsSep " " (map (x: "-p ${x}") cfg.plugins);
+in {
+  ###### interface
+
+  options = {
+
+    services.fluentd = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable fluentd.";
+      };
+
+      config = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Fluentd config.";
+      };
+
+      package = mkOption {
+        type = types.path;
+        default = pkgs.fluentd;
+        defaultText = "pkgs.fluentd";
+        description = "The fluentd package to use.";
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          A list of plugin paths to pass into fluentd. It will make plugins defined in ruby files
+          there available in your config.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.fluentd = with pkgs; {
+      description = "Fluentd Daemon";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/fluentd -c ${pkgs.writeText "fluentd.conf" cfg.config} ${pluginArgs}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/graylog.nix b/nixpkgs/nixos/modules/services/logging/graylog.nix
new file mode 100644
index 000000000000..a889a44d4b2b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/graylog.nix
@@ -0,0 +1,170 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.graylog;
+
+  confFile = pkgs.writeText "graylog.conf" ''
+    is_master = ${boolToString cfg.isMaster}
+    node_id_file = ${cfg.nodeIdFile}
+    password_secret = ${cfg.passwordSecret}
+    root_username = ${cfg.rootUsername}
+    root_password_sha2 = ${cfg.rootPasswordSha2}
+    elasticsearch_hosts = ${concatStringsSep "," cfg.elasticsearchHosts}
+    message_journal_dir = ${cfg.messageJournalDir}
+    mongodb_uri = ${cfg.mongodbUri}
+    plugin_dir = /var/lib/graylog/plugins
+
+    ${cfg.extraConfig}
+  '';
+
+  glPlugins = pkgs.buildEnv {
+    name = "graylog-plugins";
+    paths = cfg.plugins;
+  };
+
+in
+
+{
+  ###### interface
+
+  options = {
+
+    services.graylog = {
+
+      enable = mkEnableOption "Graylog";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.graylog;
+        defaultText = "pkgs.graylog";
+        example = literalExample "pkgs.graylog";
+        description = "Graylog package to use.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "graylog";
+        example = literalExample "graylog";
+        description = "User account under which graylog runs";
+      };
+
+      isMaster = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether this is the master instance of your Graylog cluster";
+      };
+
+      nodeIdFile = mkOption {
+        type = types.str;
+        default = "/var/lib/graylog/server/node-id";
+        description = "Path of the file containing the graylog node-id";
+      };
+
+      passwordSecret = mkOption {
+        type = types.str;
+        description = ''
+          You MUST set a secret to secure/pepper the stored user passwords here. Use at least 64 characters.
+          Generate one by using for example: pwgen -N 1 -s 96
+        '';
+      };
+
+      rootUsername = mkOption {
+        type = types.str;
+        default = "admin";
+        description = "Name of the default administrator user";
+      };
+
+      rootPasswordSha2 = mkOption {
+        type = types.str;
+        example = "e3c652f0ba0b4801205814f8b6bc49672c4c74e25b497770bb89b22cdeb4e952";
+        description = ''
+          You MUST specify a hash password for the root user (which you only need to initially set up the
+          system and in case you lose connectivity to your authentication backend)
+          This password cannot be changed using the API or via the web interface. If you need to change it,
+          modify it here.
+          Create one by using for example: echo -n yourpassword | shasum -a 256
+          and use the resulting hash value as string for the option
+        '';
+      };
+
+      elasticsearchHosts = mkOption {
+        type = types.listOf types.str;
+        example = literalExample ''[ "http://node1:9200" "http://user:password@node2:19200" ]'';
+        description = "List of valid URIs of the http ports of your elastic nodes. If one or more of your elasticsearch hosts require authentication, include the credentials in each node URI that requires authentication";
+      };
+
+      messageJournalDir = mkOption {
+        type = types.str;
+        default = "/var/lib/graylog/data/journal";
+        description = "The directory which will be used to store the message journal. The directory must be exclusively used by Graylog and must not contain any other files than the ones created by Graylog itself";
+      };
+
+      mongodbUri = mkOption {
+        type = types.str;
+        default = "mongodb://localhost/graylog";
+        description = "MongoDB connection string. See http://docs.mongodb.org/manual/reference/connection-string/ for details";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Any other configuration options you might want to add";
+      };
+
+      plugins = mkOption {
+        description = "Extra graylog plugins";
+        default = [ ];
+        type = types.listOf types.package;
+      };
+
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = mkIf (cfg.user == "graylog") {
+      graylog = {
+        uid = config.ids.uids.graylog;
+        description = "Graylog server daemon user";
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.messageJournalDir}' - ${cfg.user} - - -"
+    ];
+
+    systemd.services.graylog = with pkgs; {
+      description = "Graylog Server";
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        JAVA_HOME = jre;
+        GRAYLOG_CONF = "${confFile}";
+      };
+      path = [ pkgs.jre_headless pkgs.which pkgs.procps ];
+      preStart = ''
+        rm -rf /var/lib/graylog/plugins || true
+        mkdir -p /var/lib/graylog/plugins -m 755
+
+        mkdir -p "$(dirname ${cfg.nodeIdFile})"
+        chown -R ${cfg.user} "$(dirname ${cfg.nodeIdFile})"
+
+        for declarativeplugin in `ls ${glPlugins}/bin/`; do
+          ln -sf ${glPlugins}/bin/$declarativeplugin /var/lib/graylog/plugins/$declarativeplugin
+        done
+        for includedplugin in `ls ${cfg.package}/plugin/`; do
+          ln -s ${cfg.package}/plugin/$includedplugin /var/lib/graylog/plugins/$includedplugin || true
+        done
+      '';
+      serviceConfig = {
+        User="${cfg.user}";
+        StateDirectory = "graylog";
+        ExecStart = "${cfg.package}/bin/graylogctl run";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/heartbeat.nix b/nixpkgs/nixos/modules/services/logging/heartbeat.nix
new file mode 100644
index 000000000000..56fb4deabda5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/heartbeat.nix
@@ -0,0 +1,74 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.heartbeat;
+
+  heartbeatYml = pkgs.writeText "heartbeat.yml" ''
+    name: ${cfg.name}
+    tags: ${builtins.toJSON cfg.tags}
+
+    ${cfg.extraConfig}
+  '';
+
+in
+{
+  options = {
+
+    services.heartbeat = {
+
+      enable = mkEnableOption "heartbeat";
+
+      name = mkOption {
+        type = types.str;
+        default = "heartbeat";
+        description = "Name of the beat";
+      };
+
+      tags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Tags to place on the shipped log messages";
+      };
+
+      stateDir = mkOption {
+        type = types.str;
+        default = "/var/lib/heartbeat";
+        description = "The state directory. heartbeat's own logs and other data are stored here.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = ''
+          heartbeat.monitors:
+          - type: http
+            urls: ["http://localhost:9200"]
+            schedule: '@every 10s'
+        '';
+        description = "Any other configuration options you want to add";
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - nobody nogroup - -"
+    ];
+
+    systemd.services.heartbeat = with pkgs; {
+      description = "heartbeat log shipper";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -p "${cfg.stateDir}"/{data,logs}
+      '';
+      serviceConfig = {
+        User = "nobody";
+        AmbientCapabilities = "cap_net_raw";
+        ExecStart = "${pkgs.heartbeat}/bin/heartbeat -c \"${heartbeatYml}\" -path.data \"${cfg.stateDir}/data\" -path.logs \"${cfg.stateDir}/logs\"";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/journalbeat.nix b/nixpkgs/nixos/modules/services/logging/journalbeat.nix
new file mode 100644
index 000000000000..89f53b1b2454
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/journalbeat.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.journalbeat;
+
+  lt6 = builtins.compareVersions cfg.package.version "6" < 0;
+
+  journalbeatYml = pkgs.writeText "journalbeat.yml" ''
+    name: ${cfg.name}
+    tags: ${builtins.toJSON cfg.tags}
+
+    ${optionalString lt6 "journalbeat.cursor_state_file: /var/lib/${cfg.stateDir}/cursor-state"}
+
+    ${cfg.extraConfig}
+  '';
+
+in
+{
+  options = {
+
+    services.journalbeat = {
+
+      enable = mkEnableOption "journalbeat";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.journalbeat;
+        defaultText = "pkgs.journalbeat";
+        example = literalExample "pkgs.journalbeat7";
+        description = ''
+          The journalbeat package to use
+        '';
+      };
+
+      name = mkOption {
+        type = types.str;
+        default = "journalbeat";
+        description = "Name of the beat";
+      };
+
+      tags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Tags to place on the shipped log messages";
+      };
+
+      stateDir = mkOption {
+        type = types.str;
+        default = "journalbeat";
+        description = ''
+          Directory below <literal>/var/lib/</literal> to store journalbeat's
+          own logs and other data. This directory will be created automatically
+          using systemd's StateDirectory mechanism.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = optionalString lt6 ''
+          journalbeat:
+            seek_position: cursor
+            cursor_seek_fallback: tail
+            write_cursor_state: true
+            cursor_flush_period: 5s
+            clean_field_names: true
+            convert_to_numbers: false
+            move_metadata_to_field: journal
+            default_type: journal
+        '';
+        description = "Any other configuration options you want to add";
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = !hasPrefix "/" cfg.stateDir;
+        message =
+          "The option services.journalbeat.stateDir shouldn't be an absolute directory." +
+          " It should be a directory relative to /var/lib/.";
+      }
+    ];
+
+    systemd.services.journalbeat = {
+      description = "Journalbeat log shipper";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -p ${cfg.stateDir}/data
+        mkdir -p ${cfg.stateDir}/logs
+      '';
+      serviceConfig = {
+        StateDirectory = cfg.stateDir;
+        ExecStart = ''
+          ${cfg.package}/bin/journalbeat \
+            -c ${journalbeatYml} \
+            -path.data /var/lib/${cfg.stateDir}/data \
+            -path.logs /var/lib/${cfg.stateDir}/logs'';
+        Restart = "always";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/journaldriver.nix b/nixpkgs/nixos/modules/services/logging/journaldriver.nix
new file mode 100644
index 000000000000..9bd581e9ec0e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/journaldriver.nix
@@ -0,0 +1,112 @@
+# This module implements a systemd service for running journaldriver,
+# a log forwarding agent that sends logs from journald to Stackdriver
+# Logging.
+#
+# It can be enabled without extra configuration when running on GCP.
+# On machines hosted elsewhere, the other configuration options need
+# to be set.
+#
+# For further information please consult the documentation in the
+# upstream repository at: https://github.com/tazjin/journaldriver/
+
+{ config, lib, pkgs, ...}:
+
+with lib; let cfg = config.services.journaldriver;
+in {
+  options.services.journaldriver = {
+    enable = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = ''
+        Whether to enable journaldriver to forward journald logs to
+        Stackdriver Logging.
+      '';
+    };
+
+    logLevel = mkOption {
+      type        = types.str;
+      default     = "info";
+      description = ''
+        Log level at which journaldriver logs its own output.
+      '';
+    };
+
+    logName = mkOption {
+      type        = with types; nullOr str;
+      default     = null;
+      description = ''
+        Configures the name of the target log in Stackdriver Logging.
+        This option can be set to, for example, the hostname of a
+        machine to improve the user experience in the logging
+        overview.
+      '';
+    };
+
+    googleCloudProject = mkOption {
+      type        = with types; nullOr str;
+      default     = null;
+      description = ''
+        Configures the name of the Google Cloud project to which to
+        forward journald logs.
+
+        This option is required on non-GCP machines, but should not be
+        set on GCP instances.
+      '';
+    };
+
+    logStream = mkOption {
+      type        = with types; nullOr str;
+      default     = null;
+      description = ''
+        Configures the name of the Stackdriver Logging log stream into
+        which to write journald entries.
+
+        This option is required on non-GCP machines, but should not be
+        set on GCP instances.
+      '';
+    };
+
+    applicationCredentials = mkOption {
+      type        = with types; nullOr path;
+      default     = null;
+      description = ''
+        Path to the service account private key (in JSON-format) used
+        to forward log entries to Stackdriver Logging on non-GCP
+        instances.
+
+        This option is required on non-GCP machines, but should not be
+        set on GCP instances.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.journaldriver = {
+      description = "Stackdriver Logging journal forwarder";
+      script      = "${pkgs.journaldriver}/bin/journaldriver";
+      after       = [ "network-online.target" ];
+      wantedBy    = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Restart        = "always";
+        DynamicUser    = true;
+
+        # This directive lets systemd automatically configure
+        # permissions on /var/lib/journaldriver, the directory in
+        # which journaldriver persists its cursor state.
+        StateDirectory = "journaldriver";
+
+        # This group is required for accessing journald.
+        SupplementaryGroups = "systemd-journal";
+      };
+
+      environment = {
+        RUST_LOG                       = cfg.logLevel;
+        LOG_NAME                       = cfg.logName;
+        LOG_STREAM                     = cfg.logStream;
+        GOOGLE_CLOUD_PROJECT           = cfg.googleCloudProject;
+        GOOGLE_APPLICATION_CREDENTIALS = cfg.applicationCredentials;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/journalwatch.nix b/nixpkgs/nixos/modules/services/logging/journalwatch.nix
new file mode 100644
index 000000000000..576c646c0f58
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/journalwatch.nix
@@ -0,0 +1,264 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.services.journalwatch;
+  user = "journalwatch";
+  # for journal access
+  group = "systemd-journal";
+  dataDir = "/var/lib/${user}";
+
+  journalwatchConfig = pkgs.writeText "config" (''
+    # (File Generated by NixOS journalwatch module.)
+    [DEFAULT]
+    mail_binary = ${cfg.mailBinary}
+    priority = ${toString cfg.priority}
+    mail_from = ${cfg.mailFrom}
+  ''
+  + optionalString (cfg.mailTo != null) ''
+    mail_to = ${cfg.mailTo}
+  ''
+  + cfg.extraConfig);
+
+  journalwatchPatterns = pkgs.writeText "patterns" ''
+    # (File Generated by NixOS journalwatch module.)
+
+    ${mkPatterns cfg.filterBlocks}
+  '';
+
+  # empty line at the end needed to to separate the blocks
+  mkPatterns = filterBlocks: concatStringsSep "\n" (map (block: ''
+    ${block.match}
+    ${block.filters}
+
+  '') filterBlocks);
+
+  # can't use joinSymlinks directly, because when we point $XDG_CONFIG_HOME
+  # to the /nix/store path, we still need the subdirectory "journalwatch" inside that
+  # to match journalwatch's expectations
+  journalwatchConfigDir = pkgs.runCommand "journalwatch-config"
+    { preferLocalBuild = true; allowSubstitutes = false; }
+    ''
+      mkdir -p $out/journalwatch
+      ln -sf ${journalwatchConfig} $out/journalwatch/config
+      ln -sf ${journalwatchPatterns} $out/journalwatch/patterns
+    '';
+
+
+in {
+  options = {
+    services.journalwatch = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If enabled, periodically check the journal with journalwatch and report the results by mail.
+        '';
+      };
+
+      priority = mkOption {
+        type = types.int;
+        default = 6;
+        description = ''
+          Lowest priority of message to be considered.
+          A value between 7 ("debug"), and 0 ("emerg"). Defaults to 6 ("info").
+          If you don't care about anything with "info" priority, you can reduce
+          this to e.g. 5 ("notice") to considerably reduce the amount of
+          messages without needing many <option>filterBlocks</option>.
+        '';
+      };
+
+      # HACK: this is a workaround for journalwatch's usage of socket.getfqdn() which always returns localhost if
+      # there's an alias for the localhost on a separate line in /etc/hosts, or take for ages if it's not present and
+      # then return something right-ish in the direction of /etc/hostname. Just bypass it completely.
+      mailFrom = mkOption {
+        type = types.str;
+        default = "journalwatch@${config.networking.hostName}";
+        description = ''
+          Mail address to send journalwatch reports from.
+        '';
+      };
+
+      mailTo = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Mail address to send journalwatch reports to.
+        '';
+      };
+
+      mailBinary = mkOption {
+        type = types.path;
+        default = "/run/wrappers/bin/sendmail";
+        description = ''
+          Sendmail-compatible binary to be used to send the messages.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Extra lines to be added verbatim to the journalwatch/config configuration file.
+          You can add any commandline argument to the config, without the '--'.
+          See <literal>journalwatch --help</literal> for all arguments and their description.
+          '';
+      };
+
+      filterBlocks = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+           match = mkOption {
+              type = types.str;
+              example = "SYSLOG_IDENTIFIER = systemd";
+              description = ''
+                Syntax: <literal>field = value</literal>
+                Specifies the log entry <literal>field</literal> this block should apply to.
+                If the <literal>field</literal> of a message matches this <literal>value</literal>,
+                this patternBlock's <option>filters</option> are applied.
+                If <literal>value</literal> starts and ends with a slash, it is interpreted as
+                an extended python regular expression, if not, it's an exact match.
+                The journal fields are explained in systemd.journal-fields(7).
+              '';
+            };
+
+            filters = mkOption {
+              type = types.str;
+              example = ''
+                (Stopped|Stopping|Starting|Started) .*
+                (Reached target|Stopped target) .*
+              '';
+              description = ''
+                The filters to apply on all messages which satisfy <option>match</option>.
+                Any of those messages that match any specified filter will be removed from journalwatch's output.
+                Each filter is an extended Python regular expression.
+                You can specify multiple filters and separate them by newlines.
+                Lines starting with '#' are comments. Inline-comments are not permitted.
+              '';
+            };
+          };
+        });
+
+        example = [
+          # examples taken from upstream
+          {
+            match = "_SYSTEMD_UNIT = systemd-logind.service";
+            filters = ''
+              New session [a-z]?\d+ of user \w+\.
+              Removed session [a-z]?\d+\.
+            '';
+          }
+
+          {
+            match = "SYSLOG_IDENTIFIER = /(CROND|crond)/";
+            filters = ''
+              pam_unix\(crond:session\): session (opened|closed) for user \w+
+              \(\w+\) CMD .*
+            '';
+          }
+        ];
+
+        # another example from upstream.
+        # very useful on priority = 6, and required as journalwatch throws an error when no pattern is defined at all.
+        default = [
+          {
+            match = "SYSLOG_IDENTIFIER = systemd";
+            filters = ''
+              (Stopped|Stopping|Starting|Started) .*
+              (Created slice|Removed slice) user-\d*\.slice\.
+              Received SIGRTMIN\+24 from PID .*
+              (Reached target|Stopped target) .*
+              Startup finished in \d*ms\.
+            '';
+          }
+        ];
+
+
+        description = ''
+          filterBlocks can be defined to blacklist journal messages which are not errors.
+          Each block matches on a log entry field, and the filters in that block then are matched
+          against all messages with a matching log entry field.
+
+          All messages whose PRIORITY is at least 6 (INFO) are processed by journalwatch.
+          If you don't specify any filterBlocks, PRIORITY is reduced to 5 (NOTICE) by default.
+
+          All regular expressions are extended Python regular expressions, for details
+          see: http://doc.pyschools.com/html/regex.html
+        '';
+      };
+
+      interval = mkOption {
+        type = types.str;
+        default = "hourly";
+        description = ''
+          How often to run journalwatch.
+
+          The format is described in systemd.time(7).
+        '';
+      };
+      accuracy = mkOption {
+        type = types.str;
+        default = "10min";
+        description = ''
+          The time window around the interval in which the journalwatch run will be scheduled.
+
+          The format is described in systemd.time(7).
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.${user} = {
+      isSystemUser = true;
+      home = dataDir;
+      group = group;
+    };
+
+    systemd.tmpfiles.rules = [
+      # present since NixOS 19.09: remove old stateful symlink join directory,
+      # which has been replaced with the journalwatchConfigDir store path
+      "R ${dataDir}/config"
+    ];
+
+    systemd.services.journalwatch = {
+
+      environment = {
+        # journalwatch stores the last processed timpestamp here
+        # the share subdirectory is historic now that config home lives in /nix/store,
+        # but moving this in a backwards-compatible way is much more work than what's justified
+        # for cleaning that up.
+        XDG_DATA_HOME = "${dataDir}/share";
+        XDG_CONFIG_HOME = journalwatchConfigDir;
+      };
+      serviceConfig = {
+        User = user;
+        Group = group;
+        Type = "oneshot";
+        # requires a relative directory name to create beneath /var/lib
+        StateDirectory = user;
+        StateDirectoryMode = 0750;
+        ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail";
+        # lowest CPU and IO priority, but both still in best-effort class to prevent starvation
+        Nice=19;
+        IOSchedulingPriority=7;
+      };
+    };
+
+    systemd.timers.journalwatch = {
+      description = "Periodic journalwatch run";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = cfg.interval;
+        AccuracySec = cfg.accuracy;
+        Persistent = true;
+      };
+    };
+
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ florianjacob ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/klogd.nix b/nixpkgs/nixos/modules/services/logging/klogd.nix
new file mode 100644
index 000000000000..2d1f515da920
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/klogd.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  ###### interface
+
+  options = {
+
+    services.klogd.enable = mkOption {
+      type = types.bool;
+      default = versionOlder (getVersion config.boot.kernelPackages.kernel) "3.5";
+      description = ''
+        Whether to enable klogd, the kernel log message processing
+        daemon.  Since systemd handles logging of kernel messages on
+        Linux 3.5 and later, this is only useful if you're running an
+        older kernel.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.klogd.enable {
+    systemd.services.klogd = {
+      description = "Kernel Log Daemon";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.sysklogd ];
+      unitConfig.ConditionVirtualization = "!systemd-nspawn";
+      script =
+        "klogd -c 1 -2 -n " +
+        "-k $(dirname $(readlink -f /run/booted-system/kernel))/System.map";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/logcheck.nix b/nixpkgs/nixos/modules/services/logging/logcheck.nix
new file mode 100644
index 000000000000..4296b2270c29
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/logcheck.nix
@@ -0,0 +1,238 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.logcheck;
+
+  defaultRules = pkgs.runCommand "logcheck-default-rules" { preferLocalBuild = true; } ''
+                   cp -prd ${pkgs.logcheck}/etc/logcheck $out
+                   chmod u+w $out
+                   rm -r $out/logcheck.*
+                 '';
+
+  rulesDir = pkgs.symlinkJoin
+    { name = "logcheck-rules-dir";
+      paths = ([ defaultRules ] ++ cfg.extraRulesDirs);
+    };
+
+  configFile = pkgs.writeText "logcheck.conf" cfg.config;
+
+  logFiles = pkgs.writeText "logcheck.logfiles" cfg.files;
+
+  flags = "-r ${rulesDir} -c ${configFile} -L ${logFiles} -${levelFlag} -m ${cfg.mailTo}";
+
+  levelFlag = getAttrFromPath [cfg.level]
+    { paranoid    = "p";
+      server      = "s";
+      workstation = "w";
+    };
+
+  cronJob = ''
+    @reboot   logcheck env PATH=/run/wrappers/bin:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck -R ${flags}
+    2 ${cfg.timeOfDay} * * * logcheck env PATH=/run/wrappers/bin:$PATH nice -n10 ${pkgs.logcheck}/sbin/logcheck ${flags}
+  '';
+
+  writeIgnoreRule = name: {level, regex, ...}:
+    pkgs.writeTextFile
+      { inherit name;
+        destination = "/ignore.d.${level}/${name}";
+        text = ''
+          ^\w{3} [ :[:digit:]]{11} [._[:alnum:]-]+ ${regex}
+        '';
+      };
+
+  writeIgnoreCronRule = name: {level, user, regex, cmdline, ...}:
+    let escapeRegex = escape (stringToCharacters "\\[]{}()^$?*+|.");
+        cmdline_ = builtins.unsafeDiscardStringContext cmdline;
+        re = if regex != "" then regex else if cmdline_ == "" then ".*" else escapeRegex cmdline_;
+    in writeIgnoreRule "cron-${name}" {
+      inherit level;
+      regex = ''
+        (/usr/bin/)?cron\[[0-9]+\]: \(${user}\) CMD \(${re}\)$
+      '';
+    };
+
+  levelOption = mkOption {
+    default = "server";
+    type = types.enum [ "workstation" "server" "paranoid" ];
+    description = ''
+      Set the logcheck level.
+    '';
+  };
+
+  ignoreOptions = {
+    options = {
+      level = levelOption;
+
+      regex = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Regex specifying which log lines to ignore.
+        '';
+      };
+    };
+  };
+
+  ignoreCronOptions = {
+    options = {
+      user = mkOption {
+        default = "root";
+        type = types.str;
+        description = ''
+          User that runs the cronjob.
+        '';
+      };
+
+      cmdline = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Command line for the cron job. Will be turned into a regex for the logcheck ignore rule.
+        '';
+      };
+
+      timeArgs = mkOption {
+        default = null;
+        type = types.nullOr (types.str);
+        example = "02 06 * * *";
+        description = ''
+          "min hr dom mon dow" crontab time args, to auto-create a cronjob too.
+          Leave at null to not do this and just add a logcheck ignore rule.
+        '';
+      };
+    };
+  };
+
+in
+{
+  options = {
+    services.logcheck = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enable the logcheck cron job.
+        '';
+      };
+
+      user = mkOption {
+        default = "logcheck";
+        type = types.str;
+        description = ''
+          Username for the logcheck user.
+        '';
+      };
+
+      timeOfDay = mkOption {
+        default = "*";
+        example = "6";
+        type = types.str;
+        description = ''
+          Time of day to run logcheck. A logcheck will be scheduled at xx:02 each day.
+          Leave default (*) to run every hour. Of course when nothing special was logged,
+          logcheck will be silent.
+        '';
+      };
+
+      mailTo = mkOption {
+        default = "root";
+        example = "you@domain.com";
+        type = types.str;
+        description = ''
+          Email address to send reports to.
+        '';
+      };
+
+      level = mkOption {
+        default = "server";
+        type = types.str;
+        description = ''
+          Set the logcheck level. Either "workstation", "server", or "paranoid".
+        '';
+      };
+
+      config = mkOption {
+        default = "FQDN=1";
+        type = types.lines;
+        description = ''
+          Config options that you would like in logcheck.conf.
+        '';
+      };
+
+      files = mkOption {
+        default = [ "/var/log/messages" ];
+        type = types.listOf types.path;
+        example = [ "/var/log/messages" "/var/log/mail" ];
+        description = ''
+          Which log files to check.
+        '';
+      };
+
+      extraRulesDirs = mkOption {
+        default = [];
+        example = "/etc/logcheck";
+        type = types.listOf types.path;
+        description = ''
+          Directories with extra rules.
+        '';
+      };
+
+      ignore = mkOption {
+        default = {};
+        description = ''
+          This option defines extra ignore rules.
+        '';
+        type = with types; attrsOf (submodule ignoreOptions);
+      };
+
+      ignoreCron = mkOption {
+        default = {};
+        description = ''
+          This option defines extra ignore rules for cronjobs.
+        '';
+        type = with types; attrsOf (submodule ignoreCronOptions);
+      };
+
+      extraGroups = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        example = [ "postdrop" "mongodb" ];
+        description = ''
+          Extra groups for the logcheck user, for example to be able to use sendmail,
+          or to access certain log files.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.logcheck.extraRulesDirs =
+        mapAttrsToList writeIgnoreRule cfg.ignore
+        ++ mapAttrsToList writeIgnoreCronRule cfg.ignoreCron;
+
+    users.users = optionalAttrs (cfg.user == "logcheck") {
+      logcheck = {
+        uid = config.ids.uids.logcheck;
+        shell = "/bin/sh";
+        description = "Logcheck user account";
+        extraGroups = cfg.extraGroups;
+      };
+    };
+
+    system.activationScripts.logcheck = ''
+      mkdir -m 700 -p /var/{lib,lock}/logcheck
+      chown ${cfg.user} /var/{lib,lock}/logcheck
+    '';
+
+    services.cron.systemCronJobs =
+        let withTime = name: {timeArgs, ...}: timeArgs != null;
+            mkCron = name: {user, cmdline, timeArgs, ...}: ''
+              ${timeArgs} ${user} ${cmdline}
+            '';
+        in mapAttrsToList mkCron (filterAttrs withTime cfg.ignoreCron)
+           ++ [ cronJob ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/logrotate.nix b/nixpkgs/nixos/modules/services/logging/logrotate.nix
new file mode 100644
index 000000000000..565618b27a87
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/logrotate.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.logrotate;
+
+  pathOptions = {
+    options = {
+      path = mkOption {
+        type = types.str;
+        description = "The path to log files to be rotated";
+      };
+      user = mkOption {
+        type = types.str;
+        description = "The user account to use for rotation";
+      };
+      group = mkOption {
+        type = types.str;
+        description = "The group to use for rotation";
+      };
+      frequency = mkOption {
+        type = types.enum [
+          "daily" "weekly" "monthly" "yearly"
+        ];
+        default = "daily";
+        description = "How often to rotate the logs";
+      };
+      keep = mkOption {
+        type = types.int;
+        default = 20;
+        description = "How many rotations to keep";
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra logrotate config options for this path";
+      };
+    };
+  };
+
+  pathConfig = options: ''
+    "${options.path}" {
+      su ${options.user} ${options.group}
+      ${options.frequency}
+      missingok
+      notifempty
+      rotate ${toString options.keep}
+      ${options.extraConfig}
+    }
+  '';
+
+  configFile = pkgs.writeText "logrotate.conf" (
+    (concatStringsSep "\n" ((map pathConfig cfg.paths) ++ [cfg.extraConfig]))
+  );
+
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "logrotate" "config" ] [ "services" "logrotate" "extraConfig" ])
+  ];
+
+  options = {
+    services.logrotate = {
+      enable = mkEnableOption "the logrotate systemd service";
+
+      paths = mkOption {
+        type = types.listOf (types.submodule pathOptions);
+        default = [];
+        description = "List of attribute sets with paths to rotate";
+        example = {
+          "/var/log/myapp/*.log" = {
+            user = "myuser";
+            group = "mygroup";
+            rotate = "weekly";
+            keep = 5;
+          };
+        };
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra contents to add to the logrotate config file.
+          See https://linux.die.net/man/8/logrotate
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.logrotate = {
+      description   = "Logrotate Service";
+      wantedBy      = [ "multi-user.target" ];
+      startAt       = "*-*-* *:05:00";
+
+      serviceConfig.Restart = "no";
+      serviceConfig.User    = "root";
+      script = ''
+        exec ${pkgs.logrotate}/sbin/logrotate ${configFile}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/logstash.nix b/nixpkgs/nixos/modules/services/logging/logstash.nix
new file mode 100644
index 000000000000..21a83803fd8c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/logstash.nix
@@ -0,0 +1,185 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.logstash;
+  pluginPath = lib.concatStringsSep ":" cfg.plugins;
+  havePluginPath = lib.length cfg.plugins > 0;
+  ops = lib.optionalString;
+  verbosityFlag = "--log.level " + cfg.logLevel;
+
+  pluginsPath = "--path.plugins ${pluginPath}";
+
+  logstashConf = pkgs.writeText "logstash.conf" ''
+    input {
+      ${cfg.inputConfig}
+    }
+
+    filter {
+      ${cfg.filterConfig}
+    }
+
+    output {
+      ${cfg.outputConfig}
+    }
+  '';
+
+  logstashSettingsYml = pkgs.writeText "logstash.yml" cfg.extraSettings;
+
+  logstashSettingsDir = pkgs.runCommand "logstash-settings" {
+      inherit logstashSettingsYml;
+      preferLocalBuild = true;
+    } ''
+    mkdir -p $out
+    ln -s $logstashSettingsYml $out/logstash.yml
+  '';
+in
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "logstash" "address" ] [ "services" "logstash" "listenAddress" ])
+    (mkRemovedOptionModule [ "services" "logstash" "enableWeb" ] "The web interface was removed from logstash")
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.logstash = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable logstash.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.logstash;
+        defaultText = "pkgs.logstash";
+        example = literalExample "pkgs.logstash";
+        description = "Logstash package to use.";
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        example = literalExample "[ pkgs.logstash-contrib ]";
+        description = "The paths to find other logstash plugins in.";
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/logstash";
+        description = ''
+          A path to directory writable by logstash that it uses to store data.
+          Plugins will also have access to this path.
+        '';
+      };
+
+      logLevel = mkOption {
+        type = types.enum [ "debug" "info" "warn" "error" "fatal" ];
+        default = "warn";
+        description = "Logging verbosity level.";
+      };
+
+      filterWorkers = mkOption {
+        type = types.int;
+        default = 1;
+        description = "The quantity of filter workers to run.";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "Address on which to start webserver.";
+      };
+
+      port = mkOption {
+        type = types.str;
+        default = "9292";
+        description = "Port on which to start webserver.";
+      };
+
+      inputConfig = mkOption {
+        type = types.lines;
+        default = ''generator { }'';
+        description = "Logstash input configuration.";
+        example = ''
+          # Read from journal
+          pipe {
+            command => "''${pkgs.systemd}/bin/journalctl -f -o json"
+            type => "syslog" codec => json {}
+          }
+        '';
+      };
+
+      filterConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "logstash filter configuration.";
+        example = ''
+          if [type] == "syslog" {
+            # Keep only relevant systemd fields
+            # http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
+            prune {
+              whitelist_names => [
+                "type", "@timestamp", "@version",
+                "MESSAGE", "PRIORITY", "SYSLOG_FACILITY"
+              ]
+            }
+          }
+        '';
+      };
+
+      outputConfig = mkOption {
+        type = types.lines;
+        default = ''stdout { codec => rubydebug }'';
+        description = "Logstash output configuration.";
+        example = ''
+          redis { host => ["localhost"] data_type => "list" key => "logstash" codec => json }
+          elasticsearch { }
+        '';
+      };
+
+      extraSettings = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra Logstash settings in YAML format.";
+        example = ''
+          pipeline:
+            batch:
+              size: 125
+              delay: 5
+        '';
+      };
+
+
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.logstash = with pkgs; {
+      description = "Logstash Daemon";
+      wantedBy = [ "multi-user.target" ];
+      environment = { JAVA_HOME = jre; };
+      path = [ pkgs.bash ];
+      serviceConfig = {
+        ExecStartPre = ''${pkgs.coreutils}/bin/mkdir -p "${cfg.dataDir}" ; ${pkgs.coreutils}/bin/chmod 700 "${cfg.dataDir}"'';
+        ExecStart = concatStringsSep " " (filter (s: stringLength s != 0) [
+          "${cfg.package}/bin/logstash"
+          "-w ${toString cfg.filterWorkers}"
+          (ops havePluginPath pluginsPath)
+          "${verbosityFlag}"
+          "-f ${logstashConf}"
+          "--path.settings ${logstashSettingsDir}"
+          "--path.data ${cfg.dataDir}"
+        ]);
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/logging/rsyslogd.nix b/nixpkgs/nixos/modules/services/logging/rsyslogd.nix
new file mode 100644
index 000000000000..b924d94e0b0d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/rsyslogd.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.rsyslogd;
+
+  syslogConf = pkgs.writeText "syslog.conf" ''
+    $ModLoad imuxsock
+    $SystemLogSocketName /run/systemd/journal/syslog
+    $WorkDirectory /var/spool/rsyslog
+
+    ${cfg.defaultConfig}
+    ${cfg.extraConfig}
+  '';
+
+  defaultConf = ''
+    # "local1" is used for dhcpd messages.
+    local1.*                     -/var/log/dhcpd
+
+    mail.*                       -/var/log/mail
+
+    *.=warning;*.=err            -/var/log/warn
+    *.crit                        /var/log/warn
+
+    *.*;mail.none;local1.none    -/var/log/messages
+  '';
+
+in
+
+{
+  ###### interface
+
+  options = {
+
+    services.rsyslogd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable syslogd.  Note that systemd also logs
+          syslog messages, so you normally don't need to run syslogd.
+        '';
+      };
+
+      defaultConfig = mkOption {
+        type = types.lines;
+        default = defaultConf;
+        description = ''
+          The default <filename>syslog.conf</filename> file configures a
+          fairly standard setup of log files, which can be extended by
+          means of <varname>extraConfig</varname>.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = "news.* -/var/log/news";
+        description = ''
+          Additional text appended to <filename>syslog.conf</filename>,
+          i.e. the contents of <varname>defaultConfig</varname>.
+        '';
+      };
+
+      extraParams = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "-m 0" ];
+        description = ''
+          Additional parameters passed to <command>rsyslogd</command>.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.rsyslog ];
+
+    systemd.services.syslog =
+      { description = "Syslog Daemon";
+
+        requires = [ "syslog.socket" ];
+
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig =
+          { ExecStart = "${pkgs.rsyslog}/sbin/rsyslogd ${toString cfg.extraParams} -f ${syslogConf} -n";
+            ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/spool/rsyslog";
+            # Prevent syslogd output looping back through journald.
+            StandardOutput = "null";
+          };
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/logging/syslog-ng.nix b/nixpkgs/nixos/modules/services/logging/syslog-ng.nix
new file mode 100644
index 000000000000..35055311680b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/syslog-ng.nix
@@ -0,0 +1,101 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.syslog-ng;
+
+  syslogngConfig = pkgs.writeText "syslog-ng.conf" ''
+    ${cfg.configHeader}
+    ${cfg.extraConfig}
+  '';
+
+  ctrlSocket = "/run/syslog-ng/syslog-ng.ctl";
+  pidFile = "/run/syslog-ng/syslog-ng.pid";
+  persistFile = "/var/syslog-ng/syslog-ng.persist";
+
+  syslogngOptions = [
+    "--foreground"
+    "--module-path=${concatStringsSep ":" (["${cfg.package}/lib/syslog-ng"] ++ cfg.extraModulePaths)}"
+    "--cfgfile=${syslogngConfig}"
+    "--control=${ctrlSocket}"
+    "--persist-file=${persistFile}"
+    "--pidfile=${pidFile}"
+  ];
+
+in {
+  imports = [
+    (mkRemovedOptionModule [ "services" "syslog-ng" "serviceName" ] "")
+    (mkRemovedOptionModule [ "services" "syslog-ng" "listenToJournal" ] "")
+  ];
+
+  options = {
+
+    services.syslog-ng = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the syslog-ng daemon.
+        '';
+      };
+      package = mkOption {
+        type = types.package;
+        default = pkgs.syslogng;
+        defaultText = "pkgs.syslogng";
+        description = ''
+          The package providing syslog-ng binaries.
+        '';
+      };
+      extraModulePaths = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = literalExample ''
+          [ "''${pkgs.syslogng_incubator}/lib/syslog-ng" ]
+        '';
+        description = ''
+          A list of paths that should be included in syslog-ng's
+          <literal>--module-path</literal> option. They should usually
+          end in <literal>/lib/syslog-ng</literal>
+        '';
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Configuration added to the end of <literal>syslog-ng.conf</literal>.
+        '';
+      };
+      configHeader = mkOption {
+        type = types.lines;
+        default = ''
+          @version: 3.6
+          @include "scl.conf"
+        '';
+        description = ''
+          The very first lines of the configuration file. Should usually contain
+          the syslog-ng version header.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.syslog-ng = {
+      description = "syslog-ng daemon";
+      preStart = "mkdir -p /{var,run}/syslog-ng";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "multi-user.target" ]; # makes sure hostname etc is set
+      serviceConfig = {
+        Type = "notify";
+        PIDFile = pidFile;
+        StandardOutput = "null";
+        Restart = "on-failure";
+        ExecStart = "${cfg.package}/sbin/syslog-ng ${concatStringsSep " " syslogngOptions}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/logging/syslogd.nix b/nixpkgs/nixos/modules/services/logging/syslogd.nix
new file mode 100644
index 000000000000..fe0b0490811d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/logging/syslogd.nix
@@ -0,0 +1,130 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.syslogd;
+
+  syslogConf = pkgs.writeText "syslog.conf" ''
+    ${if (cfg.tty != "") then "kern.warning;*.err;authpriv.none /dev/${cfg.tty}" else ""}
+    ${cfg.defaultConfig}
+    ${cfg.extraConfig}
+  '';
+
+  defaultConf = ''
+    # Send emergency messages to all users.
+    *.emerg                       *
+
+    # "local1" is used for dhcpd messages.
+    local1.*                     -/var/log/dhcpd
+
+    mail.*                       -/var/log/mail
+
+    *.=warning;*.=err            -/var/log/warn
+    *.crit                        /var/log/warn
+
+    *.*;mail.none;local1.none    -/var/log/messages
+  '';
+
+in
+
+{
+  ###### interface
+
+  options = {
+
+    services.syslogd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable syslogd.  Note that systemd also logs
+          syslog messages, so you normally don't need to run syslogd.
+        '';
+      };
+
+      tty = mkOption {
+        type = types.str;
+        default = "tty10";
+        description = ''
+          The tty device on which syslogd will print important log
+          messages. Leave this option blank to disable tty logging.
+        '';
+      };
+
+      defaultConfig = mkOption {
+        type = types.lines;
+        default = defaultConf;
+        description = ''
+          The default <filename>syslog.conf</filename> file configures a
+          fairly standard setup of log files, which can be extended by
+          means of <varname>extraConfig</varname>.
+        '';
+      };
+
+      enableNetworkInput = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Accept logging through UDP. Option -r of syslogd(8).
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = "news.* -/var/log/news";
+        description = ''
+          Additional text appended to <filename>syslog.conf</filename>,
+          i.e. the contents of <varname>defaultConfig</varname>.
+        '';
+      };
+
+      extraParams = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "-m 0" ];
+        description = ''
+          Additional parameters passed to <command>syslogd</command>.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions =
+      [ { assertion = !config.services.rsyslogd.enable;
+          message = "rsyslogd conflicts with syslogd";
+        }
+      ];
+
+    environment.systemPackages = [ pkgs.sysklogd ];
+
+    services.syslogd.extraParams = optional cfg.enableNetworkInput "-r";
+
+    # FIXME: restarting syslog seems to break journal logging.
+    systemd.services.syslog =
+      { description = "Syslog Daemon";
+
+        requires = [ "syslog.socket" ];
+
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig =
+          { ExecStart = "${pkgs.sysklogd}/sbin/syslogd ${toString cfg.extraParams} -f ${syslogConf} -n";
+            # Prevent syslogd output looping back through journald.
+            StandardOutput = "null";
+          };
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/clamsmtp.nix b/nixpkgs/nixos/modules/services/mail/clamsmtp.nix
new file mode 100644
index 000000000000..fc1267c5d280
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/clamsmtp.nix
@@ -0,0 +1,181 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.clamsmtp;
+  clamdSocket = "/run/clamav/clamd.ctl"; # See services/security/clamav.nix
+in
+{
+  ##### interface
+  options = {
+    services.clamsmtp = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable clamsmtp.";
+      };
+
+      instances = mkOption {
+        description = "Instances of clamsmtp to run.";
+        type = types.listOf (types.submodule { options = {
+          action = mkOption {
+            type = types.enum [ "bounce" "drop" "pass" ];
+            default = "drop";
+            description =
+              ''
+                Action to take when a virus is detected.
+
+                Note that viruses often spoof sender addresses, so bouncing is
+                in most cases not a good idea.
+              '';
+          };
+
+          header = mkOption {
+            type = types.str;
+            default = "";
+            example = "X-Virus-Scanned: ClamAV using ClamSMTP";
+            description =
+              ''
+                A header to add to scanned messages. See clamsmtpd.conf(5) for
+                more details. Empty means no header.
+              '';
+          };
+
+          keepAlives = mkOption {
+            type = types.int;
+            default = 0;
+            description =
+              ''
+                Number of seconds to wait between each NOOP sent to the sending
+                server. 0 to disable.
+
+                This is meant for slow servers where the sending MTA times out
+                waiting for clamd to scan the file.
+              '';
+          };
+
+          listen = mkOption {
+            type = types.str;
+            example = "127.0.0.1:10025";
+            description =
+              ''
+                Address to wait for incoming SMTP connections on. See
+                clamsmtpd.conf(5) for more details.
+              '';
+          };
+
+          quarantine = mkOption {
+            type = types.bool;
+            default = false;
+            description =
+              ''
+                Whether to quarantine files that contain viruses by leaving them
+                in the temporary directory.
+              '';
+          };
+
+          maxConnections = mkOption {
+            type = types.int;
+            default = 64;
+            description = "Maximum number of connections to accept at once.";
+          };
+
+          outAddress = mkOption {
+            type = types.str;
+            description =
+              ''
+                Address of the SMTP server to send email to once it has been
+                scanned.
+              '';
+          };
+
+          tempDirectory = mkOption {
+            type = types.str;
+            default = "/tmp";
+            description =
+              ''
+                Temporary directory that needs to be accessible to both clamd
+                and clamsmtpd.
+              '';
+          };
+
+          timeout = mkOption {
+            type = types.int;
+            default = 180;
+            description = "Time-out for network connections.";
+          };
+
+          transparentProxy = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Enable clamsmtp's transparent proxy support.";
+          };
+
+          virusAction = mkOption {
+            type = with types; nullOr path;
+            default = null;
+            description =
+              ''
+                Command to run when a virus is found. Please see VIRUS ACTION in
+                clamsmtpd(8) for a discussion of this option and its safe use.
+              '';
+          };
+
+          xClient = mkOption {
+            type = types.bool;
+            default = false;
+            description =
+              ''
+                Send the XCLIENT command to the receiving server, for forwarding
+                client addresses and connection information if the receiving
+                server supports this feature.
+              '';
+          };
+        };});
+      };
+    };
+  };
+
+  ##### implementation
+  config = let
+    configfile = conf: pkgs.writeText "clamsmtpd.conf"
+      ''
+        Action: ${conf.action}
+        ClamAddress: ${clamdSocket}
+        Header: ${conf.header}
+        KeepAlives: ${toString conf.keepAlives}
+        Listen: ${conf.listen}
+        Quarantine: ${if conf.quarantine then "on" else "off"}
+        MaxConnections: ${toString conf.maxConnections}
+        OutAddress: ${conf.outAddress}
+        TempDirectory: ${conf.tempDirectory}
+        TimeOut: ${toString conf.timeout}
+        TransparentProxy: ${if conf.transparentProxy then "on" else "off"}
+        User: clamav
+        ${optionalString (conf.virusAction != null) "VirusAction: ${conf.virusAction}"}
+        XClient: ${if conf.xClient then "on" else "off"}
+      '';
+  in
+    mkIf cfg.enable {
+      assertions = [
+        { assertion = config.services.clamav.daemon.enable;
+          message = "clamsmtp requires clamav to be enabled";
+        }
+      ];
+
+      systemd.services = listToAttrs (imap1 (i: conf:
+        nameValuePair "clamsmtp-${toString i}" {
+          description = "ClamSMTP instance ${toString i}";
+          wantedBy = [ "multi-user.target" ];
+          script = "exec ${pkgs.clamsmtp}/bin/clamsmtpd -f ${configfile conf}";
+          after = [ "clamav-daemon.service" ];
+          requires = [ "clamav-daemon.service" ];
+          serviceConfig.Type = "forking";
+          serviceConfig.PrivateTmp = "yes";
+          unitConfig.JoinsNamespaceOf = "clamav-daemon.service";
+        }
+      ) cfg.instances);
+    };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixpkgs/nixos/modules/services/mail/davmail.nix b/nixpkgs/nixos/modules/services/mail/davmail.nix
new file mode 100644
index 000000000000..374a3dd75c1c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/davmail.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.davmail;
+
+  configType = with types;
+    oneOf [ (attrsOf configType) str int bool ] // {
+      description = "davmail config type (str, int, bool or attribute set thereof)";
+    };
+
+  toStr = val: if isBool val then boolToString val else toString val;
+
+  linesForAttrs = attrs: concatMap (name: let value = attrs.${name}; in
+    if isAttrs value
+      then map (line: name + "." + line) (linesForAttrs value)
+      else [ "${name}=${toStr value}" ]
+  ) (attrNames attrs);
+
+  configFile = pkgs.writeText "davmail.properties" (concatStringsSep "\n" (linesForAttrs cfg.config));
+
+in
+
+  {
+    options.services.davmail = {
+      enable = mkEnableOption "davmail, an MS Exchange gateway";
+
+      url = mkOption {
+        type = types.str;
+        description = "Outlook Web Access URL to access the exchange server, i.e. the base webmail URL.";
+        example = "https://outlook.office365.com/EWS/Exchange.asmx";
+      };
+
+      config = mkOption {
+        type = configType;
+        default = {};
+        description = ''
+          Davmail configuration. Refer to
+          <link xlink:href="http://davmail.sourceforge.net/serversetup.html"/>
+          and <link xlink:href="http://davmail.sourceforge.net/advanced.html"/>
+          for details on supported values.
+        '';
+        example = literalExample ''
+          {
+            davmail.allowRemote = true;
+            davmail.imapPort = 55555;
+            davmail.bindAddress = "10.0.1.2";
+            davmail.smtpSaveInSent = true;
+            davmail.folderSizeLimit = 10;
+            davmail.caldavAutoSchedule = false;
+            log4j.logger.rootLogger = "DEBUG";
+          }
+        '';
+      };
+    };
+
+    config = mkIf cfg.enable {
+
+      services.davmail.config = {
+        davmail = mapAttrs (name: mkDefault) {
+          server = true;
+          disableUpdateCheck = true;
+          logFilePath = "/var/log/davmail/davmail.log";
+          logFileSize = "1MB";
+          mode = "auto";
+          url = cfg.url;
+          caldavPort = 1080;
+          imapPort = 1143;
+          ldapPort = 1389;
+          popPort = 1110;
+          smtpPort = 1025;
+        };
+        log4j = {
+          logger.davmail = mkDefault "WARN";
+          logger.httpclient.wire = mkDefault "WARN";
+          logger.org.apache.commons.httpclient = mkDefault "WARN";
+          rootLogger = mkDefault "WARN";
+        };
+      };
+
+      systemd.services.davmail = {
+        description = "DavMail POP/IMAP/SMTP Exchange Gateway";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = {
+          Type = "simple";
+          ExecStart = "${pkgs.davmail}/bin/davmail ${configFile}";
+          Restart = "on-failure";
+          DynamicUser = "yes";
+          LogsDirectory = "davmail";
+        };
+      };
+
+      environment.systemPackages = [ pkgs.davmail ];
+    };
+  }
diff --git a/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix b/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix
new file mode 100644
index 000000000000..f4ac9e47007a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/dkimproxy-out.nix
@@ -0,0 +1,120 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.dkimproxy-out;
+  keydir = "/var/lib/dkimproxy-out";
+  privkey = "${keydir}/private.key";
+  pubkey = "${keydir}/public.key";
+in
+{
+  ##### interface
+  options = {
+    services.dkimproxy-out = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          ''
+            Whether to enable dkimproxy_out.
+
+            Note that a key will be auto-generated, and can be found in
+            ${keydir}.
+          '';
+      };
+
+      listen = mkOption {
+        type = types.str;
+        example = "127.0.0.1:10027";
+        description = "Address:port DKIMproxy should listen on.";
+      };
+
+      relay = mkOption {
+        type = types.str;
+        example = "127.0.0.1:10028";
+        description = "Address:port DKIMproxy should forward mail to.";
+      };
+
+      domains = mkOption {
+        type = with types; listOf str;
+        example = [ "example.org" "example.com" ];
+        description = "List of domains DKIMproxy can sign for.";
+      };
+
+      selector = mkOption {
+        type = types.str;
+        example = "selector1";
+        description =
+          ''
+            The selector to use for DKIM key identification.
+
+            For example, if 'selector1' is used here, then for each domain
+            'example.org' given in `domain`, 'selector1._domainkey.example.org'
+            should contain the TXT record indicating the public key is the one
+            in ${pubkey}: "v=DKIM1; t=s; p=[THE PUBLIC KEY]".
+          '';
+      };
+
+      keySize = mkOption {
+        type = types.int;
+        default = 2048;
+        description =
+          ''
+            Size of the RSA key to use to sign outgoing emails. Note that the
+            maximum mandatorily verified as per RFC6376 is 2048.
+          '';
+      };
+
+      # TODO: allow signature for other schemes than dkim(c=relaxed/relaxed)?
+      # This being the scheme used by gmail, maybe nothing more is needed for
+      # reasonable use.
+    };
+  };
+
+  ##### implementation
+  config = let
+    configfile = pkgs.writeText "dkimproxy_out.conf"
+      ''
+        listen ${cfg.listen}
+        relay ${cfg.relay}
+
+        domain ${concatStringsSep "," cfg.domains}
+        selector ${cfg.selector}
+
+        signature dkim(c=relaxed/relaxed)
+
+        keyfile ${privkey}
+      '';
+  in
+    mkIf cfg.enable {
+      users.groups.dkimproxy-out = {};
+      users.users.dkimproxy-out = {
+        description = "DKIMproxy_out daemon";
+        group = "dkimproxy-out";
+        isSystemUser = true;
+      };
+
+      systemd.services.dkimproxy-out = {
+        description = "DKIMproxy_out";
+        wantedBy = [ "multi-user.target" ];
+        preStart = ''
+          if [ ! -d "${keydir}" ]; then
+            mkdir -p "${keydir}"
+            chmod 0700 "${keydir}"
+            ${pkgs.openssl}/bin/openssl genrsa -out "${privkey}" ${toString cfg.keySize}
+            ${pkgs.openssl}/bin/openssl rsa -in "${privkey}" -pubout -out "${pubkey}"
+            chown -R dkimproxy-out:dkimproxy-out "${keydir}"
+          fi
+        '';
+        script = ''
+          exec ${pkgs.dkimproxy}/bin/dkimproxy.out --conf_file=${configfile}
+        '';
+        serviceConfig = {
+          User = "dkimproxy-out";
+          PermissionsStartOnly = true;
+        };
+      };
+    };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixpkgs/nixos/modules/services/mail/dovecot.nix b/nixpkgs/nixos/modules/services/mail/dovecot.nix
new file mode 100644
index 000000000000..51cbcbf1cbc8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/dovecot.nix
@@ -0,0 +1,496 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dovecot2;
+  dovecotPkg = pkgs.dovecot;
+
+  baseDir = "/run/dovecot2";
+  stateDir = "/var/lib/dovecot";
+
+  dovecotConf = concatStrings [
+    ''
+      base_dir = ${baseDir}
+      protocols = ${concatStringsSep " " cfg.protocols}
+      sendmail_path = /run/wrappers/bin/sendmail
+      # defining mail_plugins must be done before the first protocol {} filter because of https://doc.dovecot.org/configuration_manual/config_file/config_file_syntax/#variable-expansion
+      mail_plugins = $mail_plugins ${concatStringsSep " " cfg.mailPlugins.globally.enable}
+    ''
+
+    (
+      concatStringsSep "\n" (
+        mapAttrsToList (
+          protocol: plugins: ''
+            protocol ${protocol} {
+              mail_plugins = $mail_plugins ${concatStringsSep " " plugins.enable}
+            }
+          ''
+        ) cfg.mailPlugins.perProtocol
+      )
+    )
+
+    (
+      if cfg.sslServerCert == null then ''
+        ssl = no
+        disable_plaintext_auth = no
+      '' else ''
+        ssl_cert = <${cfg.sslServerCert}
+        ssl_key = <${cfg.sslServerKey}
+        ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)}
+        ssl_dh = <${config.security.dhparams.params.dovecot2.path}
+        disable_plaintext_auth = yes
+      ''
+    )
+
+    ''
+      default_internal_user = ${cfg.user}
+      default_internal_group = ${cfg.group}
+      ${optionalString (cfg.mailUser != null) "mail_uid = ${cfg.mailUser}"}
+      ${optionalString (cfg.mailGroup != null) "mail_gid = ${cfg.mailGroup}"}
+
+      mail_location = ${cfg.mailLocation}
+
+      maildir_copy_with_hardlinks = yes
+      pop3_uidl_format = %08Xv%08Xu
+
+      auth_mechanisms = plain login
+
+      service auth {
+        user = root
+      }
+    ''
+
+    (
+      optionalString cfg.enablePAM ''
+        userdb {
+          driver = passwd
+        }
+
+        passdb {
+          driver = pam
+          args = ${optionalString cfg.showPAMFailure "failure_show_msg=yes"} dovecot2
+        }
+      ''
+    )
+
+    (
+      optionalString (cfg.sieveScripts != {}) ''
+        plugin {
+          ${concatStringsSep "\n" (mapAttrsToList (to: from: "sieve_${to} = ${stateDir}/sieve/${to}") cfg.sieveScripts)}
+        }
+      ''
+    )
+
+    (
+      optionalString (cfg.mailboxes != []) ''
+        protocol imap {
+          namespace inbox {
+            inbox=yes
+            ${concatStringsSep "\n" (map mailboxConfig cfg.mailboxes)}
+          }
+        }
+      ''
+    )
+
+    (
+      optionalString cfg.enableQuota ''
+        service quota-status {
+          executable = ${dovecotPkg}/libexec/dovecot/quota-status -p postfix
+          inet_listener {
+            port = ${cfg.quotaPort}
+          }
+          client_limit = 1
+        }
+
+        plugin {
+          quota_rule = *:storage=${cfg.quotaGlobalPerUser}
+          quota = maildir:User quota # per virtual mail user quota # BUG/FIXME broken, we couldn't get this working
+          quota_status_success = DUNNO
+          quota_status_nouser = DUNNO
+          quota_status_overquota = "552 5.2.2 Mailbox is full"
+          quota_grace = 10%%
+        }
+      ''
+    )
+
+    cfg.extraConfig
+  ];
+
+  modulesDir = pkgs.symlinkJoin {
+    name = "dovecot-modules";
+    paths = map (pkg: "${pkg}/lib/dovecot") ([ dovecotPkg ] ++ map (module: module.override { dovecot = dovecotPkg; }) cfg.modules);
+  };
+
+  mailboxConfig = mailbox: ''
+    mailbox "${mailbox.name}" {
+      auto = ${toString mailbox.auto}
+  '' + optionalString (mailbox.autoexpunge != null) ''
+    autoexpunge = ${mailbox.autoexpunge}
+  '' + optionalString (mailbox.specialUse != null) ''
+    special_use = \${toString mailbox.specialUse}
+  '' + "}";
+
+  mailboxes = { ... }: {
+    options = {
+      name = mkOption {
+        type = types.nullOr (types.strMatching ''[^"]+'');
+        example = "Spam";
+        default = null;
+        description = "The name of the mailbox.";
+      };
+      auto = mkOption {
+        type = types.enum [ "no" "create" "subscribe" ];
+        default = "no";
+        example = "subscribe";
+        description = "Whether to automatically create or create and subscribe to the mailbox or not.";
+      };
+      specialUse = mkOption {
+        type = types.nullOr (types.enum [ "All" "Archive" "Drafts" "Flagged" "Junk" "Sent" "Trash" ]);
+        default = null;
+        example = "Junk";
+        description = "Null if no special use flag is set. Other than that every use flag mentioned in the RFC is valid.";
+      };
+      autoexpunge = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "60d";
+        description = ''
+          To automatically remove all email from the mailbox which is older than the
+          specified time.
+        '';
+      };
+    };
+  };
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "")
+  ];
+
+  options.services.dovecot2 = {
+    enable = mkEnableOption "Dovecot 2.x POP3/IMAP server";
+
+    enablePop3 = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Start the POP3 listener (when Dovecot is enabled).";
+    };
+
+    enableImap = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Start the IMAP listener (when Dovecot is enabled).";
+    };
+
+    enableLmtp = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Start the LMTP listener (when Dovecot is enabled).";
+    };
+
+    protocols = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = "Additional listeners to start when Dovecot is enabled.";
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "dovecot2";
+      description = "Dovecot user name.";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "dovecot2";
+      description = "Dovecot group name.";
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = "mail_debug = yes";
+      description = "Additional entries to put verbatim into Dovecot's config file.";
+    };
+
+    mailPlugins =
+      let
+        plugins = hint: types.submodule {
+          options = {
+            enable = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              description = "mail plugins to enable as a list of strings to append to the ${hint} <literal>$mail_plugins</literal> configuration variable";
+            };
+          };
+        };
+      in
+        mkOption {
+          type = with types; submodule {
+            options = {
+              globally = mkOption {
+                description = "Additional entries to add to the mail_plugins variable for all protocols";
+                type = plugins "top-level";
+                example = { enable = [ "virtual" ]; };
+                default = { enable = []; };
+              };
+              perProtocol = mkOption {
+                description = "Additional entries to add to the mail_plugins variable, per protocol";
+                type = attrsOf (plugins "corresponding per-protocol");
+                default = {};
+                example = { imap = [ "imap_acl" ]; };
+              };
+            };
+          };
+          description = "Additional entries to add to the mail_plugins variable, globally and per protocol";
+          example = {
+            globally.enable = [ "acl" ];
+            perProtocol.imap.enable = [ "imap_acl" ];
+          };
+          default = { globally.enable = []; perProtocol = {}; };
+        };
+
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = "Config file used for the whole dovecot configuration.";
+      apply = v: if v != null then v else pkgs.writeText "dovecot.conf" dovecotConf;
+    };
+
+    mailLocation = mkOption {
+      type = types.str;
+      default = "maildir:/var/spool/mail/%u"; /* Same as inbox, as postfix */
+      example = "maildir:~/mail:INBOX=/var/spool/mail/%u";
+      description = ''
+        Location that dovecot will use for mail folders. Dovecot mail_location option.
+      '';
+    };
+
+    mailUser = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "Default user to store mail for virtual users.";
+    };
+
+    mailGroup = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "Default group to store mail for virtual users.";
+    };
+
+    createMailUser = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''Whether to automatically create the user
+        given in <option>services.dovecot.user</option> and the group
+        given in <option>services.dovecot.group</option>.'';
+    };
+
+    modules = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExample "[ pkgs.dovecot_pigeonhole ]";
+      description = ''
+        Symlinks the contents of lib/dovecot of every given package into
+        /etc/dovecot/modules. This will make the given modules available
+        if a dovecot package with the module_dir patch applied is being used.
+      '';
+    };
+
+    sslCACert = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "Path to the server's CA certificate key.";
+    };
+
+    sslServerCert = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "Path to the server's public key.";
+    };
+
+    sslServerKey = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = "Path to the server's private key.";
+    };
+
+    enablePAM = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to create a own Dovecot PAM service and configure PAM user logins.";
+    };
+
+    sieveScripts = mkOption {
+      type = types.attrsOf types.path;
+      default = {};
+      description = "Sieve scripts to be executed. Key is a sequence, e.g. 'before2', 'after' etc.";
+    };
+
+    showPAMFailure = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Show the PAM failure message on authentication error (useful for OTPW).";
+    };
+
+    mailboxes = mkOption {
+      type = with types; let m = submodule mailboxes; in either (listOf m) (attrsOf m);
+      default = {};
+      apply = x:
+        if isList x then warn "Declaring `services.dovecot2.mailboxes' as a list is deprecated and will break eval in 21.03!" x
+        else mapAttrsToList (name: value:
+          if value.name != null
+            then throw ''
+              When specifying dovecot2 mailboxes as attributes, declaring
+              a `name'-attribute is prohibited! The name ${value.name} should
+              be the attribute key!
+            ''
+          else value // { inherit name; }
+        ) x;
+      example = literalExample ''
+        {
+          Spam = { specialUse = "Junk"; auto = "create"; };
+        }
+      '';
+      description = "Configure mailboxes and auto create or subscribe them.";
+    };
+
+    enableQuota = mkOption {
+      type = types.bool;
+      default = false;
+      example = true;
+      description = "Whether to enable the dovecot quota service.";
+    };
+
+    quotaPort = mkOption {
+      type = types.str;
+      default = "12340";
+      description = ''
+        The Port the dovecot quota service binds to.
+        If using postfix, add check_policy_service inet:localhost:12340 to your smtpd_recipient_restrictions in your postfix config.
+      '';
+    };
+    quotaGlobalPerUser = mkOption {
+      type = types.str;
+      default = "100G";
+      example = "10G";
+      description = "Quota limit for the user in bytes. Supports suffixes b, k, M, G, T and %.";
+    };
+
+  };
+
+
+  config = mkIf cfg.enable {
+    security.pam.services.dovecot2 = mkIf cfg.enablePAM {};
+
+    security.dhparams = mkIf (cfg.sslServerCert != null) {
+      enable = true;
+      params.dovecot2 = {};
+    };
+    services.dovecot2.protocols =
+      optional cfg.enableImap "imap"
+      ++ optional cfg.enablePop3 "pop3"
+      ++ optional cfg.enableLmtp "lmtp";
+
+    services.dovecot2.mailPlugins = mkIf cfg.enableQuota {
+      globally.enable = [ "quota" ];
+      perProtocol.imap.enable = [ "imap_quota" ];
+    };
+
+    users.users = {
+      dovenull =
+        {
+          uid = config.ids.uids.dovenull2;
+          description = "Dovecot user for untrusted logins";
+          group = "dovenull";
+        };
+    } // optionalAttrs (cfg.user == "dovecot2") {
+      dovecot2 =
+        {
+          uid = config.ids.uids.dovecot2;
+          description = "Dovecot user";
+          group = cfg.group;
+        };
+    } // optionalAttrs (cfg.createMailUser && cfg.mailUser != null) {
+      ${cfg.mailUser} =
+        { description = "Virtual Mail User"; } // optionalAttrs (cfg.mailGroup != null)
+          { group = cfg.mailGroup; };
+    };
+
+    users.groups = {
+      dovenull.gid = config.ids.gids.dovenull2;
+    } // optionalAttrs (cfg.group == "dovecot2") {
+      dovecot2.gid = config.ids.gids.dovecot2;
+    } // optionalAttrs (cfg.createMailUser && cfg.mailGroup != null) {
+      ${cfg.mailGroup} = {};
+    };
+
+    environment.etc."dovecot/modules".source = modulesDir;
+    environment.etc."dovecot/dovecot.conf".source = cfg.configFile;
+
+    systemd.services.dovecot2 = {
+      description = "Dovecot IMAP/POP3 server";
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ cfg.configFile modulesDir ];
+
+      serviceConfig = {
+        ExecStart = "${dovecotPkg}/sbin/dovecot -F";
+        ExecReload = "${dovecotPkg}/sbin/doveadm reload";
+        Restart = "on-failure";
+        RestartSec = "1s";
+        StartLimitInterval = "1min";
+        RuntimeDirectory = [ "dovecot2" ];
+      };
+
+      # When copying sieve scripts preserve the original time stamp
+      # (should be 0) so that the compiled sieve script is newer than
+      # the source file and Dovecot won't try to compile it.
+      preStart = ''
+        rm -rf ${stateDir}/sieve
+      '' + optionalString (cfg.sieveScripts != {}) ''
+        mkdir -p ${stateDir}/sieve
+        ${concatStringsSep "\n" (
+        mapAttrsToList (
+          to: from: ''
+            if [ -d '${from}' ]; then
+              mkdir '${stateDir}/sieve/${to}'
+              cp -p "${from}/"*.sieve '${stateDir}/sieve/${to}'
+            else
+              cp -p '${from}' '${stateDir}/sieve/${to}'
+            fi
+            ${pkgs.dovecot_pigeonhole}/bin/sievec '${stateDir}/sieve/${to}'
+          ''
+        ) cfg.sieveScripts
+      )}
+        chown -R '${cfg.mailUser}:${cfg.mailGroup}' '${stateDir}/sieve'
+      '';
+    };
+
+    environment.systemPackages = [ dovecotPkg ];
+
+    assertions = [
+      {
+        assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != [];
+        message = "dovecot needs at least one of the IMAP or POP3 listeners enabled";
+      }
+      {
+        assertion = (cfg.sslServerCert == null) == (cfg.sslServerKey == null)
+        && (cfg.sslCACert != null -> !(cfg.sslServerCert == null || cfg.sslServerKey == null));
+        message = "dovecot needs both sslServerCert and sslServerKey defined for working crypto";
+      }
+      {
+        assertion = cfg.showPAMFailure -> cfg.enablePAM;
+        message = "dovecot is configured with showPAMFailure while enablePAM is disabled";
+      }
+      {
+        assertion = cfg.sieveScripts != {} -> (cfg.mailUser != null && cfg.mailGroup != null);
+        message = "dovecot requires mailUser and mailGroup to be set when sieveScripts is set";
+      }
+    ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/dspam.nix b/nixpkgs/nixos/modules/services/mail/dspam.nix
new file mode 100644
index 000000000000..766ebc8095a0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/dspam.nix
@@ -0,0 +1,150 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.dspam;
+
+  dspam = pkgs.dspam;
+
+  defaultSock = "/run/dspam/dspam.sock";
+
+  cfgfile = pkgs.writeText "dspam.conf" ''
+    Home /var/lib/dspam
+    StorageDriver ${dspam}/lib/dspam/lib${cfg.storageDriver}_drv.so
+
+    Trust root
+    Trust ${cfg.user}
+    SystemLog on
+    UserLog on
+
+    ${optionalString (cfg.domainSocket != null) ''
+      ServerDomainSocketPath "${cfg.domainSocket}"
+      ClientHost "${cfg.domainSocket}"
+    ''}
+
+    ${cfg.extraConfig}
+  '';
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.dspam = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the dspam spam filter.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "dspam";
+        description = "User for the dspam daemon.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "dspam";
+        description = "Group for the dspam daemon.";
+      };
+
+      storageDriver = mkOption {
+        type = types.str;
+        default = "hash";
+        description =  "Storage driver backend to use for dspam.";
+      };
+
+      domainSocket = mkOption {
+        type = types.nullOr types.path;
+        default = defaultSock;
+        description = "Path to local domain socket which is used for communication with the daemon. Set to null to disable UNIX socket.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional dspam configuration.";
+      };
+
+      maintenanceInterval = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "If set, maintenance script will be run at specified (in systemd.timer format) interval";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      users.users = optionalAttrs (cfg.user == "dspam") {
+        dspam = {
+          group = cfg.group;
+          uid = config.ids.uids.dspam;
+        };
+      };
+
+      users.groups = optionalAttrs (cfg.group == "dspam") {
+        dspam.gid = config.ids.gids.dspam;
+      };
+
+      environment.systemPackages = [ dspam ];
+
+      environment.etc."dspam/dspam.conf".source = cfgfile;
+
+      systemd.services.dspam = {
+        description = "dspam spam filtering daemon";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "postgresql.service" ];
+        restartTriggers = [ cfgfile ];
+
+        serviceConfig = {
+          ExecStart = "${dspam}/bin/dspam --daemon --nofork";
+          User = cfg.user;
+          Group = cfg.group;
+          RuntimeDirectory = optional (cfg.domainSocket == defaultSock) "dspam";
+          RuntimeDirectoryMode = optional (cfg.domainSocket == defaultSock) "0750";
+          StateDirectory = "dspam";
+          StateDirectoryMode = "0750";
+          LogsDirectory = "dspam";
+          LogsDirectoryMode = "0750";
+          # DSPAM segfaults on just about every error
+          Restart = "on-abort";
+          RestartSec = "1s";
+        };
+      };
+    }
+
+    (mkIf (cfg.maintenanceInterval != null) {
+      systemd.timers.dspam-maintenance = {
+        description = "Timer for dspam maintenance script";
+        wantedBy = [ "timers.target" ];
+        timerConfig = {
+          OnCalendar = cfg.maintenanceInterval;
+          Unit = "dspam-maintenance.service";
+        };
+      };
+
+      systemd.services.dspam-maintenance = {
+        description = "dspam maintenance script";
+        restartTriggers = [ cfgfile ];
+
+        serviceConfig = {
+          ExecStart = "${dspam}/bin/dspam_maintenance --verbose";
+          Type = "oneshot";
+          User = cfg.user;
+          Group = cfg.group;
+        };
+      };
+    })
+  ]);
+}
diff --git a/nixpkgs/nixos/modules/services/mail/exim.nix b/nixpkgs/nixos/modules/services/mail/exim.nix
new file mode 100644
index 000000000000..892fbd33214a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/exim.nix
@@ -0,0 +1,120 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mkIf mkOption singleton types;
+  inherit (pkgs) coreutils;
+  cfg = config.services.exim;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.exim = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the Exim mail transfer agent.";
+      };
+
+      config = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Verbatim Exim configuration.  This should not contain exim_user,
+          exim_group, exim_path, or spool_directory.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "exim";
+        description = ''
+          User to use when no root privileges are required.
+          In particular, this applies when receiving messages and when doing
+          remote deliveries.  (Local deliveries run as various non-root users,
+          typically as the owner of a local mailbox.) Specifying this value
+          as root is not supported.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "exim";
+        description = ''
+          Group to use when no root privileges are required.
+        '';
+      };
+
+      spoolDir = mkOption {
+        type = types.path;
+        default = "/var/spool/exim";
+        description = ''
+          Location of the spool directory of exim.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.exim;
+        defaultText = "pkgs.exim";
+        description = ''
+          The Exim derivation to use.
+          This can be used to enable features such as LDAP or PAM support.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment = {
+      etc."exim.conf".text = ''
+        exim_user = ${cfg.user}
+        exim_group = ${cfg.group}
+        exim_path = /run/wrappers/bin/exim
+        spool_directory = ${cfg.spoolDir}
+        ${cfg.config}
+      '';
+      systemPackages = [ cfg.package ];
+    };
+
+    users.users.${cfg.user} = {
+      description = "Exim mail transfer agent user";
+      uid = config.ids.uids.exim;
+      group = cfg.group;
+    };
+
+    users.groups.${cfg.group} = {
+      gid = config.ids.gids.exim;
+    };
+
+    security.wrappers.exim.source = "${cfg.package}/bin/exim";
+
+    systemd.services.exim = {
+      description = "Exim Mail Daemon";
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ config.environment.etc."exim.conf".source ];
+      serviceConfig = {
+        ExecStart   = "${cfg.package}/bin/exim -bdf -q30m";
+        ExecReload  = "${coreutils}/bin/kill -HUP $MAINPID";
+      };
+      preStart = ''
+        if ! test -d ${cfg.spoolDir}; then
+          ${coreutils}/bin/mkdir -p ${cfg.spoolDir}
+          ${coreutils}/bin/chown ${cfg.user}:${cfg.group} ${cfg.spoolDir}
+        fi
+      '';
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/freepops.nix b/nixpkgs/nixos/modules/services/mail/freepops.nix
new file mode 100644
index 000000000000..5b729ca50a5e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/freepops.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mail.freepopsd;
+in
+
+{
+  options = {
+    services.mail.freepopsd = {
+      enable = mkOption {
+        default = false;
+        type = with types; bool;
+        description = ''
+          Enables Freepops, a POP3 webmail wrapper.
+        '';
+      };
+
+      port = mkOption {
+        default = 2000;
+        type = with types; uniq int;
+        description = ''
+          Port on which the pop server will listen.
+        '';
+      };
+
+      threads = mkOption {
+        default = 5;
+        type = with types; uniq int;
+        description = ''
+          Max simultaneous connections.
+        '';
+      };
+
+      bind = mkOption {
+        default = "0.0.0.0";
+        type = types.str;
+        description = ''
+          Bind over an IPv4 address instead of any.
+        '';
+      };
+
+      logFile = mkOption {
+        default = "/var/log/freepopsd";
+        example = "syslog";
+        type = types.str;
+        description = ''
+          Filename of the log file or syslog to rely on the logging daemon.
+        '';
+      };
+
+      suid = {
+        user = mkOption {
+          default = "nobody";
+          type = types.str;
+          description = ''
+            User name under which freepopsd will be after binding the port.
+          '';
+        };
+
+        group = mkOption {
+          default = "nogroup";
+          type = types.str;
+          description = ''
+            Group under which freepopsd will be after binding the port.
+          '';
+        };
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.freepopsd = {
+      description = "Freepopsd (webmail over POP3)";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        ${pkgs.freepops}/bin/freepopsd \
+          -p ${toString cfg.port} \
+          -t ${toString cfg.threads} \
+          -b ${cfg.bind} \
+          -vv -l ${cfg.logFile} \
+          -s ${cfg.suid.user}.${cfg.suid.group}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mail.nix b/nixpkgs/nixos/modules/services/mail/mail.nix
new file mode 100644
index 000000000000..fed313e4738e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mail.nix
@@ -0,0 +1,33 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.mail = {
+
+      sendmailSetuidWrapper = mkOption {
+        default = null;
+        internal = true;
+        description = ''
+          Configuration for the sendmail setuid wapper.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf (config.services.mail.sendmailSetuidWrapper != null) {
+
+    security.wrappers.sendmail = config.services.mail.sendmailSetuidWrapper;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mailcatcher.nix b/nixpkgs/nixos/modules/services/mail/mailcatcher.nix
new file mode 100644
index 000000000000..84f06ed199dc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mailcatcher.nix
@@ -0,0 +1,68 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.mailcatcher;
+
+  inherit (lib) mkEnableOption mkIf mkOption types optionalString;
+in
+{
+  # interface
+
+  options = {
+
+    services.mailcatcher = {
+      enable = mkEnableOption "MailCatcher";
+
+      http.ip = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "The ip address of the http server.";
+      };
+
+      http.port = mkOption {
+        type = types.port;
+        default = 1080;
+        description = "The port address of the http server.";
+      };
+
+      http.path = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "Prefix to all HTTP paths.";
+        example = "/mailcatcher";
+      };
+
+      smtp.ip = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "The ip address of the smtp server.";
+      };
+
+      smtp.port = mkOption {
+        type = types.port;
+        default = 1025;
+        description = "The port address of the smtp server.";
+      };
+    };
+
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.mailcatcher ];
+
+    systemd.services.mailcatcher = {
+      description = "MailCatcher Service";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        Restart = "always";
+        ExecStart = "${pkgs.mailcatcher}/bin/mailcatcher --foreground --no-quit --http-ip ${cfg.http.ip} --http-port ${toString cfg.http.port} --smtp-ip ${cfg.smtp.ip} --smtp-port ${toString cfg.smtp.port}" + optionalString (cfg.http.path != null) " --http-path ${cfg.http.path}";
+        AmbientCapabilities = optionalString (cfg.http.port < 1024 || cfg.smtp.port < 1024) "cap_net_bind_service";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mailhog.nix b/nixpkgs/nixos/modules/services/mail/mailhog.nix
new file mode 100644
index 000000000000..0f998c6d0ea6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mailhog.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mailhog;
+in {
+  ###### interface
+
+  options = {
+
+    services.mailhog = {
+      enable = mkEnableOption "MailHog";
+      user = mkOption {
+        type = types.str;
+        default = "mailhog";
+        description = "User account under which mailhog runs.";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.mailhog = {
+      name = cfg.user;
+      description = "MailHog service user";
+      isSystemUser = true;
+    };
+
+    systemd.services.mailhog = {
+      description = "MailHog service";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.mailhog}/bin/MailHog";
+        User = cfg.user;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mailman.nix b/nixpkgs/nixos/modules/services/mail/mailman.nix
new file mode 100644
index 000000000000..011e3db63954
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mailman.nix
@@ -0,0 +1,400 @@
+{ config, pkgs, lib, ... }:          # mailman.nix
+
+with lib;
+
+let
+
+  cfg = config.services.mailman;
+
+  # This deliberately doesn't use recursiveUpdate so users can
+  # override the defaults.
+  settings = {
+    DEFAULT_FROM_EMAIL = cfg.siteOwner;
+    SERVER_EMAIL = cfg.siteOwner;
+    ALLOWED_HOSTS = [ "localhost" "127.0.0.1" ] ++ cfg.webHosts;
+    COMPRESS_OFFLINE = true;
+    STATIC_ROOT = "/var/lib/mailman-web/static";
+    MEDIA_ROOT = "/var/lib/mailman-web/media";
+  } // cfg.webSettings;
+
+  settingsJSON = pkgs.writeText "settings.json" (builtins.toJSON settings);
+
+  mailmanCfg = ''
+    [mailman]
+    site_owner: ${cfg.siteOwner}
+    layout: fhs
+
+    [paths.fhs]
+    bin_dir: ${pkgs.python3Packages.mailman}/bin
+    var_dir: /var/lib/mailman
+    queue_dir: $var_dir/queue
+    template_dir: $var_dir/templates
+    log_dir: $var_dir/log
+    lock_dir: $var_dir/lock
+    etc_dir: /etc
+    ext_dir: $etc_dir/mailman.d
+    pid_file: /run/mailman/master.pid
+  '' + optionalString cfg.hyperkitty.enable ''
+
+    [archiver.hyperkitty]
+    class: mailman_hyperkitty.Archiver
+    enable: yes
+    configuration: /var/lib/mailman/mailman-hyperkitty.cfg
+  '' + cfg.extraConfig;
+
+  mailmanHyperkittyCfg = pkgs.writeText "mailman-hyperkitty.cfg" ''
+    [general]
+    # This is your HyperKitty installation, preferably on the localhost. This
+    # address will be used by Mailman to forward incoming emails to HyperKitty
+    # for archiving. It does not need to be publicly available, in fact it's
+    # better if it is not.
+    base_url: ${cfg.hyperkitty.baseUrl}
+
+    # Shared API key, must be the identical to the value in HyperKitty's
+    # settings.
+    api_key: @API_KEY@
+  '';
+
+in {
+
+  ###### interface
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "mailman" "hyperkittyBaseUrl" ]
+      [ "services" "mailman" "hyperkitty" "baseUrl" ])
+
+    (mkRemovedOptionModule [ "services" "mailman" "hyperkittyApiKey" ] ''
+      The Hyperkitty API key is now generated on first run, and not
+      stored in the world-readable Nix store.  To continue using
+      Hyperkitty, you must set services.mailman.hyperkitty.enable = true.
+    '')
+  ];
+
+  options = {
+
+    services.mailman = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable Mailman on this host. Requires an active Postfix installation.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.mailman;
+        defaultText = "pkgs.mailman";
+        example = "pkgs.mailman.override { archivers = []; }";
+        description = "Mailman package to use";
+      };
+
+      siteOwner = mkOption {
+        type = types.str;
+        example = "postmaster@example.org";
+        description = ''
+          Certain messages that must be delivered to a human, but which can't
+          be delivered to a list owner (e.g. a bounce from a list owner), will
+          be sent to this address. It should point to a human.
+        '';
+      };
+
+      webRoot = mkOption {
+        type = types.path;
+        default = "${pkgs.mailman-web}/${pkgs.python3.sitePackages}";
+        defaultText = "\${pkgs.mailman-web}/\${pkgs.python3.sitePackages}";
+        description = ''
+          The web root for the Hyperkity + Postorius apps provided by Mailman.
+          This variable can be set, of course, but it mainly exists so that site
+          admins can refer to it in their own hand-written web server
+          configuration files.
+        '';
+      };
+
+      webHosts = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          The list of hostnames and/or IP addresses from which the Mailman Web
+          UI will accept requests. By default, "localhost" and "127.0.0.1" are
+          enabled. All additional names under which your web server accepts
+          requests for the UI must be listed here or incoming requests will be
+          rejected.
+        '';
+      };
+
+      webUser = mkOption {
+        type = types.str;
+        default = config.services.httpd.user;
+        description = ''
+          User to run mailman-web as
+        '';
+      };
+
+      webSettings = mkOption {
+        type = types.attrs;
+        default = {};
+        description = ''
+          Overrides for the default mailman-web Django settings.
+        '';
+      };
+
+      hyperkitty = {
+        enable = mkEnableOption "the Hyperkitty archiver for Mailman";
+
+        baseUrl = mkOption {
+          type = types.str;
+          default = "http://localhost/hyperkitty/";
+          description = ''
+            Where can Mailman connect to Hyperkitty's internal API, preferably on
+            localhost?
+          '';
+        };
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra lines for the mailman configuration file";
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = let
+      inherit (config.services) postfix;
+
+      requirePostfixHash = optionPath: dataFile:
+        with lib;
+        let
+          expected = "hash:/var/lib/mailman/data/${dataFile}";
+          value = attrByPath optionPath [] postfix;
+        in
+          { assertion = postfix.enable -> isList value && elem expected value;
+            message = ''
+              services.postfix.${concatStringsSep "." optionPath} must contain
+              "${expected}".
+              See <https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html>.
+            '';
+          };
+    in [
+      { assertion = postfix.enable;
+        message = "Mailman requires Postfix";
+      }
+      (requirePostfixHash [ "relayDomains" ] "postfix_domains")
+      (requirePostfixHash [ "config" "transport_maps" ] "postfix_lmtp")
+      (requirePostfixHash [ "config" "local_recipient_maps" ] "postfix_lmtp")
+    ];
+
+    users.users.mailman = { description = "GNU Mailman"; isSystemUser = true; };
+
+    environment.etc."mailman3/mailman.cfg".text = mailmanCfg;
+
+    environment.etc."mailman3/settings.py".text = ''
+      import os
+
+      # Required by mailman_web.settings, but will be overridden when
+      # settings_local.json is loaded.
+      os.environ["SECRET_KEY"] = ""
+
+      from mailman_web.settings import *
+
+      import json
+
+      with open('${settingsJSON}') as f:
+          globals().update(json.load(f))
+
+      with open('/var/lib/mailman-web/settings_local.json') as f:
+          globals().update(json.load(f))
+    '';
+
+    environment.systemPackages = [ cfg.package ] ++ (with pkgs; [ mailman-web ]);
+
+    services.postfix = {
+      recipientDelimiter = "+";         # bake recipient addresses in mail envelopes via VERP
+      config = {
+        owner_request_special = "no";   # Mailman handles -owner addresses on its own
+      };
+    };
+
+    systemd.services.mailman = {
+      description = "GNU Mailman Master Process";
+      after = [ "network.target" ];
+      restartTriggers = [ config.environment.etc."mailman3/mailman.cfg".source ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/mailman start";
+        ExecStop = "${cfg.package}/bin/mailman stop";
+        User = "mailman";
+        Type = "forking";
+        RuntimeDirectory = "mailman";
+        PIDFile = "/run/mailman/master.pid";
+      };
+    };
+
+    systemd.services.mailman-settings = {
+      description = "Generate settings files (including secrets) for Mailman";
+      before = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ];
+      requiredBy = [ "mailman.service" "mailman-web.service" "hyperkitty.service" "httpd.service" "uwsgi.service" ];
+      path = with pkgs; [ jq ];
+      script = ''
+        mailmanDir=/var/lib/mailman
+        mailmanWebDir=/var/lib/mailman-web
+
+        mailmanCfg=$mailmanDir/mailman-hyperkitty.cfg
+        mailmanWebCfg=$mailmanWebDir/settings_local.json
+
+        install -m 0700 -o mailman -g nogroup -d $mailmanDir
+        install -m 0700 -o ${cfg.webUser} -g nogroup -d $mailmanWebDir
+
+        if [ ! -e $mailmanWebCfg ]; then
+            hyperkittyApiKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
+            secretKey=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 64)
+
+            mailmanWebCfgTmp=$(mktemp)
+            jq -n '.MAILMAN_ARCHIVER_KEY=$archiver_key | .SECRET_KEY=$secret_key' \
+                --arg archiver_key "$hyperkittyApiKey" \
+                --arg secret_key "$secretKey" \
+                >"$mailmanWebCfgTmp"
+            chown ${cfg.webUser} "$mailmanWebCfgTmp"
+            mv -n "$mailmanWebCfgTmp" $mailmanWebCfg
+        fi
+
+        hyperkittyApiKey="$(jq -r .MAILMAN_ARCHIVER_KEY $mailmanWebCfg)"
+        mailmanCfgTmp=$(mktemp)
+        sed "s/@API_KEY@/$hyperkittyApiKey/g" ${mailmanHyperkittyCfg} >"$mailmanCfgTmp"
+        chown mailman "$mailmanCfgTmp"
+        mv "$mailmanCfgTmp" $mailmanCfg
+      '';
+      serviceConfig = {
+        Type = "oneshot";
+        # RemainAfterExit makes restartIfChanged work for this service, so
+        # downstream services will get updated automatically when things like
+        # services.mailman.hyperkitty.baseUrl change.  Otherwise users have to
+        # restart things manually, which is confusing.
+        RemainAfterExit = "yes";
+      };
+    };
+
+    systemd.services.mailman-web = {
+      description = "Init Postorius DB";
+      before = [ "httpd.service" "uwsgi.service" ];
+      requiredBy = [ "httpd.service" "uwsgi.service" ];
+      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+      script = ''
+        ${pkgs.mailman-web}/bin/mailman-web migrate
+        rm -rf static
+        ${pkgs.mailman-web}/bin/mailman-web collectstatic
+        ${pkgs.mailman-web}/bin/mailman-web compress
+      '';
+      serviceConfig = {
+        User = cfg.webUser;
+        Type = "oneshot";
+        # Similar to mailman-settings.service, this makes restartTriggers work
+        # properly for this service.
+        RemainAfterExit = "yes";
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.mailman-daily = {
+      description = "Trigger daily Mailman events";
+      startAt = "daily";
+      restartTriggers = [ config.environment.etc."mailman3/mailman.cfg".source ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/mailman digests --send";
+        User = "mailman";
+      };
+    };
+
+    systemd.services.hyperkitty = {
+      inherit (cfg.hyperkitty) enable;
+      description = "GNU Hyperkitty QCluster Process";
+      after = [ "network.target" ];
+      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+      wantedBy = [ "mailman.service" "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.mailman-web}/bin/mailman-web qcluster";
+        User = cfg.webUser;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-minutely = {
+      inherit (cfg.hyperkitty) enable;
+      description = "Trigger minutely Hyperkitty events";
+      startAt = "minutely";
+      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+      serviceConfig = {
+        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs minutely";
+        User = cfg.webUser;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-quarter-hourly = {
+      inherit (cfg.hyperkitty) enable;
+      description = "Trigger quarter-hourly Hyperkitty events";
+      startAt = "*:00/15";
+      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+      serviceConfig = {
+        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs quarter_hourly";
+        User = cfg.webUser;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-hourly = {
+      inherit (cfg.hyperkitty) enable;
+      description = "Trigger hourly Hyperkitty events";
+      startAt = "hourly";
+      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+      serviceConfig = {
+        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs hourly";
+        User = cfg.webUser;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-daily = {
+      inherit (cfg.hyperkitty) enable;
+      description = "Trigger daily Hyperkitty events";
+      startAt = "daily";
+      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+      serviceConfig = {
+        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs daily";
+        User = cfg.webUser;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-weekly = {
+      inherit (cfg.hyperkitty) enable;
+      description = "Trigger weekly Hyperkitty events";
+      startAt = "weekly";
+      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+      serviceConfig = {
+        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs weekly";
+        User = cfg.webUser;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-yearly = {
+      inherit (cfg.hyperkitty) enable;
+      description = "Trigger yearly Hyperkitty events";
+      startAt = "yearly";
+      restartTriggers = [ config.environment.etc."mailman3/settings.py".source ];
+      serviceConfig = {
+        ExecStart = "${pkgs.mailman-web}/bin/mailman-web runjobs yearly";
+        User = cfg.webUser;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/mlmmj.nix b/nixpkgs/nixos/modules/services/mail/mlmmj.nix
new file mode 100644
index 000000000000..d58d93c4214c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/mlmmj.nix
@@ -0,0 +1,154 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  concatMapLines = f: l: lib.concatStringsSep "\n" (map f l);
+
+  cfg = config.services.mlmmj;
+  stateDir = "/var/lib/mlmmj";
+  spoolDir = "/var/spool/mlmmj";
+  listDir = domain: list: "${spoolDir}/${domain}/${list}";
+  listCtl = domain: list: "${listDir domain list}/control";
+  transport = domain: list: "${domain}--${list}@local.list.mlmmj mlmmj:${domain}/${list}";
+  virtual = domain: list: "${list}@${domain} ${domain}--${list}@local.list.mlmmj";
+  alias = domain: list: "${list}: \"|${pkgs.mlmmj}/bin/mlmmj-receive -L ${listDir domain list}/\"";
+  subjectPrefix = list: "[${list}]";
+  listAddress = domain: list: "${list}@${domain}";
+  customHeaders = domain: list: [ "List-Id: ${list}" "Reply-To: ${list}@${domain}" ];
+  footer = domain: list: "To unsubscribe send a mail to ${list}+unsubscribe@${domain}";
+  createList = d: l:
+    let ctlDir = listCtl d l; in
+    ''
+      for DIR in incoming queue queue/discarded archive text subconf unsubconf \
+                 bounce control moderation subscribers.d digesters.d requeue \
+                 nomailsubs.d
+      do
+             mkdir -p '${listDir d l}'/"$DIR"
+      done
+      ${pkgs.coreutils}/bin/mkdir -p ${ctlDir}
+      echo ${listAddress d l} > '${ctlDir}/listaddress'
+      [ ! -e ${ctlDir}/customheaders ] && \
+          echo "${lib.concatStringsSep "\n" (customHeaders d l)}" > '${ctlDir}/customheaders'
+      [ ! -e ${ctlDir}/footer ] && \
+          echo ${footer d l} > '${ctlDir}/footer'
+      [ ! -e ${ctlDir}/prefix ] && \
+          echo ${subjectPrefix l} > '${ctlDir}/prefix'
+    '';
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.mlmmj = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable mlmmj";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "mlmmj";
+        description = "mailinglist local user";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "mlmmj";
+        description = "mailinglist local group";
+      };
+
+      listDomain = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Set the mailing list domain";
+      };
+
+      mailLists = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "The collection of hosted maillists";
+      };
+
+      maintInterval = mkOption {
+        type = types.str;
+        default = "20min";
+        description = ''
+          Time interval between mlmmj-maintd runs, see
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry> for format information.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.${cfg.user} = {
+      description = "mlmmj user";
+      home = stateDir;
+      createHome = true;
+      uid = config.ids.uids.mlmmj;
+      group = cfg.group;
+      useDefaultShell = true;
+    };
+
+    users.groups.${cfg.group} = {
+      gid = config.ids.gids.mlmmj;
+    };
+
+    services.postfix = {
+      enable = true;
+      recipientDelimiter= "+";
+      extraMasterConf = ''
+        mlmmj unix - n n - - pipe flags=ORhu user=mlmmj argv=${pkgs.mlmmj}/bin/mlmmj-receive -F -L ${spoolDir}/$nexthop
+      '';
+
+      extraAliases = concatMapLines (alias cfg.listDomain) cfg.mailLists;
+
+      extraConfig = ''
+        transport_maps = hash:${stateDir}/transports
+        virtual_alias_maps = hash:${stateDir}/virtuals
+        propagate_unmatched_extensions = virtual
+      '';
+    };
+
+    environment.systemPackages = [ pkgs.mlmmj ];
+
+    system.activationScripts.mlmmj = ''
+          ${pkgs.coreutils}/bin/mkdir -p ${stateDir} ${spoolDir}/${cfg.listDomain}
+          ${pkgs.coreutils}/bin/chown -R ${cfg.user}:${cfg.group} ${spoolDir}
+          ${concatMapLines (createList cfg.listDomain) cfg.mailLists}
+          echo "${concatMapLines (virtual cfg.listDomain) cfg.mailLists}" > ${stateDir}/virtuals
+          echo "${concatMapLines (transport cfg.listDomain) cfg.mailLists}" > ${stateDir}/transports
+          ${pkgs.postfix}/bin/postmap ${stateDir}/virtuals
+          ${pkgs.postfix}/bin/postmap ${stateDir}/transports
+      '';
+
+    systemd.services.mlmmj-maintd = {
+      description = "mlmmj maintenance daemon";
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${pkgs.mlmmj}/bin/mlmmj-maintd -F -d ${spoolDir}/${cfg.listDomain}";
+      };
+    };
+
+    systemd.timers.mlmmj-maintd = {
+      description = "mlmmj maintenance timer";
+      timerConfig.OnUnitActiveSec = cfg.maintInterval;
+      wantedBy = [ "timers.target" ];
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/nullmailer.nix b/nixpkgs/nixos/modules/services/mail/nullmailer.nix
new file mode 100644
index 000000000000..fe3f8ef9b391
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/nullmailer.nix
@@ -0,0 +1,243 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  options = {
+
+    services.nullmailer = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable nullmailer daemon.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "nullmailer";
+        description = ''
+          User to use to run nullmailer-send.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nullmailer";
+        description = ''
+          Group to use to run nullmailer-send.
+        '';
+      };
+
+      setSendmail = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether to set the system sendmail to nullmailer's.";
+      };
+
+      remotesFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Path to the <code>remotes</code> control file. This file contains a
+          list of remote servers to which to send each message.
+
+          See <code>man 8 nullmailer-send</code> for syntax and available
+          options.
+        '';
+      };
+
+      config = {
+        adminaddr = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            If set, all recipients to users at either "localhost" (the literal string)
+            or the canonical host name (from the me control attribute) are remapped to this address.
+            This is provided to allow local daemons to be able to send email to
+            "somebody@localhost" and have it go somewhere sensible instead of being  bounced
+            by your relay host. To send to multiple addresses,
+            put them all on one line separated by a comma.
+          '';
+        };
+
+        allmailfrom = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            If set, content will override the envelope sender on all messages.
+          '';
+        };
+
+        defaultdomain = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+             The content of this attribute is appended to any host name that
+             does not contain a period (except localhost), including defaulthost
+             and idhost. Defaults to the value of the me attribute, if it exists,
+             otherwise the literal name defauldomain.
+          '';
+        };
+
+        defaulthost = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+             The content of this attribute is appended to any address that
+             is missing a host name. Defaults to the value of the me control
+             attribute, if it exists, otherwise the literal name defaulthost.
+          '';
+        };
+
+        doublebounceto = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            If the original sender was empty (the original message was a
+            delivery status or disposition notification), the double bounce
+            is sent to the address in this attribute.
+          '';
+        };
+
+        helohost = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Sets  the  environment variable $HELOHOST which is used by the
+            SMTP protocol module to set the parameter given to the HELO command.
+            Defaults to the value of the me configuration attribute.
+          '';
+        };
+
+        idhost = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            The content of this attribute is used when building the message-id
+            string for the message. Defaults to the canonicalized value of defaulthost.
+          '';
+        };
+
+        maxpause = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+             The maximum time to pause between successive queue runs, in seconds.
+             Defaults to 24 hours (86400).
+          '';
+        };
+
+        me = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+             The fully-qualifiled host name of the computer running nullmailer.
+             Defaults to the literal name me.
+          '';
+        };
+
+        pausetime = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            The minimum time to pause between successive queue runs when there
+            are messages in the queue, in seconds. Defaults to 1 minute (60).
+            Each time this timeout is reached, the timeout is doubled to a
+            maximum of maxpause. After new messages are injected, the timeout
+            is reset.  If this is set to 0, nullmailer-send will exit
+            immediately after going through the queue once (one-shot mode).
+          '';
+        };
+
+        remotes = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            A list of remote servers to which to send each message. Each line
+            contains a remote host name or address followed by an optional
+            protocol string, separated by white space.
+
+            See <code>man 8 nullmailer-send</code> for syntax and available
+            options.
+
+            WARNING: This is stored world-readable in the nix store. If you need
+            to specify any secret credentials here, consider using the
+            <code>remotesFile</code> option instead.
+          '';
+        };
+
+        sendtimeout = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            The  time to wait for a remote module listed above to complete sending
+            a message before killing it and trying again, in seconds.
+            Defaults to 1 hour (3600).  If this is set to 0, nullmailer-send
+            will wait forever for messages to complete sending.
+          '';
+        };
+      };
+    };
+  };
+
+  config = let
+    cfg = config.services.nullmailer;
+  in mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.config.remotes == null || cfg.remotesFile == null;
+        message = "Only one of `remotesFile` or `config.remotes` may be used at a time.";
+      }
+    ];
+
+    environment = {
+      systemPackages = [ pkgs.nullmailer ];
+      etc = let
+        validAttrs = filterAttrs (name: value: value != null) cfg.config;
+      in
+        (foldl' (as: name: as // { "nullmailer/${name}".text = validAttrs.${name}; }) {} (attrNames validAttrs))
+          // optionalAttrs (cfg.remotesFile != null) { "nullmailer/remotes".source = cfg.remotesFile; };
+    };
+
+    users = {
+      users.${cfg.user} = {
+        description = "Nullmailer relay-only mta user";
+        group = cfg.group;
+      };
+
+      groups.${cfg.group} = { };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d /var/spool/nullmailer - ${cfg.user} - - -"
+    ];
+
+    systemd.services.nullmailer = {
+      description = "nullmailer";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        mkdir -p /var/spool/nullmailer/{queue,tmp}
+        rm -f /var/spool/nullmailer/trigger && mkfifo -m 660 /var/spool/nullmailer/trigger
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${pkgs.nullmailer}/bin/nullmailer-send";
+        Restart = "always";
+      };
+    };
+
+    services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail {
+      program = "sendmail";
+      source = "${pkgs.nullmailer}/bin/sendmail";
+      owner = cfg.user;
+      group = cfg.group;
+      setuid = true;
+      setgid = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/offlineimap.nix b/nixpkgs/nixos/modules/services/mail/offlineimap.nix
new file mode 100644
index 000000000000..294e3806f94a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/offlineimap.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.offlineimap;
+in {
+
+  options.services.offlineimap = {
+    enable = mkEnableOption "OfflineIMAP, a software to dispose your mailbox(es) as a local Maildir(s)";
+
+    install = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to install a user service for Offlineimap. Once
+        the service is started, emails will be fetched automatically.
+
+        The service must be manually started for each user with
+        "systemctl --user start offlineimap" or globally through
+        <varname>services.offlineimap.enable</varname>.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.offlineimap;
+      defaultText = "pkgs.offlineimap";
+      description = "Offlineimap derivation to use.";
+    };
+
+    path = mkOption {
+      type = types.listOf types.path;
+      default = [];
+      example = literalExample "[ pkgs.pass pkgs.bash pkgs.notmuch ]";
+      description = "List of derivations to put in Offlineimap's path.";
+    };
+
+    onCalendar = mkOption {
+      type = types.str;
+      default = "*:0/3"; # every 3 minutes
+      description = "How often is offlineimap started. Default is '*:0/3' meaning every 3 minutes. See systemd.time(7) for more information about the format.";
+    };
+
+    timeoutStartSec = mkOption {
+      type = types.str;
+      default = "120sec"; # Kill if still alive after 2 minutes
+      description = "How long waiting for offlineimap before killing it. Default is '120sec' meaning every 2 minutes. See systemd.time(7) for more information about the format.";
+    };
+  };
+  config = mkIf (cfg.enable || cfg.install) {
+    systemd.user.services.offlineimap = {
+      description = "Offlineimap: a software to dispose your mailbox(es) as a local Maildir(s)";
+      serviceConfig = {
+        Type      = "oneshot";
+        ExecStart = "${cfg.package}/bin/offlineimap -u syslog -o -1";
+        TimeoutStartSec = cfg.timeoutStartSec;
+      };
+      path = cfg.path;
+    };
+    environment.systemPackages = [ cfg.package ];
+    systemd.user.timers.offlineimap = {
+      description = "offlineimap timer";
+      timerConfig               = {
+        Unit = "offlineimap.service";
+        OnCalendar = cfg.onCalendar;
+        # start immediately after computer is started:
+        Persistent = "true";
+      };
+    } // optionalAttrs cfg.enable { wantedBy = [ "default.target" ]; };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/opendkim.nix b/nixpkgs/nixos/modules/services/mail/opendkim.nix
new file mode 100644
index 000000000000..eb6a426684d4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/opendkim.nix
@@ -0,0 +1,136 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.opendkim;
+
+  defaultSock = "local:/run/opendkim/opendkim.sock";
+
+  keyFile = "${cfg.keyPath}/${cfg.selector}.private";
+
+  args = [ "-f" "-l"
+           "-p" cfg.socket
+           "-d" cfg.domains
+           "-k" keyFile
+           "-s" cfg.selector
+         ] ++ optionals (cfg.configFile != null) [ "-x" cfg.configFile ];
+
+in {
+  imports = [
+    (mkRenamedOptionModule [ "services" "opendkim" "keyFile" ] [ "services" "opendkim" "keyPath" ])
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.opendkim = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the OpenDKIM sender authentication system.";
+      };
+
+      socket = mkOption {
+        type = types.str;
+        default = defaultSock;
+        description = "Socket which is used for communication with OpenDKIM.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "opendkim";
+        description = "User for the daemon.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "opendkim";
+        description = "Group for the daemon.";
+      };
+
+      domains = mkOption {
+        type = types.str;
+        default = "csl:${config.networking.hostName}";
+        example = "csl:example.com,mydomain.net";
+        description = ''
+          Local domains set (see <literal>opendkim(8)</literal> for more information on datasets).
+          Messages from them are signed, not verified.
+        '';
+      };
+
+      keyPath = mkOption {
+        type = types.path;
+        description = ''
+          The path that opendkim should put its generated private keys into.
+          The DNS settings will be found in this directory with the name selector.txt.
+        '';
+        default = "/var/lib/opendkim/keys";
+      };
+
+      selector = mkOption {
+        type = types.str;
+        description = "Selector to use when signing.";
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "Additional opendkim configuration.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = optionalAttrs (cfg.user == "opendkim") {
+      opendkim = {
+        group = cfg.group;
+        uid = config.ids.uids.opendkim;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "opendkim") {
+      opendkim.gid = config.ids.gids.opendkim;
+    };
+
+    environment.systemPackages = [ pkgs.opendkim ];
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.keyPath}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.opendkim = {
+      description = "OpenDKIM signing and verification daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        cd "${cfg.keyPath}"
+        if ! test -f ${cfg.selector}.private; then
+          ${pkgs.opendkim}/bin/opendkim-genkey -s ${cfg.selector} -d all-domains-generic-key
+          echo "Generated OpenDKIM key! Please update your DNS settings:\n"
+          echo "-------------------------------------------------------------"
+          cat ${cfg.selector}.txt
+          echo "-------------------------------------------------------------"
+        fi
+      '';
+
+      serviceConfig = {
+        ExecStart = "${pkgs.opendkim}/bin/opendkim ${escapeShellArgs args}";
+        User = cfg.user;
+        Group = cfg.group;
+        RuntimeDirectory = optional (cfg.socket == defaultSock) "opendkim";
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/opensmtpd.nix b/nixpkgs/nixos/modules/services/mail/opensmtpd.nix
new file mode 100644
index 000000000000..c838d3b949db
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/opensmtpd.nix
@@ -0,0 +1,132 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.opensmtpd;
+  conf = pkgs.writeText "smtpd.conf" cfg.serverConfiguration;
+  args = concatStringsSep " " cfg.extraServerArgs;
+
+  sendmail = pkgs.runCommand "opensmtpd-sendmail" { preferLocalBuild = true; } ''
+    mkdir -p $out/bin
+    ln -s ${cfg.package}/sbin/smtpctl $out/bin/sendmail
+  '';
+
+in {
+
+  ###### interface
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "opensmtpd" "addSendmailToSystemPath" ] [ "services" "opensmtpd" "setSendmail" ])
+  ];
+
+  options = {
+
+    services.opensmtpd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the OpenSMTPD server.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.opensmtpd;
+        defaultText = "pkgs.opensmtpd";
+        description = "The OpenSMTPD package to use.";
+      };
+
+      setSendmail = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether to set the system sendmail to OpenSMTPD's.";
+      };
+
+      extraServerArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "-v" "-P mta" ];
+        description = ''
+          Extra command line arguments provided when the smtpd process
+          is started.
+        '';
+      };
+
+      serverConfiguration = mkOption {
+        type = types.lines;
+        example = ''
+          listen on lo
+          accept for any deliver to lmtp localhost:24
+        '';
+        description = ''
+          The contents of the smtpd.conf configuration file. See the
+          OpenSMTPD documentation for syntax information.
+        '';
+      };
+
+      procPackages = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = ''
+          Packages to search for filters, tables, queues, and schedulers.
+
+          Add OpenSMTPD-extras here if you want to use the filters, etc. from
+          that package.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable rec {
+    users.groups = {
+      smtpd.gid = config.ids.gids.smtpd;
+      smtpq.gid = config.ids.gids.smtpq;
+    };
+
+    users.users = {
+      smtpd = {
+        description = "OpenSMTPD process user";
+        uid = config.ids.uids.smtpd;
+        group = "smtpd";
+      };
+      smtpq = {
+        description = "OpenSMTPD queue user";
+        uid = config.ids.uids.smtpq;
+        group = "smtpq";
+      };
+    };
+
+    security.wrappers.smtpctl = {
+      group = "smtpq";
+      setgid = true;
+      source = "${cfg.package}/bin/smtpctl";
+    };
+
+    services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail security.wrappers.smtpctl;
+
+    systemd.tmpfiles.rules = [
+      "d /var/spool/smtpd 711 root - - -"
+      "d /var/spool/smtpd/offline 770 root smtpq - -"
+      "d /var/spool/smtpd/purge 700 smtpq root - -"
+    ];
+
+    systemd.services.opensmtpd = let
+      procEnv = pkgs.buildEnv {
+        name = "opensmtpd-procs";
+        paths = [ cfg.package ] ++ cfg.procPackages;
+        pathsToLink = [ "/libexec/opensmtpd" ];
+      };
+    in {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig.ExecStart = "${cfg.package}/sbin/smtpd -d -f ${conf} ${args}";
+      environment.OPENSMTPD_PROC_PATH = "${procEnv}/libexec/opensmtpd";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix b/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
new file mode 100644
index 000000000000..38984f896d6a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/pfix-srsd.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.pfix-srsd = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to run the postfix sender rewriting scheme daemon.";
+      };
+
+      domain = mkOption {
+        description = "The domain for which to enable srs";
+        type = types.str;
+        example = "example.com";
+      };
+
+      secretsFile = mkOption {
+        description = ''
+          The secret data used to encode the SRS address.
+          to generate, use a command like:
+          <literal>for n in $(seq 5); do dd if=/dev/urandom count=1 bs=1024 status=none | sha256sum | sed 's/  -$//' | sed 's/^/          /'; done</literal>
+        '';
+        type = types.path;
+        default = "/var/lib/pfix-srsd/secrets";
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.pfix-srsd.enable {
+    environment = {
+      systemPackages = [ pkgs.pfixtools ];
+    };
+
+    systemd.services.pfix-srsd = {
+      description = "Postfix sender rewriting scheme daemon";
+      before = [ "postfix.service" ];
+      #note that we use requires rather than wants because postfix
+      #is unable to process (almost) all mail without srsd
+      requiredBy = [ "postfix.service" ];
+      serviceConfig = {
+        Type = "forking";
+        PIDFile = "/run/pfix-srsd.pid";
+        ExecStart = "${pkgs.pfixtools}/bin/pfix-srsd -p /run/pfix-srsd.pid -I ${config.services.pfix-srsd.domain} ${config.services.pfix-srsd.secretsFile}";
+      };
+    };
+  };
+}
\ No newline at end of file
diff --git a/nixpkgs/nixos/modules/services/mail/postfix.nix b/nixpkgs/nixos/modules/services/mail/postfix.nix
new file mode 100644
index 000000000000..608f64a68fb0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/postfix.nix
@@ -0,0 +1,903 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.postfix;
+  user = cfg.user;
+  group = cfg.group;
+  setgidGroup = cfg.setgidGroup;
+
+  haveAliases = cfg.postmasterAlias != "" || cfg.rootAlias != ""
+                      || cfg.extraAliases != "";
+  haveTransport = cfg.transport != "";
+  haveVirtual = cfg.virtual != "";
+  haveLocalRecipients = cfg.localRecipients != null;
+
+  clientAccess =
+    optional (cfg.dnsBlacklistOverrides != "")
+      "check_client_access hash:/etc/postfix/client_access";
+
+  dnsBl =
+    optionals (cfg.dnsBlacklists != [])
+      (map (s: "reject_rbl_client " + s) cfg.dnsBlacklists);
+
+  clientRestrictions = concatStringsSep ", " (clientAccess ++ dnsBl);
+
+  mainCf = let
+    escape = replaceStrings ["$"] ["$$"];
+    mkList = items: "\n  " + concatStringsSep ",\n  " items;
+    mkVal = value:
+      if isList value then mkList value
+        else " " + (if value == true then "yes"
+        else if value == false then "no"
+        else toString value);
+    mkEntry = name: value: "${escape name} =${mkVal value}";
+  in
+    concatStringsSep "\n" (mapAttrsToList mkEntry cfg.config)
+      + "\n" + cfg.extraConfig;
+
+  masterCfOptions = { options, config, name, ... }: {
+    options = {
+      name = mkOption {
+        type = types.str;
+        default = name;
+        example = "smtp";
+        description = ''
+          The name of the service to run. Defaults to the attribute set key.
+        '';
+      };
+
+      type = mkOption {
+        type = types.enum [ "inet" "unix" "fifo" "pass" ];
+        default = "unix";
+        example = "inet";
+        description = "The type of the service";
+      };
+
+      private = mkOption {
+        type = types.bool;
+        example = false;
+        description = ''
+          Whether the service's sockets and storage directory is restricted to
+          be only available via the mail system. If <literal>null</literal> is
+          given it uses the postfix default <literal>true</literal>.
+        '';
+      };
+
+      privileged = mkOption {
+        type = types.bool;
+        example = true;
+        description = "";
+      };
+
+      chroot = mkOption {
+        type = types.bool;
+        example = true;
+        description = ''
+          Whether the service is chrooted to have only access to the
+          <option>services.postfix.queueDir</option> and the closure of
+          store paths specified by the <option>program</option> option.
+        '';
+      };
+
+      wakeup = mkOption {
+        type = types.int;
+        example = 60;
+        description = ''
+          Automatically wake up the service after the specified number of
+          seconds. If <literal>0</literal> is given, never wake the service
+          up.
+        '';
+      };
+
+      wakeupUnusedComponent = mkOption {
+        type = types.bool;
+        example = false;
+        description = ''
+          If set to <literal>false</literal> the component will only be woken
+          up if it is used. This is equivalent to postfix' notion of adding a
+          question mark behind the wakeup time in
+          <filename>master.cf</filename>
+        '';
+      };
+
+      maxproc = mkOption {
+        type = types.int;
+        example = 1;
+        description = ''
+          The maximum number of processes to spawn for this service. If the
+          value is <literal>0</literal> it doesn't have any limit. If
+          <literal>null</literal> is given it uses the postfix default of
+          <literal>100</literal>.
+        '';
+      };
+
+      command = mkOption {
+        type = types.str;
+        default = name;
+        example = "smtpd";
+        description = ''
+          A program name specifying a Postfix service/daemon process.
+          By default it's the attribute <option>name</option>.
+        '';
+      };
+
+      args = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "-o" "smtp_helo_timeout=5" ];
+        description = ''
+          Arguments to pass to the <option>command</option>. There is no shell
+          processing involved and shell syntax is passed verbatim to the
+          process.
+        '';
+      };
+
+      rawEntry = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        internal = true;
+        description = ''
+          The raw configuration line for the <filename>master.cf</filename>.
+        '';
+      };
+    };
+
+    config.rawEntry = let
+      mkBool = bool: if bool then "y" else "n";
+      mkArg = arg: "${optionalString (hasPrefix "-" arg) "\n  "}${arg}";
+
+      maybeOption = fun: option:
+        if options.${option}.isDefined then fun config.${option} else "-";
+
+      # This is special, because we have two options for this value.
+      wakeup = let
+        wakeupDefined = options.wakeup.isDefined;
+        wakeupUCDefined = options.wakeupUnusedComponent.isDefined;
+        finalValue = toString config.wakeup
+                   + optionalString (wakeupUCDefined && !config.wakeupUnusedComponent) "?";
+      in if wakeupDefined then finalValue else "-";
+
+    in [
+      config.name
+      config.type
+      (maybeOption mkBool "private")
+      (maybeOption (b: mkBool (!b)) "privileged")
+      (maybeOption mkBool "chroot")
+      wakeup
+      (maybeOption toString "maxproc")
+      (config.command + " " + concatMapStringsSep " " mkArg config.args)
+    ];
+  };
+
+  masterCfContent = let
+
+    labels = [
+      "# service" "type" "private" "unpriv" "chroot" "wakeup" "maxproc"
+      "command + args"
+    ];
+
+    labelDefaults = [
+      "# " "" "(yes)" "(yes)" "(no)" "(never)" "(100)" "" ""
+    ];
+
+    masterCf = mapAttrsToList (const (getAttr "rawEntry")) cfg.masterConfig;
+
+    # A list of the maximum width of the columns across all lines and labels
+    maxWidths = let
+      foldLine = line: acc: let
+        columnLengths = map stringLength line;
+      in zipListsWith max acc columnLengths;
+      # We need to handle the last column specially here, because it's
+      # open-ended (command + args).
+      lines = [ labels labelDefaults ] ++ (map (l: init l ++ [""]) masterCf);
+    in fold foldLine (genList (const 0) (length labels)) lines;
+
+    # Pad a string with spaces from the right (opposite of fixedWidthString).
+    pad = width: str: let
+      padWidth = width - stringLength str;
+      padding = concatStrings (genList (const " ") padWidth);
+    in str + optionalString (padWidth > 0) padding;
+
+    # It's + 2 here, because that's the amount of spacing between columns.
+    fullWidth = fold (width: acc: acc + width + 2) 0 maxWidths;
+
+    formatLine = line: concatStringsSep "  " (zipListsWith pad maxWidths line);
+
+    formattedLabels = let
+      sep = "# " + concatStrings (genList (const "=") (fullWidth + 5));
+      lines = [ sep (formatLine labels) (formatLine labelDefaults) sep ];
+    in concatStringsSep "\n" lines;
+
+  in formattedLabels + "\n" + concatMapStringsSep "\n" formatLine masterCf + "\n" + cfg.extraMasterConf;
+
+  headerCheckOptions = { ... }:
+  {
+    options = {
+      pattern = mkOption {
+        type = types.str;
+        default = "/^.*/";
+        example = "/^X-Mailer:/";
+        description = "A regexp pattern matching the header";
+      };
+      action = mkOption {
+        type = types.str;
+        default = "DUNNO";
+        example = "BCC mail@example.com";
+        description = "The action to be executed when the pattern is matched";
+      };
+    };
+  };
+
+  headerChecks = concatStringsSep "\n" (map (x: "${x.pattern} ${x.action}") cfg.headerChecks) + cfg.extraHeaderChecks;
+
+  aliases = let seperator = if cfg.aliasMapType == "hash" then ":" else ""; in
+    optionalString (cfg.postmasterAlias != "") ''
+      postmaster${seperator} ${cfg.postmasterAlias}
+    ''
+    + optionalString (cfg.rootAlias != "") ''
+      root${seperator} ${cfg.rootAlias}
+    ''
+    + cfg.extraAliases
+  ;
+
+  aliasesFile = pkgs.writeText "postfix-aliases" aliases;
+  virtualFile = pkgs.writeText "postfix-virtual" cfg.virtual;
+  localRecipientMapFile = pkgs.writeText "postfix-local-recipient-map" (concatMapStrings (x: x + " ACCEPT\n") cfg.localRecipients);
+  checkClientAccessFile = pkgs.writeText "postfix-check-client-access" cfg.dnsBlacklistOverrides;
+  mainCfFile = pkgs.writeText "postfix-main.cf" mainCf;
+  masterCfFile = pkgs.writeText "postfix-master.cf" masterCfContent;
+  transportFile = pkgs.writeText "postfix-transport" cfg.transport;
+  headerChecksFile = pkgs.writeText "postfix-header-checks" headerChecks;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.postfix = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to run the Postfix mail server.";
+      };
+
+      enableSmtp = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether to enable smtp in master.cf.";
+      };
+
+      enableSubmission = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable smtp submission.";
+      };
+
+      submissionOptions = mkOption {
+        type = types.attrs;
+        default = {
+          smtpd_tls_security_level = "encrypt";
+          smtpd_sasl_auth_enable = "yes";
+          smtpd_client_restrictions = "permit_sasl_authenticated,reject";
+          milter_macro_daemon_name = "ORIGINATING";
+        };
+        example = {
+          smtpd_tls_security_level = "encrypt";
+          smtpd_sasl_auth_enable = "yes";
+          smtpd_sasl_type = "dovecot";
+          smtpd_client_restrictions = "permit_sasl_authenticated,reject";
+          milter_macro_daemon_name = "ORIGINATING";
+        };
+        description = "Options for the submission config in master.cf";
+      };
+
+      setSendmail = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether to set the system sendmail to postfix's.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "postfix";
+        description = "What to call the Postfix user (must be used only for postfix).";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "postfix";
+        description = "What to call the Postfix group (must be used only for postfix).";
+      };
+
+      setgidGroup = mkOption {
+        type = types.str;
+        default = "postdrop";
+        description = "
+          How to call postfix setgid group (for postdrop). Should
+          be uniquely used group.
+        ";
+      };
+
+      networks = mkOption {
+        type = types.nullOr (types.listOf types.str);
+        default = null;
+        example = ["192.168.0.1/24"];
+        description = "
+          Net masks for trusted - allowed to relay mail to third parties -
+          hosts. Leave empty to use mynetworks_style configuration or use
+          default (localhost-only).
+        ";
+      };
+
+      networksStyle = mkOption {
+        type = types.str;
+        default = "";
+        description = "
+          Name of standard way of trusted network specification to use,
+          leave blank if you specify it explicitly or if you want to use
+          default (localhost-only).
+        ";
+      };
+
+      hostname = mkOption {
+        type = types.str;
+        default = "";
+        description ="
+          Hostname to use. Leave blank to use just the hostname of machine.
+          It should be FQDN.
+        ";
+      };
+
+      domain = mkOption {
+        type = types.str;
+        default = "";
+        description ="
+          Domain to use. Leave blank to use hostname minus first component.
+        ";
+      };
+
+      origin = mkOption {
+        type = types.str;
+        default = "";
+        description ="
+          Origin to use in outgoing e-mail. Leave blank to use hostname.
+        ";
+      };
+
+      destination = mkOption {
+        type = types.nullOr (types.listOf types.str);
+        default = null;
+        example = ["localhost"];
+        description = "
+          Full (!) list of domains we deliver locally. Leave blank for
+          acceptable Postfix default.
+        ";
+      };
+
+      relayDomains = mkOption {
+        type = types.nullOr (types.listOf types.str);
+        default = null;
+        example = ["localdomain"];
+        description = "
+          List of domains we agree to relay to. Default is empty.
+        ";
+      };
+
+      relayHost = mkOption {
+        type = types.str;
+        default = "";
+        description = "
+          Mail relay for outbound mail.
+        ";
+      };
+
+      relayPort = mkOption {
+        type = types.int;
+        default = 25;
+        description = "
+          SMTP port for relay mail relay.
+        ";
+      };
+
+      lookupMX = mkOption {
+        type = types.bool;
+        default = false;
+        description = "
+          Whether relay specified is just domain whose MX must be used.
+        ";
+      };
+
+      postmasterAlias = mkOption {
+        type = types.str;
+        default = "root";
+        description = "
+          Who should receive postmaster e-mail. Multiple values can be added by
+          separating values with comma.
+        ";
+      };
+
+      rootAlias = mkOption {
+        type = types.str;
+        default = "";
+        description = "
+          Who should receive root e-mail. Blank for no redirection.
+          Multiple values can be added by separating values with comma.
+        ";
+      };
+
+      extraAliases = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Additional entries to put verbatim into aliases file, cf. man-page aliases(8).
+        ";
+      };
+
+      aliasMapType = mkOption {
+        type = with types; enum [ "hash" "regexp" "pcre" ];
+        default = "hash";
+        example = "regexp";
+        description = "The format the alias map should have. Use regexp if you want to use regular expressions.";
+      };
+
+      config = mkOption {
+        type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
+        description = ''
+          The main.cf configuration file as key value set.
+        '';
+        example = {
+          mail_owner = "postfix";
+          smtp_use_tls = true;
+        };
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Extra lines to be added verbatim to the main.cf configuration file.
+        ";
+      };
+
+      sslCert = mkOption {
+        type = types.str;
+        default = "";
+        description = "SSL certificate to use.";
+      };
+
+      sslCACert = mkOption {
+        type = types.str;
+        default = "";
+        description = "SSL certificate of CA.";
+      };
+
+      sslKey = mkOption {
+        type = types.str;
+        default = "";
+        description = "SSL key to use.";
+      };
+
+      recipientDelimiter = mkOption {
+        type = types.str;
+        default = "";
+        example = "+";
+        description = "
+          Delimiter for address extension: so mail to user+test can be handled by ~user/.forward+test
+        ";
+      };
+
+      virtual = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Entries for the virtual alias map, cf. man-page virtual(8).
+        ";
+      };
+
+      virtualMapType = mkOption {
+        type = types.enum ["hash" "regexp" "pcre"];
+        default = "hash";
+        description = ''
+          What type of virtual alias map file to use. Use <literal>"regexp"</literal> for regular expressions.
+        '';
+      };
+
+      localRecipients = mkOption {
+        type = with types; nullOr (listOf str);
+        default = null;
+        description = ''
+          List of accepted local users. Specify a bare username, an
+          <literal>"@domain.tld"</literal> wild-card, or a complete
+          <literal>"user@domain.tld"</literal> address. If set, these names end
+          up in the local recipient map -- see the local(8) man-page -- and
+          effectively replace the system user database lookup that's otherwise
+          used by default.
+        '';
+      };
+
+      transport = mkOption {
+        default = "";
+        description = "
+          Entries for the transport map, cf. man-page transport(8).
+        ";
+      };
+
+      dnsBlacklists = mkOption {
+        default = [];
+        type = with types; listOf str;
+        description = "dns blacklist servers to use with smtpd_client_restrictions";
+      };
+
+      dnsBlacklistOverrides = mkOption {
+        default = "";
+        description = "contents of check_client_access for overriding dnsBlacklists";
+      };
+
+      masterConfig = mkOption {
+        type = types.attrsOf (types.submodule masterCfOptions);
+        default = {};
+        example =
+          { submission = {
+              type = "inet";
+              args = [ "-o" "smtpd_tls_security_level=encrypt" ];
+            };
+          };
+        description = ''
+          An attribute set of service options, which correspond to the service
+          definitions usually done within the Postfix
+          <filename>master.cf</filename> file.
+        '';
+      };
+
+      extraMasterConf = mkOption {
+        type = types.lines;
+        default = "";
+        example = "submission inet n - n - - smtpd";
+        description = "Extra lines to append to the generated master.cf file.";
+      };
+
+      enableHeaderChecks = mkOption {
+        type = types.bool;
+        default = false;
+        example = true;
+        description = "Whether to enable postfix header checks";
+      };
+
+      headerChecks = mkOption {
+        type = types.listOf (types.submodule headerCheckOptions);
+        default = [];
+        example = [ { pattern = "/^X-Spam-Flag:/"; action = "REDIRECT spam@example.com"; } ];
+        description = "Postfix header checks.";
+      };
+
+      extraHeaderChecks = mkOption {
+        type = types.lines;
+        default = "";
+        example = "/^X-Spam-Flag:/ REDIRECT spam@example.com";
+        description = "Extra lines to /etc/postfix/header_checks file.";
+      };
+
+      aliasFiles = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        description = "Aliases' tables to be compiled and placed into /var/lib/postfix/conf.";
+      };
+
+      mapFiles = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        description = "Maps to be compiled and placed into /var/lib/postfix/conf.";
+      };
+
+      useSrs = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable sender rewriting scheme";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.postfix.enable (mkMerge [
+    {
+
+      environment = {
+        etc.postfix.source = "/var/lib/postfix/conf";
+
+        # This makes it comfortable to run 'postqueue/postdrop' for example.
+        systemPackages = [ pkgs.postfix ];
+      };
+
+      services.pfix-srsd.enable = config.services.postfix.useSrs;
+
+      services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail {
+        program = "sendmail";
+        source = "${pkgs.postfix}/bin/sendmail";
+        group = setgidGroup;
+        setuid = false;
+        setgid = true;
+      };
+
+      security.wrappers.mailq = {
+        program = "mailq";
+        source = "${pkgs.postfix}/bin/mailq";
+        group = setgidGroup;
+        setuid = false;
+        setgid = true;
+      };
+
+      security.wrappers.postqueue = {
+        program = "postqueue";
+        source = "${pkgs.postfix}/bin/postqueue";
+        group = setgidGroup;
+        setuid = false;
+        setgid = true;
+      };
+
+      security.wrappers.postdrop = {
+        program = "postdrop";
+        source = "${pkgs.postfix}/bin/postdrop";
+        group = setgidGroup;
+        setuid = false;
+        setgid = true;
+      };
+
+      users.users = optionalAttrs (user == "postfix")
+        { postfix = {
+            description = "Postfix mail server user";
+            uid = config.ids.uids.postfix;
+            group = group;
+          };
+        };
+
+      users.groups =
+        optionalAttrs (group == "postfix")
+        { ${group}.gid = config.ids.gids.postfix;
+        }
+        // optionalAttrs (setgidGroup == "postdrop")
+        { ${setgidGroup}.gid = config.ids.gids.postdrop;
+        };
+
+      systemd.services.postfix =
+        { description = "Postfix mail server";
+
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+          path = [ pkgs.postfix ];
+
+          serviceConfig = {
+            Type = "forking";
+            Restart = "always";
+            PIDFile = "/var/lib/postfix/queue/pid/master.pid";
+            ExecStart = "${pkgs.postfix}/bin/postfix start";
+            ExecStop = "${pkgs.postfix}/bin/postfix stop";
+            ExecReload = "${pkgs.postfix}/bin/postfix reload";
+          };
+
+          preStart = ''
+            # Backwards compatibility
+            if [ ! -d /var/lib/postfix ] && [ -d /var/postfix ]; then
+              mkdir -p /var/lib
+              mv /var/postfix /var/lib/postfix
+            fi
+
+            # All permissions set according ${pkgs.postfix}/etc/postfix/postfix-files script
+            mkdir -p /var/lib/postfix /var/lib/postfix/queue/{pid,public,maildrop}
+            chmod 0755 /var/lib/postfix
+            chown root:root /var/lib/postfix
+
+            rm -rf /var/lib/postfix/conf
+            mkdir -p /var/lib/postfix/conf
+            chmod 0755 /var/lib/postfix/conf
+            ln -sf ${pkgs.postfix}/etc/postfix/postfix-files /var/lib/postfix/conf/postfix-files
+            ln -sf ${mainCfFile} /var/lib/postfix/conf/main.cf
+            ln -sf ${masterCfFile} /var/lib/postfix/conf/master.cf
+
+            ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
+              ln -sf ${from} /var/lib/postfix/conf/${to}
+              ${pkgs.postfix}/bin/postalias /var/lib/postfix/conf/${to}
+            '') cfg.aliasFiles)}
+            ${concatStringsSep "\n" (mapAttrsToList (to: from: ''
+              ln -sf ${from} /var/lib/postfix/conf/${to}
+              ${pkgs.postfix}/bin/postmap /var/lib/postfix/conf/${to}
+            '') cfg.mapFiles)}
+
+            mkdir -p /var/spool/mail
+            chown root:root /var/spool/mail
+            chmod a+rwxt /var/spool/mail
+            ln -sf /var/spool/mail /var/
+
+            #Finally delegate to postfix checking remain directories in /var/lib/postfix and set permissions on them
+            ${pkgs.postfix}/bin/postfix set-permissions config_directory=/var/lib/postfix/conf
+          '';
+        };
+
+      services.postfix.config = (mapAttrs (_: v: mkDefault v) {
+        compatibility_level  = "9999";
+        mail_owner           = cfg.user;
+        default_privs        = "nobody";
+
+        # NixOS specific locations
+        data_directory       = "/var/lib/postfix/data";
+        queue_directory      = "/var/lib/postfix/queue";
+
+        # Default location of everything in package
+        meta_directory       = "${pkgs.postfix}/etc/postfix";
+        command_directory    = "${pkgs.postfix}/bin";
+        sample_directory     = "/etc/postfix";
+        newaliases_path      = "${pkgs.postfix}/bin/newaliases";
+        mailq_path           = "${pkgs.postfix}/bin/mailq";
+        readme_directory     = false;
+        sendmail_path        = "${pkgs.postfix}/bin/sendmail";
+        daemon_directory     = "${pkgs.postfix}/libexec/postfix";
+        manpage_directory    = "${pkgs.postfix}/share/man";
+        html_directory       = "${pkgs.postfix}/share/postfix/doc/html";
+        shlib_directory      = false;
+        mail_spool_directory = "/var/spool/mail/";
+        setgid_group         = cfg.setgidGroup;
+      })
+      // optionalAttrs (cfg.relayHost != "") { relayhost = if cfg.lookupMX
+                                                           then "${cfg.relayHost}:${toString cfg.relayPort}"
+                                                           else "[${cfg.relayHost}]:${toString cfg.relayPort}"; }
+      // optionalAttrs config.networking.enableIPv6 { inet_protocols = mkDefault "all"; }
+      // optionalAttrs (cfg.networks != null) { mynetworks = cfg.networks; }
+      // optionalAttrs (cfg.networksStyle != "") { mynetworks_style = cfg.networksStyle; }
+      // optionalAttrs (cfg.hostname != "") { myhostname = cfg.hostname; }
+      // optionalAttrs (cfg.domain != "") { mydomain = cfg.domain; }
+      // optionalAttrs (cfg.origin != "") { myorigin =  cfg.origin; }
+      // optionalAttrs (cfg.destination != null) { mydestination = cfg.destination; }
+      // optionalAttrs (cfg.relayDomains != null) { relay_domains = cfg.relayDomains; }
+      // optionalAttrs (cfg.recipientDelimiter != "") { recipient_delimiter = cfg.recipientDelimiter; }
+      // optionalAttrs haveAliases { alias_maps = [ "${cfg.aliasMapType}:/etc/postfix/aliases" ]; }
+      // optionalAttrs haveTransport { transport_maps = [ "hash:/etc/postfix/transport" ]; }
+      // optionalAttrs haveVirtual { virtual_alias_maps = [ "${cfg.virtualMapType}:/etc/postfix/virtual" ]; }
+      // optionalAttrs haveLocalRecipients { local_recipient_maps = [ "hash:/etc/postfix/local_recipients" ] ++ optional haveAliases "$alias_maps"; }
+      // optionalAttrs (cfg.dnsBlacklists != []) { smtpd_client_restrictions = clientRestrictions; }
+      // optionalAttrs cfg.useSrs {
+        sender_canonical_maps = [ "tcp:127.0.0.1:10001" ];
+        sender_canonical_classes = [ "envelope_sender" ];
+        recipient_canonical_maps = [ "tcp:127.0.0.1:10002" ];
+        recipient_canonical_classes = [ "envelope_recipient" ];
+      }
+      // optionalAttrs cfg.enableHeaderChecks { header_checks = [ "regexp:/etc/postfix/header_checks" ]; }
+      // optionalAttrs (cfg.sslCert != "") {
+        smtp_tls_CAfile = cfg.sslCACert;
+        smtp_tls_cert_file = cfg.sslCert;
+        smtp_tls_key_file = cfg.sslKey;
+
+        smtp_use_tls = true;
+
+        smtpd_tls_CAfile = cfg.sslCACert;
+        smtpd_tls_cert_file = cfg.sslCert;
+        smtpd_tls_key_file = cfg.sslKey;
+
+        smtpd_use_tls = true;
+      };
+
+      services.postfix.masterConfig = {
+        smtp_inet = {
+          name = "smtp";
+          type = "inet";
+          private = false;
+          command = "smtpd";
+        };
+        pickup = {
+          private = false;
+          wakeup = 60;
+          maxproc = 1;
+        };
+        cleanup = {
+          private = false;
+          maxproc = 0;
+        };
+        qmgr = {
+          private = false;
+          wakeup = 300;
+          maxproc = 1;
+        };
+        tlsmgr = {
+          wakeup = 1000;
+          wakeupUnusedComponent = false;
+          maxproc = 1;
+        };
+        rewrite = {
+          command = "trivial-rewrite";
+        };
+        bounce = {
+          maxproc = 0;
+        };
+        defer = {
+          maxproc = 0;
+          command = "bounce";
+        };
+        trace = {
+          maxproc = 0;
+          command = "bounce";
+        };
+        verify = {
+          maxproc = 1;
+        };
+        flush = {
+          private = false;
+          wakeup = 1000;
+          wakeupUnusedComponent = false;
+          maxproc = 0;
+        };
+        proxymap = {
+          command = "proxymap";
+        };
+        proxywrite = {
+          maxproc = 1;
+          command = "proxymap";
+        };
+        showq = {
+          private = false;
+        };
+        error = {};
+        retry = {
+          command = "error";
+        };
+        discard = {};
+        local = {
+          privileged = true;
+        };
+        virtual = {
+          privileged = true;
+        };
+        lmtp = {
+        };
+        anvil = {
+          maxproc = 1;
+        };
+        scache = {
+          maxproc = 1;
+        };
+      } // optionalAttrs cfg.enableSubmission {
+        submission = {
+          type = "inet";
+          private = false;
+          command = "smtpd";
+          args = let
+            mkKeyVal = opt: val: [ "-o" (opt + "=" + val) ];
+          in concatLists (mapAttrsToList mkKeyVal cfg.submissionOptions);
+        };
+      } // optionalAttrs cfg.enableSmtp {
+        smtp = {};
+        relay = {
+          command = "smtp";
+          args = [ "-o" "smtp_fallback_relay=" ];
+        };
+      };
+    }
+
+    (mkIf haveAliases {
+      services.postfix.aliasFiles.aliases = aliasesFile;
+    })
+    (mkIf haveTransport {
+      services.postfix.mapFiles.transport = transportFile;
+    })
+    (mkIf haveVirtual {
+      services.postfix.mapFiles.virtual = virtualFile;
+    })
+    (mkIf haveLocalRecipients {
+      services.postfix.mapFiles.local_recipients = localRecipientMapFile;
+    })
+    (mkIf cfg.enableHeaderChecks {
+      services.postfix.mapFiles.header_checks = headerChecksFile;
+    })
+    (mkIf (cfg.dnsBlacklists != []) {
+      services.postfix.mapFiles.client_access = checkClientAccessFile;
+    })
+  ]);
+}
diff --git a/nixpkgs/nixos/modules/services/mail/postgrey.nix b/nixpkgs/nixos/modules/services/mail/postgrey.nix
new file mode 100644
index 000000000000..709f6b21aa0a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/postgrey.nix
@@ -0,0 +1,205 @@
+{ config, lib, pkgs, ... }:
+
+with lib; let
+
+  cfg = config.services.postgrey;
+
+  natural = with types; addCheck int (x: x >= 0);
+  natural' = with types; addCheck int (x: x > 0);
+
+  socket = with types; addCheck (either (submodule unixSocket) (submodule inetSocket)) (x: x ? path || x ? port);
+
+  inetSocket = with types; {
+    options = {
+      addr = mkOption {
+        type = nullOr str;
+        default = null;
+        example = "127.0.0.1";
+        description = "The address to bind to. Localhost if null";
+      };
+      port = mkOption {
+        type = natural';
+        default = 10030;
+        description = "Tcp port to bind to";
+      };
+    };
+  };
+
+  unixSocket = with types; {
+    options = {
+      path = mkOption {
+        type = path;
+        default = "/run/postgrey.sock";
+        description = "Path of the unix socket";
+      };
+
+      mode = mkOption {
+        type = str;
+        default = "0777";
+        description = "Mode of the unix socket";
+      };
+    };
+  };
+
+in {
+  imports = [
+    (mkMergedOptionModule [ [ "services" "postgrey" "inetAddr" ] [ "services" "postgrey" "inetPort" ] ] [ "services" "postgrey" "socket" ] (config: let
+        value = p: getAttrFromPath p config;
+        inetAddr = [ "services" "postgrey" "inetAddr" ];
+        inetPort = [ "services" "postgrey" "inetPort" ];
+      in
+        if value inetAddr == null
+        then { path = "/run/postgrey.sock"; }
+        else { addr = value inetAddr; port = value inetPort; }
+    ))
+  ];
+
+  options = {
+    services.postgrey = with types; {
+      enable = mkOption {
+        type = bool;
+        default = false;
+        description = "Whether to run the Postgrey daemon";
+      };
+      socket = mkOption {
+        type = socket;
+        default = {
+          path = "/run/postgrey.sock";
+          mode = "0777";
+        };
+        example = {
+          addr = "127.0.0.1";
+          port = 10030;
+        };
+        description = "Socket to bind to";
+      };
+      greylistText = mkOption {
+        type = str;
+        default = "Greylisted for %%s seconds";
+        description = "Response status text for greylisted messages; use %%s for seconds left until greylisting is over and %%r for mail domain of recipient";
+      };
+      greylistAction = mkOption {
+        type = str;
+        default = "DEFER_IF_PERMIT";
+        description = "Response status for greylisted messages (see access(5))";
+      };
+      greylistHeader = mkOption {
+        type = str;
+        default = "X-Greylist: delayed %%t seconds by postgrey-%%v at %%h; %%d";
+        description = "Prepend header to greylisted mails; use %%t for seconds delayed due to greylisting, %%v for the version of postgrey, %%d for the date, and %%h for the host";
+      };
+      delay = mkOption {
+        type = natural;
+        default = 300;
+        description = "Greylist for N seconds";
+      };
+      maxAge = mkOption {
+        type = natural;
+        default = 35;
+        description = "Delete entries from whitelist if they haven't been seen for N days";
+      };
+      retryWindow = mkOption {
+        type = either str natural;
+        default = 2;
+        example = "12h";
+        description = "Allow N days for the first retry. Use string with appended 'h' to specify time in hours";
+      };
+      lookupBySubnet = mkOption {
+        type = bool;
+        default = true;
+        description = "Strip the last N bits from IP addresses, determined by IPv4CIDR and IPv6CIDR";
+      };
+      IPv4CIDR = mkOption {
+        type = natural;
+        default = 24;
+        description = "Strip N bits from IPv4 addresses if lookupBySubnet is true";
+      };
+      IPv6CIDR = mkOption {
+        type = natural;
+        default = 64;
+        description = "Strip N bits from IPv6 addresses if lookupBySubnet is true";
+      };
+      privacy = mkOption {
+        type = bool;
+        default = true;
+        description = "Store data using one-way hash functions (SHA1)";
+      };
+      autoWhitelist = mkOption {
+        type = nullOr natural';
+        default = 5;
+        description = "Whitelist clients after successful delivery of N messages";
+      };
+      whitelistClients = mkOption {
+        type = listOf path;
+        default = [];
+        description = "Client address whitelist files (see postgrey(8))";
+      };
+      whitelistRecipients = mkOption {
+        type = listOf path;
+        default = [];
+        description = "Recipient address whitelist files (see postgrey(8))";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.postgrey ];
+
+    users = {
+      users = {
+        postgrey = {
+          description = "Postgrey Daemon";
+          uid = config.ids.uids.postgrey;
+          group = "postgrey";
+        };
+      };
+      groups = {
+        postgrey = {
+          gid = config.ids.gids.postgrey;
+        };
+      };
+    };
+
+    systemd.services.postgrey = let
+      bind-flag = if cfg.socket ? path then
+        ''--unix=${cfg.socket.path} --socketmode=${cfg.socket.mode}''
+      else
+        ''--inet=${optionalString (cfg.socket.addr != null) (cfg.socket.addr + ":")}${toString cfg.socket.port}'';
+    in {
+      description = "Postfix Greylisting Service";
+      wantedBy = [ "multi-user.target" ];
+      before = [ "postfix.service" ];
+      preStart = ''
+        mkdir -p /var/postgrey
+        chown postgrey:postgrey /var/postgrey
+        chmod 0770 /var/postgrey
+      '';
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = ''${pkgs.postgrey}/bin/postgrey \
+          ${bind-flag} \
+          --group=postgrey --user=postgrey \
+          --dbdir=/var/postgrey \
+          --delay=${toString cfg.delay} \
+          --max-age=${toString cfg.maxAge} \
+          --retry-window=${toString cfg.retryWindow} \
+          ${if cfg.lookupBySubnet then "--lookup-by-subnet" else "--lookup-by-host"} \
+          --ipv4cidr=${toString cfg.IPv4CIDR} --ipv6cidr=${toString cfg.IPv6CIDR} \
+          ${optionalString cfg.privacy "--privacy"} \
+          --auto-whitelist-clients=${toString (if cfg.autoWhitelist == null then 0 else cfg.autoWhitelist)} \
+          --greylist-action=${cfg.greylistAction} \
+          --greylist-text="${cfg.greylistText}" \
+          --x-greylist-header="${cfg.greylistHeader}" \
+          ${concatMapStringsSep " " (x: "--whitelist-clients=" + x) cfg.whitelistClients} \
+          ${concatMapStringsSep " " (x: "--whitelist-recipients=" + x) cfg.whitelistRecipients}
+        '';
+        Restart = "always";
+        RestartSec = 5;
+        TimeoutSec = 10;
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/mail/postsrsd.nix b/nixpkgs/nixos/modules/services/mail/postsrsd.nix
new file mode 100644
index 000000000000..2ebc675ab10a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/postsrsd.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.postsrsd;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.postsrsd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the postsrsd SRS server for Postfix.";
+      };
+
+      secretsFile = mkOption {
+        type = types.path;
+        default = "/var/lib/postsrsd/postsrsd.secret";
+        description = "Secret keys used for signing and verification";
+      };
+
+      domain = mkOption {
+        type = types.str;
+        description = "Domain name for rewrite";
+      };
+
+      separator = mkOption {
+        type = types.enum ["-" "=" "+"];
+        default = "=";
+        description = "First separator character in generated addresses";
+      };
+
+      # bindAddress = mkOption { # uncomment once 1.5 is released
+      #   type = types.str;
+      #   default = "127.0.0.1";
+      #   description = "Socket listen address";
+      # };
+
+      forwardPort = mkOption {
+        type = types.int;
+        default = 10001;
+        description = "Port for the forward SRS lookup";
+      };
+
+      reversePort = mkOption {
+        type = types.int;
+        default = 10002;
+        description = "Port for the reverse SRS lookup";
+      };
+
+      timeout = mkOption {
+        type = types.int;
+        default = 1800;
+        description = "Timeout for idle client connections in seconds";
+      };
+
+      excludeDomains = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Origin domains to exclude from rewriting in addition to primary domain";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "postsrsd";
+        description = "User for the daemon";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "postsrsd";
+        description = "Group for the daemon";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.postsrsd.domain = mkDefault config.networking.hostName;
+
+    users.users = optionalAttrs (cfg.user == "postsrsd") {
+      postsrsd = {
+        group = cfg.group;
+        uid = config.ids.uids.postsrsd;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "postsrsd") {
+      postsrsd.gid = config.ids.gids.postsrsd;
+    };
+
+    systemd.services.postsrsd = {
+      description = "PostSRSd SRS rewriting server";
+      after = [ "network.target" ];
+      before = [ "postfix.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ pkgs.coreutils ];
+
+      serviceConfig = {
+        ExecStart = ''${pkgs.postsrsd}/sbin/postsrsd "-s${cfg.secretsFile}" "-d${cfg.domain}" -a${cfg.separator} -f${toString cfg.forwardPort} -r${toString cfg.reversePort} -t${toString cfg.timeout} "-X${concatStringsSep "," cfg.excludeDomains}"'';
+        User = cfg.user;
+        Group = cfg.group;
+        PermissionsStartOnly = true;
+      };
+
+      preStart = ''
+        if [ ! -e "${cfg.secretsFile}" ]; then
+          echo "WARNING: secrets file not found, autogenerating!"
+          DIR="$(dirname "${cfg.secretsFile}")"
+          if [ ! -d "$DIR" ]; then
+            mkdir -p -m750 "$DIR"
+            chown "${cfg.user}:${cfg.group}" "$DIR"
+          fi
+          dd if=/dev/random bs=18 count=1 | base64 > "${cfg.secretsFile}"
+          chmod 600 "${cfg.secretsFile}"
+        fi
+        chown "${cfg.user}:${cfg.group}" "${cfg.secretsFile}"
+      '';
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/public-inbox.nix b/nixpkgs/nixos/modules/services/mail/public-inbox.nix
new file mode 100644
index 000000000000..d503f5aa2ef6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/public-inbox.nix
@@ -0,0 +1,455 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.public-inbox;
+
+  inboxesDir = "/var/lib/public-inbox/inboxes";
+  inboxPath = name: "${inboxesDir}/${name}";
+  gitPath = name: "${inboxPath name}/all.git";
+
+  inboxes = mapAttrs (name: inbox:
+    (recursiveUpdate {
+      inherit (inbox) address url newsgroup watch;
+      mainrepo = inboxPath name;
+      watchheader = inbox.watchHeader;
+    } inbox.config))
+    cfg.inboxes;
+
+  concat = concatMap id;
+
+  configToList = attrs:
+    concat (mapAttrsToList (name': value':
+      if isAttrs value' then
+        map ({ name, value }: nameValuePair "${name'}.${name}" value)
+          (configToList value')
+      else if isList value' then map (nameValuePair name') value'
+      else if value' == null then []
+      else [ (nameValuePair name' value') ]) attrs);
+
+  configFull = recursiveUpdate {
+    publicinbox = inboxes // {
+      nntpserver = cfg.nntpServer;
+      wwwlisting = cfg.wwwListing;
+    };
+    publicinboxmda.spamcheck = cfg.mda.spamCheck;
+    publicinboxwatch.spamcheck = cfg.watch.spamCheck;
+    publicinboxwatch.watchspam = cfg.watch.watchSpam;
+  } cfg.config;
+
+  configList = configToList configFull;
+
+  gitConfig = key: val: ''
+    ${pkgs.git}/bin/git config --add --file $out ${escapeShellArgs [ key val ]}
+  '';
+
+  configFile = pkgs.runCommand "public-inbox-config" {}
+    (concatStrings (map ({ name, value }: gitConfig name value) configList));
+
+  environment = {
+    PI_EMERGENCY = "/var/lib/public-inbox/emergency";
+    PI_CONFIG = configFile;
+  };
+
+  envList = mapAttrsToList (n: v: "${n}=${v}") environment;
+
+  # Can't use pkgs.linkFarm,
+  # because Postfix rejects .forward if it's a symlink.
+  home = pkgs.runCommand "public-inbox-home" {
+    forward = ''
+      |"env ${concatStringsSep " " envList} PATH=\"${makeBinPath cfg.path}:$PATH\" ${cfg.package}/bin/public-inbox-mda ${escapeShellArgs cfg.mda.args}
+    '';
+    passAsFile = [ "forward" ];
+  } ''
+    mkdir $out
+    ln -s /var/lib/public-inbox/spamassassin $out/.spamassassin
+    cp $forwardPath $out/.forward
+  '';
+
+  psgi = pkgs.writeText "public-inbox.psgi" ''
+    #!${cfg.package.fullperl} -w
+    # Copyright (C) 2014-2019 all contributors <meta@public-inbox.org>
+    # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
+    use strict;
+    use PublicInbox::WWW;
+    use Plack::Builder;
+
+    my $www = PublicInbox::WWW->new;
+    $www->preload;
+
+    builder {
+      enable 'Head';
+      enable 'ReverseProxy';
+      ${concatMapStrings (path: ''
+      mount q(${path}) => sub { $www->call(@_); };
+      '') cfg.http.mounts}
+    }
+  '';
+
+  descriptionFile = { description, ... }:
+    pkgs.writeText "description" description;
+
+  enableWatch = (any (i: i.watch != []) (attrValues cfg.inboxes))
+                || (cfg.watch.watchSpam != null);
+
+  useSpamAssassin = cfg.mda.spamCheck == "spamc" ||
+                    cfg.watch.spamCheck == "spamc";
+
+in
+
+{
+  options = {
+    services.public-inbox = {
+      enable = mkEnableOption "the public-inbox mail archiver";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.public-inbox;
+        description = ''
+          public-inbox package to use with the public-inbox module
+        '';
+      };
+
+      path = mkOption {
+        type = with types; listOf package;
+        default = [];
+        example = literalExample "with pkgs; [ spamassassin ]";
+        description = ''
+          Additional packages to place in the path of public-inbox-mda,
+          public-inbox-watch, etc.
+        '';
+      };
+
+      inboxes = mkOption {
+        description = ''
+          Inboxes to configure, where attribute names are inbox names
+        '';
+        type = with types; loaOf (submodule {
+          options = {
+            address = mkOption {
+              type = listOf str;
+              example = "example-discuss@example.org";
+            };
+
+            url = mkOption {
+              type = nullOr str;
+              default = null;
+              example = "https://example.org/lists/example-discuss";
+              description = ''
+                URL where this inbox can be accessed over HTTP
+              '';
+            };
+
+            description = mkOption {
+              type = str;
+              example = "user/dev discussion of public-inbox itself";
+              description = ''
+                User-visible description for the repository
+              '';
+            };
+
+            config = mkOption {
+              type = attrs;
+              default = {};
+              description = ''
+                Additional structured config for the inbox
+              '';
+            };
+
+            newsgroup = mkOption {
+              type = nullOr str;
+              default = null;
+              description = ''
+                NNTP group name for the inbox
+              '';
+            };
+
+            watch = mkOption {
+              type = listOf str;
+              default = [];
+              description = ''
+                Paths for public-inbox-watch(1) to monitor for new mail
+              '';
+              example = [ "maildir:/path/to/test.example.com.git" ];
+            };
+
+            watchHeader = mkOption {
+              type = nullOr str;
+              default = null;
+              example = "List-Id:<test@example.com>";
+              description = ''
+                If specified, public-inbox-watch(1) will only process
+                mail containing a matching header.
+              '';
+            };
+          };
+        });
+      };
+
+      mda = {
+        args = mkOption {
+          type = with types; listOf str;
+          default = [];
+          description = ''
+            Command-line arguments to pass to public-inbox-mda(1).
+          '';
+        };
+
+        spamCheck = mkOption {
+          type = with types; nullOr (enum [ "spamc" ]);
+          default = "spamc";
+          description = ''
+            If set to spamc, public-inbox-mda(1) will filter spam
+            using SpamAssassin
+          '';
+        };
+      };
+
+      watch = {
+        spamCheck = mkOption {
+          type = with types; nullOr (enum [ "spamc" ]);
+          default = "spamc";
+          description = ''
+            If set to spamc, public-inbox-watch(1) will filter spam
+            using SpamAssassin
+          '';
+        };
+
+        watchSpam = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          example = "maildir:/path/to/spam";
+          description = ''
+            If set, mail in this maildir will be trained as spam and
+            deleted from all watched inboxes
+          '';
+        };
+      };
+
+      http = {
+        mounts = mkOption {
+          type = with types; listOf str;
+          default = [ "/" ];
+          example = [ "/lists/archives" ];
+          description = ''
+            Root paths or URLs that public-inbox will be served on.
+            If domain parts are present, only requests to those
+            domains will be accepted.
+          '';
+        };
+
+        listenStreams = mkOption {
+          type = with types; listOf str;
+          default = [ "/run/public-inbox-httpd.sock" ];
+          description = ''
+            systemd.socket(5) ListenStream values for the
+            public-inbox-httpd service to listen on
+          '';
+        };
+      };
+
+      nntp = {
+        listenStreams = mkOption {
+          type = with types; listOf str;
+          default = [ "0.0.0.0:119" "0.0.0.0:563" ];
+          description = ''
+            systemd.socket(5) ListenStream values for the
+            public-inbox-nntpd service to listen on
+          '';
+        };
+
+        cert = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          example = "/path/to/fullchain.pem";
+          description = ''
+            Path to TLS certificate to use for public-inbox NNTP connections
+          '';
+        };
+
+        key = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          example = "/path/to/key.pem";
+          description = ''
+            Path to TLS key to use for public-inbox NNTP connections
+          '';
+        };
+
+        extraGroups = mkOption {
+          type = with types; listOf str;
+          default = [];
+          example = [ "tls" ];
+          description = ''
+            Secondary groups to assign to the systemd DynamicUser
+            running public-inbox-nntpd, in addition to the
+            public-inbox group.  This is useful for giving
+            public-inbox-nntpd access to a TLS certificate / key, for
+            example.
+          '';
+        };
+      };
+
+      nntpServer = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = [ "nntp://news.public-inbox.org" "nntps://news.public-inbox.org" ];
+        description = ''
+          NNTP URLs to this public-inbox instance
+        '';
+      };
+
+      wwwListing = mkOption {
+        type = with types; enum [ "all" "404" "match=domain" ];
+        default = "404";
+        description = ''
+          Controls which lists (if any) are listed for when the root
+          public-inbox URL is accessed over HTTP.
+        '';
+      };
+
+      spamAssassinRules = mkOption {
+        type = with types; nullOr path;
+        default = "${cfg.package.sa_config}/user/.spamassassin/user_prefs";
+        description = ''
+          SpamAssassin configuration specific to public-inbox
+        '';
+      };
+
+      config = mkOption {
+        type = with types; attrsOf attrs;
+        default = {};
+        description = ''
+          Additional structured config for the public-inbox config file
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = config.services.spamassassin.enable || !useSpamAssassin;
+        message = ''
+          public-inbox is configured to use SpamAssassin, but
+          services.spamassassin.enable is false.  If you don't need
+          spam checking, set services.public-inbox.mda.spamCheck and
+          services.public-inbox.watch.spamCheck to null.
+        '';
+      }
+      { assertion = cfg.path != [] || !useSpamAssassin;
+        message = ''
+          public-inbox is configured to use SpamAssassin, but there is
+          no spamc executable in services.public-inbox.path.  If you
+          don't need spam checking, set
+          services.public-inbox.mda.spamCheck and
+          services.public-inbox.watch.spamCheck to null.
+        '';
+      }
+    ];
+
+    users.users.public-inbox = {
+      inherit home;
+      group = "public-inbox";
+      isSystemUser = true;
+    };
+
+    users.groups.public-inbox = {};
+
+    systemd.sockets.public-inbox-httpd = {
+      inherit (cfg.http) listenStreams;
+      wantedBy = [ "sockets.target" ];
+    };
+
+    systemd.sockets.public-inbox-nntpd = {
+      inherit (cfg.nntp) listenStreams;
+      wantedBy = [ "sockets.target" ];
+    };
+
+    systemd.services.public-inbox-httpd = {
+      inherit environment;
+      serviceConfig.ExecStart = "${cfg.package}/bin/public-inbox-httpd ${psgi}";
+      serviceConfig.NonBlocking = true;
+      serviceConfig.DynamicUser = true;
+      serviceConfig.SupplementaryGroups = [ "public-inbox" ];
+    };
+
+    systemd.services.public-inbox-nntpd = {
+      inherit environment;
+      serviceConfig.ExecStart = escapeShellArgs (
+        [ "${cfg.package}/bin/public-inbox-nntpd" ] ++
+        (optionals (cfg.nntp.cert != null) [ "--cert" cfg.nntp.cert ]) ++
+        (optionals (cfg.nntp.key != null) [ "--key" cfg.nntp.key ])
+      );
+      serviceConfig.NonBlocking = true;
+      serviceConfig.DynamicUser = true;
+      serviceConfig.SupplementaryGroups = [ "public-inbox" ] ++ cfg.nntp.extraGroups;
+    };
+
+    systemd.services.public-inbox-watch = {
+      inherit environment;
+      inherit (cfg) path;
+      after = optional (cfg.watch.spamCheck == "spamc") "spamassassin.service";
+      wantedBy = optional enableWatch "multi-user.target";
+      serviceConfig.ExecStart = "${cfg.package}/bin/public-inbox-watch";
+      serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      serviceConfig.User = "public-inbox";
+    };
+
+    system.activationScripts.public-inbox = stringAfter [ "users" ] ''
+      install -m 0755 -o public-inbox -g public-inbox -d /var/lib/public-inbox
+      install -m 0750 -o public-inbox -g public-inbox -d ${inboxesDir}
+      install -m 0700 -o public-inbox -g public-inbox -d /var/lib/public-inbox/emergency
+
+      ${optionalString useSpamAssassin ''
+        install -m 0700 -o spamd -d /var/lib/public-inbox/spamassassin
+        ${optionalString (cfg.spamAssassinRules != null) ''
+          ln -sf ${cfg.spamAssassinRules} /var/lib/public-inbox/spamassassin/user_prefs
+        ''}
+      ''}
+
+      ${concatStrings (mapAttrsToList (name: { address, url, ... } @ inbox: ''
+        if [ ! -e ${escapeShellArg (inboxPath name)} ]; then
+            # public-inbox-init creates an inbox and adds it to a config file.
+            # It tries to atomically write the config file by creating
+            # another file in the same directory, and renaming it.
+            # This has the sad consequence that we can't use
+            # /dev/null, or it would try to create a file in /dev.
+            conf_dir="$(${pkgs.sudo}/bin/sudo -u public-inbox mktemp -d)"
+
+            ${pkgs.sudo}/bin/sudo -u public-inbox \
+                env PI_CONFIG=$conf_dir/conf \
+                ${cfg.package}/bin/public-inbox-init -V2 \
+                ${escapeShellArgs ([ name (inboxPath name) url ] ++ address)}
+
+            rm -rf $conf_dir
+        fi
+
+        ln -sf ${descriptionFile inbox} ${inboxPath name}/description
+
+        if [ -d ${escapeShellArg (gitPath name)} ]; then
+            # Config is inherited by each epoch repository,
+            # so just needs to be set for all.git.
+            ${pkgs.git}/bin/git --git-dir ${gitPath name} \
+                config core.sharedRepository 0640
+        fi
+      '') cfg.inboxes)}
+
+      for inbox in /var/lib/public-inbox/inboxes/*/; do
+          ls -1 "$inbox" | grep -q '^xap' && continue
+
+          # This should be idempotent, but only do it for new
+          # inboxes anyway because it's only needed once, and could
+          # be slow for large pre-existing inboxes.
+          ${pkgs.sudo}/bin/sudo -u public-inbox \
+              env ${concatStringsSep " " envList} \
+              ${cfg.package}/bin/public-inbox-index "$inbox"
+      done
+
+    '';
+
+    environment.systemPackages = with pkgs; [ cfg.package ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/roundcube.nix b/nixpkgs/nixos/modules/services/mail/roundcube.nix
new file mode 100644
index 000000000000..ed1439745ac9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/roundcube.nix
@@ -0,0 +1,231 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.roundcube;
+  fpm = config.services.phpfpm.pools.roundcube;
+  localDB = cfg.database.host == "localhost";
+  user = cfg.database.username;
+  phpWithPspell = pkgs.php.withExtensions ({ enabled, all }: [ all.pspell ] ++ enabled);
+in
+{
+  options.services.roundcube = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable roundcube.
+
+        Also enables nginx virtual host management.
+        Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
+        See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+      '';
+    };
+
+    hostName = mkOption {
+      type = types.str;
+      example = "webmail.example.com";
+      description = "Hostname to use for the nginx vhost";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.roundcube;
+
+      example = literalExample ''
+        roundcube.withPlugins (plugins: [ plugins.persistent_login ])
+      '';
+
+      description = ''
+        The package which contains roundcube's sources. Can be overriden to create
+        an environment which contains roundcube and third-party plugins.
+      '';
+    };
+
+    database = {
+      username = mkOption {
+        type = types.str;
+        default = "roundcube";
+        description = ''
+          Username for the postgresql connection.
+          If <literal>database.host</literal> is set to <literal>localhost</literal>, a unix user and group of the same name will be created as well.
+        '';
+      };
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          Host of the postgresql server. If this is not set to
+          <literal>localhost</literal>, you have to create the
+          postgresql user and database yourself, with appropriate
+          permissions.
+        '';
+      };
+      password = mkOption {
+        type = types.str;
+        description = "Password for the postgresql connection. Do not use: the password will be stored world readable in the store; use <literal>passwordFile</literal> instead.";
+        default = "";
+      };
+      passwordFile = mkOption {
+        type = types.str;
+        description = "Password file for the postgresql connection. Must be readable by user <literal>nginx</literal>. Ignored if <literal>database.host</literal> is set to <literal>localhost</literal>, as peer authentication will be used.";
+      };
+      dbname = mkOption {
+        type = types.str;
+        default = "roundcube";
+        description = "Name of the postgresql database";
+      };
+    };
+
+    plugins = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        List of roundcube plugins to enable. Currently, only those directly shipped with Roundcube are supported.
+      '';
+    };
+
+    dicts = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      example = literalExample "with pkgs.aspellDicts; [ en fr de ]";
+      description = ''
+        List of aspell dictionnaries for spell checking. If empty, spell checking is disabled.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Extra configuration for roundcube webmail instance";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # backward compatibility: if password is set but not passwordFile, make one.
+    services.roundcube.database.passwordFile = mkIf (!localDB && cfg.database.password != "") (mkDefault ("${pkgs.writeText "roundcube-password" cfg.database.password}"));
+    warnings = lib.optional (!localDB && cfg.database.password != "") "services.roundcube.database.password is deprecated and insecure; use services.roundcube.database.passwordFile instead";
+
+    environment.etc."roundcube/config.inc.php".text = ''
+      <?php
+
+      ${lib.optionalString (!localDB) "$password = file_get_contents('${cfg.database.passwordFile}');"}
+
+      $config = array();
+      $config['db_dsnw'] = 'pgsql://${cfg.database.username}${lib.optionalString (!localDB) ":' . $password . '"}@${if localDB then "unix(/run/postgresql)" else cfg.database.host}/${cfg.database.dbname}';
+      $config['log_driver'] = 'syslog';
+      $config['max_message_size'] = '25M';
+      $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}];
+      $config['des_key'] = file_get_contents('/var/lib/roundcube/des_key');
+      $config['mime_types'] = '${pkgs.nginx}/conf/mime.types';
+      $config['enable_spellcheck'] = ${if cfg.dicts == [] then "false" else "true"};
+      # by default, spellchecking uses a third-party cloud services
+      $config['spellcheck_engine'] = 'pspell';
+      $config['spellcheck_languages'] = array(${lib.concatMapStringsSep ", " (dict: let p = builtins.parseDrvName dict.shortName; in "'${p.name}' => '${dict.fullName}'") cfg.dicts});
+
+      ${cfg.extraConfig}
+    '';
+
+    services.nginx = {
+      enable = true;
+      virtualHosts = {
+        ${cfg.hostName} = {
+          forceSSL = mkDefault true;
+          enableACME = mkDefault true;
+          locations."/" = {
+            root = cfg.package;
+            index = "index.php";
+            extraConfig = ''
+              location ~* \.php$ {
+                fastcgi_split_path_info ^(.+\.php)(/.+)$;
+                fastcgi_pass unix:${fpm.socket};
+                include ${pkgs.nginx}/conf/fastcgi_params;
+                include ${pkgs.nginx}/conf/fastcgi.conf;
+              }
+            '';
+          };
+        };
+      };
+    };
+
+    services.postgresql = mkIf localDB {
+      enable = true;
+      ensureDatabases = [ cfg.database.dbname ];
+      ensureUsers = [ {
+        name = cfg.database.username;
+        ensurePermissions = {
+          "DATABASE ${cfg.database.username}" = "ALL PRIVILEGES";
+        };
+      } ];
+    };
+
+    users.users.${user} = mkIf localDB {
+      group = user;
+      isSystemUser = true;
+      createHome = false;
+    };
+    users.groups.${user} = mkIf localDB {};
+
+    services.phpfpm.pools.roundcube = {
+      user = if localDB then user else "nginx";
+      phpOptions = ''
+        error_log = 'stderr'
+        log_errors = on
+        post_max_size = 25M
+        upload_max_filesize = 25M
+      '';
+      settings = mapAttrs (name: mkDefault) {
+        "listen.owner" = "nginx";
+        "listen.group" = "nginx";
+        "listen.mode" = "0660";
+        "pm" = "dynamic";
+        "pm.max_children" = 75;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 1;
+        "pm.max_spare_servers" = 20;
+        "pm.max_requests" = 500;
+        "catch_workers_output" = true;
+      };
+      phpPackage = phpWithPspell;
+      phpEnv.ASPELL_CONF = "dict-dir ${pkgs.aspellWithDicts (_: cfg.dicts)}/lib/aspell";
+    };
+    systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ];
+
+    systemd.services.roundcube-setup = mkMerge [
+      (mkIf (cfg.database.host == "localhost") {
+        requires = [ "postgresql.service" ];
+        after = [ "postgresql.service" ];
+        path = [ config.services.postgresql.package ];
+      })
+      {
+        wantedBy = [ "multi-user.target" ];
+        script = let
+          psql = "${lib.optionalString (!localDB) "PGPASSFILE=${cfg.database.passwordFile}"} ${pkgs.postgresql}/bin/psql ${lib.optionalString (!localDB) "-h ${cfg.database.host} -U ${cfg.database.username} "} ${cfg.database.dbname}";
+        in
+        ''
+          version="$(${psql} -t <<< "select value from system where name = 'roundcube-version';" || true)"
+          if ! (grep -E '[a-zA-Z0-9]' <<< "$version"); then
+            ${psql} -f ${cfg.package}/SQL/postgres.initial.sql
+          fi
+
+          if [ ! -f /var/lib/roundcube/des_key ]; then
+            base64 /dev/urandom | head -c 24 > /var/lib/roundcube/des_key;
+            # we need to log out everyone in case change the des_key
+            # from the default when upgrading from nixos 19.09
+            ${psql} <<< 'TRUNCATE TABLE session;'
+          fi
+
+          ${phpWithPspell}/bin/php ${cfg.package}/bin/update.sh
+        '';
+        serviceConfig = {
+          Type = "oneshot";
+          StateDirectory = "roundcube";
+          User = if localDB then user else "nginx";
+          # so that the des_key is not world readable
+          StateDirectoryMode = "0700";
+        };
+      }
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/mail/rspamd.nix b/nixpkgs/nixos/modules/services/mail/rspamd.nix
new file mode 100644
index 000000000000..aacdbe2aeed2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/rspamd.nix
@@ -0,0 +1,416 @@
+{ config, options, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.rspamd;
+  postfixCfg = config.services.postfix;
+
+  bindSocketOpts = {options, config, ... }: {
+    options = {
+      socket = mkOption {
+        type = types.str;
+        example = "localhost:11333";
+        description = ''
+          Socket for this worker to listen on in a format acceptable by rspamd.
+        '';
+      };
+      mode = mkOption {
+        type = types.str;
+        default = "0644";
+        description = "Mode to set on unix socket";
+      };
+      owner = mkOption {
+        type = types.str;
+        default = "${cfg.user}";
+        description = "Owner to set on unix socket";
+      };
+      group = mkOption {
+        type = types.str;
+        default = "${cfg.group}";
+        description = "Group to set on unix socket";
+      };
+      rawEntry = mkOption {
+        type = types.str;
+        internal = true;
+      };
+    };
+    config.rawEntry = let
+      maybeOption = option:
+        optionalString options.${option}.isDefined " ${option}=${config.${option}}";
+    in
+      if (!(hasPrefix "/" config.socket)) then "${config.socket}"
+      else "${config.socket}${maybeOption "mode"}${maybeOption "owner"}${maybeOption "group"}";
+  };
+
+  traceWarning = w: x: builtins.trace "warning: ${w}" x;
+
+  workerOpts = { name, options, ... }: {
+    options = {
+      enable = mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = "Whether to run the rspamd worker.";
+      };
+      name = mkOption {
+        type = types.nullOr types.str;
+        default = name;
+        description = "Name of the worker";
+      };
+      type = mkOption {
+        type = types.nullOr (types.enum [
+          "normal" "controller" "fuzzy" "rspamd_proxy" "lua" "proxy"
+        ]);
+        description = ''
+          The type of this worker. The type <literal>proxy</literal> is
+          deprecated and only kept for backwards compatibility and should be
+          replaced with <literal>rspamd_proxy</literal>.
+        '';
+        apply = let
+            from = "services.rspamd.workers.\"${name}\".type";
+            files = options.type.files;
+            warning = "The option `${from}` defined in ${showFiles files} has enum value `proxy` which has been renamed to `rspamd_proxy`";
+          in x: if x == "proxy" then traceWarning warning "rspamd_proxy" else x;
+      };
+      bindSockets = mkOption {
+        type = types.listOf (types.either types.str (types.submodule bindSocketOpts));
+        default = [];
+        description = ''
+          List of sockets to listen, in format acceptable by rspamd
+        '';
+        example = [{
+          socket = "/run/rspamd.sock";
+          mode = "0666";
+          owner = "rspamd";
+        } "*:11333"];
+        apply = value: map (each: if (isString each)
+          then if (isUnixSocket each)
+            then {socket = each; owner = cfg.user; group = cfg.group; mode = "0644"; rawEntry = "${each}";}
+            else {socket = each; rawEntry = "${each}";}
+          else each) value;
+      };
+      count = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Number of worker instances to run
+        '';
+      };
+      includes = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          List of files to include in configuration
+        '';
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional entries to put verbatim into worker section of rspamd config file.";
+      };
+    };
+    config = mkIf (name == "normal" || name == "controller" || name == "fuzzy" || name == "rspamd_proxy") {
+      type = mkDefault name;
+      includes = mkDefault [ "$CONFDIR/worker-${if name == "rspamd_proxy" then "proxy" else name}.inc" ];
+      bindSockets =
+        let
+          unixSocket = name: {
+            mode = "0660";
+            socket = "/run/rspamd/${name}.sock";
+            owner = cfg.user;
+            group = cfg.group;
+          };
+        in mkDefault (if name == "normal" then [(unixSocket "rspamd")]
+          else if name == "controller" then [ "localhost:11334" ]
+          else if name == "rspamd_proxy" then [ (unixSocket "proxy") ]
+          else [] );
+    };
+  };
+
+  isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket);
+
+  mkBindSockets = enabled: socks: concatStringsSep "\n  "
+    (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks));
+
+  rspamdConfFile = pkgs.writeText "rspamd.conf"
+    ''
+      .include "$CONFDIR/common.conf"
+
+      options {
+        pidfile = "$RUNDIR/rspamd.pid";
+        .include "$CONFDIR/options.inc"
+        .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc"
+        .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc"
+      }
+
+      logging {
+        type = "syslog";
+        .include "$CONFDIR/logging.inc"
+        .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc"
+        .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc"
+      }
+
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: let
+          includeName = if name == "rspamd_proxy" then "proxy" else name;
+          tryOverride = if value.extraConfig == "" then "true" else "false";
+        in ''
+        worker "${value.type}" {
+          type = "${value.type}";
+          ${optionalString (value.enable != null)
+            "enabled = ${if value.enable != false then "yes" else "no"};"}
+          ${mkBindSockets value.enable value.bindSockets}
+          ${optionalString (value.count != null) "count = ${toString value.count};"}
+          ${concatStringsSep "\n  " (map (each: ".include \"${each}\"") value.includes)}
+          .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-${includeName}.inc"
+          .include(try=${tryOverride}; priority=10) "$LOCAL_CONFDIR/override.d/worker-${includeName}.inc"
+        }
+      '') cfg.workers)}
+
+      ${optionalString (cfg.extraConfig != "") ''
+        .include(priority=10) "$LOCAL_CONFDIR/override.d/extra-config.inc"
+      ''}
+   '';
+
+  filterFiles = files: filterAttrs (n: v: v.enable) files;
+  rspamdDir = pkgs.linkFarm "etc-rspamd-dir" (
+    (mapAttrsToList (name: file: { name = "local.d/${name}"; path = file.source; }) (filterFiles cfg.locals)) ++
+    (mapAttrsToList (name: file: { name = "override.d/${name}"; path = file.source; }) (filterFiles cfg.overrides)) ++
+    (optional (cfg.localLuaRules != null) { name = "rspamd.local.lua"; path = cfg.localLuaRules; }) ++
+    [ { name = "rspamd.conf"; path = rspamdConfFile; } ]
+  );
+
+  configFileModule = prefix: { name, config, ... }: {
+    options = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether this file ${prefix} should be generated.  This
+          option allows specific ${prefix} files to be disabled.
+        '';
+      };
+
+      text = mkOption {
+        default = null;
+        type = types.nullOr types.lines;
+        description = "Text of the file.";
+      };
+
+      source = mkOption {
+        type = types.path;
+        description = "Path of the source file.";
+      };
+    };
+    config = {
+      source = mkIf (config.text != null) (
+        let name' = "rspamd-${prefix}-" + baseNameOf name;
+        in mkDefault (pkgs.writeText name' config.text));
+    };
+  };
+
+  configOverrides =
+    (mapAttrs' (n: v: nameValuePair "worker-${if n == "rspamd_proxy" then "proxy" else n}.inc" {
+      text = v.extraConfig;
+    })
+    (filterAttrs (n: v: v.extraConfig != "") cfg.workers))
+    // (if cfg.extraConfig == "" then {} else {
+      "extra-config.inc".text = cfg.extraConfig;
+    });
+in
+
+{
+  ###### interface
+
+  options = {
+
+    services.rspamd = {
+
+      enable = mkEnableOption "rspamd, the Rapid spam filtering system";
+
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to run the rspamd daemon in debug mode.";
+      };
+
+      locals = mkOption {
+        type = with types; attrsOf (submodule (configFileModule "locals"));
+        default = {};
+        description = ''
+          Local configuration files, written into <filename>/etc/rspamd/local.d/{name}</filename>.
+        '';
+        example = literalExample ''
+          { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
+            "arc.conf".text = "allow_envfrom_empty = true;";
+          }
+        '';
+      };
+
+      overrides = mkOption {
+        type = with types; attrsOf (submodule (configFileModule "overrides"));
+        default = {};
+        description = ''
+          Overridden configuration files, written into <filename>/etc/rspamd/override.d/{name}</filename>.
+        '';
+        example = literalExample ''
+          { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
+            "arc.conf".text = "allow_envfrom_empty = true;";
+          }
+        '';
+      };
+
+      localLuaRules = mkOption {
+        default = null;
+        type = types.nullOr types.path;
+        description = ''
+          Path of file to link to <filename>/etc/rspamd/rspamd.local.lua</filename> for local
+          rules written in Lua
+        '';
+      };
+
+      workers = mkOption {
+        type = with types; attrsOf (submodule workerOpts);
+        description = ''
+          Attribute set of workers to start.
+        '';
+        default = {
+          normal = {};
+          controller = {};
+        };
+        example = literalExample ''
+          {
+            normal = {
+              includes = [ "$CONFDIR/worker-normal.inc" ];
+              bindSockets = [{
+                socket = "/run/rspamd/rspamd.sock";
+                mode = "0660";
+                owner = "${cfg.user}";
+                group = "${cfg.group}";
+              }];
+            };
+            controller = {
+              includes = [ "$CONFDIR/worker-controller.inc" ];
+              bindSockets = [ "[::1]:11334" ];
+            };
+          }
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration to add at the end of the rspamd configuration
+          file.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "rspamd";
+        description = ''
+          User to use when no root privileges are required.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "rspamd";
+        description = ''
+          Group to use when no root privileges are required.
+        '';
+      };
+
+      postfix = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Add rspamd milter to postfix main.conf";
+        };
+
+        config = mkOption {
+          type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
+          description = ''
+            Addon to postfix configuration
+          '';
+          default = {
+            smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+            non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+          };
+          example = {
+            smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+            non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+          };
+        };
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.rspamd.overrides = configOverrides;
+    services.rspamd.workers = mkIf cfg.postfix.enable {
+      controller = {};
+      rspamd_proxy = {
+        bindSockets = [ {
+          mode = "0660";
+          socket = "/run/rspamd/rspamd-milter.sock";
+          owner = cfg.user;
+          group = postfixCfg.group;
+        } ];
+        extraConfig = ''
+          upstream "local" {
+            default = yes; # Self-scan upstreams are always default
+            self_scan = yes; # Enable self-scan
+          }
+        '';
+      };
+    };
+    services.postfix.config = mkIf cfg.postfix.enable cfg.postfix.config;
+
+    # Allow users to run 'rspamc' and 'rspamadm'.
+    environment.systemPackages = [ pkgs.rspamd ];
+
+    users.users.${cfg.user} = {
+      description = "rspamd daemon";
+      uid = config.ids.uids.rspamd;
+      group = cfg.group;
+    };
+
+    users.groups.${cfg.group} = {
+      gid = config.ids.gids.rspamd;
+    };
+
+    environment.etc.rspamd.source = rspamdDir;
+
+    systemd.services.rspamd = {
+      description = "Rspamd Service";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      restartTriggers = [ rspamdDir ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} --user=${cfg.user} --group=${cfg.group} --pid=/run/rspamd.pid -c /etc/rspamd/rspamd.conf -f";
+        Restart = "always";
+        RuntimeDirectory = "rspamd";
+        PrivateTmp = true;
+      };
+
+      preStart = ''
+        ${pkgs.coreutils}/bin/mkdir -p /var/lib/rspamd
+        ${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} /var/lib/rspamd
+      '';
+    };
+  };
+  imports = [
+    (mkRemovedOptionModule [ "services" "rspamd" "socketActivation" ]
+	     "Socket activation never worked correctly and could at this time not be fixed and so was removed")
+    (mkRenamedOptionModule [ "services" "rspamd" "bindSocket" ] [ "services" "rspamd" "workers" "normal" "bindSockets" ])
+    (mkRenamedOptionModule [ "services" "rspamd" "bindUISocket" ] [ "services" "rspamd" "workers" "controller" "bindSockets" ])
+    (mkRemovedOptionModule [ "services" "rmilter" ] "Use services.rspamd.* instead to set up milter service")
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/mail/rss2email.nix b/nixpkgs/nixos/modules/services/mail/rss2email.nix
new file mode 100644
index 000000000000..196e10bf7f28
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/rss2email.nix
@@ -0,0 +1,138 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.rss2email;
+in {
+
+  ###### interface
+
+  options = {
+
+    services.rss2email = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable rss2email.";
+      };
+
+      to = mkOption {
+        type = types.str;
+        description = "Mail address to which to send emails";
+      };
+
+      interval = mkOption {
+        type = types.str;
+        default = "12h";
+        description = "How often to check the feeds, in systemd interval format";
+      };
+
+      config = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool ]);
+        default = {};
+        description = ''
+          The configuration to give rss2email.
+
+          Default will use system-wide <literal>sendmail</literal> to send the
+          email. This is rss2email's default when running
+          <literal>r2e new</literal>.
+
+          This set contains key-value associations that will be set in the
+          <literal>[DEFAULT]</literal> block along with the
+          <literal>to</literal> parameter.
+
+          See <literal>man r2e</literal> for more information on which
+          parameters are accepted.
+        '';
+      };
+
+      feeds = mkOption {
+        description = "The feeds to watch.";
+        type = types.attrsOf (types.submodule {
+          options = {
+            url = mkOption {
+              type = types.str;
+              description = "The URL at which to fetch the feed.";
+            };
+
+            to = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Email address to which to send feed items.
+
+                If <literal>null</literal>, this will not be set in the
+                configuration file, and rss2email will make it default to
+                <literal>rss2email.to</literal>.
+              '';
+            };
+          };
+        });
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    users.groups = {
+      rss2email.gid = config.ids.gids.rss2email;
+    };
+
+    users.users = {
+      rss2email = {
+        description = "rss2email user";
+        uid = config.ids.uids.rss2email;
+        group = "rss2email";
+      };
+    };
+
+    environment.systemPackages = with pkgs; [ rss2email ];
+
+    services.rss2email.config.to = cfg.to;
+
+    system.activationScripts.rss2email = lib.stringAfter [ "users" ] ''
+      if [ -e /var/rss2email -a ! -e /var/lib/rss2email ]; then
+          mv /var/rss2email /var/lib/rss2email
+      fi
+    '';
+
+    systemd.services.rss2email = let
+      conf = pkgs.writeText "rss2email.cfg" (lib.generators.toINI {} ({
+          DEFAULT = cfg.config;
+        } // lib.mapAttrs' (name: feed: nameValuePair "feed.${name}" (
+          { inherit (feed) url; } //
+          lib.optionalAttrs (feed.to != null) { inherit (feed) to; }
+        )) cfg.feeds
+      ));
+    in
+    {
+      preStart = ''
+        cp -f ${conf} /var/lib/rss2email/conf.cfg
+        if [ ! -f /var/lib/rss2email/db.json ]; then
+          echo '{"version":2,"feeds":[]}' > /var/lib/rss2email/db.json
+        fi
+      '';
+      path = [ pkgs.system-sendmail ];
+      serviceConfig = {
+        StateDirectory = "rss2email";
+        ExecStart =
+          "${pkgs.rss2email}/bin/r2e -c /var/lib/rss2email/conf.cfg -d /var/lib/rss2email/db.json run";
+        User = "rss2email";
+      };
+    };
+
+    systemd.timers.rss2email = {
+      partOf = [ "rss2email.service" ];
+      wantedBy = [ "timers.target" ];
+      timerConfig.OnBootSec = "0";
+      timerConfig.OnUnitActiveSec = cfg.interval;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixpkgs/nixos/modules/services/mail/spamassassin.nix b/nixpkgs/nixos/modules/services/mail/spamassassin.nix
new file mode 100644
index 000000000000..4e642542ec66
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/spamassassin.nix
@@ -0,0 +1,184 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.spamassassin;
+  spamassassin-local-cf = pkgs.writeText "local.cf" cfg.config;
+
+in
+
+{
+  options = {
+
+    services.spamassassin = {
+      enable = mkEnableOption "the SpamAssassin daemon";
+
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        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 = with types; either str path;
+        description = "The SpamAssassin init.pre config.";
+        apply = val: if builtins.isPath val then val else pkgs.writeText "init.pre" val;
+        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
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."mail/spamassassin/init.pre".source = cfg.initPreConf;
+    environment.etc."mail/spamassassin/local.cf".source = spamassassin-local-cf;
+
+    # Allow users to run 'spamc'.
+    environment.systemPackages = [ pkgs.spamassassin ];
+
+    users.users.spamd = {
+      description = "Spam Assassin Daemon";
+      uid = config.ids.uids.spamd;
+      group = "spamd";
+    };
+
+    users.groups.spamd = {
+      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/" 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" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.spamassassin}/bin/spamd ${optionalString cfg.debug "-D"} --username=spamd --groupname=spamd --virtual-config-dir=/var/lib/spamassassin/user-%u --allow-tell --pidfile=/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 = ''
+        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/" 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/nixpkgs/nixos/modules/services/mail/sympa.nix b/nixpkgs/nixos/modules/services/mail/sympa.nix
new file mode 100644
index 000000000000..0cad09927b2f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/mail/sympa.nix
@@ -0,0 +1,594 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sympa;
+  dataDir = "/var/lib/sympa";
+  user = "sympa";
+  group = "sympa";
+  pkg = pkgs.sympa;
+  fqdns = attrNames cfg.domains;
+  usingNginx = cfg.web.enable && cfg.web.server == "nginx";
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "MySQL";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "PostgreSQL";
+
+  sympaSubServices = [
+    "sympa-archive.service"
+    "sympa-bounce.service"
+    "sympa-bulk.service"
+    "sympa-task.service"
+  ];
+
+  # common for all services including wwsympa
+  commonServiceConfig = {
+    StateDirectory = "sympa";
+    ProtectHome = true;
+    ProtectSystem = "full";
+    ProtectControlGroups = true;
+  };
+
+  # wwsympa has its own service config
+  sympaServiceConfig = srv: {
+    Type = "simple";
+    Restart = "always";
+    ExecStart = "${pkg}/bin/${srv}.pl --foreground";
+    PIDFile = "/run/sympa/${srv}.pid";
+    User = user;
+    Group = group;
+
+    # avoid duplicating log messageges in journal
+    StandardError = "null";
+  } // commonServiceConfig;
+
+  configVal = value:
+    if isBool value then
+      if value then "on" else "off"
+    else toString value;
+  configGenerator = c: concatStrings (flip mapAttrsToList c (key: val: "${key}\t${configVal val}\n"));
+
+  mainConfig = pkgs.writeText "sympa.conf" (configGenerator cfg.settings);
+  robotConfig = fqdn: domain: pkgs.writeText "${fqdn}-robot.conf" (configGenerator domain.settings);
+
+  transport = pkgs.writeText "transport.sympa" (concatStringsSep "\n" (flip map fqdns (domain: ''
+    ${domain}                        error:User unknown in recipient table
+    sympa@${domain}                  sympa:sympa@${domain}
+    listmaster@${domain}             sympa:listmaster@${domain}
+    bounce@${domain}                 sympabounce:sympa@${domain}
+    abuse-feedback-report@${domain}  sympabounce:sympa@${domain}
+  '')));
+
+  virtual = pkgs.writeText "virtual.sympa" (concatStringsSep "\n" (flip map fqdns (domain: ''
+    sympa-request@${domain}  postmaster@localhost
+    sympa-owner@${domain}    postmaster@localhost
+  '')));
+
+  listAliases = pkgs.writeText "list_aliases.tt2" ''
+    #--- [% list.name %]@[% list.domain %]: list transport map created at [% date %]
+    [% list.name %]@[% list.domain %] sympa:[% list.name %]@[% list.domain %]
+    [% list.name %]-request@[% list.domain %] sympa:[% list.name %]-request@[% list.domain %]
+    [% list.name %]-editor@[% list.domain %] sympa:[% list.name %]-editor@[% list.domain %]
+    #[% list.name %]-subscribe@[% list.domain %] sympa:[% list.name %]-subscribe@[%list.domain %]
+    [% list.name %]-unsubscribe@[% list.domain %] sympa:[% list.name %]-unsubscribe@[% list.domain %]
+    [% list.name %][% return_path_suffix %]@[% list.domain %] sympabounce:[% list.name %]@[% list.domain %]
+  '';
+
+  enabledFiles = filterAttrs (n: v: v.enable) cfg.settingsFile;
+in
+{
+
+  ###### interface
+  options.services.sympa = with types; {
+
+    enable = mkEnableOption "Sympa mailing list manager";
+
+    lang = mkOption {
+      type = str;
+      default = "en_US";
+      example = "cs";
+      description = ''
+        Default Sympa language.
+        See <link xlink:href='https://github.com/sympa-community/sympa/tree/sympa-6.2/po/sympa' />
+        for available options.
+      '';
+    };
+
+    listMasters = mkOption {
+      type = listOf str;
+      example = [ "postmaster@sympa.example.org" ];
+      description = ''
+        The list of the email addresses of the listmasters
+        (users authorized to perform global server commands).
+      '';
+    };
+
+    mainDomain = mkOption {
+      type = nullOr str;
+      default = null;
+      example = "lists.example.org";
+      description = ''
+        Main domain to be used in <filename>sympa.conf</filename>.
+        If <literal>null</literal>, one of the <option>services.sympa.domains</option> is chosen for you.
+      '';
+    };
+
+    domains = mkOption {
+      type = attrsOf (submodule ({ name, config, ... }: {
+        options = {
+          webHost = mkOption {
+            type = nullOr str;
+            default = null;
+            example = "archive.example.org";
+            description = ''
+              Domain part of the web interface URL (no web interface for this domain if <literal>null</literal>).
+              DNS record of type A (or AAAA or CNAME) has to exist with this value.
+            '';
+          };
+          webLocation = mkOption {
+            type = str;
+            default = "/";
+            example = "/sympa";
+            description = "URL path part of the web interface.";
+          };
+          settings = mkOption {
+            type = attrsOf (oneOf [ str int bool ]);
+            default = {};
+            example = {
+              default_max_list_members = 3;
+            };
+            description = ''
+              The <filename>robot.conf</filename> configuration file as key value set.
+              See <link xlink:href='https://sympa-community.github.io/gpldoc/man/sympa.conf.5.html' />
+              for list of configuration parameters.
+            '';
+          };
+        };
+
+        config.settings = mkIf (cfg.web.enable && config.webHost != null) {
+          wwsympa_url = mkDefault "https://${config.webHost}${strings.removeSuffix "/" config.webLocation}";
+        };
+      }));
+
+      description = ''
+        Email domains handled by this instance. There have
+        to be MX records for keys of this attribute set.
+      '';
+      example = literalExample ''
+        {
+          "lists.example.org" = {
+            webHost = "lists.example.org";
+            webLocation = "/";
+          };
+          "sympa.example.com" = {
+            webHost = "example.com";
+            webLocation = "/sympa";
+          };
+        }
+      '';
+    };
+
+    database = {
+      type = mkOption {
+        type = enum [ "SQLite" "PostgreSQL" "MySQL" ];
+        default = "SQLite";
+        example = "MySQL";
+        description = "Database engine to use.";
+      };
+
+      host = mkOption {
+        type = nullOr str;
+        default = null;
+        description = ''
+          Database host address.
+
+          For MySQL, use <literal>localhost</literal> to connect using Unix domain socket.
+
+          For PostgreSQL, use path to directory (e.g. <filename>/run/postgresql</filename>)
+          to connect using Unix domain socket located in this directory.
+
+          Use <literal>null</literal> to fall back on Sympa default, or when using
+          <option>services.sympa.database.createLocally</option>.
+        '';
+      };
+
+      port = mkOption {
+        type = nullOr port;
+        default = null;
+        description = "Database port. Use <literal>null</literal> for default port.";
+      };
+
+      name = mkOption {
+        type = str;
+        default = if cfg.database.type == "SQLite" then "${dataDir}/sympa.sqlite" else "sympa";
+        defaultText = ''if database.type == "SQLite" then "${dataDir}/sympa.sqlite" else "sympa"'';
+        description = ''
+          Database name. When using SQLite this must be an absolute
+          path to the database file.
+        '';
+      };
+
+      user = mkOption {
+        type = nullOr str;
+        default = user;
+        description = "Database user. The system user name is used as a default.";
+      };
+
+      passwordFile = mkOption {
+        type = nullOr path;
+        default = null;
+        example = "/run/keys/sympa-dbpassword";
+        description = ''
+          A file containing the password for <option>services.sympa.database.user</option>.
+        '';
+      };
+
+      createLocally = mkOption {
+        type = bool;
+        default = true;
+        description = "Whether to create a local database automatically.";
+      };
+    };
+
+    web = {
+      enable = mkOption {
+        type = bool;
+        default = true;
+        description = "Whether to enable Sympa web interface.";
+      };
+
+      server = mkOption {
+        type = enum [ "nginx" "none" ];
+        default = "nginx";
+        description = ''
+          The webserver used for the Sympa web interface. Set it to `none` if you want to configure it yourself.
+          Further nginx configuration can be done by adapting
+          <option>services.nginx.virtualHosts.<replaceable>name</replaceable></option>.
+        '';
+      };
+
+      https = mkOption {
+        type = bool;
+        default = true;
+        description = ''
+          Whether to use HTTPS. When nginx integration is enabled, this option forces SSL and enables ACME.
+          Please note that Sympa web interface always uses https links even when this option is disabled.
+        '';
+      };
+
+      fcgiProcs = mkOption {
+        type = ints.positive;
+        default = 2;
+        description = "Number of FastCGI processes to fork.";
+      };
+    };
+
+    mta = {
+      type = mkOption {
+        type = enum [ "postfix" "none" ];
+        default = "postfix";
+        description = ''
+          Mail transfer agent (MTA) integration. Use <literal>none</literal> if you want to configure it yourself.
+
+          The <literal>postfix</literal> integration sets up local Postfix instance that will pass incoming
+          messages from configured domains to Sympa. You still need to configure at least outgoing message
+          handling using e.g. <option>services.postfix.relayHost</option>.
+        '';
+      };
+    };
+
+    settings = mkOption {
+      type = attrsOf (oneOf [ str int bool ]);
+      default = {};
+      example = literalExample ''
+        {
+          default_home = "lists";
+          viewlogs_page_size = 50;
+        }
+      '';
+      description = ''
+        The <filename>sympa.conf</filename> configuration file as key value set.
+        See <link xlink:href='https://sympa-community.github.io/gpldoc/man/sympa.conf.5.html' />
+        for list of configuration parameters.
+      '';
+    };
+
+    settingsFile = mkOption {
+      type = attrsOf (submodule ({ name, config, ... }: {
+        options = {
+          enable = mkOption {
+            type = bool;
+            default = true;
+            description = "Whether this file should be generated. This option allows specific files to be disabled.";
+          };
+          text = mkOption {
+            default = null;
+            type = nullOr lines;
+            description = "Text of the file.";
+          };
+          source = mkOption {
+            type = path;
+            description = "Path of the source file.";
+          };
+        };
+
+        config.source = mkIf (config.text != null) (mkDefault (pkgs.writeText "sympa-${baseNameOf name}" config.text));
+      }));
+      default = {};
+      example = literalExample ''
+        {
+          "list_data/lists.example.org/help" = {
+            text = "subject This list provides help to users";
+          };
+        }
+      '';
+      description = "Set of files to be linked in <filename>${dataDir}</filename>.";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.sympa.settings = (mapAttrs (_: v: mkDefault v) {
+      domain     = if cfg.mainDomain != null then cfg.mainDomain else head fqdns;
+      listmaster = concatStringsSep "," cfg.listMasters;
+      lang       = cfg.lang;
+
+      home        = "${dataDir}/list_data";
+      arc_path    = "${dataDir}/arc";
+      bounce_path = "${dataDir}/bounce";
+
+      sendmail = "${pkgs.system-sendmail}/bin/sendmail";
+
+      db_type = cfg.database.type;
+      db_name = cfg.database.name;
+    }
+    // (optionalAttrs (cfg.database.host != null) {
+      db_host = cfg.database.host;
+    })
+    // (optionalAttrs mysqlLocal {
+      db_host = "localhost"; # use unix domain socket
+    })
+    // (optionalAttrs pgsqlLocal {
+      db_host = "/run/postgresql"; # use unix domain socket
+    })
+    // (optionalAttrs (cfg.database.port != null) {
+      db_port = cfg.database.port;
+    })
+    // (optionalAttrs (cfg.database.user != null) {
+      db_user = cfg.database.user;
+    })
+    // (optionalAttrs (cfg.mta.type == "postfix") {
+      sendmail_aliases = "${dataDir}/sympa_transport";
+      aliases_program  = "${pkgs.postfix}/bin/postmap";
+      aliases_db_type  = "hash";
+    })
+    // (optionalAttrs cfg.web.enable {
+      static_content_path = "${dataDir}/static_content";
+      css_path            = "${dataDir}/static_content/css";
+      pictures_path       = "${dataDir}/static_content/pictures";
+      mhonarc             = "${pkgs.perlPackages.MHonArc}/bin/mhonarc";
+    }));
+
+    services.sympa.settingsFile = {
+      "virtual.sympa"        = mkDefault { source = virtual; };
+      "transport.sympa"      = mkDefault { source = transport; };
+      "etc/list_aliases.tt2" = mkDefault { source = listAliases; };
+    }
+    // (flip mapAttrs' cfg.domains (fqdn: domain:
+          nameValuePair "etc/${fqdn}/robot.conf" (mkDefault { source = robotConfig fqdn domain; })));
+
+    environment = {
+      systemPackages = [ pkg ];
+    };
+
+    users.users.${user} = {
+      description = "Sympa mailing list manager user";
+      group = group;
+      home = dataDir;
+      createHome = false;
+      isSystemUser = true;
+    };
+
+    users.groups.${group} = {};
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.sympa.database.user must be set to ${user} if services.sympa.database.createLocally is set to true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.sympa.database.createLocally is set to true";
+      }
+    ];
+
+    systemd.tmpfiles.rules = [
+      "d  ${dataDir}                   0711 ${user} ${group} - -"
+      "d  ${dataDir}/etc               0700 ${user} ${group} - -"
+      "d  ${dataDir}/spool             0700 ${user} ${group} - -"
+      "d  ${dataDir}/list_data         0700 ${user} ${group} - -"
+      "d  ${dataDir}/arc               0700 ${user} ${group} - -"
+      "d  ${dataDir}/bounce            0700 ${user} ${group} - -"
+      "f  ${dataDir}/sympa_transport   0600 ${user} ${group} - -"
+
+      # force-copy static_content so it's up to date with package
+      # set permissions for wwsympa which needs write access (...)
+      "R  ${dataDir}/static_content    -    -       -        - -"
+      "C  ${dataDir}/static_content    0711 ${user} ${group} - ${pkg}/var/lib/sympa/static_content"
+      "e  ${dataDir}/static_content/*  0711 ${user} ${group} - -"
+
+      "d  /run/sympa                   0755 ${user} ${group} - -"
+    ]
+    ++ (flip concatMap fqdns (fqdn: [
+      "d  ${dataDir}/etc/${fqdn}       0700 ${user} ${group} - -"
+      "d  ${dataDir}/list_data/${fqdn} 0700 ${user} ${group} - -"
+    ]))
+    #++ (flip mapAttrsToList enabledFiles (k: v:
+    #  "L+ ${dataDir}/${k}              -    -       -        - ${v.source}"
+    #))
+    ++ (concatLists (flip mapAttrsToList enabledFiles (k: v: [
+      # sympa doesn't handle symlinks well (e.g. fails to create locks)
+      # force-copy instead
+      "R ${dataDir}/${k}              -    -       -        - -"
+      "C ${dataDir}/${k}              0700 ${user}  ${group} - ${v.source}"
+    ])));
+
+    systemd.services.sympa = {
+      description = "Sympa mailing list manager";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = sympaSubServices;
+      before = sympaSubServices;
+      serviceConfig = sympaServiceConfig "sympa_msg";
+
+      preStart = ''
+        umask 0077
+
+        cp -f ${mainConfig} ${dataDir}/etc/sympa.conf
+        ${optionalString (cfg.database.passwordFile != null) ''
+          chmod u+w ${dataDir}/etc/sympa.conf
+          echo -n "db_passwd " >> ${dataDir}/etc/sympa.conf
+          cat ${cfg.database.passwordFile} >> ${dataDir}/etc/sympa.conf
+        ''}
+
+        ${optionalString (cfg.mta.type == "postfix") ''
+          ${pkgs.postfix}/bin/postmap hash:${dataDir}/virtual.sympa
+          ${pkgs.postfix}/bin/postmap hash:${dataDir}/transport.sympa
+        ''}
+        ${pkg}/bin/sympa_newaliases.pl
+        ${pkg}/bin/sympa.pl --health_check
+      '';
+    };
+    systemd.services.sympa-archive = {
+      description = "Sympa mailing list manager (archiving)";
+      bindsTo = [ "sympa.service" ];
+      serviceConfig = sympaServiceConfig "archived";
+    };
+    systemd.services.sympa-bounce = {
+      description = "Sympa mailing list manager (bounce processing)";
+      bindsTo = [ "sympa.service" ];
+      serviceConfig = sympaServiceConfig "bounced";
+    };
+    systemd.services.sympa-bulk = {
+      description = "Sympa mailing list manager (message distribution)";
+      bindsTo = [ "sympa.service" ];
+      serviceConfig = sympaServiceConfig "bulk";
+    };
+    systemd.services.sympa-task = {
+      description = "Sympa mailing list manager (task management)";
+      bindsTo = [ "sympa.service" ];
+      serviceConfig = sympaServiceConfig "task_manager";
+    };
+
+    systemd.services.wwsympa = mkIf usingNginx {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "sympa.service" ];
+      serviceConfig = {
+        Type = "forking";
+        PIDFile = "/run/sympa/wwsympa.pid";
+        Restart = "always";
+        ExecStart = ''${pkgs.spawn_fcgi}/bin/spawn-fcgi \
+          -u ${user} \
+          -g ${group} \
+          -U nginx \
+          -M 0600 \
+          -F ${toString cfg.web.fcgiProcs} \
+          -P /run/sympa/wwsympa.pid \
+          -s /run/sympa/wwsympa.socket \
+          -- ${pkg}/lib/sympa/cgi/wwsympa.fcgi
+        '';
+
+      } // commonServiceConfig;
+    };
+
+    services.nginx.enable = mkIf usingNginx true;
+    services.nginx.virtualHosts = mkIf usingNginx (let
+      vHosts = unique (remove null (mapAttrsToList (_k: v: v.webHost) cfg.domains));
+      hostLocations = host: map (v: v.webLocation) (filter (v: v.webHost == host) (attrValues cfg.domains));
+      httpsOpts = optionalAttrs cfg.web.https { forceSSL = mkDefault true; enableACME = mkDefault true; };
+    in
+    genAttrs vHosts (host: {
+      locations = genAttrs (hostLocations host) (loc: {
+        extraConfig = ''
+          include ${config.services.nginx.package}/conf/fastcgi_params;
+
+          fastcgi_pass unix:/run/sympa/wwsympa.socket;
+          fastcgi_split_path_info ^(${loc})(.*)$;
+
+          fastcgi_param PATH_INFO       $fastcgi_path_info;
+          fastcgi_param SCRIPT_FILENAME ${pkg}/lib/sympa/cgi/wwsympa.fcgi;
+        '';
+      }) // {
+        "/static-sympa/".alias = "${dataDir}/static_content/";
+      };
+    } // httpsOpts));
+
+    services.postfix = mkIf (cfg.mta.type == "postfix") {
+      enable = true;
+      recipientDelimiter = "+";
+      config = {
+        virtual_alias_maps = [ "hash:${dataDir}/virtual.sympa" ];
+        virtual_mailbox_maps = [
+          "hash:${dataDir}/transport.sympa"
+          "hash:${dataDir}/sympa_transport"
+          "hash:${dataDir}/virtual.sympa"
+        ];
+        virtual_mailbox_domains = [ "hash:${dataDir}/transport.sympa" ];
+        transport_maps = [
+          "hash:${dataDir}/transport.sympa"
+          "hash:${dataDir}/sympa_transport"
+        ];
+      };
+      masterConfig = {
+        "sympa" = {
+          type = "unix";
+          privileged = true;
+          chroot = false;
+          command = "pipe";
+          args = [
+            "flags=hqRu"
+            "user=${user}"
+            "argv=${pkg}/libexec/queue"
+            "\${nexthop}"
+          ];
+        };
+        "sympabounce" = {
+          type = "unix";
+          privileged = true;
+          chroot = false;
+          command = "pipe";
+          args = [
+            "flags=hqRu"
+            "user=${user}"
+            "argv=${pkg}/libexec/bouncequeue"
+            "\${nexthop}"
+          ];
+        };
+      };
+    };
+
+    services.mysql = optionalAttrs mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.postgresql = optionalAttrs pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ mmilata sorki ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/airsonic.nix b/nixpkgs/nixos/modules/services/misc/airsonic.nix
new file mode 100644
index 000000000000..5cc2ff7f4bd1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/airsonic.nix
@@ -0,0 +1,155 @@
+{ 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.
+        '';
+      };
+
+      virtualHost = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        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.
+        '';
+      };
+
+      jvmOptions = mkOption {
+        description = ''
+          Extra command line options for the JVM running AirSonic.
+          Useful for sending jukebox output to non-default alsa
+          devices.
+        '';
+        default = [
+        ];
+        type = types.listOf types.str;
+        example = [
+          "-Djavax.sound.sampled.Clip='#CODEC [plughw:1,0]'"
+          "-Djavax.sound.sampled.Port='#Port CODEC [hw:1]'"
+          "-Djavax.sound.sampled.SourceDataLine='#CODEC [plughw:1,0]'"
+          "-Djavax.sound.sampled.TargetDataLine='#CODEC [plughw:1,0]'"
+        ];
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.airsonic = {
+      description = "Airsonic Media Server";
+      after = [ "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 \
+          ${optionalString (cfg.virtualHost != null)
+            "-Dserver.use-forward-headers=true"} \
+          ${toString cfg.jvmOptions} \
+          -verbose:gc \
+          -jar ${pkgs.airsonic}/webapps/airsonic.war
+        '';
+        Restart = "always";
+        User = "airsonic";
+        UMask = "0022";
+      };
+    };
+
+    services.nginx = mkIf (cfg.virtualHost != null) {
+      enable = true;
+      recommendedProxySettings = true;
+      virtualHosts.${cfg.virtualHost} = {
+        locations.${cfg.contextPath}.proxyPass = "http://${cfg.listenAddress}:${toString cfg.port}";
+      };
+    };
+
+    users.users.airsonic = {
+      description = "Airsonic service user";
+      name = cfg.user;
+      home = cfg.home;
+      createHome = true;
+      isSystemUser = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/ankisyncd.nix b/nixpkgs/nixos/modules/services/misc/ankisyncd.nix
new file mode 100644
index 000000000000..5fc19649d3d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ankisyncd.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ankisyncd;
+
+  name = "ankisyncd";
+
+  stateDir = "/var/lib/${name}";
+
+  authDbPath = "${stateDir}/auth.db";
+
+  sessionDbPath = "${stateDir}/session.db";
+
+  configFile = pkgs.writeText "ankisyncd.conf" (lib.generators.toINI {} {
+    sync_app = {
+      host = cfg.host;
+      port = cfg.port;
+      data_root = stateDir;
+      auth_db_path = authDbPath;
+      session_db_path = sessionDbPath;
+
+      base_url = "/sync/";
+      base_media_url = "/msync/";
+    };
+  });
+in
+  {
+    options.services.ankisyncd = {
+      enable = mkEnableOption "ankisyncd";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.ankisyncd;
+        defaultText = literalExample "pkgs.ankisyncd";
+        description = "The package to use for the ankisyncd command.";
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "ankisyncd host";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 27701;
+        description = "ankisyncd port";
+      };
+
+      openFirewall = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to open the firewall for the specified port.";
+      };
+    };
+
+    config = mkIf cfg.enable {
+      networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+      environment.etc."ankisyncd/ankisyncd.conf".source = configFile;
+
+      systemd.services.ankisyncd = {
+        description = "ankisyncd - Anki sync server";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        path = [ cfg.package ];
+
+        serviceConfig = {
+          Type = "simple";
+          DynamicUser = true;
+          StateDirectory = name;
+          ExecStart = "${cfg.package}/bin/ankisyncd";
+          Restart = "always";
+        };
+      };
+    };
+  }
diff --git a/nixpkgs/nixos/modules/services/misc/apache-kafka.nix b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix
new file mode 100644
index 000000000000..f3a650a260f1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/apache-kafka.nix
@@ -0,0 +1,154 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.apache-kafka;
+
+  serverProperties =
+    if cfg.serverProperties != null then
+      cfg.serverProperties
+    else
+      ''
+        # Generated by nixos
+        broker.id=${toString cfg.brokerId}
+        port=${toString cfg.port}
+        host.name=${cfg.hostname}
+        log.dirs=${concatStringsSep "," cfg.logDirs}
+        zookeeper.connect=${cfg.zookeeper}
+        ${toString cfg.extraProperties}
+      '';
+
+  serverConfig = pkgs.writeText "server.properties" serverProperties;
+  logConfig = pkgs.writeText "log4j.properties" cfg.log4jProperties;
+
+in {
+
+  options.services.apache-kafka = {
+    enable = mkOption {
+      description = "Whether to enable Apache Kafka.";
+      default = false;
+      type = types.bool;
+    };
+
+    brokerId = mkOption {
+      description = "Broker ID.";
+      default = -1;
+      type = types.int;
+    };
+
+    port = mkOption {
+      description = "Port number the broker should listen on.";
+      default = 9092;
+      type = types.int;
+    };
+
+    hostname = mkOption {
+      description = "Hostname the broker should bind to.";
+      default = "localhost";
+      type = types.str;
+    };
+
+    logDirs = mkOption {
+      description = "Log file directories";
+      default = [ "/tmp/kafka-logs" ];
+      type = types.listOf types.path;
+    };
+
+    zookeeper = mkOption {
+      description = "Zookeeper connection string";
+      default = "localhost:2181";
+      type = types.str;
+    };
+
+    extraProperties = mkOption {
+      description = "Extra properties for server.properties.";
+      type = types.nullOr types.lines;
+      default = null;
+    };
+
+    serverProperties = mkOption {
+      description = ''
+        Complete server.properties content. Other server.properties config
+        options will be ignored if this option is used.
+      '';
+      type = types.nullOr types.lines;
+      default = null;
+    };
+
+    log4jProperties = mkOption {
+      description = "Kafka log4j property configuration.";
+      default = ''
+        log4j.rootLogger=INFO, stdout
+
+        log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+        log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+        log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n
+      '';
+      type = types.lines;
+    };
+
+    jvmOptions = mkOption {
+      description = "Extra command line options for the JVM running Kafka.";
+      default = [
+        "-server"
+        "-Xmx1G"
+        "-Xms1G"
+        "-XX:+UseCompressedOops"
+        "-XX:+UseParNewGC"
+        "-XX:+UseConcMarkSweepGC"
+        "-XX:+CMSClassUnloadingEnabled"
+        "-XX:+CMSScavengeBeforeRemark"
+        "-XX:+DisableExplicitGC"
+        "-Djava.awt.headless=true"
+        "-Djava.net.preferIPv4Stack=true"
+      ];
+      type = types.listOf types.str;
+      example = [
+        "-Djava.net.preferIPv4Stack=true"
+        "-Dcom.sun.management.jmxremote"
+        "-Dcom.sun.management.jmxremote.local.only=true"
+      ];
+    };
+
+    package = mkOption {
+      description = "The kafka package to use";
+      default = pkgs.apacheKafka;
+      defaultText = "pkgs.apacheKafka";
+      type = types.package;
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [cfg.package];
+
+    users.users.apache-kafka = {
+      uid = config.ids.uids.apache-kafka;
+      description = "Apache Kafka daemon user";
+      home = head cfg.logDirs;
+    };
+
+    systemd.tmpfiles.rules = map (logDir: "d '${logDir}' 0700 apache-kafka - - -") cfg.logDirs;
+
+    systemd.services.apache-kafka = {
+      description = "Apache Kafka Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.jre}/bin/java \
+            -cp "${cfg.package}/libs/*" \
+            -Dlog4j.configuration=file:${logConfig} \
+            ${toString cfg.jvmOptions} \
+            kafka.Kafka \
+            ${serverConfig}
+        '';
+        User = "apache-kafka";
+        SuccessExitStatus = "0 143";
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/autofs.nix b/nixpkgs/nixos/modules/services/misc/autofs.nix
new file mode 100644
index 000000000000..5e7c1e668288
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/autofs.nix
@@ -0,0 +1,99 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.autofs;
+
+  autoMaster = pkgs.writeText "auto.master" cfg.autoMaster;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.autofs = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Mount filesystems on demand. Unmount them automatically.
+          You may also be interested in afuse.
+        '';
+      };
+
+      autoMaster = mkOption {
+        type = types.str;
+        example = literalExample ''
+          let
+            mapConf = pkgs.writeText "auto" '''
+             kernel    -ro,soft,intr       ftp.kernel.org:/pub/linux
+             boot      -fstype=ext2        :/dev/hda1
+             windoze   -fstype=smbfs       ://windoze/c
+             removable -fstype=ext2        :/dev/hdd
+             cd        -fstype=iso9660,ro  :/dev/hdc
+             floppy    -fstype=auto        :/dev/fd0
+             server    -rw,hard,intr       / -ro myserver.me.org:/ \
+                                           /usr myserver.me.org:/usr \
+                                           /home myserver.me.org:/home
+            ''';
+          in '''
+            /auto file:''${mapConf}
+          '''
+        '';
+        description = ''
+          Contents of <literal>/etc/auto.master</literal> file. See <command>auto.master(5)</command> and <command>autofs(5)</command>.
+        '';
+      };
+
+      timeout = mkOption {
+        default = 600;
+        description = "Set the global minimum timeout, in seconds, until directories are unmounted";
+      };
+
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Pass -d and -7 to automount and write log to the system journal.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    boot.kernelModules = [ "autofs4" ];
+
+    systemd.services.autofs =
+      { description = "Automounts filesystems on demand";
+        after = [ "network.target" "ypbind.service" "sssd.service" "network-online.target" ];
+        wants = [ "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        preStart = ''
+          # There should be only one autofs service managed by systemd, so this should be safe.
+          rm -f /tmp/autofs-running
+        '';
+
+        serviceConfig = {
+          Type = "forking";
+          PIDFile = "/run/autofs.pid";
+          ExecStart = "${pkgs.autofs5}/bin/automount ${optionalString cfg.debug "-d"} -p /run/autofs.pid -t ${builtins.toString cfg.timeout} ${autoMaster}";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        };
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/autorandr.nix b/nixpkgs/nixos/modules/services/misc/autorandr.nix
new file mode 100644
index 000000000000..cf7fb5f78d3d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/autorandr.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.autorandr;
+
+in {
+
+  options = {
+
+    services.autorandr = {
+      enable = mkEnableOption "handling of hotplug and sleep events by autorandr";
+
+      defaultTarget = mkOption {
+        default = "default";
+        type = types.str;
+        description = ''
+          Fallback if no monitor layout can be detected. See the docs
+          (https://github.com/phillipberndt/autorandr/blob/v1.0/README.md#how-to-use)
+          for further reference.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    services.udev.packages = [ pkgs.autorandr ];
+
+    environment.systemPackages = [ pkgs.autorandr ];
+
+    systemd.services.autorandr = {
+      wantedBy = [ "sleep.target" ];
+      description = "Autorandr execution hook";
+      after = [ "sleep.target" ];
+
+      serviceConfig = {
+        StartLimitInterval = 5;
+        StartLimitBurst = 1;
+        ExecStart = "${pkgs.autorandr}/bin/autorandr --batch --change --default ${cfg.defaultTarget}";
+        Type = "oneshot";
+        RemainAfterExit = false;
+      };
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/bazarr.nix b/nixpkgs/nixos/modules/services/misc/bazarr.nix
new file mode 100644
index 000000000000..d3fd5b08cc84
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/bazarr.nix
@@ -0,0 +1,76 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.bazarr;
+in
+{
+  options = {
+    services.bazarr = {
+      enable = mkEnableOption "bazarr, a subtitle manager for Sonarr and Radarr";
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open ports in the firewall for the bazarr web interface.";
+      };
+
+      listenPort = mkOption {
+        type = types.port;
+        default = 6767;
+        description = "Port on which the bazarr web interface should listen";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "bazarr";
+        description = "User account under which bazarr runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "bazarr";
+        description = "Group under which bazarr runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.bazarr = {
+      description = "bazarr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = rec {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectory = "bazarr";
+        SyslogIdentifier = "bazarr";
+        ExecStart = pkgs.writeShellScript "start-bazarr" ''
+          ${pkgs.bazarr}/bin/bazarr \
+            --config '/var/lib/${StateDirectory}' \
+            --port ${toString cfg.listenPort} \
+            --no-update True
+        '';
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listenPort ];
+    };
+
+    users.users = mkIf (cfg.user == "bazarr") {
+      bazarr = {
+        group = cfg.group;
+        home = "/var/lib/${config.systemd.services.bazarr.serviceConfig.StateDirectory}";
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "bazarr") {
+      bazarr = {};
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/beanstalkd.nix b/nixpkgs/nixos/modules/services/misc/beanstalkd.nix
new file mode 100644
index 000000000000..bcd133c97411
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/beanstalkd.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.beanstalkd;
+  pkg = pkgs.beanstalkd;
+in
+
+{
+  # interface
+
+  options = {
+    services.beanstalkd = {
+      enable = mkEnableOption "the Beanstalk work queue";
+
+      listen = {
+        port = mkOption {
+          type = types.int;
+          description = "TCP port that will be used to accept client connections.";
+          default = 11300;
+        };
+
+        address = mkOption {
+          type = types.str;
+          description = "IP address to listen on.";
+          default = "127.0.0.1";
+          example = "0.0.0.0";
+        };
+      };
+    };
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkg ];
+
+    systemd.services.beanstalkd = {
+      description = "Beanstalk Work Queue";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        Restart = "always";
+        ExecStart = "${pkg}/bin/beanstalkd -l ${cfg.listen.address} -p ${toString cfg.listen.port} -b $STATE_DIRECTORY";
+        StateDirectory = "beanstalkd";
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/bees.nix b/nixpkgs/nixos/modules/services/misc/bees.nix
new file mode 100644
index 000000000000..b0ed2d5c2862
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/bees.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.beesd;
+
+  logLevels = { emerg = 0; alert = 1; crit = 2; err = 3; warning = 4; notice = 5; info = 6; debug = 7; };
+
+  fsOptions = with types; {
+    options.spec = mkOption {
+      type = str;
+      description = ''
+        Description of how to identify the filesystem to be duplicated by this
+        instance of bees. Note that deduplication crosses subvolumes; one must
+        not configure multiple instances for subvolumes of the same filesystem
+        (or block devices which are part of the same filesystem), but only for
+        completely independent btrfs filesystems.
+        </para>
+        <para>
+        This must be in a format usable by findmnt; that could be a key=value
+        pair, or a bare path to a mount point.
+      '';
+      example = "LABEL=MyBulkDataDrive";
+    };
+    options.hashTableSizeMB = mkOption {
+      type = types.addCheck types.int (n: mod n 16 == 0);
+      default = 1024; # 1GB; default from upstream beesd script
+      description = ''
+        Hash table size in MB; must be a multiple of 16.
+        </para>
+        <para>
+        A larger ratio of index size to storage size means smaller blocks of
+        duplicate content are recognized.
+        </para>
+        <para>
+        If you have 1TB of data, a 4GB hash table (which is to say, a value of
+        4096) will permit 4KB extents (the smallest possible size) to be
+        recognized, whereas a value of 1024 -- creating a 1GB hash table --
+        will recognize only aligned duplicate blocks of 16KB.
+      '';
+    };
+    options.verbosity = mkOption {
+      type = types.enum (attrNames logLevels ++ attrValues logLevels);
+      apply = v: if isString v then logLevels.${v} else v;
+      default = "info";
+      description = "Log verbosity (syslog keyword/level).";
+    };
+    options.workDir = mkOption {
+      type = str;
+      default = ".beeshome";
+      description = ''
+        Name (relative to the root of the filesystem) of the subvolume where
+        the hash table will be stored.
+      '';
+    };
+    options.extraOptions = mkOption {
+      type = listOf str;
+      default = [];
+      description = ''
+        Extra command-line options passed to the daemon. See upstream bees documentation.
+      '';
+      example = literalExample ''
+        [ "--thread-count" "4" ]
+      '';
+    };
+  };
+
+in {
+
+  options.services.beesd = {
+    filesystems = mkOption {
+      type = with types; attrsOf (submodule fsOptions);
+      description = "BTRFS filesystems to run block-level deduplication on.";
+      default = { };
+      example = literalExample ''
+        {
+          root = {
+            spec = "LABEL=root";
+            hashTableSizeMB = 2048;
+            verbosity = "crit";
+            extraOptions = [ "--loadavg-target" "5.0" ];
+          };
+        }
+      '';
+    };
+  };
+  config = {
+    systemd.services = mapAttrs' (name: fs: nameValuePair "beesd@${name}" {
+      description = "Block-level BTRFS deduplication for %i";
+      after = [ "sysinit.target" ];
+
+      serviceConfig = let
+        configOpts = [
+          fs.spec
+          "verbosity=${toString fs.verbosity}"
+          "idxSizeMB=${toString fs.hashTableSizeMB}"
+          "workDir=${fs.workDir}"
+        ];
+        configOptsStr = escapeShellArgs configOpts;
+      in {
+        # Values from https://github.com/Zygo/bees/blob/v0.6.1/scripts/beesd%40.service.in
+        ExecStart = "${pkgs.bees}/bin/bees-service-wrapper run ${configOptsStr} -- --no-timestamps ${escapeShellArgs fs.extraOptions}";
+        ExecStopPost = "${pkgs.bees}/bin/bees-service-wrapper cleanup ${configOptsStr}";
+        CPUAccounting = true;
+        CPUWeight = 12;
+        IOSchedulingClass = "idle";
+        IOSchedulingPriority = 7;
+        IOWeight = 10;
+        KillMode = "control-group";
+        KillSignal = "SIGTERM";
+        MemoryAccounting = true;
+        Nice = 19;
+        Restart = "on-abnormal";
+        StartupCPUWeight = 25;
+        StartupIOWeight = 25;
+        SyslogIdentifier = "bees"; # would otherwise be "bees-service-wrapper"
+      };
+      wantedBy = ["multi-user.target"];
+    }) cfg.filesystems;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/bepasty.nix b/nixpkgs/nixos/modules/services/misc/bepasty.nix
new file mode 100644
index 000000000000..f69832e5b2bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/bepasty.nix
@@ -0,0 +1,179 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  gunicorn = pkgs.python3Packages.gunicorn;
+  bepasty = pkgs.bepasty;
+  gevent = pkgs.python3Packages.gevent;
+  python = pkgs.python3Packages.python;
+  cfg = config.services.bepasty;
+  user = "bepasty";
+  group = "bepasty";
+  default_home = "/var/lib/bepasty";
+in
+{
+  options.services.bepasty = {
+    enable = mkEnableOption "Bepasty servers";
+
+    servers = mkOption {
+      default = {};
+      description = ''
+        configure a number of bepasty servers which will be started with
+        gunicorn.
+        '';
+      type = with types ; attrsOf (submodule ({ config, ... } : {
+
+        options = {
+
+          bind = mkOption {
+            type = types.str;
+            description = ''
+              Bind address to be used for this server.
+              '';
+            example = "0.0.0.0:8000";
+            default = "127.0.0.1:8000";
+          };
+
+          dataDir = mkOption {
+            type = types.str;
+            description = ''
+              Path to the directory where the pastes will be saved to
+              '';
+            default = default_home+"/data";
+          };
+
+          defaultPermissions = mkOption {
+            type = types.str;
+            description = ''
+              default permissions for all unauthenticated accesses.
+              '';
+            example = "read,create,delete";
+            default = "read";
+          };
+
+          extraConfig = mkOption {
+            type = types.lines;
+            description = ''
+              Extra configuration for bepasty server to be appended on the
+              configuration.
+              see https://bepasty-server.readthedocs.org/en/latest/quickstart.html#configuring-bepasty
+              for all options.
+              '';
+            default = "";
+            example = ''
+              PERMISSIONS = {
+                'myadminsecret': 'admin,list,create,read,delete',
+              }
+              MAX_ALLOWED_FILE_SIZE = 5 * 1000 * 1000
+              '';
+          };
+
+          secretKey = mkOption {
+            type = types.str;
+            description = ''
+              server secret for safe session cookies, must be set.
+
+              Warning: this secret is stored in the WORLD-READABLE Nix store!
+
+              It's recommended to use <option>secretKeyFile</option>
+              which takes precedence over <option>secretKey</option>.
+              '';
+            default = "";
+          };
+
+          secretKeyFile = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            description = ''
+              A file that contains the server secret for safe session cookies, must be set.
+
+              <option>secretKeyFile</option> takes precedence over <option>secretKey</option>.
+
+              Warning: when <option>secretKey</option> is non-empty <option>secretKeyFile</option>
+              defaults to a file in the WORLD-READABLE Nix store containing that secret.
+              '';
+          };
+
+          workDir = mkOption {
+            type = types.str;
+            description = ''
+              Path to the working directory (used for config and pidfile).
+              Defaults to the users home directory.
+              '';
+            default = default_home;
+          };
+
+        };
+        config = {
+          secretKeyFile = mkDefault (
+            if config.secretKey != ""
+            then toString (pkgs.writeTextFile {
+              name = "bepasty-secret-key";
+              text = config.secretKey;
+            })
+            else null
+          );
+        };
+      }));
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ bepasty ];
+
+    # creates gunicorn systemd service for each configured server
+    systemd.services = mapAttrs' (name: server:
+      nameValuePair ("bepasty-server-${name}-gunicorn")
+        ({
+          description = "Bepasty Server ${name}";
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+          restartIfChanged = true;
+
+          environment = let
+            penv = python.buildEnv.override {
+              extraLibs = [ bepasty gevent ];
+            };
+          in {
+            BEPASTY_CONFIG = "${server.workDir}/bepasty-${name}.conf";
+            PYTHONPATH= "${penv}/${python.sitePackages}/";
+          };
+
+          serviceConfig = {
+            Type = "simple";
+            PrivateTmp = true;
+            ExecStartPre = assert server.secretKeyFile != null; pkgs.writeScript "bepasty-server.${name}-init" ''
+              #!/bin/sh
+              mkdir -p "${server.workDir}"
+              mkdir -p "${server.dataDir}"
+              chown ${user}:${group} "${server.workDir}" "${server.dataDir}"
+              cat > ${server.workDir}/bepasty-${name}.conf <<EOF
+              SITENAME="${name}"
+              STORAGE_FILESYSTEM_DIRECTORY="${server.dataDir}"
+              SECRET_KEY="$(cat "${server.secretKeyFile}")"
+              DEFAULT_PERMISSIONS="${server.defaultPermissions}"
+              ${server.extraConfig}
+              EOF
+            '';
+            ExecStart = ''${gunicorn}/bin/gunicorn bepasty.wsgi --name ${name} \
+              -u ${user} \
+              -g ${group} \
+              --workers 3 --log-level=info \
+              --bind=${server.bind} \
+              --pid ${server.workDir}/gunicorn-${name}.pid \
+              -k gevent
+            '';
+          };
+        })
+    ) cfg.servers;
+
+    users.users.${user} =
+      { uid = config.ids.uids.bepasty;
+        group = group;
+        home = default_home;
+      };
+
+    users.groups.${group}.gid = config.ids.gids.bepasty;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/calibre-server.nix b/nixpkgs/nixos/modules/services/misc/calibre-server.nix
new file mode 100644
index 000000000000..84c04f403d3a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/calibre-server.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.calibre-server;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.calibre-server = {
+
+      enable = mkEnableOption "calibre-server";
+
+      libraryDir = mkOption {
+        description = ''
+          The directory where the Calibre library to serve is.
+          '';
+          type = types.path;
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.calibre-server =
+      {
+        description = "Calibre Server";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = "calibre-server";
+          Restart = "always";
+          ExecStart = "${pkgs.calibre}/bin/calibre-server ${cfg.libraryDir}";
+        };
+
+      };
+
+    environment.systemPackages = [ pkgs.calibre ];
+
+    users.users.calibre-server = {
+        uid = config.ids.uids.calibre-server;
+        group = "calibre-server";
+      };
+
+    users.groups.calibre-server = {
+        gid = config.ids.gids.calibre-server;
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/canto-daemon.nix b/nixpkgs/nixos/modules/services/misc/canto-daemon.nix
new file mode 100644
index 000000000000..db51a263aab5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/canto-daemon.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+cfg = config.services.canto-daemon;
+
+in {
+
+##### interface
+
+  options = {
+
+    services.canto-daemon = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the canto RSS daemon.";
+      };
+    };
+
+  };
+
+##### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.user.services.canto-daemon = {
+      description = "Canto RSS Daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "default.target" ];
+      serviceConfig.ExecStart = "${pkgs.canto-daemon}/bin/canto-daemon";
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/cfdyndns.nix b/nixpkgs/nixos/modules/services/misc/cfdyndns.nix
new file mode 100644
index 000000000000..dcf416022734
--- /dev/null
+++ b/nixpkgs/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.users = {
+      cfdyndns = {
+        group = "cfdyndns";
+        uid = config.ids.uids.cfdyndns;
+      };
+    };
+
+    users.groups = {
+      cfdyndns = {
+        gid = config.ids.gids.cfdyndns;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/cgminer.nix b/nixpkgs/nixos/modules/services/misc/cgminer.nix
new file mode 100644
index 000000000000..7635c2a0f4e9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/cgminer.nix
@@ -0,0 +1,140 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cgminer;
+
+  convType = with builtins;
+    v: if isBool v then boolToString v else toString v;
+  mergedHwConfig =
+    mapAttrsToList (n: v: ''"${n}": "${(concatStringsSep "," (map convType v))}"'')
+      (foldAttrs (n: a: [n] ++ a) [] cfg.hardware);
+  mergedConfig = with builtins;
+    mapAttrsToList (n: v: ''"${n}":  ${if isBool v then "" else ''"''}${convType v}${if isBool v then "" else ''"''}'')
+      cfg.config;
+
+  cgminerConfig = pkgs.writeText "cgminer.conf" ''
+  {
+  ${concatStringsSep ",\n" mergedHwConfig},
+  ${concatStringsSep ",\n" mergedConfig},
+  "pools": [
+  ${concatStringsSep ",\n"
+    (map (v: ''{"url": "${v.url}", "user": "${v.user}", "pass": "${v.pass}"}'')
+          cfg.pools)}]
+  }
+  '';
+in
+{
+  ###### interface
+  options = {
+
+    services.cgminer = {
+
+      enable = mkEnableOption "cgminer, an ASIC/FPGA/GPU miner for bitcoin and litecoin";
+
+      package = mkOption {
+        default = pkgs.cgminer;
+        defaultText = "pkgs.cgminer";
+        description = "Which cgminer derivation to use.";
+        type = types.package;
+      };
+
+      user = mkOption {
+        default = "cgminer";
+        description = "User account under which cgminer runs";
+      };
+
+      pools = mkOption {
+        default = [];  # Run benchmark
+        description = "List of pools where to mine";
+        example = [{
+          url = "http://p2pool.org:9332";
+          username = "17EUZxTvs9uRmPsjPZSYUU3zCz9iwstudk";
+          password="X";
+        }];
+      };
+
+      hardware = mkOption {
+        default = []; # Run without options
+        description= "List of config options for every GPU";
+        example = [
+        {
+          intensity = 9;
+          gpu-engine = "0-985";
+          gpu-fan = "0-85";
+          gpu-memclock = 860;
+          gpu-powertune = 20;
+          temp-cutoff = 95;
+          temp-overheat = 85;
+          temp-target = 75;
+        }
+        {
+          intensity = 9;
+          gpu-engine = "0-950";
+          gpu-fan = "0-85";
+          gpu-memclock = 825;
+          gpu-powertune = 20;
+          temp-cutoff = 95;
+          temp-overheat = 85;
+          temp-target = 75;
+        }];
+      };
+
+      config = mkOption {
+        default = {};
+        description = "Additional config";
+        example = {
+          auto-fan = true;
+          auto-gpu = true;
+          expiry = 120;
+          failover-only = true;
+          gpu-threads = 2;
+          log = 5;
+          queue = 1;
+          scan-time = 60;
+          temp-histeresys = 3;
+        };
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.cgminer.enable {
+
+    users.users = optionalAttrs (cfg.user == "cgminer") {
+      cgminer = {
+        uid = config.ids.uids.cgminer;
+        description = "Cgminer user";
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.cgminer = {
+      path = [ pkgs.cgminer ];
+
+      after = [ "network.target" "display-manager.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        LD_LIBRARY_PATH = ''/run/opengl-driver/lib:/run/opengl-driver-32/lib'';
+        DISPLAY = ":${toString config.services.xserver.display}";
+        GPU_MAX_ALLOC_PERCENT = "100";
+        GPU_USE_SYNC_OBJECTS = "1";
+      };
+
+      serviceConfig = {
+        ExecStart = "${pkgs.cgminer}/bin/cgminer --syslog --text-only --config ${cgminerConfig}";
+        User = cfg.user;
+        RestartSec = "30s";
+        Restart = "always";
+        StartLimitInterval = "1m";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/clipmenu.nix b/nixpkgs/nixos/modules/services/misc/clipmenu.nix
new file mode 100644
index 000000000000..3ba050044cac
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/clipmenu.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.clipmenu;
+in {
+
+  options.services.clipmenu = {
+    enable = mkEnableOption "clipmenu, the clipboard management daemon";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.clipmenu;
+      defaultText = "pkgs.clipmenu";
+      description = "clipmenu derivation to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.clipmenu = {
+      enable      = true;
+      description = "Clipboard management daemon";
+      wantedBy = [ "graphical-session.target" ];
+      after    = [ "graphical-session.target" ];
+      serviceConfig.ExecStart = "${cfg.package}/bin/clipmenud";
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/confd.nix b/nixpkgs/nixos/modules/services/misc/confd.nix
new file mode 100755
index 000000000000..c1ebdb3dde91
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/confd.nix
@@ -0,0 +1,90 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.confd;
+
+  confdConfig = ''
+    backend = "${cfg.backend}"
+    confdir = "${cfg.confDir}"
+    interval = ${toString cfg.interval}
+    nodes = [ ${concatMapStringsSep "," (s: ''"${s}"'') cfg.nodes}, ]
+    prefix = "${cfg.prefix}"
+    log-level = "${cfg.logLevel}"
+    watch = ${boolToString cfg.watch}
+  '';
+
+in {
+  options.services.confd = {
+    enable = mkEnableOption "confd service";
+
+    backend = mkOption {
+      description = "Confd config storage backend to use.";
+      default = "etcd";
+      type = types.enum ["etcd" "consul" "redis" "zookeeper"];
+    };
+
+    interval = mkOption {
+      description = "Confd check interval.";
+      default = 10;
+      type = types.int;
+    };
+
+    nodes = mkOption {
+      description = "Confd list of nodes to connect to.";
+      default = [ "http://127.0.0.1:2379" ];
+      type = types.listOf types.str;
+    };
+
+    watch = mkOption {
+      description = "Confd, whether to watch etcd config for changes.";
+      default = true;
+      type = types.bool;
+    };
+
+    prefix = mkOption {
+      description = "The string to prefix to keys.";
+      default = "/";
+      type = types.path;
+    };
+
+    logLevel = mkOption {
+      description = "Confd log level.";
+      default = "info";
+      type = types.enum ["info" "debug"];
+    };
+
+    confDir = mkOption {
+      description = "The path to the confd configs.";
+      default = "/etc/confd";
+      type = types.path;
+    };
+
+    package = mkOption {
+      description = "Confd package to use.";
+      default = pkgs.confd;
+      defaultText = "pkgs.confd";
+      type = types.package;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.confd = {
+      description = "Confd Service.";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/confd";
+      };
+    };
+
+    environment.etc = {
+      "confd/confd.toml".text = confdConfig;
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    services.etcd.enable = mkIf (cfg.backend == "etcd") (mkDefault true);
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/couchpotato.nix b/nixpkgs/nixos/modules/services/misc/couchpotato.nix
new file mode 100644
index 000000000000..f5163cf86cf5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/couchpotato.nix
@@ -0,0 +1,42 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.couchpotato;
+
+in
+{
+  options = {
+    services.couchpotato = {
+      enable = mkEnableOption "CouchPotato Server";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.couchpotato = {
+      description = "CouchPotato Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = "couchpotato";
+        Group = "couchpotato";
+        StateDirectory = "couchpotato";
+        ExecStart = "${pkgs.couchpotato}/bin/couchpotato";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users.couchpotato =
+      { group = "couchpotato";
+        home = "/var/lib/couchpotato/";
+        description = "CouchPotato daemon user";
+        uid = config.ids.uids.couchpotato;
+      };
+
+    users.groups.couchpotato =
+      { gid = config.ids.gids.couchpotato; };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix b/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix
new file mode 100644
index 000000000000..907b9d90da29
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/cpuminer-cryptonight.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cpuminer-cryptonight;
+
+  json = builtins.toJSON (
+    cfg // {
+       enable = null;
+       threads =
+         if cfg.threads == 0 then null else toString cfg.threads;
+    }
+  );
+
+  confFile = builtins.toFile "cpuminer.json" json;
+in
+{
+
+  options = {
+
+    services.cpuminer-cryptonight = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the cpuminer cryptonight miner.
+        '';
+      };
+      url = mkOption {
+        type = types.str;
+        description = "URL of mining server";
+      };
+      user = mkOption {
+        type = types.str;
+        description = "Username for mining server";
+      };
+      pass = mkOption {
+        type = types.str;
+        default = "x";
+        description = "Password for mining server";
+      };
+      threads = mkOption {
+        type = types.int;
+        default = 0;
+        description = "Number of miner threads, defaults to available processors";
+      };
+    };
+
+  };
+
+  config = mkIf config.services.cpuminer-cryptonight.enable {
+
+    systemd.services.cpuminer-cryptonight = {
+      description = "Cryptonight cpuminer";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.cpuminer-multi}/bin/minerd --syslog --config=${confFile}";
+        User = "nobody";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb b/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb
new file mode 100644
index 000000000000..0b58c59c7a51
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/defaultUnicornConfig.rb
@@ -0,0 +1,69 @@
+worker_processes 3
+
+listen ENV["UNICORN_PATH"] + "/tmp/sockets/gitlab.socket", :backlog => 1024
+listen "/run/gitlab/gitlab.socket", :backlog => 1024
+
+working_directory ENV["GITLAB_PATH"]
+
+pid ENV["UNICORN_PATH"] + "/tmp/pids/unicorn.pid"
+
+timeout 60
+
+# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
+# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
+preload_app true
+GC.respond_to?(:copy_on_write_friendly=) and
+  GC.copy_on_write_friendly = true
+
+check_client_connection false
+
+before_fork do |server, worker|
+  # the following is highly recommended for Rails + "preload_app true"
+  # as there's no need for the master process to hold a connection
+  defined?(ActiveRecord::Base) and
+    ActiveRecord::Base.connection.disconnect!
+
+  # The following is only recommended for memory/DB-constrained
+  # installations.  It is not needed if your system can house
+  # twice as many worker_processes as you have configured.
+  #
+  # This allows a new master process to incrementally
+  # phase out the old master process with SIGTTOU to avoid a
+  # thundering herd (especially in the "preload_app false" case)
+  # when doing a transparent upgrade.  The last worker spawned
+  # will then kill off the old master process with a SIGQUIT.
+  old_pid = "#{server.config[:pid]}.oldbin"
+  if old_pid != server.pid
+    begin
+      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
+      Process.kill(sig, File.read(old_pid).to_i)
+    rescue Errno::ENOENT, Errno::ESRCH
+    end
+  end
+
+  # Throttle the master from forking too quickly by sleeping.  Due
+  # to the implementation of standard Unix signal handlers, this
+  # helps (but does not completely) prevent identical, repeated signals
+  # from being lost when the receiving process is busy.
+  # sleep 1
+end
+
+after_fork do |server, worker|
+  # per-process listener ports for debugging/admin/migrations
+  # addr = "127.0.0.1:#{9293 + worker.nr}"
+  # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
+
+  # the following is *required* for Rails + "preload_app true",
+  defined?(ActiveRecord::Base) and
+    ActiveRecord::Base.establish_connection
+
+  # reset prometheus client, this will cause any opened metrics files to be closed
+  defined?(::Prometheus::Client.reinitialize_on_pid_change) &&
+    Prometheus::Client.reinitialize_on_pid_change
+
+  # if preload_app is true, then you may also want to check and
+  # restart any other shared sockets/descriptors such as Memcached,
+  # and Redis.  TokyoCabinet file handles are safe to reuse
+  # between any number of forked children (assuming your kernel
+  # correctly implements pread()/pwrite() system calls)
+end
diff --git a/nixpkgs/nixos/modules/services/misc/devmon.nix b/nixpkgs/nixos/modules/services/misc/devmon.nix
new file mode 100644
index 000000000000..e4a3348646b1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/devmon.nix
@@ -0,0 +1,25 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.devmon;
+
+in {
+  options = {
+    services.devmon = {
+      enable = mkEnableOption "devmon, an automatic device mounting daemon";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.devmon = {
+      description = "devmon automatic device mounting daemon";
+      wantedBy = [ "default.target" ];
+      path = [ pkgs.udevil pkgs.procps pkgs.udisks2 pkgs.which ];
+      serviceConfig.ExecStart = "${pkgs.udevil}/bin/devmon";
+    };
+
+    services.udisks2.enable = true;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/dictd.nix b/nixpkgs/nixos/modules/services/misc/dictd.nix
new file mode 100644
index 000000000000..ae477dc3b634
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/dictd.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dictd;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.dictd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the DICT.org dictionary server.
+        '';
+      };
+
+      DBs = mkOption {
+        type = types.listOf types.package;
+        default = with pkgs.dictdDBs; [ wiktionary wordnet ];
+        defaultText = "with pkgs.dictdDBs; [ wiktionary wordnet ]";
+        example = literalExample "[ pkgs.dictdDBs.nld2eng ]";
+        description = ''List of databases to make available.'';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = let dictdb = pkgs.dictDBCollector { dictlist = map (x: {
+               name = x.name;
+               filename = x; } ) cfg.DBs; };
+  in mkIf cfg.enable {
+
+    # get the command line client on system path to make some use of the service
+    environment.systemPackages = [ pkgs.dict ];
+
+    environment.etc."dict.conf".text = ''
+      server localhost
+    '';
+
+    users.users.dictd =
+      { group = "dictd";
+        description = "DICT.org dictd server";
+        home = "${dictdb}/share/dictd";
+        uid = config.ids.uids.dictd;
+      };
+
+    users.groups.dictd.gid = config.ids.gids.dictd;
+
+    systemd.services.dictd = {
+      description = "DICT.org Dictionary Server";
+      wantedBy = [ "multi-user.target" ];
+      environment = { LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; };
+      serviceConfig.Type = "forking";
+      script = "${pkgs.dict}/sbin/dictd -s -c ${dictdb}/share/dictd/dictd.conf --locale en_US.UTF-8";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/disnix.nix b/nixpkgs/nixos/modules/services/misc/disnix.nix
new file mode 100644
index 000000000000..69386cdbb381
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/disnix.nix
@@ -0,0 +1,88 @@
+# Disnix server
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.disnix;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.disnix = {
+
+      enable = mkEnableOption "Disnix";
+
+      enableMultiUser = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether to support multi-user mode by enabling the Disnix D-Bus service";
+      };
+
+      useWebServiceInterface = mkEnableOption "the DisnixWebService interface running on Apache Tomcat";
+
+      package = mkOption {
+        type = types.path;
+        description = "The Disnix package";
+        default = pkgs.disnix;
+        defaultText = "pkgs.disnix";
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    dysnomia.enable = true;
+
+    environment.systemPackages = [ pkgs.disnix ] ++ optional cfg.useWebServiceInterface pkgs.DisnixWebService;
+
+    services.dbus.enable = true;
+    services.dbus.packages = [ pkgs.disnix ];
+
+    services.tomcat.enable = cfg.useWebServiceInterface;
+    services.tomcat.extraGroups = [ "disnix" ];
+    services.tomcat.javaOpts = "${optionalString cfg.useWebServiceInterface "-Djava.library.path=${pkgs.libmatthew_java}/lib/jni"} ";
+    services.tomcat.sharedLibs = optional cfg.useWebServiceInterface "${pkgs.DisnixWebService}/share/java/DisnixConnection.jar"
+      ++ optional cfg.useWebServiceInterface "${pkgs.dbus_java}/share/java/dbus.jar";
+    services.tomcat.webapps = optional cfg.useWebServiceInterface pkgs.DisnixWebService;
+
+    users.groups.disnix.gid = config.ids.gids.disnix;
+
+    systemd.services = {
+      disnix = mkIf cfg.enableMultiUser {
+        description = "Disnix server";
+        wants = [ "dysnomia.target" ];
+        wantedBy = [ "multi-user.target" ];
+        after = [ "dbus.service" ]
+          ++ optional config.services.httpd.enable "httpd.service"
+          ++ optional config.services.mysql.enable "mysql.service"
+          ++ optional config.services.postgresql.enable "postgresql.service"
+          ++ optional config.services.tomcat.enable "tomcat.service"
+          ++ optional config.services.svnserve.enable "svnserve.service"
+          ++ optional config.services.mongodb.enable "mongodb.service";
+
+        restartIfChanged = false;
+
+        path = [ config.nix.package cfg.package config.dysnomia.package "/run/current-system/sw" ];
+
+        environment = {
+          HOME = "/root";
+        }
+        // (if config.environment.variables ? DYSNOMIA_CONTAINERS_PATH then { inherit (config.environment.variables) DYSNOMIA_CONTAINERS_PATH; } else {})
+        // (if config.environment.variables ? DYSNOMIA_MODULES_PATH then { inherit (config.environment.variables) DYSNOMIA_MODULES_PATH; } else {});
+
+        serviceConfig.ExecStart = "${cfg.package}/bin/disnix-service";
+      };
+
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/docker-registry.nix b/nixpkgs/nixos/modules/services/misc/docker-registry.nix
new file mode 100644
index 000000000000..1c2e2cc53590
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/docker-registry.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dockerRegistry;
+
+  blobCache = if cfg.enableRedisCache
+    then "redis"
+    else "inmemory";
+
+  registryConfig = {
+    version =  "0.1";
+    log.fields.service = "registry";
+    storage = {
+      cache.blobdescriptor = blobCache;
+      delete.enabled = cfg.enableDelete;
+    } // (if cfg.storagePath != null
+          then { filesystem.rootdirectory = cfg.storagePath; }
+          else {});
+    http = {
+      addr = "${cfg.listenAddress}:${builtins.toString cfg.port}";
+      headers.X-Content-Type-Options = ["nosniff"];
+    };
+    health.storagedriver = {
+      enabled = true;
+      interval = "10s";
+      threshold = 3;
+    };
+  };
+
+  registryConfig.redis = mkIf cfg.enableRedisCache {
+    addr = "${cfg.redisUrl}";
+    password = "${cfg.redisPassword}";
+    db = 0;
+    dialtimeout = "10ms";
+    readtimeout = "10ms";
+    writetimeout = "10ms";
+    pool = {
+      maxidle = 16;
+      maxactive = 64;
+      idletimeout = "300s";
+    };
+  };
+
+  configFile = pkgs.writeText "docker-registry-config.yml" (builtins.toJSON (recursiveUpdate registryConfig cfg.extraConfig));
+
+in {
+  options.services.dockerRegistry = {
+    enable = mkEnableOption "Docker Registry";
+
+    listenAddress = mkOption {
+      description = "Docker registry host or ip to bind to.";
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    port = mkOption {
+      description = "Docker registry port to bind to.";
+      default = 5000;
+      type = types.int;
+    };
+
+    storagePath = mkOption {
+      type = types.nullOr types.path;
+      default = "/var/lib/docker-registry";
+      description = ''
+        Docker registry storage path for the filesystem storage backend. Set to
+        null to configure another backend via extraConfig.
+      '';
+    };
+
+    enableDelete = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable delete for manifests and blobs.";
+    };
+
+    enableRedisCache = mkEnableOption "redis as blob cache";
+
+    redisUrl = mkOption {
+      type = types.str;
+      default = "localhost:6379";
+      description = "Set redis host and port.";
+    };
+
+    redisPassword = mkOption {
+      type = types.str;
+      default = "";
+      description = "Set redis password.";
+    };
+
+    extraConfig = mkOption {
+      description = ''
+        Docker extra registry configuration via environment variables.
+      '';
+      default = {};
+      type = types.attrs;
+    };
+
+    enableGarbageCollect = mkEnableOption "garbage collect";
+
+    garbageCollectDates = mkOption {
+      default = "daily";
+      type = types.str;
+      description = ''
+        Specification (in the format described by
+        <citerefentry><refentrytitle>systemd.time</refentrytitle>
+        <manvolnum>7</manvolnum></citerefentry>) of the time at
+        which the garbage collect will occur.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.docker-registry = {
+      description = "Docker Container Registry";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      script = ''
+        ${pkgs.docker-distribution}/bin/registry serve ${configFile}
+      '';
+
+      serviceConfig = {
+        User = "docker-registry";
+        WorkingDirectory = cfg.storagePath;
+        AmbientCapabilities = mkIf (cfg.port < 1024) "cap_net_bind_service";
+      };
+    };
+
+    systemd.services.docker-registry-garbage-collect = {
+      description = "Run Garbage Collection for docker registry";
+
+      restartIfChanged = false;
+      unitConfig.X-StopOnRemoval = false;
+
+      serviceConfig.Type = "oneshot";
+
+      script = ''
+        ${pkgs.docker-distribution}/bin/registry garbage-collect ${configFile}
+        /run/current-system/systemd/bin/systemctl restart docker-registry.service
+      '';
+
+      startAt = optional cfg.enableGarbageCollect cfg.garbageCollectDates;
+    };
+
+    users.users.docker-registry =
+      (if cfg.storagePath != null
+      then {
+        createHome = true;
+        home = cfg.storagePath;
+      }
+      else {}) // {
+        isSystemUser = true;
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/dwm-status.nix b/nixpkgs/nixos/modules/services/misc/dwm-status.nix
new file mode 100644
index 000000000000..b98a42e6a6d2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/dwm-status.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dwm-status;
+
+  order = concatMapStringsSep "," (feature: ''"${feature}"'') cfg.order;
+
+  configFile = pkgs.writeText "dwm-status.toml" ''
+    order = [${order}]
+
+    ${cfg.extraConfig}
+  '';
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.dwm-status = {
+
+      enable = mkEnableOption "dwm-status user service";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.dwm-status;
+        defaultText = "pkgs.dwm-status";
+        example = "pkgs.dwm-status.override { enableAlsaUtils = false; }";
+        description = ''
+          Which dwm-status package to use.
+        '';
+      };
+
+      order = mkOption {
+        type = types.listOf (types.enum [ "audio" "backlight" "battery" "cpu_load" "network" "time" ]);
+        description = ''
+          List of enabled features in order.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra config in TOML format.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.upower.enable = elem "battery" cfg.order;
+
+    systemd.user.services.dwm-status = {
+      description = "Highly performant and configurable DWM status service";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+
+      serviceConfig.ExecStart = "${cfg.package}/bin/dwm-status ${configFile}";
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/dysnomia.nix b/nixpkgs/nixos/modules/services/misc/dysnomia.nix
new file mode 100644
index 000000000000..4b52963500d1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/dysnomia.nix
@@ -0,0 +1,217 @@
+{pkgs, lib, config, ...}:
+
+with lib;
+
+let
+  cfg = config.dysnomia;
+
+  printProperties = properties:
+    concatMapStrings (propertyName:
+      let
+        property = properties.${propertyName};
+      in
+      if isList property then "${propertyName}=(${lib.concatMapStrings (elem: "\"${toString elem}\" ") (properties.${propertyName})})\n"
+      else "${propertyName}=\"${toString property}\"\n"
+    ) (builtins.attrNames properties);
+
+  properties = pkgs.stdenv.mkDerivation {
+    name = "dysnomia-properties";
+    buildCommand = ''
+      cat > $out << "EOF"
+      ${printProperties cfg.properties}
+      EOF
+    '';
+  };
+
+  containersDir = pkgs.stdenv.mkDerivation {
+    name = "dysnomia-containers";
+    buildCommand = ''
+      mkdir -p $out
+      cd $out
+
+      ${concatMapStrings (containerName:
+        let
+          containerProperties = cfg.containers.${containerName};
+        in
+        ''
+          cat > ${containerName} <<EOF
+          ${printProperties containerProperties}
+          type=${containerName}
+          EOF
+        ''
+      ) (builtins.attrNames cfg.containers)}
+    '';
+  };
+
+  linkMutableComponents = {containerName}:
+    ''
+      mkdir ${containerName}
+
+      ${concatMapStrings (componentName:
+        let
+          component = cfg.components.${containerName}.${componentName};
+        in
+        "ln -s ${component} ${containerName}/${componentName}\n"
+      ) (builtins.attrNames (cfg.components.${containerName} or {}))}
+    '';
+
+  componentsDir = pkgs.stdenv.mkDerivation {
+    name = "dysnomia-components";
+    buildCommand = ''
+      mkdir -p $out
+      cd $out
+
+      ${concatMapStrings (containerName:
+        linkMutableComponents { inherit containerName; }
+      ) (builtins.attrNames cfg.components)}
+    '';
+  };
+in
+{
+  options = {
+    dysnomia = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable Dysnomia";
+      };
+
+      enableAuthentication = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to publish privacy-sensitive authentication credentials";
+      };
+
+      package = mkOption {
+        type = types.path;
+        description = "The Dysnomia package";
+      };
+
+      properties = mkOption {
+        description = "An attribute set in which each attribute represents a machine property. Optionally, these values can be shell substitutions.";
+        default = {};
+      };
+
+      containers = mkOption {
+        description = "An attribute set in which each key represents a container and each value an attribute set providing its configuration properties";
+        default = {};
+      };
+
+      components = mkOption {
+        description = "An atttribute set in which each key represents a container and each value an attribute set in which each key represents a component and each value a derivation constructing its initial state";
+        default = {};
+      };
+
+      extraContainerProperties = mkOption {
+        description = "An attribute set providing additional container settings in addition to the default properties";
+        default = {};
+      };
+
+      extraContainerPaths = mkOption {
+        description = "A list of paths containing additional container configurations that are added to the search folders";
+        default = [];
+      };
+
+      extraModulePaths = mkOption {
+        description = "A list of paths containing additional modules that are added to the search folders";
+        default = [];
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.etc = {
+      "dysnomia/containers" = {
+        source = containersDir;
+      };
+      "dysnomia/components" = {
+        source = componentsDir;
+      };
+      "dysnomia/properties" = {
+        source = properties;
+      };
+    };
+
+    environment.variables = {
+      DYSNOMIA_STATEDIR = "/var/state/dysnomia-nixos";
+      DYSNOMIA_CONTAINERS_PATH = "${lib.concatMapStrings (containerPath: "${containerPath}:") cfg.extraContainerPaths}/etc/dysnomia/containers";
+      DYSNOMIA_MODULES_PATH = "${lib.concatMapStrings (modulePath: "${modulePath}:") cfg.extraModulePaths}/etc/dysnomia/modules";
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    dysnomia.package = pkgs.dysnomia.override (origArgs: {
+      enableApacheWebApplication = config.services.httpd.enable;
+      enableAxis2WebService = config.services.tomcat.axis2.enable;
+      enableEjabberdDump = config.services.ejabberd.enable;
+      enableMySQLDatabase = config.services.mysql.enable;
+      enablePostgreSQLDatabase = config.services.postgresql.enable;
+      enableSubversionRepository = config.services.svnserve.enable;
+      enableTomcatWebApplication = config.services.tomcat.enable;
+      enableMongoDatabase = config.services.mongodb.enable;
+      enableInfluxDatabase = config.services.influxdb.enable;
+    });
+
+    dysnomia.properties = {
+      hostname = config.networking.hostName;
+      inherit (config.nixpkgs.localSystem) system;
+
+      supportedTypes = (import "${pkgs.stdenv.mkDerivation {
+        name = "supportedtypes";
+        buildCommand = ''
+          ( echo -n "[ "
+            cd ${cfg.package}/libexec/dysnomia
+            for i in *
+            do
+                echo -n "\"$i\" "
+            done
+            echo -n " ]") > $out
+        '';
+      }}");
+    };
+
+    dysnomia.containers = lib.recursiveUpdate ({
+      process = {};
+      wrapper = {};
+    }
+    // lib.optionalAttrs (config.services.httpd.enable) { apache-webapplication = {
+      documentRoot = config.services.httpd.virtualHosts.localhost.documentRoot;
+    }; }
+    // lib.optionalAttrs (config.services.tomcat.axis2.enable) { axis2-webservice = {}; }
+    // lib.optionalAttrs (config.services.ejabberd.enable) { ejabberd-dump = {
+      ejabberdUser = config.services.ejabberd.user;
+    }; }
+    // lib.optionalAttrs (config.services.mysql.enable) { mysql-database = {
+        mysqlPort = config.services.mysql.port;
+      } // lib.optionalAttrs cfg.enableAuthentication {
+        mysqlUsername = "root";
+        mysqlPassword = builtins.readFile (config.services.mysql.rootPassword);
+      };
+    }
+    // lib.optionalAttrs (config.services.postgresql.enable) { postgresql-database = {
+      } // lib.optionalAttrs (cfg.enableAuthentication) {
+        postgresqlUsername = "postgres";
+      };
+    }
+    // lib.optionalAttrs (config.services.tomcat.enable) { tomcat-webapplication = {
+      tomcatPort = 8080;
+    }; }
+    // lib.optionalAttrs (config.services.mongodb.enable) { mongo-database = {}; }
+    // lib.optionalAttrs (config.services.svnserve.enable) { subversion-repository = {
+      svnBaseDir = config.services.svnserve.svnBaseDir;
+    }; }) cfg.extraContainerProperties;
+
+    system.activationScripts.dysnomia = ''
+      mkdir -p /etc/systemd-mutable/system
+      if [ ! -f /etc/systemd-mutable/system/dysnomia.target ]
+      then
+          ( echo "[Unit]"
+            echo "Description=Services that are activated and deactivated by Dysnomia"
+            echo "After=final.target"
+          ) > /etc/systemd-mutable/system/dysnomia.target
+      fi
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/errbot.nix b/nixpkgs/nixos/modules/services/misc/errbot.nix
new file mode 100644
index 000000000000..b447ba5d438d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/errbot.nix
@@ -0,0 +1,104 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.errbot;
+  pluginEnv = plugins: pkgs.buildEnv {
+    name = "errbot-plugins";
+    paths = plugins;
+  };
+  mkConfigDir = instanceCfg: dataDir: pkgs.writeTextDir "config.py" ''
+    import logging
+    BACKEND = '${instanceCfg.backend}'
+    BOT_DATA_DIR = '${dataDir}'
+    BOT_EXTRA_PLUGIN_DIR = '${pluginEnv instanceCfg.plugins}'
+
+    BOT_LOG_LEVEL = logging.${instanceCfg.logLevel}
+    BOT_LOG_FILE = False
+
+    BOT_ADMINS = (${concatMapStringsSep "," (name: "'${name}'") instanceCfg.admins})
+
+    BOT_IDENTITY = ${builtins.toJSON instanceCfg.identity}
+
+    ${instanceCfg.extraConfig}
+  '';
+in {
+  options = {
+    services.errbot.instances = mkOption {
+      default = {};
+      description = "Errbot instance configs";
+      type = types.attrsOf (types.submodule {
+        options = {
+          dataDir = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            description = "Data directory for errbot instance.";
+          };
+
+          plugins = mkOption {
+            type = types.listOf types.package;
+            default = [];
+            description = "List of errbot plugin derivations.";
+          };
+
+          logLevel = mkOption {
+            type = types.str;
+            default = "INFO";
+            description = "Errbot log level";
+          };
+
+          admins = mkOption {
+            type = types.listOf types.str;
+            default = [];
+            description = "List of identifiers of errbot admins.";
+          };
+
+          backend = mkOption {
+            type = types.str;
+            default = "XMPP";
+            description = "Errbot backend name.";
+          };
+
+          identity = mkOption {
+            type = types.attrs;
+            description = "Errbot identity configuration";
+          };
+
+          extraConfig = mkOption {
+            type = types.lines;
+            default = "";
+            description = "String to be appended to the config verbatim";
+          };
+        };
+      });
+    };
+  };
+
+  config = mkIf (cfg.instances != {}) {
+    users.users.errbot = {
+      group = "errbot";
+      isSystemUser = true;
+    };
+    users.groups.errbot = {};
+
+    systemd.services = mapAttrs' (name: instanceCfg: nameValuePair "errbot-${name}" (
+    let
+      dataDir = if instanceCfg.dataDir != null then instanceCfg.dataDir else
+        "/var/lib/errbot/${name}";
+    in {
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -p ${dataDir}
+        chown -R errbot:errbot ${dataDir}
+      '';
+      serviceConfig = {
+        User = "errbot";
+        Restart = "on-failure";
+        ExecStart = "${pkgs.errbot}/bin/errbot -c ${mkConfigDir instanceCfg dataDir}/config.py";
+        PermissionsStartOnly = true;
+      };
+    })) cfg.instances;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/etcd.nix b/nixpkgs/nixos/modules/services/misc/etcd.nix
new file mode 100644
index 000000000000..32360d43768a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/etcd.nix
@@ -0,0 +1,195 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.etcd;
+
+in {
+
+  options.services.etcd = {
+    enable = mkOption {
+      description = "Whether to enable etcd.";
+      default = false;
+      type = types.bool;
+    };
+
+    name = mkOption {
+      description = "Etcd unique node name.";
+      default = config.networking.hostName;
+      type = types.str;
+    };
+
+    advertiseClientUrls = mkOption {
+      description = "Etcd list of this member's client URLs to advertise to the rest of the cluster.";
+      default = cfg.listenClientUrls;
+      type = types.listOf types.str;
+    };
+
+    listenClientUrls = mkOption {
+      description = "Etcd list of URLs to listen on for client traffic.";
+      default = ["http://127.0.0.1:2379"];
+      type = types.listOf types.str;
+    };
+
+    listenPeerUrls = mkOption {
+      description = "Etcd list of URLs to listen on for peer traffic.";
+      default = ["http://127.0.0.1:2380"];
+      type = types.listOf types.str;
+    };
+
+    initialAdvertisePeerUrls = mkOption {
+      description = "Etcd list of this member's peer URLs to advertise to rest of the cluster.";
+      default = cfg.listenPeerUrls;
+      type = types.listOf types.str;
+    };
+
+    initialCluster = mkOption {
+      description = "Etcd initial cluster configuration for bootstrapping.";
+      default = ["${cfg.name}=http://127.0.0.1:2380"];
+      type = types.listOf types.str;
+    };
+
+    initialClusterState = mkOption {
+      description = "Etcd initial cluster configuration for bootstrapping.";
+      default = "new";
+      type = types.enum ["new" "existing"];
+    };
+
+    initialClusterToken = mkOption {
+      description = "Etcd initial cluster token for etcd cluster during bootstrap.";
+      default = "etcd-cluster";
+      type = types.str;
+    };
+
+    discovery = mkOption {
+      description = "Etcd discovery url";
+      default = "";
+      type = types.str;
+    };
+
+    clientCertAuth = mkOption {
+      description = "Whether to use certs for client authentication";
+      default = false;
+      type = types.bool;
+    };
+
+    trustedCaFile = mkOption {
+      description = "Certificate authority file to use for clients";
+      default = null;
+      type = types.nullOr types.path;
+    };
+
+    certFile = mkOption {
+      description = "Cert file to use for clients";
+      default = null;
+      type = types.nullOr types.path;
+    };
+
+    keyFile = mkOption {
+      description = "Key file to use for clients";
+      default = null;
+      type = types.nullOr types.path;
+    };
+
+    peerCertFile = mkOption {
+      description = "Cert file to use for peer to peer communication";
+      default = cfg.certFile;
+      type = types.nullOr types.path;
+    };
+
+    peerKeyFile = mkOption {
+      description = "Key file to use for peer to peer communication";
+      default = cfg.keyFile;
+      type = types.nullOr types.path;
+    };
+
+    peerTrustedCaFile = mkOption {
+      description = "Certificate authority file to use for peer to peer communication";
+      default = cfg.trustedCaFile;
+      type = types.nullOr types.path;
+    };
+
+    peerClientCertAuth = mkOption {
+      description = "Whether to check all incoming peer requests from the cluster for valid client certificates signed by the supplied CA";
+      default = false;
+      type = types.bool;
+    };
+
+    extraConf = mkOption {
+      description = ''
+        Etcd extra configuration. See
+        <link xlink:href='https://github.com/coreos/etcd/blob/master/Documentation/op-guide/configuration.md#configuration-flags' />
+      '';
+      type = types.attrsOf types.str;
+      default = {};
+      example = literalExample ''
+        {
+          "CORS" = "*";
+          "NAME" = "default-name";
+          "MAX_RESULT_BUFFER" = "1024";
+          "MAX_CLUSTER_SIZE" = "9";
+          "MAX_RETRY_ATTEMPTS" = "3";
+        }
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/etcd";
+      description = "Etcd data directory.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 etcd - - -"
+    ];
+
+    systemd.services.etcd = {
+      description = "etcd key-value store";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      environment = (filterAttrs (n: v: v != null) {
+        ETCD_NAME = cfg.name;
+        ETCD_DISCOVERY = cfg.discovery;
+        ETCD_DATA_DIR = cfg.dataDir;
+        ETCD_ADVERTISE_CLIENT_URLS = concatStringsSep "," cfg.advertiseClientUrls;
+        ETCD_LISTEN_CLIENT_URLS = concatStringsSep "," cfg.listenClientUrls;
+        ETCD_LISTEN_PEER_URLS = concatStringsSep "," cfg.listenPeerUrls;
+        ETCD_INITIAL_ADVERTISE_PEER_URLS = concatStringsSep "," cfg.initialAdvertisePeerUrls;
+        ETCD_PEER_TRUSTED_CA_FILE = cfg.peerTrustedCaFile;
+        ETCD_PEER_CERT_FILE = cfg.peerCertFile;
+        ETCD_PEER_KEY_FILE = cfg.peerKeyFile;
+        ETCD_CLIENT_CERT_AUTH = toString cfg.peerClientCertAuth;
+        ETCD_TRUSTED_CA_FILE = cfg.trustedCaFile;
+        ETCD_CERT_FILE = cfg.certFile;
+        ETCD_KEY_FILE = cfg.keyFile;
+      }) // (optionalAttrs (cfg.discovery == ""){
+        ETCD_INITIAL_CLUSTER = concatStringsSep "," cfg.initialCluster;
+        ETCD_INITIAL_CLUSTER_STATE = cfg.initialClusterState;
+        ETCD_INITIAL_CLUSTER_TOKEN = cfg.initialClusterToken;
+      }) // (mapAttrs' (n: v: nameValuePair "ETCD_${n}" v) cfg.extraConf);
+
+      unitConfig = {
+        Documentation = "https://github.com/coreos/etcd";
+      };
+
+      serviceConfig = {
+        Type = "notify";
+        ExecStart = "${pkgs.etcd}/bin/etcd";
+        User = "etcd";
+        LimitNOFILE = 40000;
+      };
+    };
+
+    environment.systemPackages = [ pkgs.etcdctl ];
+
+    users.users.etcd = {
+      uid = config.ids.uids.etcd;
+      description = "Etcd daemon user";
+      home = cfg.dataDir;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/ethminer.nix b/nixpkgs/nixos/modules/services/misc/ethminer.nix
new file mode 100644
index 000000000000..95afb0460fb8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ethminer.nix
@@ -0,0 +1,117 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ethminer;
+  poolUrl = escapeShellArg "stratum1+tcp://${cfg.wallet}@${cfg.pool}:${toString cfg.stratumPort}/${cfg.rig}/${cfg.registerMail}";
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.ethminer = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable ethminer ether mining.";
+      };
+
+      recheckInterval = mkOption {
+        type = types.int;
+        default = 2000;
+        description = "Interval in milliseconds between farm rechecks.";
+      };
+
+      toolkit = mkOption {
+        type = types.enum [ "cuda" "opencl" ];
+        default = "cuda";
+        description = "Cuda or opencl toolkit.";
+      };
+
+      apiPort = mkOption {
+        type = types.int;
+        default = -3333;
+        description = "Ethminer api port. minus sign puts api in read-only mode.";
+      };
+
+      wallet = mkOption {
+        type = types.str;
+        example = "0x0123456789abcdef0123456789abcdef01234567";
+        description = "Ethereum wallet address.";
+      };
+
+      pool = mkOption {
+        type = types.str;
+        example = "eth-us-east1.nanopool.org";
+        description = "Mining pool address.";
+      };
+
+      stratumPort = mkOption {
+        type = types.port;
+        default = 9999;
+        description = "Stratum protocol tcp port.";
+      };
+
+      rig = mkOption {
+        type = types.str;
+        default = "mining-rig-name";
+        description = "Mining rig name.";
+      };
+
+      registerMail = mkOption {
+        type = types.str;
+        example = "email%40example.org";
+        description = "Url encoded email address to register with pool.";
+      };
+
+      maxPower = mkOption {
+        type = types.int;
+        default = 113;
+        description = "Miner max watt usage.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.ethminer = {
+      path = [ pkgs.cudatoolkit ];
+      description = "ethminer ethereum mining service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStartPre = "${pkgs.ethminer}/bin/.ethminer-wrapped --list-devices";
+        ExecStartPost = optional (cfg.toolkit == "cuda") "+${getBin config.boot.kernelPackages.nvidia_x11}/bin/nvidia-smi -pl ${toString cfg.maxPower}";
+        Restart = "always";
+      };
+
+      environment = {
+        LD_LIBRARY_PATH = "${config.boot.kernelPackages.nvidia_x11}/lib";
+      };
+
+      script = ''
+        ${pkgs.ethminer}/bin/.ethminer-wrapped \
+          --farm-recheck ${toString cfg.recheckInterval} \
+          --report-hashrate \
+          --${cfg.toolkit} \
+          --api-port ${toString cfg.apiPort} \
+          --pool ${poolUrl}
+      '';
+
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/exhibitor.nix b/nixpkgs/nixos/modules/services/misc/exhibitor.nix
new file mode 100644
index 000000000000..f8c79f892da3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/exhibitor.nix
@@ -0,0 +1,419 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.exhibitor;
+  exhibitorConfig = ''
+    zookeeper-install-directory=${cfg.baseDir}/zookeeper
+    zookeeper-data-directory=${cfg.zkDataDir}
+    zookeeper-log-directory=${cfg.zkLogDir}
+    zoo-cfg-extra=${cfg.zkExtraCfg}
+    client-port=${toString cfg.zkClientPort}
+    connect-port=${toString cfg.zkConnectPort}
+    election-port=${toString cfg.zkElectionPort}
+    cleanup-period-ms=${toString cfg.zkCleanupPeriod}
+    servers-spec=${concatStringsSep "," cfg.zkServersSpec}
+    auto-manage-instances=${toString cfg.autoManageInstances}
+    ${cfg.extraConf}
+  '';
+  # NB: toString rather than lib.boolToString on cfg.autoManageInstances is intended.
+  # Exhibitor tests if it's an integer not equal to 0, so the empty string (toString false)
+  # will operate in the same fashion as a 0.
+  configDir = pkgs.writeTextDir "exhibitor.properties" exhibitorConfig;
+  cliOptionsCommon = {
+    configtype = cfg.configType;
+    defaultconfig = "${configDir}/exhibitor.properties";
+    port = toString cfg.port;
+    hostname = cfg.hostname;
+    headingtext = if (cfg.headingText != null) then (lib.escapeShellArg cfg.headingText) else null;
+    nodemodification = lib.boolToString cfg.nodeModification;
+    configcheckms = toString cfg.configCheckMs;
+    jquerystyle = cfg.jqueryStyle;
+    loglines = toString cfg.logLines;
+    servo = lib.boolToString cfg.servo;
+    timeout = toString cfg.timeout;
+  };
+  s3CommonOptions = { s3region = cfg.s3Region; s3credentials = cfg.s3Credentials; };
+  cliOptionsPerConfig = {
+    s3 = {
+      s3config = "${cfg.s3Config.bucketName}:${cfg.s3Config.objectKey}";
+      s3configprefix = cfg.s3Config.configPrefix;
+    };
+    zookeeper = {
+      zkconfigconnect = concatStringsSep "," cfg.zkConfigConnect;
+      zkconfigexhibitorpath = cfg.zkConfigExhibitorPath;
+      zkconfigpollms = toString cfg.zkConfigPollMs;
+      zkconfigretry = "${toString cfg.zkConfigRetry.sleepMs}:${toString cfg.zkConfigRetry.retryQuantity}";
+      zkconfigzpath = cfg.zkConfigZPath;
+      zkconfigexhibitorport = toString cfg.zkConfigExhibitorPort; # NB: This might be null
+    };
+    file = {
+      fsconfigdir = cfg.fsConfigDir;
+      fsconfiglockprefix = cfg.fsConfigLockPrefix;
+      fsConfigName = fsConfigName;
+    };
+    none = {
+      noneconfigdir = configDir;
+    };
+  };
+  cliOptions = concatStringsSep " " (mapAttrsToList (k: v: "--${k} ${v}") (filterAttrs (k: v: v != null && v != "") (cliOptionsCommon //
+               cliOptionsPerConfig.${cfg.configType} //
+               s3CommonOptions //
+               optionalAttrs cfg.s3Backup { s3backup = "true"; } //
+               optionalAttrs cfg.fileSystemBackup { filesystembackup = "true"; }
+               )));
+in
+{
+  options = {
+    services.exhibitor = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "
+          Whether to enable the exhibitor server.
+        ";
+      };
+      # See https://github.com/soabase/exhibitor/wiki/Running-Exhibitor for what these mean
+      # General options for any type of config
+      port = mkOption {
+        type = types.int;
+        default = 8080;
+        description = ''
+          The port for exhibitor to listen on and communicate with other exhibitors.
+        '';
+      };
+      baseDir = mkOption {
+        type = types.str;
+        default = "/var/exhibitor";
+        description = ''
+          Baseline directory for exhibitor runtime config.
+        '';
+      };
+      configType = mkOption {
+        type = types.enum [ "file" "s3" "zookeeper" "none" ];
+        description = ''
+          Which configuration type you want to use. Additional config will be
+          required depending on which type you are using.
+        '';
+      };
+      hostname = mkOption {
+        type = types.nullOr types.str;
+        description = ''
+          Hostname to use and advertise
+        '';
+        default = null;
+      };
+      nodeModification = mkOption {
+        type = types.bool;
+        description = ''
+          Whether the Explorer UI will allow nodes to be modified (use with caution).
+        '';
+        default = true;
+      };
+      configCheckMs = mkOption {
+        type = types.int;
+        description = ''
+          Period (ms) to check for shared config updates.
+        '';
+        default = 30000;
+      };
+      headingText = mkOption {
+        type = types.nullOr types.str;
+        description = ''
+          Extra text to display in UI header
+        '';
+        default = null;
+      };
+      jqueryStyle = mkOption {
+        type = types.enum [ "red" "black" "custom" ];
+        description = ''
+          Styling used for the JQuery-based UI.
+        '';
+        default = "red";
+      };
+      logLines = mkOption {
+        type = types.int;
+        description = ''
+        Max lines of logging to keep in memory for display.
+        '';
+        default = 1000;
+      };
+      servo = mkOption {
+        type = types.bool;
+        description = ''
+          ZooKeeper will be queried once a minute for its state via the 'mntr' four
+          letter word (this requires ZooKeeper 3.4.x+). Servo will be used to publish
+          this data via JMX.
+        '';
+        default = false;
+      };
+      timeout = mkOption {
+        type = types.int;
+        description = ''
+          Connection timeout (ms) for ZK connections.
+        '';
+        default = 30000;
+      };
+      autoManageInstances = mkOption {
+        type = types.bool;
+        description = ''
+          Automatically manage ZooKeeper instances in the ensemble
+        '';
+        default = false;
+      };
+      zkDataDir = mkOption {
+        type = types.str;
+        default = "${cfg.baseDir}/zkData";
+        description = ''
+          The Zookeeper data directory
+        '';
+      };
+      zkLogDir = mkOption {
+        type = types.path;
+        default = "${cfg.baseDir}/zkLogs";
+        description = ''
+          The Zookeeper logs directory
+        '';
+      };
+      extraConf = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Extra Exhibitor configuration to put in the ZooKeeper config file.
+        '';
+      };
+      zkExtraCfg = mkOption {
+        type = types.str;
+        default = ''initLimit=5&syncLimit=2&tickTime=2000'';
+        description = ''
+          Extra options to pass into Zookeeper
+        '';
+      };
+      zkClientPort = mkOption {
+        type = types.int;
+        default = 2181;
+        description = ''
+          Zookeeper client port
+        '';
+      };
+      zkConnectPort = mkOption {
+        type = types.int;
+        default = 2888;
+        description = ''
+          The port to use for followers to talk to each other.
+        '';
+      };
+      zkElectionPort = mkOption {
+        type = types.int;
+        default = 3888;
+        description = ''
+          The port for Zookeepers to use for leader election.
+        '';
+      };
+      zkCleanupPeriod = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          How often (in milliseconds) to run the Zookeeper log cleanup task.
+        '';
+      };
+      zkServersSpec = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Zookeeper server spec for all servers in the ensemble.
+        '';
+        example = [ "S:1:zk1.example.com" "S:2:zk2.example.com" "S:3:zk3.example.com" "O:4:zk-observer.example.com" ];
+      };
+
+      # Backup options
+      s3Backup = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable backups to S3
+        '';
+      };
+      fileSystemBackup = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enables file system backup of ZooKeeper log files
+        '';
+      };
+
+      # Options for using zookeeper configType
+      zkConfigConnect = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          The initial connection string for ZooKeeper shared config storage
+        '';
+        example = ["host1:2181" "host2:2181"];
+      };
+      zkConfigExhibitorPath = mkOption {
+        type = types.str;
+        description = ''
+          If the ZooKeeper shared config is also running Exhibitor, the URI path for the REST call
+        '';
+        default = "/";
+      };
+      zkConfigExhibitorPort = mkOption {
+        type = types.nullOr types.int;
+        description = ''
+          If the ZooKeeper shared config is also running Exhibitor, the port that
+          Exhibitor is listening on. IMPORTANT: if this value is not set it implies
+          that Exhibitor is not being used on the ZooKeeper shared config.
+        '';
+      };
+      zkConfigPollMs = mkOption {
+        type = types.int;
+        description = ''
+          The period in ms to check for changes in the config ensemble
+        '';
+        default = 10000;
+      };
+      zkConfigRetry = {
+        sleepMs = mkOption {
+          type = types.int;
+          default = 1000;
+          description = ''
+            Retry sleep time connecting to the ZooKeeper config
+          '';
+        };
+        retryQuantity = mkOption {
+          type = types.int;
+          default = 3;
+          description = ''
+            Retries connecting to the ZooKeeper config
+          '';
+        };
+      };
+      zkConfigZPath = mkOption {
+        type = types.str;
+        description = ''
+          The base ZPath that Exhibitor should use
+        '';
+        example = "/exhibitor/config";
+      };
+
+      # Config options for s3 configType
+      s3Config = {
+        bucketName = mkOption {
+          type = types.str;
+          description = ''
+            Bucket name to store config
+          '';
+        };
+        objectKey = mkOption {
+          type = types.str;
+          description = ''
+            S3 key name to store the config
+          '';
+        };
+        configPrefix = mkOption {
+          type = types.str;
+          description = ''
+            When using AWS S3 shared config files, the prefix to use for values such as locks
+          '';
+          default = "exhibitor-";
+        };
+      };
+
+      # The next two are used for either s3backup or s3 configType
+      s3Credentials = mkOption {
+        type = types.nullOr types.path;
+        description = ''
+          Optional credentials to use for s3backup or s3config. Argument is the path
+          to an AWS credential properties file with two properties:
+          com.netflix.exhibitor.s3.access-key-id and com.netflix.exhibitor.s3.access-secret-key
+        '';
+        default = null;
+      };
+      s3Region = mkOption {
+        type = types.nullOr types.str;
+        description = ''
+          Optional region for S3 calls
+        '';
+        default = null;
+      };
+
+      # Config options for file config type
+      fsConfigDir = mkOption {
+        type = types.path;
+        description = ''
+          Directory to store Exhibitor properties (cannot be used with s3config).
+          Exhibitor uses file system locks so you can specify a shared location
+          so as to enable complete ensemble management.
+        '';
+      };
+      fsConfigLockPrefix = mkOption {
+        type = types.str;
+        description = ''
+          A prefix for a locking mechanism used in conjunction with fsconfigdir
+        '';
+        default = "exhibitor-lock-";
+      };
+      fsConfigName = mkOption {
+        type = types.str;
+        description = ''
+          The name of the file to store config in
+        '';
+        default = "exhibitor.properties";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.exhibitor = {
+      description = "Exhibitor Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      environment = {
+        ZOO_LOG_DIR = cfg.baseDir;
+      };
+      serviceConfig = {
+        /***
+          Exhibitor is a bit un-nixy. It wants to present to you a user interface in order to
+          mutate the configuration of both itself and ZooKeeper, and to coordinate changes
+          among the members of the Zookeeper ensemble. I'm going for a different approach here,
+          which is to manage all the configuration via nix and have it write out the configuration
+          files that exhibitor will use, and to reduce the amount of inter-exhibitor orchestration.
+        ***/
+        ExecStart = ''
+          ${pkgs.exhibitor}/bin/startExhibitor.sh ${cliOptions}
+        '';
+        User = "zookeeper";
+        PermissionsStartOnly = true;
+      };
+      # This is a bit wonky, but the reason for this is that Exhibitor tries to write to
+      # ${cfg.baseDir}/zookeeper/bin/../conf/zoo.cfg
+      # I want everything but the conf directory to be in the immutable nix store, and I want defaults
+      # from the nix store
+      # If I symlink the bin directory in, then bin/../ will resolve to the parent of the symlink in the
+      # immutable nix store. Bind mounting a writable conf over the existing conf might work, but it gets very
+      # messy with trying to copy the existing out into a mutable store.
+      # Another option is to try to patch upstream exhibitor, but the current package just pulls down the
+      # prebuild JARs off of Maven, rather than building them ourselves, as Maven support in Nix isn't
+      # very mature. So, it seems like a reasonable compromise is to just copy out of the immutable store
+      # just before starting the service, so we're running binaries from the immutable store, but we work around
+      # Exhibitor's desire to mutate its current installation.
+      preStart = ''
+        mkdir -m 0700 -p ${cfg.baseDir}/zookeeper
+        # Not doing a chown -R to keep the base ZK files owned by root
+        chown zookeeper ${cfg.baseDir} ${cfg.baseDir}/zookeeper
+        cp -Rf ${pkgs.zookeeper}/* ${cfg.baseDir}/zookeeper
+        chown -R zookeeper ${cfg.baseDir}/zookeeper/conf
+        chmod -R u+w ${cfg.baseDir}/zookeeper/conf
+        replace_what=$(echo ${pkgs.zookeeper} | sed 's/[\/&]/\\&/g')
+        replace_with=$(echo ${cfg.baseDir}/zookeeper | sed 's/[\/&]/\\&/g')
+        sed -i 's/'"$replace_what"'/'"$replace_with"'/g' ${cfg.baseDir}/zookeeper/bin/zk*.sh
+      '';
+    };
+    users.users.zookeeper = {
+      uid = config.ids.uids.zookeeper;
+      description = "Zookeeper daemon user";
+      home = cfg.baseDir;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/felix.nix b/nixpkgs/nixos/modules/services/misc/felix.nix
new file mode 100644
index 000000000000..21740c8c0b72
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/felix.nix
@@ -0,0 +1,102 @@
+# Felix server
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.felix;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.felix = {
+
+      enable = mkEnableOption "the Apache Felix OSGi service";
+
+      bundles = mkOption {
+        type = types.listOf types.package;
+        default = [ pkgs.felix_remoteshell ];
+        defaultText = "[ pkgs.felix_remoteshell ]";
+        description = "List of bundles that should be activated on startup";
+      };
+
+      user = mkOption {
+        default = "osgi";
+        description = "User account under which Apache Felix runs.";
+      };
+
+      group = mkOption {
+        default = "osgi";
+        description = "Group account under which Apache Felix runs.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    users.groups.osgi.gid = config.ids.gids.osgi;
+
+    users.users.osgi =
+      { uid = config.ids.uids.osgi;
+        description = "OSGi user";
+        home = "/homeless-shelter";
+      };
+
+    systemd.services.felix = {
+      description = "Felix server";
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        # Initialise felix instance on first startup
+        if [ ! -d /var/felix ]
+        then
+          # Symlink system files
+
+          mkdir -p /var/felix
+          chown ${cfg.user}:${cfg.group} /var/felix
+
+          for i in ${pkgs.felix}/*
+          do
+              if [ "$i" != "${pkgs.felix}/bundle" ]
+              then
+                  ln -sfn $i /var/felix/$(basename $i)
+              fi
+          done
+
+          # Symlink bundles
+          mkdir -p /var/felix/bundle
+          chown ${cfg.user}:${cfg.group} /var/felix/bundle
+
+          for i in ${pkgs.felix}/bundle/* ${toString cfg.bundles}
+          do
+              if [ -f $i ]
+              then
+                  ln -sfn $i /var/felix/bundle/$(basename $i)
+              elif [ -d $i ]
+              then
+                  for j in $i/bundle/*
+              do
+                  ln -sfn $j /var/felix/bundle/$(basename $j)
+              done
+              fi
+          done
+        fi
+      '';
+
+      script = ''
+        cd /var/felix
+        ${pkgs.su}/bin/su -s ${pkgs.bash}/bin/sh ${cfg.user} -c '${pkgs.jre}/bin/java -jar bin/felix.jar'
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/freeswitch.nix b/nixpkgs/nixos/modules/services/misc/freeswitch.nix
new file mode 100644
index 000000000000..b42f36e86637
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/freeswitch.nix
@@ -0,0 +1,105 @@
+{ config, lib, pkgs, ...}:
+with lib;
+let
+  cfg = config.services.freeswitch;
+  pkg = cfg.package;
+  configDirectory = pkgs.runCommand "freeswitch-config-d" { } ''
+    mkdir -p $out
+    cp -rT ${cfg.configTemplate} $out
+    chmod -R +w $out
+    ${concatStringsSep "\n" (mapAttrsToList (fileName: filePath: ''
+      mkdir -p $out/$(dirname ${fileName})
+      cp ${filePath} $out/${fileName}
+    '') cfg.configDir)}
+  '';
+  configPath = if cfg.enableReload
+    then "/etc/freeswitch"
+    else configDirectory;
+in {
+  options = {
+    services.freeswitch = {
+      enable = mkEnableOption "FreeSWITCH";
+      enableReload = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Issue the <literal>reloadxml</literal> command to FreeSWITCH when configuration directory changes (instead of restart).
+          See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Reloading">FreeSWITCH documentation</link> for more info.
+          The configuration directory is exposed at <filename>/etc/freeswitch</filename>.
+          See also <literal>systemd.services.*.restartIfChanged</literal>.
+        '';
+      };
+      configTemplate = mkOption {
+        type = types.path;
+        default = "${config.services.freeswitch.package}/share/freeswitch/conf/vanilla";
+        defaultText = literalExample "\${config.services.freeswitch.package}/share/freeswitch/conf/vanilla";
+        example = literalExample "\${config.services.freeswitch.package}/share/freeswitch/conf/minimal";
+        description = ''
+          Configuration template to use.
+          See available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>.
+          You can also set your own configuration directory.
+        '';
+      };
+      configDir = mkOption {
+        type = with types; attrsOf path;
+        default = { };
+        example = literalExample ''
+          {
+            "freeswitch.xml" = ./freeswitch.xml;
+            "dialplan/default.xml" = pkgs.writeText "dialplan-default.xml" '''
+              [xml lines]
+            ''';
+          }
+        '';
+        description = ''
+          Override file in FreeSWITCH config template directory.
+          Each top-level attribute denotes a file path in the configuration directory, its value is the file path.
+          See <link xlink:href="https://freeswitch.org/confluence/display/FREESWITCH/Default+Configuration">FreeSWITCH documentation</link> for more info.
+          Also check available templates in <link xlink:href="https://github.com/signalwire/freeswitch/tree/master/conf">FreeSWITCH repository</link>.
+        '';
+      };
+      package = mkOption {
+        type = types.package;
+        default = pkgs.freeswitch;
+        defaultText = literalExample "pkgs.freeswitch";
+        example = literalExample "pkgs.freeswitch";
+        description = ''
+          FreeSWITCH package.
+        '';
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+    environment.etc.freeswitch = mkIf cfg.enableReload {
+      source = configDirectory;
+    };
+    systemd.services.freeswitch-config-reload = mkIf cfg.enableReload {
+      before = [ "freeswitch.service" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ configDirectory ];
+      serviceConfig = {
+        ExecStart = "/run/current-system/systemd/bin/systemctl try-reload-or-restart freeswitch.service";
+        RemainAfterExit = true;
+        Type = "oneshot";
+      };
+    };
+    systemd.services.freeswitch = {
+      description = "Free and open-source application server for real-time communication";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = "freeswitch";
+        ExecStart = "${pkg}/bin/freeswitch -nf \\
+          -mod ${pkg}/lib/freeswitch/mod \\
+          -conf ${configPath} \\
+          -base /var/lib/freeswitch";
+        ExecReload = "${pkg}/bin/fs_cli -x reloadxml";
+        Restart = "on-failure";
+        RestartSec = "5s";
+        CPUSchedulingPolicy = "fifo";
+      };
+    };
+    environment.systemPackages = [ pkg ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/fstrim.nix b/nixpkgs/nixos/modules/services/misc/fstrim.nix
new file mode 100644
index 000000000000..b8841a7fe74c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/fstrim.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.fstrim;
+
+in {
+
+  options = {
+
+    services.fstrim = {
+      enable = mkEnableOption "periodic SSD TRIM of mounted partitions in background";
+
+      interval = mkOption {
+        type = types.str;
+        default = "weekly";
+        description = ''
+          How often we run fstrim. For most desktop and server systems
+          a sufficient trimming frequency is once a week.
+
+          The format is described in
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.packages = [ pkgs.utillinux ];
+
+    systemd.timers.fstrim = {
+      timerConfig = {
+        OnCalendar = cfg.interval;
+      };
+      wantedBy = [ "timers.target" ];
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix b/nixpkgs/nixos/modules/services/misc/gammu-smsd.nix
new file mode 100644
index 000000000000..3057d7fd1a09
--- /dev/null
+++ b/nixpkgs/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.users.${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}/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/nixpkgs/nixos/modules/services/misc/geoip-updater.nix b/nixpkgs/nixos/modules/services/misc/geoip-updater.nix
new file mode 100644
index 000000000000..baf0a8d73d19
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/geoip-updater.nix
@@ -0,0 +1,306 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.geoip-updater;
+
+  dbBaseUrl = "https://geolite.maxmind.com/download/geoip/database";
+
+  randomizedTimerDelaySec = "3600";
+
+  # Use writeScriptBin instead of writeScript, so that argv[0] (logged to the
+  # journal) doesn't include the long nix store path hash. (Prefixing the
+  # ExecStart= command with '@' doesn't work because we start a shell (new
+  # process) that creates a new argv[0].)
+  geoip-updater = pkgs.writeScriptBin "geoip-updater" ''
+    #!${pkgs.runtimeShell}
+    skipExisting=0
+    debug()
+    {
+        echo "<7>$@"
+    }
+    info()
+    {
+        echo "<6>$@"
+    }
+    error()
+    {
+        echo "<3>$@"
+    }
+    die()
+    {
+        error "$@"
+        exit 1
+    }
+    waitNetworkOnline()
+    {
+        ret=1
+        for i in $(seq 6); do
+            curl_out=$("${pkgs.curl.bin}/bin/curl" \
+                --silent --fail --show-error --max-time 60 "${dbBaseUrl}" 2>&1)
+            if [ $? -eq 0 ]; then
+                debug "Server is reachable (try $i)"
+                ret=0
+                break
+            else
+                debug "Server is unreachable (try $i): $curl_out"
+                sleep 10
+            fi
+        done
+        return $ret
+    }
+    dbFnameTmp()
+    {
+        dburl=$1
+        echo "${cfg.databaseDir}/.$(basename "$dburl")"
+    }
+    dbFnameTmpDecompressed()
+    {
+        dburl=$1
+        echo "${cfg.databaseDir}/.$(basename "$dburl")" | sed 's/\.\(gz\|xz\)$//'
+    }
+    dbFname()
+    {
+        dburl=$1
+        echo "${cfg.databaseDir}/$(basename "$dburl")" | sed 's/\.\(gz\|xz\)$//'
+    }
+    downloadDb()
+    {
+        dburl=$1
+        curl_out=$("${pkgs.curl.bin}/bin/curl" \
+            --silent --fail --show-error --max-time 900 -L -o "$(dbFnameTmp "$dburl")" "$dburl" 2>&1)
+        if [ $? -ne 0 ]; then
+            error "Failed to download $dburl: $curl_out"
+            return 1
+        fi
+    }
+    decompressDb()
+    {
+        fn=$(dbFnameTmp "$1")
+        ret=0
+        case "$fn" in
+            *.gz)
+                cmd_out=$("${pkgs.gzip}/bin/gzip" --decompress --force "$fn" 2>&1)
+                ;;
+            *.xz)
+                cmd_out=$("${pkgs.xz.bin}/bin/xz" --decompress --force "$fn" 2>&1)
+                ;;
+            *)
+                cmd_out=$(echo "File \"$fn\" is neither a .gz nor .xz file")
+                false
+                ;;
+        esac
+        if [ $? -ne 0 ]; then
+            error "$cmd_out"
+            ret=1
+        fi
+    }
+    atomicRename()
+    {
+        dburl=$1
+        mv "$(dbFnameTmpDecompressed "$dburl")" "$(dbFname "$dburl")"
+    }
+    removeIfNotInConfig()
+    {
+        # Arg 1 is the full path of an installed DB.
+        # If the corresponding database is not specified in the NixOS config we
+        # remove it.
+        db=$1
+        for cdb in ${lib.concatStringsSep " " cfg.databases}; do
+            confDb=$(echo "$cdb" | sed 's/\.\(gz\|xz\)$//')
+            if [ "$(basename "$db")" = "$(basename "$confDb")" ]; then
+                return 0
+            fi
+        done
+        rm "$db"
+        if [ $? -eq 0 ]; then
+            debug "Removed $(basename "$db") (not listed in services.geoip-updater.databases)"
+        else
+            error "Failed to remove $db"
+        fi
+    }
+    removeUnspecifiedDbs()
+    {
+        for f in "${cfg.databaseDir}/"*; do
+            test -f "$f" || continue
+            case "$f" in
+                *.dat|*.mmdb|*.csv)
+                    removeIfNotInConfig "$f"
+                    ;;
+                *)
+                    debug "Not removing \"$f\" (unknown file extension)"
+                    ;;
+            esac
+        done
+    }
+    downloadAndInstall()
+    {
+        dburl=$1
+        if [ "$skipExisting" -eq 1 -a -f "$(dbFname "$dburl")" ]; then
+            debug "Skipping existing file: $(dbFname "$dburl")"
+            return 0
+        fi
+        downloadDb "$dburl" || return 1
+        decompressDb "$dburl" || return 1
+        atomicRename "$dburl" || return 1
+        info "Updated $(basename "$(dbFname "$dburl")")"
+    }
+    for arg in "$@"; do
+        case "$arg" in
+            --skip-existing)
+                skipExisting=1
+                info "Option --skip-existing is set: not updating existing databases"
+                ;;
+            *)
+                error "Unknown argument: $arg";;
+        esac
+    done
+    waitNetworkOnline || die "Network is down (${dbBaseUrl} is unreachable)"
+    test -d "${cfg.databaseDir}" || die "Database directory (${cfg.databaseDir}) doesn't exist"
+    debug "Starting update of GeoIP databases in ${cfg.databaseDir}"
+    all_ret=0
+    for db in ${lib.concatStringsSep " \\\n        " cfg.databases}; do
+        downloadAndInstall "${dbBaseUrl}/$db" || all_ret=1
+    done
+    removeUnspecifiedDbs || all_ret=1
+    if [ $all_ret -eq 0 ]; then
+        info "Completed GeoIP database update in ${cfg.databaseDir}"
+    else
+        error "Completed GeoIP database update in ${cfg.databaseDir}, with error(s)"
+    fi
+    # Hack to work around systemd journal race:
+    # https://github.com/systemd/systemd/issues/2913
+    sleep 2
+    exit $all_ret
+  '';
+
+in
+
+{
+  options = {
+    services.geoip-updater = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to enable periodic downloading of GeoIP databases from
+          maxmind.com. You might want to enable this if you, for instance, use
+          ntopng or Wireshark.
+        '';
+      };
+
+      interval = mkOption {
+        type = types.str;
+        default = "weekly";
+        description = ''
+          Update the GeoIP databases at this time / interval.
+          The format is described in
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>.
+          To prevent load spikes on maxmind.com, the timer interval is
+          randomized by an additional delay of ${randomizedTimerDelaySec}
+          seconds. Setting a shorter interval than this is not recommended.
+        '';
+      };
+
+      databaseDir = mkOption {
+        type = types.path;
+        default = "/var/lib/geoip-databases";
+        description = ''
+          Directory that will contain GeoIP databases.
+        '';
+      };
+
+      databases = mkOption {
+        type = types.listOf types.str;
+        default = [
+          "GeoLiteCountry/GeoIP.dat.gz"
+          "GeoIPv6.dat.gz"
+          "GeoLiteCity.dat.xz"
+          "GeoLiteCityv6-beta/GeoLiteCityv6.dat.gz"
+          "asnum/GeoIPASNum.dat.gz"
+          "asnum/GeoIPASNumv6.dat.gz"
+          "GeoLite2-Country.mmdb.gz"
+          "GeoLite2-City.mmdb.gz"
+        ];
+        description = ''
+          Which GeoIP databases to update. The full URL is ${dbBaseUrl}/ +
+          <literal>the_database</literal>.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = (builtins.filter
+          (x: builtins.match ".*\\.(gz|xz)$" x == null) cfg.databases) == [];
+        message = ''
+          services.geoip-updater.databases supports only .gz and .xz databases.
+
+          Current value:
+          ${toString cfg.databases}
+
+          Offending element(s):
+          ${toString (builtins.filter (x: builtins.match ".*\\.(gz|xz)$" x == null) cfg.databases)};
+        '';
+      }
+    ];
+
+    users.users.geoip = {
+      group = "root";
+      description = "GeoIP database updater";
+      uid = config.ids.uids.geoip;
+    };
+
+    systemd.timers.geoip-updater =
+      { description = "GeoIP Updater Timer";
+        partOf = [ "geoip-updater.service" ];
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnCalendar = cfg.interval;
+        timerConfig.Persistent = "true";
+        timerConfig.RandomizedDelaySec = randomizedTimerDelaySec;
+      };
+
+    systemd.services.geoip-updater = {
+      description = "GeoIP Updater";
+      after = [ "network-online.target" "nss-lookup.target" ];
+      wants = [ "network-online.target" ];
+      preStart = ''
+        mkdir -p "${cfg.databaseDir}"
+        chmod 755 "${cfg.databaseDir}"
+        chown geoip:root "${cfg.databaseDir}"
+      '';
+      serviceConfig = {
+        ExecStart = "${geoip-updater}/bin/geoip-updater";
+        User = "geoip";
+        PermissionsStartOnly = true;
+      };
+    };
+
+    systemd.services.geoip-updater-setup = {
+      description = "GeoIP Updater Setup";
+      after = [ "network-online.target" "nss-lookup.target" ];
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      conflicts = [ "geoip-updater.service" ];
+      preStart = ''
+        mkdir -p "${cfg.databaseDir}"
+        chmod 755 "${cfg.databaseDir}"
+        chown geoip:root "${cfg.databaseDir}"
+      '';
+      serviceConfig = {
+        ExecStart = "${geoip-updater}/bin/geoip-updater --skip-existing";
+        User = "geoip";
+        PermissionsStartOnly = true;
+        # So it won't be (needlessly) restarted:
+        RemainAfterExit = true;
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitea.nix b/nixpkgs/nixos/modules/services/misc/gitea.nix
new file mode 100644
index 000000000000..f8bcedc94fe2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitea.nix
@@ -0,0 +1,519 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gitea;
+  gitea = cfg.package;
+  pg = config.services.postgresql;
+  useMysql = cfg.database.type == "mysql";
+  usePostgresql = cfg.database.type == "postgres";
+  useSqlite = cfg.database.type == "sqlite3";
+  configFile = pkgs.writeText "app.ini" ''
+    APP_NAME = ${cfg.appName}
+    RUN_USER = ${cfg.user}
+    RUN_MODE = prod
+
+    ${generators.toINI {} cfg.settings}
+
+    ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
+  '';
+in
+
+{
+  options = {
+    services.gitea = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Enable Gitea Service.";
+      };
+
+      package = mkOption {
+        default = pkgs.gitea;
+        type = types.package;
+        defaultText = "pkgs.gitea";
+        description = "gitea derivation to use";
+      };
+
+      useWizard = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Do not generate a configuration and use gitea' installation wizard instead. The first registered user will be administrator.";
+      };
+
+      stateDir = mkOption {
+        default = "/var/lib/gitea";
+        type = types.str;
+        description = "gitea data directory.";
+      };
+
+      log = {
+        rootPath = mkOption {
+          default = "${cfg.stateDir}/log";
+          type = types.str;
+          description = "Root path for log files.";
+        };
+        level = mkOption {
+          default = "Trace";
+          type = types.enum [ "Trace" "Debug" "Info" "Warn" "Error" "Critical" ];
+          description = "General log level.";
+        };
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "gitea";
+        description = "User account under which gitea runs.";
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "sqlite3" "mysql" "postgres" ];
+          example = "mysql";
+          default = "sqlite3";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = (if !usePostgresql then 3306 else pg.port);
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "gitea";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "gitea";
+          description = "Database user.";
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            The password corresponding to <option>database.user</option>.
+            Warning: this is stored in cleartext in the Nix store!
+            Use <option>database.passwordFile</option> instead.
+          '';
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/gitea-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = if (cfg.database.createDatabase && usePostgresql) then "/run/postgresql" else if (cfg.database.createDatabase && useMysql) then "/run/mysqld/mysqld.sock" else null;
+          defaultText = "null";
+          example = "/run/mysqld/mysqld.sock";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+
+        path = mkOption {
+          type = types.str;
+          default = "${cfg.stateDir}/data/gitea.db";
+          description = "Path to the sqlite3 database file.";
+        };
+
+        createDatabase = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to create a local database automatically.";
+        };
+      };
+
+      dump = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Enable a timer that runs gitea dump to generate backup-files of the
+            current gitea database and repositories.
+          '';
+        };
+
+        interval = mkOption {
+          type = types.str;
+          default = "04:31";
+          example = "hourly";
+          description = ''
+            Run a gitea dump at this interval. Runs by default at 04:31 every day.
+
+            The format is described in
+            <citerefentry><refentrytitle>systemd.time</refentrytitle>
+            <manvolnum>7</manvolnum></citerefentry>.
+          '';
+        };
+      };
+
+      appName = mkOption {
+        type = types.str;
+        default = "gitea: Gitea Service";
+        description = "Application name.";
+      };
+
+      repositoryRoot = mkOption {
+        type = types.str;
+        default = "${cfg.stateDir}/repositories";
+        description = "Path to the git repositories.";
+      };
+
+      domain = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Domain name of your server.";
+      };
+
+      rootUrl = mkOption {
+        type = types.str;
+        default = "http://localhost:3000/";
+        description = "Full public URL of gitea server.";
+      };
+
+      httpAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = "HTTP listen address.";
+      };
+
+      httpPort = mkOption {
+        type = types.int;
+        default = 3000;
+        description = "HTTP listen port.";
+      };
+
+      cookieSecure = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Marks session cookies as "secure" as a hint for browsers to only send
+          them via HTTPS. This option is recommend, if gitea is being served over HTTPS.
+        '';
+      };
+
+      staticRootPath = mkOption {
+        type = types.str;
+        default = "${gitea.data}";
+        example = "/var/lib/gitea/data";
+        description = "Upper level of template and static files path.";
+      };
+
+      mailerPasswordFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/secrets/gitea/mailpw";
+        description = "Path to a file containing the SMTP password.";
+      };
+
+      disableRegistration = mkEnableOption "the registration lock" // {
+        description = ''
+          By default any user can create an account on this <literal>gitea</literal> instance.
+          This can be disabled by using this option.
+
+          <emphasis>Note:</emphasis> please keep in mind that this should be added after the initial
+          deploy unless <link linkend="opt-services.gitea.useWizard">services.gitea.useWizard</link>
+          is <literal>true</literal> as the first registered user will be the administrator if
+          no install wizard is used.
+        '';
+      };
+
+      settings = mkOption {
+        type = with types; attrsOf (attrsOf (oneOf [ bool int str ]));
+        default = {};
+        description = ''
+          Gitea configuration. Refer to <link xlink:href="https://docs.gitea.io/en-us/config-cheat-sheet/"/>
+          for details on supported values.
+        '';
+        example = literalExample ''
+          {
+            "cron.sync_external_users" = {
+              RUN_AT_START = true;
+              SCHEDULE = "@every 24h";
+              UPDATE_EXISTING = true;
+            };
+            mailer = {
+              ENABLED = true;
+              MAILER_TYPE = "sendmail";
+              FROM = "do-not-reply@example.org";
+              SENDMAIL_PATH = "${pkgs.system-sendmail}/bin/sendmail";
+            };
+            other = {
+              SHOW_FOOTER_VERSION = false;
+            };
+          }
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "Configuration lines appended to the generated gitea configuration file.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.database.createDatabase -> cfg.database.user == cfg.user;
+        message = "services.gitea.database.user must match services.gitea.user if the database is to be automatically provisioned";
+      }
+    ];
+
+    services.gitea.settings = {
+      database = mkMerge [
+        {
+          DB_TYPE = cfg.database.type;
+        }
+        (mkIf (useMysql || usePostgresql) {
+          HOST = if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port;
+          NAME = cfg.database.name;
+          USER = cfg.database.user;
+          PASSWD = "#dbpass#";
+        })
+        (mkIf useSqlite {
+          PATH = cfg.database.path;
+        })
+        (mkIf usePostgresql {
+          SSL_MODE = "disable";
+        })
+      ];
+
+      repository = {
+        ROOT = cfg.repositoryRoot;
+      };
+
+      server = {
+        DOMAIN = cfg.domain;
+        HTTP_ADDR = cfg.httpAddress;
+        HTTP_PORT = cfg.httpPort;
+        ROOT_URL = cfg.rootUrl;
+        STATIC_ROOT_PATH = cfg.staticRootPath;
+        LFS_JWT_SECRET = "#jwtsecret#";
+      };
+
+      session = {
+        COOKIE_NAME = "session";
+        COOKIE_SECURE = cfg.cookieSecure;
+      };
+
+      security = {
+        SECRET_KEY = "#secretkey#";
+        INSTALL_LOCK = true;
+      };
+
+      log = {
+        ROOT_PATH = cfg.log.rootPath;
+        LEVEL = cfg.log.level;
+      };
+
+      service = {
+        DISABLE_REGISTRATION = cfg.disableRegistration;
+      };
+
+      mailer = mkIf (cfg.mailerPasswordFile != null) {
+        PASSWD = "#mailerpass#";
+      };
+    };
+
+    services.postgresql = optionalAttrs (usePostgresql && cfg.database.createDatabase) {
+      enable = mkDefault true;
+
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) {
+      enable = mkDefault true;
+      package = mkDefault pkgs.mariadb;
+
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/conf' - ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/custom' - ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/custom/conf' - ${cfg.user} gitea - -"
+      "d '${cfg.stateDir}/log' - ${cfg.user} gitea - -"
+      "d '${cfg.repositoryRoot}' - ${cfg.user} gitea - -"
+      "Z '${cfg.stateDir}' - ${cfg.user} gitea - -"
+
+      # If we have a folder or symlink with gitea locales, remove it
+      # And symlink the current gitea locales in place
+      "L+ '${cfg.stateDir}/conf/locale' - - - - ${gitea.out}/locale"
+    ];
+
+    systemd.services.gitea = {
+      description = "gitea";
+      after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
+      wantedBy = [ "multi-user.target" ];
+      path = [ gitea pkgs.gitAndTools.git ];
+
+      preStart = let
+        runConfig = "${cfg.stateDir}/custom/conf/app.ini";
+        secretKey = "${cfg.stateDir}/custom/conf/secret_key";
+        jwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret";
+      in ''
+        # copy custom configuration and generate a random secret key if needed
+        ${optionalString (cfg.useWizard == false) ''
+          cp -f ${configFile} ${runConfig}
+
+          if [ ! -e ${secretKey} ]; then
+              ${gitea}/bin/gitea generate secret SECRET_KEY > ${secretKey}
+          fi
+
+          if [ ! -e ${jwtSecret} ]; then
+              ${gitea}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret}
+          fi
+
+          KEY="$(head -n1 ${secretKey})"
+          DBPASS="$(head -n1 ${cfg.database.passwordFile})"
+          JWTSECRET="$(head -n1 ${jwtSecret})"
+          ${if (cfg.mailerPasswordFile == null) then ''
+            MAILERPASSWORD="#mailerpass#"
+          '' else ''
+            MAILERPASSWORD="$(head -n1 ${cfg.mailerPasswordFile} || :)"
+          ''}
+          sed -e "s,#secretkey#,$KEY,g" \
+              -e "s,#dbpass#,$DBPASS,g" \
+              -e "s,#jwtsecret#,$JWTSECRET,g" \
+              -e "s,#mailerpass#,$MAILERPASSWORD,g" \
+              -i ${runConfig}
+          chmod 640 ${runConfig} ${secretKey} ${jwtSecret}
+        ''}
+
+        # update all hooks' binary paths
+        HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*")
+        if [ "$HOOKS" ]
+        then
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
+        fi
+
+        # update command option in authorized_keys
+        if [ -r ${cfg.stateDir}/.ssh/authorized_keys ]
+        then
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gitea,${gitea}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys
+        fi
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = "gitea";
+        WorkingDirectory = cfg.stateDir;
+        ExecStart = "${gitea}/bin/gitea web";
+        Restart = "always";
+
+        # Filesystem
+        ProtectHome = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        ReadWritePaths = cfg.stateDir;
+        # Caps
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        # Misc.
+        LockPersonality = true;
+        RestrictRealtime = true;
+        PrivateMounts = true;
+        PrivateUsers = true;
+        MemoryDenyWriteExecute = true;
+        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @memlock @module @mount @obsolete @raw-io @reboot @resources @setuid @swap";
+        SystemCallArchitectures = "native";
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+      };
+
+      environment = {
+        USER = cfg.user;
+        HOME = cfg.stateDir;
+        GITEA_WORK_DIR = cfg.stateDir;
+      };
+    };
+
+    users.users = mkIf (cfg.user == "gitea") {
+      gitea = {
+        description = "Gitea Service";
+        home = cfg.stateDir;
+        useDefaultShell = true;
+        group = "gitea";
+        isSystemUser = true;
+      };
+    };
+
+    users.groups.gitea = {};
+
+    warnings =
+      optional (cfg.database.password != "") ''
+        config.services.gitea.database.password will be stored as plaintext in the Nix store. Use database.passwordFile instead.'' ++
+      optional (cfg.extraConfig != null) ''
+        services.gitea.`extraConfig` is deprecated, please use services.gitea.`settings`.
+      '';
+
+    # Create database passwordFile default when password is configured.
+    services.gitea.database.passwordFile =
+      (mkDefault (toString (pkgs.writeTextFile {
+        name = "gitea-database-password";
+        text = cfg.database.password;
+      })));
+
+    systemd.services.gitea-dump = mkIf cfg.dump.enable {
+       description = "gitea dump";
+       after = [ "gitea.service" ];
+       wantedBy = [ "default.target" ];
+       path = [ gitea ];
+
+       environment = {
+         USER = cfg.user;
+         HOME = cfg.stateDir;
+         GITEA_WORK_DIR = cfg.stateDir;
+       };
+
+       serviceConfig = {
+         Type = "oneshot";
+         User = cfg.user;
+         ExecStart = "${gitea}/bin/gitea dump";
+         WorkingDirectory = cfg.stateDir;
+       };
+    };
+
+    systemd.timers.gitea-dump = mkIf cfg.dump.enable {
+      description = "Update timer for gitea-dump";
+      partOf = [ "gitea-dump.service" ];
+      wantedBy = [ "timers.target" ];
+      timerConfig.OnCalendar = cfg.dump.interval;
+    };
+  };
+  meta.maintainers = with lib.maintainers; [ srhb ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitit.nix b/nixpkgs/nixos/modules/services/misc/gitit.nix
new file mode 100644
index 000000000000..1ec030549f98
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitit.nix
@@ -0,0 +1,724 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.gitit;
+
+  homeDir = "/var/lib/gitit";
+
+  toYesNo = b: if b then "yes" else "no";
+
+  gititShared = with cfg.haskellPackages; gitit + "/share/" + pkgs.stdenv.hostPlatform.system + "-" + ghc.name + "/" + gitit.pname + "-" + gitit.version;
+
+  gititWithPkgs = hsPkgs: extras: hsPkgs.ghcWithPackages (self: with self; [ gitit ] ++ (extras self));
+
+  gititSh = hsPkgs: extras: with pkgs; let
+    env = gititWithPkgs hsPkgs extras;
+  in writeScript "gitit" ''
+    #!${runtimeShell}
+    cd $HOME
+    export NIX_GHC="${env}/bin/ghc"
+    export NIX_GHCPKG="${env}/bin/ghc-pkg"
+    export NIX_GHC_DOCDIR="${env}/share/doc/ghc/html"
+    export NIX_GHC_LIBDIR=$( $NIX_GHC --print-libdir )
+    ${env}/bin/gitit -f ${configFile}
+  '';
+
+  gititOptions = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable the gitit service.";
+      };
+
+      haskellPackages = mkOption {
+        default = pkgs.haskellPackages;
+        defaultText = "pkgs.haskellPackages";
+        example = literalExample "pkgs.haskell.packages.ghc784";
+        description = "haskellPackages used to build gitit and plugins.";
+      };
+
+      extraPackages = mkOption {
+        default = self: [];
+        example = literalExample ''
+          haskellPackages: [
+            haskellPackages.wreq
+          ]
+        '';
+        description = ''
+          Extra packages available to ghc when running gitit. The
+          value must be a function which receives the attrset defined
+          in <varname>haskellPackages</varname> as the sole argument.
+        '';
+      };
+
+      address = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = "IP address on which the web server will listen.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 5001;
+        description = "Port on which the web server will run.";
+      };
+
+      wikiTitle = mkOption {
+        type = types.str;
+        default = "Gitit!";
+        description = "The wiki title.";
+      };
+
+      repositoryType = mkOption {
+        type = types.enum ["git" "darcs" "mercurial"];
+        default = "git";
+        description = "Specifies the type of repository used for wiki content.";
+      };
+
+      repositoryPath = mkOption {
+        type = types.path;
+        default = homeDir + "/wiki";
+        description = ''
+          Specifies the path of the repository directory. If it does not
+          exist, gitit will create it on startup.
+        '';
+      };
+
+      requireAuthentication = mkOption {
+        type = types.enum [ "none" "modify" "read" ];
+        default = "modify";
+        description = ''
+          If 'none', login is never required, and pages can be edited
+          anonymously.  If 'modify', login is required to modify the wiki
+          (edit, add, delete pages, upload files).  If 'read', login is
+          required to see any wiki pages.
+        '';
+      };
+
+      authenticationMethod = mkOption {
+        type = types.enum [ "form" "http" "generic" "github" ];
+        default = "form";
+        description = ''
+          'form' means that users will be logged in and registered using forms
+          in the gitit web interface.  'http' means that gitit will assume that
+          HTTP authentication is in place and take the logged in username from
+          the "Authorization" field of the HTTP request header (in addition,
+          the login/logout and registration links will be suppressed).
+          'generic' means that gitit will assume that some form of
+          authentication is in place that directly sets REMOTE_USER to the name
+          of the authenticated user (e.g. mod_auth_cas on apache).  'rpx' means
+          that gitit will attempt to log in through https://rpxnow.com.  This
+          requires that 'rpx-domain', 'rpx-key', and 'base-url' be set below,
+          and that 'curl' be in the system path.
+        '';
+      };
+
+      userFile = mkOption {
+        type = types.path;
+        default = homeDir + "/gitit-users";
+        description = ''
+          Specifies the path of the file containing user login information.  If
+          it does not exist, gitit will create it (with an empty user list).
+          This file is not used if 'http' is selected for
+          authentication-method.
+        '';
+      };
+
+      sessionTimeout = mkOption {
+        type = types.int;
+        default = 60;
+        description = ''
+          Number of minutes of inactivity before a session expires.
+        '';
+      };
+
+      staticDir = mkOption {
+        type = types.path;
+        default = gititShared + "/data/static";
+        description = ''
+          Specifies the path of the static directory (containing javascript,
+          css, and images).  If it does not exist, gitit will create it and
+          populate it with required scripts, stylesheets, and images.
+        '';
+      };
+
+      defaultPageType = mkOption {
+        type = types.enum [ "markdown" "rst" "latex" "html" "markdown+lhs" "rst+lhs" "latex+lhs" ];
+        default = "markdown";
+        description = ''
+          Specifies the type of markup used to interpret pages in the wiki.
+          Possible values are markdown, rst, latex, html, markdown+lhs,
+          rst+lhs, and latex+lhs. (the +lhs variants treat the input as
+          literate Haskell. See pandoc's documentation for more details.) If
+          Markdown is selected, pandoc's syntax extensions (for footnotes,
+          delimited code blocks, etc.) will be enabled. Note that pandoc's
+          restructuredtext parser is not complete, so some pages may not be
+          rendered correctly if rst is selected. The same goes for latex and
+          html.
+        '';
+      };
+
+      math = mkOption {
+        type = types.enum [ "mathml" "raw" "mathjax" "jsmath" "google" ];
+        default = "mathml";
+        description = ''
+          Specifies how LaTeX math is to be displayed.  Possible values are
+          mathml, raw, mathjax, jsmath, and google.  If mathml is selected,
+          gitit will convert LaTeX math to MathML and link in a script,
+          MathMLinHTML.js, that allows the MathML to be seen in Gecko browsers,
+          IE + mathplayer, and Opera. In other browsers you may get a jumble of
+          characters.  If raw is selected, the LaTeX math will be displayed as
+          raw LaTeX math.  If mathjax is selected, gitit will link to the
+          remote mathjax script.  If jsMath is selected, gitit will link to the
+          script /js/jsMath/easy/load.js, and will assume that jsMath has been
+          installed into the js/jsMath directory.  This is the most portable
+          solution. If google is selected, the google chart API is called to
+          render the formula as an image. This requires a connection to google,
+          and might raise a technical or a privacy problem.
+        '';
+      };
+
+      mathJaxScript = mkOption {
+        type = types.str;
+        default = "https://d3eoax9i5htok0.cloudfront.net/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
+        description = ''
+          Specifies the path to MathJax rendering script.  You might want to
+          use your own MathJax script to render formulas without Internet
+          connection or if you want to use some special LaTeX packages.  Note:
+          path specified there cannot be an absolute path to a script on your
+          hdd, instead you should run your (local if you wish) HTTP server
+          which will serve the MathJax.js script. You can easily (in four lines
+          of code) serve MathJax.js using
+          http://happstack.com/docs/crashcourse/FileServing.html Do not forget
+          the "http://" prefix (e.g. http://localhost:1234/MathJax.js).
+        '';
+      };
+
+      showLhsBirdTracks = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Specifies whether to show Haskell code blocks in "bird style", with
+          "> " at the beginning of each line.
+        '';
+      };
+
+      templatesDir = mkOption {
+        type = types.path;
+        default = gititShared + "/data/templates";
+        description = ''
+          Specifies the path of the directory containing page templates.  If it
+          does not exist, gitit will create it with default templates.  Users
+          may wish to edit the templates to customize the appearance of their
+          wiki. The template files are HStringTemplate templates.  Variables to
+          be interpolated appear between $\'s. Literal $\'s must be
+          backslash-escaped.
+        '';
+      };
+
+      logFile = mkOption {
+        type = types.path;
+        default = homeDir + "/gitit.log";
+        description = ''
+          Specifies the path of gitit's log file.  If it does not exist, gitit
+          will create it. The log is in Apache combined log format.
+        '';
+      };
+
+      logLevel = mkOption {
+        type = types.enum [ "DEBUG" "INFO" "NOTICE" "WARNING" "ERROR" "CRITICAL" "ALERT" "EMERGENCY" ];
+        default = "ERROR";
+        description = ''
+          Determines how much information is logged.  Possible values (from
+          most to least verbose) are DEBUG, INFO, NOTICE, WARNING, ERROR,
+          CRITICAL, ALERT, EMERGENCY.
+        '';
+      };
+
+      frontPage = mkOption {
+        type = types.str;
+        default = "Front Page";
+        description = ''
+          Specifies which wiki page is to be used as the wiki's front page.
+          Gitit creates a default front page on startup, if one does not exist
+          already.
+        '';
+      };
+
+      noDelete = mkOption {
+        type = types.str;
+        default = "Front Page, Help";
+        description = ''
+          Specifies pages that cannot be deleted through the web interface.
+          (They can still be deleted directly using git or darcs.) A
+          comma-separated list of page names.  Leave blank to allow every page
+          to be deleted.
+        '';
+      };
+
+      noEdit = mkOption {
+        type = types.str;
+        default = "Help";
+        description = ''
+          Specifies pages that cannot be edited through the web interface.
+          Leave blank to allow every page to be edited.
+        '';
+      };
+
+      defaultSummary = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Specifies text to be used in the change description if the author
+          leaves the "description" field blank.  If default-summary is blank
+          (the default), the author will be required to fill in the description
+          field.
+        '';
+      };
+
+      tableOfContents = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Specifies whether to print a tables of contents (with links to
+          sections) on each wiki page.
+        '';
+      };
+
+      plugins = mkOption {
+        type = with types; listOf str;
+        default = [ (gititShared + "/plugins/Dot.hs") ];
+        description = ''
+          Specifies a list of plugins to load. Plugins may be specified either
+          by their path or by their module name. If the plugin name starts
+          with Gitit.Plugin., gitit will assume that the plugin is an installed
+          module and will not try to find a source file.
+        '';
+      };
+
+      useCache = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Specifies whether to cache rendered pages.  Note that if use-feed is
+          selected, feeds will be cached regardless of the value of use-cache.
+        '';
+      };
+
+      cacheDir = mkOption {
+        type = types.path;
+        default = homeDir + "/cache";
+        description = "Path where rendered pages will be cached.";
+      };
+
+      maxUploadSize = mkOption {
+        type = types.str;
+        default = "1000K";
+        description = ''
+          Specifies an upper limit on the size (in bytes) of files uploaded
+          through the wiki's web interface.  To disable uploads, set this to
+          0K.  This will result in the uploads link disappearing and the
+          _upload url becoming inactive.
+        '';
+      };
+
+      maxPageSize = mkOption {
+        type = types.str;
+        default = "1000K";
+        description = "Specifies an upper limit on the size (in bytes) of pages.";
+      };
+
+      debugMode = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Causes debug information to be logged while gitit is running.";
+      };
+
+      compressResponses = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Specifies whether HTTP responses should be compressed.";
+      };
+
+      mimeTypesFile = mkOption {
+        type = types.path;
+        default = "/etc/mime/types.info";
+        description = ''
+          Specifies the path of a file containing mime type mappings.  Each
+          line of the file should contain two fields, separated by whitespace.
+          The first field is the mime type, the second is a file extension.
+          For example:
+<programlisting>
+video/x-ms-wmx  wmx
+</programlisting>
+          If the file is not found, some simple defaults will be used.
+        '';
+      };
+
+      useReCaptcha = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If true, causes gitit to use the reCAPTCHA service
+          (http://recaptcha.net) to prevent bots from creating accounts.
+        '';
+      };
+
+      reCaptchaPrivateKey = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Specifies the private key for the reCAPTCHA service.  To get
+          these, you need to create an account at http://recaptcha.net.
+        '';
+      };
+
+      reCaptchaPublicKey = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Specifies the public key for the reCAPTCHA service.  To get
+          these, you need to create an account at http://recaptcha.net.
+        '';
+      };
+
+      accessQuestion = mkOption {
+        type = types.str;
+        default = "What is the code given to you by Ms. X?";
+        description = ''
+          Specifies a question that users must answer when they attempt to
+          create an account
+        '';
+      };
+
+      accessQuestionAnswers = mkOption {
+        type = types.str;
+        default = "RED DOG, red dog";
+        description = ''
+          Specifies a question that users must answer when they attempt to
+          create an account, along with a comma-separated list of acceptable
+          answers.  This can be used to institute a rudimentary password for
+          signing up as a user on the wiki, or as an alternative to reCAPTCHA.
+          Example:
+          access-question:  What is the code given to you by Ms. X?
+          access-question-answers:  RED DOG, red dog
+        '';
+      };
+
+      rpxDomain = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Specifies the domain and key of your RPX account.  The domain is just
+          the prefix of the complete RPX domain, so if your full domain is
+          'https://foo.rpxnow.com/', use 'foo' as the value of rpx-domain.
+        '';
+      };
+
+      rpxKey = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "RPX account access key.";
+      };
+
+      mailCommand = mkOption {
+        type = types.str;
+        default = "sendmail %s";
+        description = ''
+          Specifies the command to use to send notification emails.  '%s' will
+          be replaced by the destination email address.  The body of the
+          message will be read from stdin.  If this field is left blank,
+          password reset will not be offered.
+        '';
+      };
+
+      resetPasswordMessage = mkOption {
+        type = types.lines;
+        default = ''
+          > From: gitit@$hostname$
+          > To: $useremail$
+          > Subject: Wiki password reset
+          >
+          > Hello $username$,
+          >
+          > To reset your password, please follow the link below:
+          > http://$hostname$:$port$$resetlink$
+          >
+          > Regards
+        '';
+        description = ''
+          Gives the text of the message that will be sent to the user should
+          she want to reset her password, or change other registration info.
+          The lines must be indented, and must begin with '>'.  The initial
+          spaces and '> ' will be stripped off.  $username$ will be replaced by
+          the user's username, $useremail$ by her email address, $hostname$ by
+          the hostname on which the wiki is running (as returned by the
+          hostname system call), $port$ by the port on which the wiki is
+          running, and $resetlink$ by the relative path of a reset link derived
+          from the user's existing hashed password. If your gitit wiki is being
+          proxied to a location other than the root path of $port$, you should
+          change the link to reflect this: for example, to
+          http://$hostname$/path/to/wiki$resetlink$ or
+          http://gitit.$hostname$$resetlink$
+        '';
+      };
+
+      useFeed = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Specifies whether an ATOM feed should be enabled (for the site and
+          for individual pages).
+        '';
+      };
+
+      baseUrl = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          The base URL of the wiki, to be used in constructing feed IDs and RPX
+          token_urls.  Set this if useFeed is false or authentication-method
+          is 'rpx'.
+        '';
+      };
+
+      absoluteUrls = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Make wikilinks absolute with respect to the base-url.  So, for
+          example, in a wiki served at the base URL '/wiki', on a page
+          Sub/Page, the wikilink '[Cactus]()' will produce a link to
+          '/wiki/Cactus' if absoluteUrls is true, and a relative link to
+          'Cactus' (referring to '/wiki/Sub/Cactus') if absolute-urls is 'no'.
+        '';
+      };
+
+      feedDays = mkOption {
+        type = types.int;
+        default = 14;
+        description = "Number of days to be included in feeds.";
+      };
+
+      feedRefreshTime = mkOption {
+        type = types.int;
+        default = 60;
+        description = "Number of minutes to cache feeds before refreshing.";
+      };
+
+      pdfExport = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If true, PDF will appear in export options. PDF will be created using
+          pdflatex, which must be installed and in the path. Note that PDF
+          exports create significant additional server load.
+        '';
+      };
+
+      pandocUserData = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = ''
+          If a directory is specified, this will be searched for pandoc
+          customizations. These can include a templates/ directory for custom
+          templates for various export formats, an S5 directory for custom S5
+          styles, and a reference.odt for ODT exports. If no directory is
+          specified, $HOME/.pandoc will be searched. See pandoc's README for
+          more information.
+        '';
+      };
+
+      xssSanitize = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If true, all HTML (including that produced by pandoc) is filtered
+          through xss-sanitize.  Set to no only if you trust all of your users.
+        '';
+      };
+
+      oauthClientId = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "OAuth client ID";
+      };
+
+      oauthClientSecret = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "OAuth client secret";
+      };
+
+      oauthCallback = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "OAuth callback URL";
+      };
+
+      oauthAuthorizeEndpoint = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "OAuth authorize endpoint";
+      };
+
+      oauthAccessTokenEndpoint = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "OAuth access token endpoint";
+      };
+
+      githubOrg = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "Github organization";
+      };
+  };
+
+  configFile = pkgs.writeText "gitit.conf" ''
+    address: ${cfg.address}
+    port: ${toString cfg.port}
+    wiki-title: ${cfg.wikiTitle}
+    repository-type: ${cfg.repositoryType}
+    repository-path: ${cfg.repositoryPath}
+    require-authentication: ${cfg.requireAuthentication}
+    authentication-method: ${cfg.authenticationMethod}
+    user-file: ${cfg.userFile}
+    session-timeout: ${toString cfg.sessionTimeout}
+    static-dir: ${cfg.staticDir}
+    default-page-type: ${cfg.defaultPageType}
+    math: ${cfg.math}
+    mathjax-script: ${cfg.mathJaxScript}
+    show-lhs-bird-tracks: ${toYesNo cfg.showLhsBirdTracks}
+    templates-dir: ${cfg.templatesDir}
+    log-file: ${cfg.logFile}
+    log-level: ${cfg.logLevel}
+    front-page: ${cfg.frontPage}
+    no-delete: ${cfg.noDelete}
+    no-edit: ${cfg.noEdit}
+    default-summary: ${cfg.defaultSummary}
+    table-of-contents: ${toYesNo cfg.tableOfContents}
+    plugins: ${concatStringsSep "," cfg.plugins}
+    use-cache: ${toYesNo cfg.useCache}
+    cache-dir: ${cfg.cacheDir}
+    max-upload-size: ${cfg.maxUploadSize}
+    max-page-size: ${cfg.maxPageSize}
+    debug-mode: ${toYesNo cfg.debugMode}
+    compress-responses: ${toYesNo cfg.compressResponses}
+    mime-types-file: ${cfg.mimeTypesFile}
+    use-recaptcha: ${toYesNo cfg.useReCaptcha}
+    recaptcha-private-key: ${toString cfg.reCaptchaPrivateKey}
+    recaptcha-public-key: ${toString cfg.reCaptchaPublicKey}
+    access-question: ${cfg.accessQuestion}
+    access-question-answers: ${cfg.accessQuestionAnswers}
+    rpx-domain: ${toString cfg.rpxDomain}
+    rpx-key: ${toString cfg.rpxKey}
+    mail-command: ${cfg.mailCommand}
+    reset-password-message: ${cfg.resetPasswordMessage}
+    use-feed: ${toYesNo cfg.useFeed}
+    base-url: ${toString cfg.baseUrl}
+    absolute-urls: ${toYesNo cfg.absoluteUrls}
+    feed-days: ${toString cfg.feedDays}
+    feed-refresh-time: ${toString cfg.feedRefreshTime}
+    pdf-export: ${toYesNo cfg.pdfExport}
+    pandoc-user-data: ${toString cfg.pandocUserData}
+    xss-sanitize: ${toYesNo cfg.xssSanitize}
+
+    [Github]
+    oauthclientid: ${toString cfg.oauthClientId}
+    oauthclientsecret: ${toString cfg.oauthClientSecret}
+    oauthcallback: ${toString cfg.oauthCallback}
+    oauthauthorizeendpoint: ${toString cfg.oauthAuthorizeEndpoint}
+    oauthaccesstokenendpoint: ${toString cfg.oauthAccessTokenEndpoint}
+    github-org: ${toString cfg.githubOrg}
+  '';
+
+in
+
+{
+
+  options.services.gitit = gititOptions;
+
+  config = mkIf cfg.enable {
+
+    users.users.gitit = {
+      group = config.users.groups.gitit.name;
+      description = "Gitit user";
+      home = homeDir;
+      createHome = true;
+      uid = config.ids.uids.gitit;
+    };
+
+    users.groups.gitit.gid = config.ids.gids.gitit;
+
+    systemd.services.gitit = let
+      uid = toString config.ids.uids.gitit;
+      gid = toString config.ids.gids.gitit;
+    in {
+      description = "Git and Pandoc Powered Wiki";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [ curl ]
+             ++ optional cfg.pdfExport texlive.combined.scheme-basic
+	     ++ optional (cfg.repositoryType == "darcs") darcs
+	     ++ optional (cfg.repositoryType == "mercurial") mercurial
+	     ++ optional (cfg.repositoryType == "git") git;
+
+      preStart = let
+        gm = "gitit@${config.networking.hostName}";
+      in
+      with cfg; ''
+        chown ${uid}:${gid} -R ${homeDir}
+        for dir in ${repositoryPath} ${staticDir} ${templatesDir} ${cacheDir}
+        do
+          if [ ! -d $dir ]
+          then
+            mkdir -p $dir
+            find $dir -type d -exec chmod 0750 {} +
+            find $dir -type f -exec chmod 0640 {} +
+          fi
+        done
+        cd ${repositoryPath}
+	${
+	  if repositoryType == "darcs" then
+	  ''
+	  if [ ! -d _darcs ]
+	  then
+	    ${pkgs.darcs}/bin/darcs initialize
+	    echo "${gm}" > _darcs/prefs/email
+	  ''
+	  else if repositoryType == "mercurial" then
+	  ''
+	  if [ ! -d .hg ]
+	  then
+	    ${pkgs.mercurial}/bin/hg init
+	    cat >> .hg/hgrc <<NAMED
+[ui]
+username = gitit ${gm}
+NAMED
+	  ''
+	  else
+	  ''
+	  if [ ! -d  .git ]
+          then
+            ${pkgs.git}/bin/git init
+            ${pkgs.git}/bin/git config user.email "${gm}"
+            ${pkgs.git}/bin/git config user.name "gitit"
+	  ''}
+          chown ${uid}:${gid} -R ${repositoryPath}
+          fi
+	cd -
+      '';
+
+      serviceConfig = {
+        User = config.users.users.gitit.name;
+        Group = config.users.groups.gitit.name;
+        ExecStart = with cfg; gititSh haskellPackages extraPackages;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.nix b/nixpkgs/nixos/modules/services/misc/gitlab.nix
new file mode 100644
index 000000000000..1ada131bd7ba
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitlab.nix
@@ -0,0 +1,881 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gitlab;
+
+  ruby = cfg.packages.gitlab.ruby;
+
+  postgresqlPackage = if config.services.postgresql.enable then
+                        config.services.postgresql.package
+                      else
+                        pkgs.postgresql;
+
+  gitlabSocket = "${cfg.statePath}/tmp/sockets/gitlab.socket";
+  gitalySocket = "${cfg.statePath}/tmp/sockets/gitaly.socket";
+  pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
+
+  databaseConfig = {
+    production = {
+      adapter = "postgresql";
+      database = cfg.databaseName;
+      host = cfg.databaseHost;
+      username = cfg.databaseUsername;
+      encoding = "utf8";
+      pool = cfg.databasePool;
+    } // cfg.extraDatabaseConfig;
+  };
+
+  # We only want to create a database if we're actually going to connect to it.
+  databaseActuallyCreateLocally = cfg.databaseCreateLocally && cfg.databaseHost == "";
+
+  gitalyToml = pkgs.writeText "gitaly.toml" ''
+    socket_path = "${lib.escape ["\""] gitalySocket}"
+    bin_dir = "${cfg.packages.gitaly}/bin"
+    prometheus_listen_addr = "localhost:9236"
+
+    [git]
+    bin_path = "${pkgs.git}/bin/git"
+
+    [gitaly-ruby]
+    dir = "${cfg.packages.gitaly.ruby}"
+
+    [gitlab-shell]
+    dir = "${cfg.packages.gitlab-shell}"
+    secret_file = "${cfg.statePath}/gitlab_shell_secret"
+    gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}"
+    http_settings = { self_signed_cert = false }
+
+    ${concatStringsSep "\n" (attrValues (mapAttrs (k: v: ''
+    [[storage]]
+    name = "${lib.escape ["\""] k}"
+    path = "${lib.escape ["\""] v.path}"
+    '') gitlabConfig.production.repositories.storages))}
+  '';
+
+  gitlabShellConfig = {
+    user = cfg.user;
+    gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}";
+    http_settings.self_signed_cert = false;
+    repos_path = "${cfg.statePath}/repositories";
+    secret_file = "${cfg.statePath}/gitlab_shell_secret";
+    log_file = "${cfg.statePath}/log/gitlab-shell.log";
+    custom_hooks_dir = "${cfg.statePath}/custom_hooks";
+    redis = {
+      bin = "${pkgs.redis}/bin/redis-cli";
+      host = "127.0.0.1";
+      port = 6379;
+      database = 0;
+      namespace = "resque:gitlab";
+    };
+  };
+
+  redisConfig.production.url = "redis://localhost:6379/";
+
+  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 = true;
+          builds = true;
+          container_registry = true;
+        };
+      };
+      repositories.storages.default.path = "${cfg.statePath}/repositories";
+      repositories.storages.default.gitaly_address = "unix:${gitalySocket}";
+      artifacts.enabled = true;
+      lfs.enabled = true;
+      gravatar.enabled = true;
+      cron_jobs = { };
+      gitlab_ci.builds_path = "${cfg.statePath}/builds";
+      ldap.enabled = false;
+      omniauth.enabled = false;
+      shared.path = "${cfg.statePath}/shared";
+      gitaly.client_path = "${cfg.packages.gitaly}/bin";
+      backup.path = "${cfg.backupPath}";
+      gitlab_shell = {
+        path = "${cfg.packages.gitlab-shell}";
+        hooks_path = "${cfg.statePath}/shell/hooks";
+        secret_file = "${cfg.statePath}/gitlab_shell_secret";
+        upload_pack = true;
+        receive_pack = true;
+      };
+      workhorse.secret_file = "${cfg.statePath}/.gitlab_workhorse_secret";
+      git.bin_path = "git";
+      monitoring = {
+        ip_whitelist = [ "127.0.0.0/8" "::1/128" ];
+        sidekiq_exporter = {
+          enable = true;
+          address = "localhost";
+          port = 3807;
+        };
+      };
+      extra = {};
+      uploads.storage_path = cfg.statePath;
+    };
+  };
+
+  gitlabEnv = {
+    HOME = "${cfg.statePath}/home";
+    UNICORN_PATH = "${cfg.statePath}/";
+    GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/";
+    SCHEMA = "${cfg.statePath}/db/structure.sql";
+    GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
+    GITLAB_LOG_PATH = "${cfg.statePath}/log";
+    GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig);
+    prometheus_multiproc_dir = "/run/gitlab";
+    RAILS_ENV = "production";
+  };
+
+  gitlab-rake = pkgs.stdenv.mkDerivation {
+    name = "gitlab-rake";
+    buildInputs = [ pkgs.makeWrapper ];
+    dontBuild = true;
+    dontUnpack = true;
+    installPhase = ''
+      mkdir -p $out/bin
+      makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \
+          ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
+          --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar postgresqlPackage pkgs.coreutils pkgs.procps ]}:$PATH' \
+          --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \
+          --run 'cd ${cfg.packages.gitlab}/share/gitlab'
+     '';
+  };
+
+  gitlab-rails = pkgs.stdenv.mkDerivation {
+    name = "gitlab-rails";
+    buildInputs = [ pkgs.makeWrapper ];
+    dontBuild = true;
+    dontUnpack = true;
+    installPhase = ''
+      mkdir -p $out/bin
+      makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rails $out/bin/gitlab-rails \
+          ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
+          --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar postgresqlPackage pkgs.coreutils pkgs.procps ]}:$PATH' \
+          --run 'cd ${cfg.packages.gitlab}/share/gitlab'
+     '';
+  };
+
+  extraGitlabRb = pkgs.writeText "extra-gitlab.rb" cfg.extraGitlabRb;
+
+  smtpSettings = pkgs.writeText "gitlab-smtp-settings.rb" ''
+    if Rails.env.production?
+      Rails.application.config.action_mailer.delivery_method = :smtp
+
+      ActionMailer::Base.delivery_method = :smtp
+      ActionMailer::Base.smtp_settings = {
+        address: "${cfg.smtp.address}",
+        port: ${toString cfg.smtp.port},
+        ${optionalString (cfg.smtp.username != null) ''user_name: "${cfg.smtp.username}",''}
+        ${optionalString (cfg.smtp.passwordFile != null) ''password: "@smtpPassword@",''}
+        domain: "${cfg.smtp.domain}",
+        ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"}
+        enable_starttls_auto: ${boolToString cfg.smtp.enableStartTLSAuto},
+        ca_file: "/etc/ssl/certs/ca-certificates.crt",
+        openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}'
+      }
+    end
+  '';
+
+in {
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ])
+    (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
+  ];
+
+  options = {
+    services.gitlab = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the gitlab service.
+        '';
+      };
+
+      packages.gitlab = mkOption {
+        type = types.package;
+        default = pkgs.gitlab;
+        defaultText = "pkgs.gitlab";
+        description = "Reference to the gitlab package";
+        example = "pkgs.gitlab-ee";
+      };
+
+      packages.gitlab-shell = mkOption {
+        type = types.package;
+        default = pkgs.gitlab-shell;
+        defaultText = "pkgs.gitlab-shell";
+        description = "Reference to the gitlab-shell package";
+      };
+
+      packages.gitlab-workhorse = mkOption {
+        type = types.package;
+        default = pkgs.gitlab-workhorse;
+        defaultText = "pkgs.gitlab-workhorse";
+        description = "Reference to the gitlab-workhorse package";
+      };
+
+      packages.gitaly = mkOption {
+        type = types.package;
+        default = pkgs.gitaly;
+        defaultText = "pkgs.gitaly";
+        description = "Reference to the gitaly package";
+      };
+
+      statePath = mkOption {
+        type = types.str;
+        default = "/var/gitlab/state";
+        description = ''
+          Gitlab state directory. Configuration, repositories and
+          logs, among other things, are stored here.
+
+          The directory will be created automatically if it doesn't
+          exist already. Its parent directories must be owned by
+          either <literal>root</literal> or the user set in
+          <option>services.gitlab.user</option>.
+        '';
+      };
+
+      backupPath = mkOption {
+        type = types.str;
+        default = cfg.statePath + "/backup";
+        description = "Gitlab path for backups.";
+      };
+
+      databaseHost = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Gitlab database hostname. An empty string means <quote>use
+          local unix socket connection</quote>.
+        '';
+      };
+
+      databasePasswordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = ''
+          File containing the Gitlab database user password.
+
+          This should be a string, not a nix path, since nix paths are
+          copied into the world-readable nix store.
+        '';
+      };
+
+      databaseCreateLocally = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether a database should be automatically created on the
+          local host. Set this to <literal>false</literal> if you plan
+          on provisioning a local database yourself. This has no effect
+          if <option>services.gitlab.databaseHost</option> is customized.
+        '';
+      };
+
+      databaseName = mkOption {
+        type = types.str;
+        default = "gitlab";
+        description = "Gitlab database name.";
+      };
+
+      databaseUsername = mkOption {
+        type = types.str;
+        default = "gitlab";
+        description = "Gitlab database user.";
+      };
+
+      databasePool = mkOption {
+        type = types.int;
+        default = 5;
+        description = "Database connection pool size.";
+      };
+
+      extraDatabaseConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        description = "Extra configuration in config/database.yml.";
+      };
+
+      extraGitlabRb = mkOption {
+        type = types.str;
+        default = "";
+        example = ''
+          if Rails.env.production?
+            Rails.application.config.action_mailer.delivery_method = :sendmail
+            ActionMailer::Base.delivery_method = :sendmail
+            ActionMailer::Base.sendmail_settings = {
+              location: "/run/wrappers/bin/sendmail",
+              arguments: "-i -t"
+            }
+          end
+        '';
+        description = ''
+          Extra configuration to be placed in config/extra-gitlab.rb. This can
+          be used to add configuration not otherwise exposed through this module's
+          options.
+        '';
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = config.networking.hostName;
+        description = "Gitlab host name. Used e.g. for copy-paste URLs.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8080;
+        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.
+        '';
+      };
+
+      initialRootPasswordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = ''
+          File containing the initial password of the root account if
+          this is a new install.
+
+          This should be a string, not a nix path, since nix paths are
+          copied into the world-readable nix store.
+        '';
+      };
+
+      smtp = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Enable gitlab mail delivery over SMTP.";
+        };
+
+        address = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = "Address of the SMTP server for Gitlab.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 465;
+          description = "Port of the SMTP server for Gitlab.";
+        };
+
+        username = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = "Username of the SMTP server for Gitlab.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          description = ''
+            File containing the password of the SMTP server for Gitlab.
+
+            This should be a string, not a nix path, since nix paths
+            are copied into the world-readable nix store.
+          '';
+        };
+
+        domain = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = "HELO domain to use for outgoing mail.";
+        };
+
+        authentication = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
+        };
+
+        enableStartTLSAuto = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to try to use StartTLS.";
+        };
+
+        opensslVerifyMode = mkOption {
+          type = types.str;
+          default = "peer";
+          description = "How OpenSSL checks the certificate, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
+        };
+      };
+
+      secrets.secretFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = ''
+          A file containing the secret used to encrypt variables in
+          the DB. If you change or lose this key you will be unable to
+          access variables stored in database.
+
+          Make sure the secret is at least 30 characters and all random,
+          no regular words or you'll be exposed to dictionary attacks.
+
+          This should be a string, not a nix path, since nix paths are
+          copied into the world-readable nix store.
+        '';
+      };
+
+      secrets.dbFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = ''
+          A file containing the secret used to encrypt variables in
+          the DB. If you change or lose this key you will be unable to
+          access variables stored in database.
+
+          Make sure the secret is at least 30 characters and all random,
+          no regular words or you'll be exposed to dictionary attacks.
+
+          This should be a string, not a nix path, since nix paths are
+          copied into the world-readable nix store.
+        '';
+      };
+
+      secrets.otpFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = ''
+          A file containing the secret used to encrypt secrets for OTP
+          tokens. If you change or lose this key, users which have 2FA
+          enabled for login won't be able to login anymore.
+
+          Make sure the secret is at least 30 characters and all random,
+          no regular words or you'll be exposed to dictionary attacks.
+
+          This should be a string, not a nix path, since nix paths are
+          copied into the world-readable nix store.
+        '';
+      };
+
+      secrets.jwsFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
+        description = ''
+          A file containing the secret used to encrypt session
+          keys. If you change or lose this key, users will be
+          disconnected.
+
+          Make sure the secret is an RSA private key in PEM format. You can
+          generate one with
+
+          openssl genrsa 2048
+
+          This should be a string, not a nix path, since nix paths are
+          copied into the world-readable nix store.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        example = literalExample ''
+          {
+            gitlab = {
+              default_projects_features = {
+                builds = false;
+              };
+            };
+            omniauth = {
+              enabled = true;
+              auto_sign_in_with_provider = "openid_connect";
+              allow_single_sign_on = ["openid_connect"];
+              block_auto_created_users = false;
+              providers = [
+                {
+                  name = "openid_connect";
+                  label = "OpenID Connect";
+                  args = {
+                    name = "openid_connect";
+                    scope = ["openid" "profile"];
+                    response_type = "code";
+                    issuer = "https://keycloak.example.com/auth/realms/My%20Realm";
+                    discovery = true;
+                    client_auth_method = "query";
+                    uid_field = "preferred_username";
+                    client_options = {
+                      identifier = "gitlab";
+                      secret = { _secret = "/var/keys/gitlab_oidc_secret"; };
+                      redirect_uri = "https://git.example.com/users/auth/openid_connect/callback";
+                    };
+                  };
+                }
+              ];
+            };
+          };
+        '';
+        description = ''
+          Extra options to be added under
+          <literal>production</literal> in
+          <filename>config/gitlab.yml</filename>, as a nix attribute
+          set.
+
+          Options containing secret data should be set to an attribute
+          set containing the attribute <literal>_secret</literal> - a
+          string pointing to a file containing the value the option
+          should be set to. See the example to get a better picture of
+          this: in the resulting
+          <filename>config/gitlab.yml</filename> file, the
+          <literal>production.omniauth.providers[0].args.client_options.secret</literal>
+          key will be set to the contents of the
+          <filename>/var/keys/gitlab_oidc_secret</filename> file.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = databaseActuallyCreateLocally -> (cfg.user == cfg.databaseUsername);
+        message = ''For local automatic database provisioning (services.gitlab.databaseCreateLocally == true) with peer authentication (services.gitlab.databaseHost == "") to work services.gitlab.user and services.gitlab.databaseUsername must be identical.'';
+      }
+      {
+        assertion = (cfg.databaseHost != "") -> (cfg.databasePasswordFile != null);
+        message = "When services.gitlab.databaseHost is customized, services.gitlab.databasePasswordFile must be set!";
+      }
+      {
+        assertion = cfg.initialRootPasswordFile != null;
+        message = "services.gitlab.initialRootPasswordFile must be set!";
+      }
+      {
+        assertion = cfg.secrets.secretFile != null;
+        message = "services.gitlab.secrets.secretFile must be set!";
+      }
+      {
+        assertion = cfg.secrets.dbFile != null;
+        message = "services.gitlab.secrets.dbFile must be set!";
+      }
+      {
+        assertion = cfg.secrets.otpFile != null;
+        message = "services.gitlab.secrets.otpFile must be set!";
+      }
+      {
+        assertion = cfg.secrets.jwsFile != null;
+        message = "services.gitlab.secrets.jwsFile must be set!";
+      }
+    ];
+
+    environment.systemPackages = [ pkgs.git gitlab-rake gitlab-rails cfg.packages.gitlab-shell ];
+
+    # Redis is required for the sidekiq queue runner.
+    services.redis.enable = mkDefault true;
+
+    # We use postgres as the main data store.
+    services.postgresql = optionalAttrs databaseActuallyCreateLocally {
+      enable = true;
+      ensureUsers = singleton { name = cfg.databaseUsername; };
+    };
+    # The postgresql module doesn't currently support concepts like
+    # objects owners and extensions; for now we tack on what's needed
+    # here.
+    systemd.services.postgresql.postStart = mkAfter (optionalString databaseActuallyCreateLocally ''
+      set -eu
+
+      $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
+      current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
+      if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
+          $PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
+          if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
+              echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
+              exit 1
+          fi
+          touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
+          $PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
+          rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
+      fi
+      $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+    '');
+
+    # Use postfix to send out mails.
+    services.postfix.enable = mkDefault true;
+
+    users.users.${cfg.user} =
+      { group = cfg.group;
+        home = "${cfg.statePath}/home";
+        shell = "${pkgs.bash}/bin/bash";
+        uid = config.ids.uids.gitlab;
+      };
+
+    users.groups.${cfg.group}.gid = config.ids.gids.gitlab;
+
+    systemd.tmpfiles.rules = [
+      "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -"
+      "d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -"
+      "z ${gitlabEnv.HOME}/.ssh/authorized_keys 0600 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.backupPath} 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath} 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/builds 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/config 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/config/initializers 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/db 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/log 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/repositories 2770 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/shell 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/tmp 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/tmp/pids 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/tmp/sockets 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/uploads 0700 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/custom_hooks 0700 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/custom_hooks/pre-receive.d 0700 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/custom_hooks/post-receive.d 0700 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.statePath}/custom_hooks/update.d 0700 ${cfg.user} ${cfg.group} -"
+      "d ${gitlabConfig.production.shared.path} 0750 ${cfg.user} ${cfg.group} -"
+      "d ${gitlabConfig.production.shared.path}/artifacts 0750 ${cfg.user} ${cfg.group} -"
+      "d ${gitlabConfig.production.shared.path}/lfs-objects 0750 ${cfg.user} ${cfg.group} -"
+      "d ${gitlabConfig.production.shared.path}/pages 0750 ${cfg.user} ${cfg.group} -"
+      "L+ /run/gitlab/config - - - - ${cfg.statePath}/config"
+      "L+ /run/gitlab/log - - - - ${cfg.statePath}/log"
+      "L+ /run/gitlab/tmp - - - - ${cfg.statePath}/tmp"
+      "L+ /run/gitlab/uploads - - - - ${cfg.statePath}/uploads"
+
+      "L+ /run/gitlab/shell-config.yml - - - - ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)}"
+
+      "L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}"
+      "L+ ${cfg.statePath}/config/initializers/extra-gitlab.rb - - - - ${extraGitlabRb}"
+    ];
+
+    systemd.services.gitlab-sidekiq = {
+      after = [ "network.target" "redis.service" "gitlab.service" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = gitlabEnv;
+      path = with pkgs; [
+        postgresqlPackage
+        gitAndTools.git
+        ruby
+        openssh
+        nodejs
+        gnupg
+
+        # Needed for GitLab project imports
+        gnutar
+        gzip
+      ];
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        TimeoutSec = "infinity";
+        Restart = "on-failure";
+        WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
+        ExecStart="${cfg.packages.gitlab.rubyEnv}/bin/sidekiq -C \"${cfg.packages.gitlab}/share/gitlab/config/sidekiq_queues.yml\" -e production -P ${cfg.statePath}/tmp/sidekiq.pid";
+      };
+    };
+
+    systemd.services.gitaly = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [
+        openssh
+        procps  # See https://gitlab.com/gitlab-org/gitaly/issues/1562
+        gitAndTools.git
+        cfg.packages.gitaly.rubyEnv
+        cfg.packages.gitaly.rubyEnv.wrappedRuby
+        gzip
+        bzip2
+      ];
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        TimeoutSec = "infinity";
+        Restart = "on-failure";
+        WorkingDirectory = gitlabEnv.HOME;
+        ExecStart = "${cfg.packages.gitaly}/bin/gitaly ${gitalyToml}";
+      };
+    };
+
+    systemd.services.gitlab-workhorse = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [
+        exiftool
+        gitAndTools.git
+        gnutar
+        gzip
+        openssh
+        gitlab-workhorse
+      ];
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        TimeoutSec = "infinity";
+        Restart = "on-failure";
+        WorkingDirectory = gitlabEnv.HOME;
+        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 "
+          + "-secretPath ${cfg.statePath}/.gitlab_workhorse_secret";
+      };
+    };
+
+    systemd.services.gitlab = {
+      after = [ "gitlab-workhorse.service" "gitaly.service" "network.target" "postgresql.service" "redis.service" ];
+      requires = [ "gitlab-sidekiq.service" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = gitlabEnv;
+      path = with pkgs; [
+        postgresqlPackage
+        gitAndTools.git
+        openssh
+        nodejs
+        procps
+        gnupg
+      ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        TimeoutSec = "infinity";
+        Restart = "on-failure";
+        WorkingDirectory = "${cfg.packages.gitlab}/share/gitlab";
+        ExecStartPre = let
+          preStartFullPrivileges = ''
+            shopt -s dotglob nullglob
+            set -eu
+
+            chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/*
+            chown --no-dereference '${cfg.user}':'${cfg.group}' '${cfg.statePath}'/config/*
+          '';
+          preStart = ''
+            set -eu
+
+            cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
+            rm -rf ${cfg.statePath}/db/*
+            rm -rf ${cfg.statePath}/config/initializers/*
+            rm -f ${cfg.statePath}/lib
+            cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
+            cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
+
+            ${cfg.packages.gitlab-shell}/bin/install
+
+            ${optionalString cfg.smtp.enable ''
+              install -m u=rw ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
+              ${optionalString (cfg.smtp.passwordFile != null) ''
+                smtp_password=$(<'${cfg.smtp.passwordFile}')
+                ${pkgs.replace}/bin/replace-literal -e '@smtpPassword@' "$smtp_password" '${cfg.statePath}/config/initializers/smtp_settings.rb'
+              ''}
+            ''}
+
+            (
+              umask u=rwx,g=,o=
+
+              ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
+
+              if [[ -h '${cfg.statePath}/config/database.yml' ]]; then
+                rm '${cfg.statePath}/config/database.yml'
+              fi
+
+              ${if cfg.databasePasswordFile != null then ''
+                  export db_password="$(<'${cfg.databasePasswordFile}')"
+
+                  if [[ -z "$db_password" ]]; then
+                    >&2 echo "Database password was an empty string!"
+                    exit 1
+                  fi
+
+                  ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
+                                    '.production.password = $ENV.db_password' \
+                                    >'${cfg.statePath}/config/database.yml'
+                ''
+                else ''
+                  ${pkgs.jq}/bin/jq <${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} \
+                                    >'${cfg.statePath}/config/database.yml'
+                ''
+              }
+
+              ${utils.genJqSecretsReplacementSnippet
+                  gitlabConfig
+                  "${cfg.statePath}/config/gitlab.yml"
+              }
+
+              if [[ -h '${cfg.statePath}/config/secrets.yml' ]]; then
+                rm '${cfg.statePath}/config/secrets.yml'
+              fi
+
+              export secret="$(<'${cfg.secrets.secretFile}')"
+              export db="$(<'${cfg.secrets.dbFile}')"
+              export otp="$(<'${cfg.secrets.otpFile}')"
+              export jws="$(<'${cfg.secrets.jwsFile}')"
+              ${pkgs.jq}/bin/jq -n '{production: {secret_key_base: $ENV.secret,
+                                                  otp_key_base: $ENV.otp,
+                                                  db_key_base: $ENV.db,
+                                                  openid_connect_signing_key: $ENV.jws}}' \
+                                > '${cfg.statePath}/config/secrets.yml'
+            )
+
+            initial_root_password="$(<'${cfg.initialRootPasswordFile}')"
+            ${gitlab-rake}/bin/gitlab-rake gitlab:db:configure GITLAB_ROOT_PASSWORD="$initial_root_password" \
+                                                               GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}' > /dev/null
+
+            # We remove potentially broken links to old gitlab-shell versions
+            rm -Rf ${cfg.statePath}/repositories/**/*.git/hooks
+
+            ${pkgs.git}/bin/git config --global core.autocrlf "input"
+          '';
+        in [
+          "+${pkgs.writeShellScript "gitlab-pre-start-full-privileges" preStartFullPrivileges}"
+          "${pkgs.writeShellScript "gitlab-pre-start" preStart}"
+        ];
+        ExecStart = "${cfg.packages.gitlab.rubyEnv}/bin/unicorn -c ${cfg.statePath}/config/unicorn.rb -E production";
+      };
+
+    };
+
+  };
+
+  meta.doc = ./gitlab.xml;
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitlab.xml b/nixpkgs/nixos/modules/services/misc/gitlab.xml
new file mode 100644
index 000000000000..b6171a9a194c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitlab.xml
@@ -0,0 +1,126 @@
+<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 xml:id="module-services-gitlab-prerequisites">
+  <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, the following configuration could be used to use nginx as
+   frontend proxy:
+<programlisting>
+<link linkend="opt-services.nginx.enable">services.nginx</link> = {
+  <link linkend="opt-services.nginx.enable">enable</link> = true;
+  <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
+  <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
+  <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
+  <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
+  <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link>."git.example.com" = {
+    <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
+    <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
+    <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">locations."/".proxyPass</link> = "http://unix:/run/gitlab/gitlab-workhorse.socket";
+  };
+};
+</programlisting>
+  </para>
+ </section>
+ <section xml:id="module-services-gitlab-configuring">
+  <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 <literal>/var/gitlab/state</literal>. This is where
+   all data like the repositories and uploads will be stored.
+  </para>
+
+  <para>
+   A basic configuration with some custom settings could look like this:
+<programlisting>
+services.gitlab = {
+  <link linkend="opt-services.gitlab.enable">enable</link> = true;
+  <link linkend="opt-services.gitlab.databasePasswordFile">databasePasswordFile</link> = "/var/keys/gitlab/db_password";
+  <link linkend="opt-services.gitlab.initialRootPasswordFile">initialRootPasswordFile</link> = "/var/keys/gitlab/root_password";
+  <link linkend="opt-services.gitlab.https">https</link> = true;
+  <link linkend="opt-services.gitlab.host">host</link> = "git.example.com";
+  <link linkend="opt-services.gitlab.port">port</link> = 443;
+  <link linkend="opt-services.gitlab.user">user</link> = "git";
+  <link linkend="opt-services.gitlab.group">group</link> = "git";
+  smtp = {
+    <link linkend="opt-services.gitlab.smtp.enable">enable</link> = true;
+    <link linkend="opt-services.gitlab.smtp.address">address</link> = "localhost";
+    <link linkend="opt-services.gitlab.smtp.port">port</link> = 25;
+  };
+  secrets = {
+    <link linkend="opt-services.gitlab.secrets.dbFile">dbFile</link> = "/var/keys/gitlab/db";
+    <link linkend="opt-services.gitlab.secrets.secretFile">secretFile</link> = "/var/keys/gitlab/secret";
+    <link linkend="opt-services.gitlab.secrets.otpFile">otpFile</link> = "/var/keys/gitlab/otp";
+    <link linkend="opt-services.gitlab.secrets.jwsFile">jwsFile</link> = "/var/keys/gitlab/jws";
+  };
+  <link linkend="opt-services.gitlab.extraConfig">extraConfig</link> = {
+    gitlab = {
+      email_from = "gitlab-no-reply@example.com";
+      email_display_name = "Example GitLab";
+      email_reply_to = "gitlab-no-reply@example.com";
+      default_projects_features = { builds = false; };
+    };
+  };
+};
+</programlisting>
+  </para>
+
+  <para>
+   If you're setting up a new Gitlab instance, generate new
+   secrets. You for instance use <literal>tr -dc A-Za-z0-9 &lt;
+   /dev/urandom | head -c 128 &gt; /var/keys/gitlab/db</literal> to
+   generate a new db secret. Make sure the files can be read by, and
+   only by, the user specified by <link
+   linkend="opt-services.gitlab.user">services.gitlab.user</link>. Gitlab
+   encrypts sensitive data stored in the database. If you're restoring
+   an existing Gitlab instance, you must specify the secrets secret
+   from <literal>config/secrets.yml</literal> located in your Gitlab
+   state folder.
+  </para>
+
+  <para>
+   Refer to <xref linkend="ch-options" /> for all available configuration
+   options for the
+   <link linkend="opt-services.gitlab.enable">services.gitlab</link> module.
+  </para>
+ </section>
+ <section xml:id="module-services-gitlab-maintenance">
+  <title>Maintenance</title>
+
+  <para>
+   You can run Gitlab's rake tasks with <literal>gitlab-rake</literal> which
+   will be available on the system when gitlab is enabled. You will have to run
+   the command as the user that you configured to run gitlab with.
+  </para>
+
+  <para>
+   For example, to backup a Gitlab instance:
+<screen>
+<prompt>$ </prompt>sudo -u git -H gitlab-rake gitlab:backup:create
+</screen>
+   A list of all availabe rake tasks can be obtained by running:
+<screen>
+<prompt>$ </prompt>sudo -u git -H gitlab-rake -T
+</screen>
+  </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/gitolite.nix b/nixpkgs/nixos/modules/services/misc/gitolite.nix
new file mode 100644
index 000000000000..cc69f81bbcc4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitolite.nix
@@ -0,0 +1,231 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gitolite;
+  # Use writeTextDir to not leak Nix store hash into file name
+  pubkeyFile = (pkgs.writeTextDir "gitolite-admin.pub" cfg.adminPubkey) + "/gitolite-admin.pub";
+  hooks = lib.concatMapStrings (hook: "${hook} ") cfg.commonHooks;
+in
+{
+  options = {
+    services.gitolite = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable gitolite management under the
+          <literal>gitolite</literal> user. After
+          switching to a configuration with Gitolite enabled, you can
+          then run <literal>git clone
+          gitolite@host:gitolite-admin.git</literal> to manage it further.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/gitolite";
+        description = ''
+          Gitolite home directory (used to store all the repositories).
+        '';
+      };
+
+      adminPubkey = mkOption {
+        type = types.str;
+        description = ''
+          Initial administrative public key for Gitolite. This should
+          be an SSH Public Key. Note that this key will only be used
+          once, upon the first initialization of the Gitolite user.
+          The key string cannot have any line breaks in it.
+        '';
+      };
+
+      enableGitAnnex = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable git-annex support. Uses the <literal>extraGitoliteRc</literal> option
+          to apply the necessary configuration.
+        '';
+      };
+
+      commonHooks = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          A list of custom git hooks that get copied to <literal>~/.gitolite/hooks/common</literal>.
+        '';
+      };
+
+      extraGitoliteRc = mkOption {
+        type = types.lines;
+        default = "";
+        example = literalExample ''
+          $RC{UMASK} = 0027;
+          $RC{SITE_INFO} = 'This is our private repository host';
+          push( @{$RC{ENABLE}}, 'Kindergarten' ); # enable the command/feature
+          @{$RC{ENABLE}} = grep { $_ ne 'desc' } @{$RC{ENABLE}}; # disable the command/feature
+        '';
+        description = ''
+          Extra configuration to append to the default <literal>~/.gitolite.rc</literal>.
+
+          This should be Perl code that modifies the <literal>%RC</literal>
+          configuration variable. The default <literal>~/.gitolite.rc</literal>
+          content is generated by invoking <literal>gitolite print-default-rc</literal>,
+          and extra configuration from this option is appended to it. The result
+          is placed to Nix store, and the <literal>~/.gitolite.rc</literal> file
+          becomes a symlink to it.
+
+          If you already have a customized (or otherwise changed)
+          <literal>~/.gitolite.rc</literal> file, NixOS will refuse to replace
+          it with a symlink, and the `gitolite-init` initialization service
+          will fail. In this situation, in order to use this option, you
+          will need to take any customizations you may have in
+          <literal>~/.gitolite.rc</literal>, convert them to appropriate Perl
+          statements, add them to this option, and remove the file.
+
+          See also the <literal>enableGitAnnex</literal> option.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "gitolite";
+        description = ''
+          Gitolite user account. This is the username of the gitolite endpoint.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "gitolite";
+        description = ''
+          Primary group of the Gitolite user account.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable (
+  let
+    manageGitoliteRc = cfg.extraGitoliteRc != "";
+    rcDir = pkgs.runCommand "gitolite-rc" { preferLocalBuild = true; } rcDirScript;
+    rcDirScript =
+      ''
+        mkdir "$out"
+        export HOME=temp-home
+        mkdir -p "$HOME/.gitolite/logs" # gitolite can't run without it
+        '${pkgs.gitolite}'/bin/gitolite print-default-rc >>"$out/gitolite.rc.default"
+        cat <<END >>"$out/gitolite.rc"
+        # This file is managed by NixOS.
+        # Use services.gitolite options to control it.
+
+        END
+        cat "$out/gitolite.rc.default" >>"$out/gitolite.rc"
+      '' +
+      optionalString (cfg.extraGitoliteRc != "") ''
+        echo -n ${escapeShellArg ''
+
+          # Added by NixOS:
+          ${removeSuffix "\n" cfg.extraGitoliteRc}
+
+          # per perl rules, this should be the last line in such a file:
+          1;
+        ''} >>"$out/gitolite.rc"
+      '';
+  in {
+    services.gitolite.extraGitoliteRc = optionalString cfg.enableGitAnnex ''
+      # Enable git-annex support:
+      push( @{$RC{ENABLE}}, 'git-annex-shell ua');
+    '';
+
+    users.users.${cfg.user} = {
+      description     = "Gitolite user";
+      home            = cfg.dataDir;
+      uid             = config.ids.uids.gitolite;
+      group           = cfg.group;
+      useDefaultShell = true;
+    };
+    users.groups.${cfg.group}.gid = config.ids.gids.gitolite;
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.dataDir}'/.gitolite - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.dataDir}'/.gitolite/logs - ${cfg.user} ${cfg.group} - -"
+
+      "Z ${cfg.dataDir} 0750 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.gitolite-init = {
+      description = "Gitolite initialization";
+      wantedBy    = [ "multi-user.target" ];
+      unitConfig.RequiresMountsFor = cfg.dataDir;
+
+      environment = {
+        GITOLITE_RC = ".gitolite.rc";
+        GITOLITE_RC_DEFAULT = "${rcDir}/gitolite.rc.default";
+      };
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = "~";
+        RemainAfterExit = true;
+      };
+
+      path = [ pkgs.gitolite pkgs.git pkgs.perl pkgs.bash pkgs.diffutils config.programs.ssh.package ];
+      script =
+      let
+        rcSetupScriptIfCustomFile =
+          if manageGitoliteRc then ''
+            cat <<END
+            <3>ERROR: NixOS can't apply declarative configuration
+            <3>to your .gitolite.rc file, because it seems to be
+            <3>already customized manually.
+            <3>See the services.gitolite.extraGitoliteRc option
+            <3>in "man configuration.nix" for more information.
+            END
+            # Not sure if the line below addresses the issue directly or just
+            # adds a delay, but without it our error message often doesn't
+            # show up in `systemctl status gitolite-init`.
+            journalctl --flush
+            exit 1
+          '' else ''
+            :
+          '';
+        rcSetupScriptIfDefaultFileOrStoreSymlink =
+          if manageGitoliteRc then ''
+            ln -sf "${rcDir}/gitolite.rc" "$GITOLITE_RC"
+          '' else ''
+            [[ -L "$GITOLITE_RC" ]] && rm -f "$GITOLITE_RC"
+          '';
+      in
+        ''
+          if ( [[ ! -e "$GITOLITE_RC" ]] && [[ ! -L "$GITOLITE_RC" ]] ) ||
+             ( [[ -f "$GITOLITE_RC" ]] && diff -q "$GITOLITE_RC" "$GITOLITE_RC_DEFAULT" >/dev/null ) ||
+             ( [[ -L "$GITOLITE_RC" ]] && [[ "$(readlink "$GITOLITE_RC")" =~ ^/nix/store/ ]] )
+          then
+        '' + rcSetupScriptIfDefaultFileOrStoreSymlink +
+        ''
+          else
+        '' + rcSetupScriptIfCustomFile +
+        ''
+          fi
+
+          if [ ! -d repositories ]; then
+            gitolite setup -pk ${pubkeyFile}
+          fi
+          if [ -n "${hooks}" ]; then
+            cp -f ${hooks} .gitolite/hooks/common/
+            chmod +x .gitolite/hooks/common/*
+          fi
+          gitolite setup # Upgrade if needed
+        '';
+    };
+
+    environment.systemPackages = [ pkgs.gitolite pkgs.git ]
+        ++ optional cfg.enableGitAnnex pkgs.gitAndTools.git-annex;
+  });
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gitweb.nix b/nixpkgs/nixos/modules/services/misc/gitweb.nix
new file mode 100644
index 000000000000..ca21366b7796
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gitweb.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gitweb;
+
+in
+{
+
+  options.services.gitweb = {
+
+    projectroot = mkOption {
+      default = "/srv/git";
+      type = types.path;
+      description = ''
+        Path to git projects (bare repositories) that should be served by
+        gitweb. Must not end with a slash.
+      '';
+    };
+
+    extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Verbatim configuration text appended to the generated gitweb.conf file.
+      '';
+      example = ''
+        $feature{'highlight'}{'default'} = [1];
+        $feature{'ctags'}{'default'} = [1];
+        $feature{'avatar'}{'default'} = ['gravatar'];
+      '';
+    };
+
+    gitwebTheme = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Use an alternative theme for gitweb, strongly inspired by GitHub.
+      '';
+    };
+
+    gitwebConfigFile = mkOption {
+      default = pkgs.writeText "gitweb.conf" ''
+        # path to git projects (<project>.git)
+        $projectroot = "${cfg.projectroot}";
+        $highlight_bin = "${pkgs.highlight}/bin/highlight";
+        ${cfg.extraConfig}
+      '';
+      type = types.path;
+      readOnly = true;
+      internal = true;
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ gnidorah ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gogs.nix b/nixpkgs/nixos/modules/services/misc/gogs.nix
new file mode 100644
index 000000000000..c5070aaa356a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gogs.nix
@@ -0,0 +1,279 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gogs;
+  configFile = pkgs.writeText "app.ini" ''
+    APP_NAME = ${cfg.appName}
+    RUN_USER = ${cfg.user}
+    RUN_MODE = prod
+
+    [database]
+    DB_TYPE = ${cfg.database.type}
+    HOST = ${cfg.database.host}:${toString cfg.database.port}
+    NAME = ${cfg.database.name}
+    USER = ${cfg.database.user}
+    PASSWD = #dbpass#
+    PATH = ${cfg.database.path}
+
+    [repository]
+    ROOT = ${cfg.repositoryRoot}
+
+    [server]
+    DOMAIN = ${cfg.domain}
+    HTTP_ADDR = ${cfg.httpAddress}
+    HTTP_PORT = ${toString cfg.httpPort}
+    ROOT_URL = ${cfg.rootUrl}
+    STATIC_ROOT_PATH = ${cfg.staticRootPath}
+
+    [session]
+    COOKIE_NAME = session
+    COOKIE_SECURE = ${boolToString cfg.cookieSecure}
+
+    [security]
+    SECRET_KEY = #secretkey#
+    INSTALL_LOCK = true
+
+    [log]
+    ROOT_PATH = ${cfg.stateDir}/log
+
+    ${cfg.extraConfig}
+  '';
+in
+
+{
+  options = {
+    services.gogs = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Enable Go Git Service.";
+      };
+
+      useWizard = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Do not generate a configuration and use Gogs' installation wizard instead. The first registered user will be administrator.";
+      };
+
+      stateDir = mkOption {
+        default = "/var/lib/gogs";
+        type = types.str;
+        description = "Gogs data directory.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "gogs";
+        description = "User account under which Gogs runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "gogs";
+        description = "Group account under which Gogs runs.";
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "sqlite3" "mysql" "postgres" ];
+          example = "mysql";
+          default = "sqlite3";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 3306;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "gogs";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "gogs";
+          description = "Database user.";
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            The password corresponding to <option>database.user</option>.
+            Warning: this is stored in cleartext in the Nix store!
+            Use <option>database.passwordFile</option> instead.
+          '';
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/gogs-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        path = mkOption {
+          type = types.str;
+          default = "${cfg.stateDir}/data/gogs.db";
+          description = "Path to the sqlite3 database file.";
+        };
+      };
+
+      appName = mkOption {
+        type = types.str;
+        default = "Gogs: Go Git Service";
+        description = "Application name.";
+      };
+
+      repositoryRoot = mkOption {
+        type = types.str;
+        default = "${cfg.stateDir}/repositories";
+        description = "Path to the git repositories.";
+      };
+
+      domain = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Domain name of your server.";
+      };
+
+      rootUrl = mkOption {
+        type = types.str;
+        default = "http://localhost:3000/";
+        description = "Full public URL of Gogs server.";
+      };
+
+      httpAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = "HTTP listen address.";
+      };
+
+      httpPort = mkOption {
+        type = types.int;
+        default = 3000;
+        description = "HTTP listen port.";
+      };
+
+      cookieSecure = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Marks session cookies as "secure" as a hint for browsers to only send
+          them via HTTPS. This option is recommend, if Gogs is being served over HTTPS.
+        '';
+      };
+
+      staticRootPath = mkOption {
+        type = types.str;
+        default = "${pkgs.gogs.data}";
+        example = "/var/lib/gogs/data";
+        description = "Upper level of template and static files path.";
+      };
+
+      extraConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = "Configuration lines appended to the generated Gogs configuration file.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.gogs = {
+      description = "Gogs (Go Git Service)";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.gogs ];
+
+      preStart = let
+        runConfig = "${cfg.stateDir}/custom/conf/app.ini";
+        secretKey = "${cfg.stateDir}/custom/conf/secret_key";
+      in ''
+        mkdir -p ${cfg.stateDir}
+
+        # copy custom configuration and generate a random secret key if needed
+        ${optionalString (cfg.useWizard == false) ''
+          mkdir -p ${cfg.stateDir}/custom/conf
+          cp -f ${configFile} ${runConfig}
+
+          if [ ! -e ${secretKey} ]; then
+              head -c 16 /dev/urandom | base64 > ${secretKey}
+          fi
+
+          KEY=$(head -n1 ${secretKey})
+          DBPASS=$(head -n1 ${cfg.database.passwordFile})
+          sed -e "s,#secretkey#,$KEY,g" \
+              -e "s,#dbpass#,$DBPASS,g" \
+              -i ${runConfig}
+          chmod 440 ${runConfig} ${secretKey}
+        ''}
+
+        mkdir -p ${cfg.repositoryRoot}
+        # update all hooks' binary paths
+        HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 4 -type f -wholename "*git/hooks/*")
+        if [ "$HOOKS" ]
+        then
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/gogs,${pkgs.gogs}/bin/gogs,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/env,${pkgs.coreutils}/bin/env,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/bash,${pkgs.bash}/bin/bash,g' $HOOKS
+          sed -ri 's,/nix/store/[a-z0-9.-]+/bin/perl,${pkgs.perl}/bin/perl,g' $HOOKS
+        fi
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.stateDir;
+        ExecStart = "${pkgs.gogs}/bin/gogs web";
+        Restart = "always";
+      };
+
+      environment = {
+        USER = cfg.user;
+        HOME = cfg.stateDir;
+        GOGS_WORK_DIR = cfg.stateDir;
+      };
+    };
+
+    users = mkIf (cfg.user == "gogs") {
+      users.gogs = {
+        description = "Go Git Service";
+        uid = config.ids.uids.gogs;
+        group = "gogs";
+        home = cfg.stateDir;
+        createHome = true;
+        shell = pkgs.bash;
+      };
+      groups.gogs.gid = config.ids.gids.gogs;
+    };
+
+    warnings = optional (cfg.database.password != "")
+      ''config.services.gogs.database.password will be stored as plaintext
+        in the Nix store. Use database.passwordFile instead.'';
+
+    # Create database passwordFile default when password is configured.
+    services.gogs.database.passwordFile =
+      (mkDefault (toString (pkgs.writeTextFile {
+        name = "gogs-database-password";
+        text = cfg.database.password;
+      })));
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gollum.nix b/nixpkgs/nixos/modules/services/misc/gollum.nix
new file mode 100644
index 000000000000..f4a9c72b1545
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gollum.nix
@@ -0,0 +1,111 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gollum;
+in
+
+{
+  options.services.gollum = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable the Gollum service.";
+    };
+
+    address = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      description = "IP address on which the web server will listen.";
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 4567;
+      description = "Port on which the web server will run.";
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Content of the configuration file";
+    };
+
+    mathjax = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable support for math rendering using MathJax";
+    };
+
+    allowUploads = mkOption {
+      type = types.nullOr (types.enum [ "dir" "page" ]);
+      default = null;
+      description = "Enable uploads of external files";
+    };
+
+    emoji = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Parse and interpret emoji tags";
+    };
+
+    branch = mkOption {
+      type = types.str;
+      default = "master";
+      example = "develop";
+      description = "Git branch to serve";
+    };
+
+    stateDir = mkOption {
+      type = types.path;
+      default = "/var/lib/gollum";
+      description = "Specifies the path of the repository directory. If it does not exist, Gollum will create it on startup.";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.gollum = {
+      group = config.users.users.gollum.name;
+      description = "Gollum user";
+      createHome = false;
+      isSystemUser = true;
+    };
+
+    users.groups.gollum = { };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - ${config.users.users.gollum.name} ${config.users.groups.gollum.name} - -"
+    ];
+
+    systemd.services.gollum = {
+      description = "Gollum wiki";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.git ];
+
+      preStart = ''
+        # This is safe to be run on an existing repo
+        git init ${cfg.stateDir}
+      '';
+
+      serviceConfig = {
+        User = config.users.users.gollum.name;
+        Group = config.users.groups.gollum.name;
+        ExecStart = ''
+          ${pkgs.gollum}/bin/gollum \
+            --port ${toString cfg.port} \
+            --host ${cfg.address} \
+            --config ${builtins.toFile "gollum-config.rb" cfg.extraConfig} \
+            --ref ${cfg.branch} \
+            ${optionalString cfg.mathjax "--mathjax"} \
+            ${optionalString cfg.emoji "--emoji"} \
+            ${optionalString (cfg.allowUploads != null) "--allow-uploads ${cfg.allowUploads}"} \
+            ${cfg.stateDir}
+        '';
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/gpsd.nix b/nixpkgs/nixos/modules/services/misc/gpsd.nix
new file mode 100644
index 000000000000..f954249942a8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/gpsd.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  uid = config.ids.uids.gpsd;
+  gid = config.ids.gids.gpsd;
+  cfg = config.services.gpsd;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.gpsd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable `gpsd', a GPS service daemon.
+        '';
+      };
+
+      device = mkOption {
+        type = types.str;
+        default = "/dev/ttyUSB0";
+        description = ''
+          A device may be a local serial device for GPS input, or a URL of the form:
+               <literal>[{dgpsip|ntrip}://][user:passwd@]host[:port][/stream]</literal>
+          in which case it specifies an input source for DGPS or ntrip data.
+        '';
+      };
+
+      readonly = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable the broken-device-safety, otherwise
+          known as read-only mode.  Some popular bluetooth and USB
+          receivers lock up or become totally inaccessible when
+          probed or reconfigured.  This switch prevents gpsd from
+          writing to a receiver.  This means that gpsd cannot
+          configure the receiver for optimal performance, but it
+          also means that gpsd cannot break the receiver.  A better
+          solution would be for Bluetooth to not be so fragile.  A
+          platform independent method to identify
+          serial-over-Bluetooth devices would also be nice.
+        '';
+      };
+
+      nowait = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          don't wait for client connects to poll GPS
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 2947;
+        description = ''
+          The port where to listen for TCP connections.
+        '';
+      };
+
+      debugLevel = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          The debugging level.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.gpsd =
+      { inherit uid;
+        description = "gpsd daemon user";
+        home = "/var/empty";
+      };
+
+    users.groups.gpsd = { inherit gid; };
+
+    systemd.services.gpsd = {
+      description = "GPSD daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = ''
+          ${pkgs.gpsd}/sbin/gpsd -D "${toString cfg.debugLevel}"  \
+            -S "${toString cfg.port}"                             \
+            ${optionalString cfg.readonly "-b"}                   \
+            ${optionalString cfg.nowait "-n"}                     \
+            "${cfg.device}"
+        '';
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/greenclip.nix b/nixpkgs/nixos/modules/services/misc/greenclip.nix
new file mode 100644
index 000000000000..9152a782d7f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/greenclip.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.greenclip;
+in {
+
+  options.services.greenclip = {
+    enable = mkEnableOption "Greenclip daemon";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.haskellPackages.greenclip;
+      defaultText = "pkgs.haskellPackages.greenclip";
+      description = "greenclip derivation to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.greenclip = {
+      enable      = true;
+      description = "greenclip daemon";
+      wantedBy = [ "graphical-session.target" ];
+      after    = [ "graphical-session.target" ];
+      serviceConfig.ExecStart = "${cfg.package}/bin/greenclip daemon";
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/headphones.nix b/nixpkgs/nixos/modules/services/misc/headphones.nix
new file mode 100644
index 000000000000..3ee0a4458bd0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/headphones.nix
@@ -0,0 +1,87 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  name = "headphones";
+
+  cfg = config.services.headphones;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.headphones = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the headphones server.";
+      };
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/${name}";
+        description = "Path where to store data files.";
+      };
+      configFile = mkOption {
+        type = types.path;
+        default = "${cfg.dataDir}/config.ini";
+        description = "Path to config file.";
+      };
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Host to listen on.";
+      };
+      port = mkOption {
+        type = types.ints.u16;
+        default = 8181;
+        description = "Port to bind to.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = name;
+        description = "User to run the service as";
+      };
+      group = mkOption {
+        type = types.str;
+        default = name;
+        description = "Group to run the service as";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = optionalAttrs (cfg.user == name) {
+      ${name} = {
+        uid = config.ids.uids.headphones;
+        group = cfg.group;
+        description = "headphones user";
+        home = cfg.dataDir;
+        createHome = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == name) {
+      ${name}.gid = config.ids.gids.headphones;
+    };
+
+    systemd.services.headphones = {
+        description = "Headphones Server";
+        wantedBy    = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        serviceConfig = {
+          User = cfg.user;
+          Group = cfg.group;
+          ExecStart = "${pkgs.headphones}/bin/headphones --datadir ${cfg.dataDir} --config ${cfg.configFile} --host ${cfg.host} --port ${toString cfg.port}";
+        };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/home-assistant.nix b/nixpkgs/nixos/modules/services/misc/home-assistant.nix
new file mode 100644
index 000000000000..8ce2437841b0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/home-assistant.nix
@@ -0,0 +1,275 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.home-assistant;
+
+  # cfg.config != null can be assumed here
+  configJSON = pkgs.writeText "configuration.json"
+    (builtins.toJSON (if cfg.applyDefaultConfig then
+    (recursiveUpdate defaultConfig cfg.config) else cfg.config));
+  configFile = pkgs.runCommand "configuration.yaml" { preferLocalBuild = true; } ''
+    ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+    # Hack to support custom yaml objects,
+    # i.e. secrets: https://www.home-assistant.io/docs/configuration/secrets/
+    sed -i -e "s/'\!\([a-z_]\+\) \(.*\)'/\!\1 \2/;s/^\!\!/\!/;" $out
+  '';
+
+  lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json"
+    (builtins.toJSON cfg.lovelaceConfig);
+  lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { preferLocalBuild = true; } ''
+    ${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out
+  '';
+
+  availableComponents = cfg.package.availableComponents;
+
+  usedPlatforms = config:
+    if isAttrs config then
+      optional (config ? platform) config.platform
+      ++ concatMap usedPlatforms (attrValues config)
+    else if isList config then
+      concatMap usedPlatforms config
+    else [ ];
+
+  # Given a component "platform", looks up whether it is used in the config
+  # as `platform = "platform";`.
+  #
+  # For example, the component mqtt.sensor is used as follows:
+  # config.sensor = [ {
+  #   platform = "mqtt";
+  #   ...
+  # } ];
+  useComponentPlatform = component: elem component (usedPlatforms cfg.config);
+
+  # Returns whether component is used in config
+  useComponent = component:
+    hasAttrByPath (splitString "." component) cfg.config
+    || useComponentPlatform component;
+
+  # List of components used in config
+  extraComponents = filter useComponent availableComponents;
+
+  package = if (cfg.autoExtraComponents && cfg.config != null)
+    then (cfg.package.override { inherit extraComponents; })
+    else cfg.package;
+
+  # If you are changing this, please update the description in applyDefaultConfig
+  defaultConfig = {
+    homeassistant.time_zone = config.time.timeZone;
+    http.server_port = cfg.port;
+  } // optionalAttrs (cfg.lovelaceConfig != null) {
+    lovelace.mode = "yaml";
+  };
+
+in {
+  meta.maintainers = with maintainers; [ dotlambda ];
+
+  options.services.home-assistant = {
+    enable = mkEnableOption "Home Assistant";
+
+    configDir = mkOption {
+      default = "/var/lib/hass";
+      type = types.path;
+      description = "The config directory, where your <filename>configuration.yaml</filename> is located.";
+    };
+
+    port = mkOption {
+      default = 8123;
+      type = types.int;
+      description = "The port on which to listen.";
+    };
+
+    applyDefaultConfig = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Setting this option enables a few configuration options for HA based on NixOS configuration (such as time zone) to avoid having to manually specify configuration we already have.
+        </para>
+        <para>
+        Currently one side effect of enabling this is that the <literal>http</literal> component will be enabled.
+        </para>
+        <para>
+        This only takes effect if <literal>config != null</literal> in order to ensure that a manually managed <filename>configuration.yaml</filename> is not overwritten.
+      '';
+    };
+
+    config = mkOption {
+      default = null;
+      # Migrate to new option types later: https://github.com/NixOS/nixpkgs/pull/75584
+      type =  with lib.types; let
+          valueType = nullOr (oneOf [
+            bool
+            int
+            float
+            str
+            (lazyAttrsOf valueType)
+            (listOf valueType)
+          ]) // {
+            description = "Yaml value";
+            emptyValue.value = {};
+          };
+        in valueType;
+      example = literalExample ''
+        {
+          homeassistant = {
+            name = "Home";
+            latitude = "!secret latitude";
+            longitude = "!secret longitude";
+            elevation = "!secret elevation";
+            unit_system = "metric";
+            time_zone = "UTC";
+          };
+          frontend = {
+            themes = "!include_dir_merge_named themes";
+          };
+          http = { };
+          feedreader.urls = [ "https://nixos.org/blogs.xml" ];
+        }
+      '';
+      description = ''
+        Your <filename>configuration.yaml</filename> as a Nix attribute set.
+        Beware that setting this option will delete your previous <filename>configuration.yaml</filename>.
+        <link xlink:href="https://www.home-assistant.io/docs/configuration/secrets/">Secrets</link>
+        are encoded as strings as shown in the example.
+      '';
+    };
+
+    configWritable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to make <filename>configuration.yaml</filename> writable.
+        This only has an effect if <option>config</option> is set.
+        This will allow you to edit it from Home Assistant's web interface.
+        However, bear in mind that it will be overwritten at every start of the service.
+      '';
+    };
+
+    lovelaceConfig = mkOption {
+      default = null;
+      type = with types; nullOr attrs;
+      # from https://www.home-assistant.io/lovelace/yaml-mode/
+      example = literalExample ''
+        {
+          title = "My Awesome Home";
+          views = [ {
+            title = "Example";
+            cards = [ {
+              type = "markdown";
+              title = "Lovelace";
+              content = "Welcome to your **Lovelace UI**.";
+            } ];
+          } ];
+        }
+      '';
+      description = ''
+        Your <filename>ui-lovelace.yaml</filename> as a Nix attribute set.
+        Setting this option will automatically add
+        <literal>lovelace.mode = "yaml";</literal> to your <option>config</option>.
+        Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename>
+      '';
+    };
+
+    lovelaceConfigWritable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to make <filename>ui-lovelace.yaml</filename> writable.
+        This only has an effect if <option>lovelaceConfig</option> is set.
+        This will allow you to edit it from Home Assistant's web interface.
+        However, bear in mind that it will be overwritten at every start of the service.
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.home-assistant;
+      defaultText = "pkgs.home-assistant";
+      type = types.package;
+      example = literalExample ''
+        pkgs.home-assistant.override {
+          extraPackages = ps: with ps; [ colorlog ];
+        }
+      '';
+      description = ''
+        Home Assistant package to use.
+        Override <literal>extraPackages</literal> or <literal>extraComponents</literal> in order to add additional dependencies.
+        If you specify <option>config</option> and do not set <option>autoExtraComponents</option>
+        to <literal>false</literal>, overriding <literal>extraComponents</literal> will have no effect.
+      '';
+    };
+
+    autoExtraComponents = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        If set to <literal>true</literal>, the components used in <literal>config</literal>
+        are set as the specified package's <literal>extraComponents</literal>.
+        This in turn adds all packaged dependencies to the derivation.
+        You might still see import errors in your log.
+        In this case, you will need to package the necessary dependencies yourself
+        or ask for someone else to package them.
+        If a dependency is packaged but not automatically added to this list,
+        you might need to specify it in <literal>extraPackages</literal>.
+      '';
+    };
+
+    openFirewall = mkOption {
+      default = false;
+      type = types.bool;
+      description = "Whether to open the firewall for the specified port.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.port ];
+
+    systemd.services.home-assistant = {
+      description = "Home Assistant";
+      after = [ "network.target" ];
+      preStart = optionalString (cfg.config != null) (if cfg.configWritable then ''
+        cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml"
+      '' else ''
+        rm -f "${cfg.configDir}/configuration.yaml"
+        ln -s ${configFile} "${cfg.configDir}/configuration.yaml"
+      '') + optionalString (cfg.lovelaceConfig != null) (if cfg.lovelaceConfigWritable then ''
+        cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+      '' else ''
+        rm -f "${cfg.configDir}/ui-lovelace.yaml"
+        ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+      '');
+      serviceConfig = {
+        ExecStart = "${package}/bin/hass --config '${cfg.configDir}'";
+        User = "hass";
+        Group = "hass";
+        Restart = "on-failure";
+        ProtectSystem = "strict";
+        ReadWritePaths = "${cfg.configDir}";
+        KillSignal = "SIGINT";
+        PrivateTmp = true;
+        RemoveIPC = true;
+        AmbientCapabilities = "cap_net_raw,cap_net_admin+eip";
+      };
+      path = [
+        "/run/wrappers" # needed for ping
+      ];
+    };
+
+    systemd.targets.home-assistant = rec {
+      description = "Home Assistant";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "home-assistant.service" ];
+      after = wants;
+    };
+
+    users.users.hass = {
+      home = cfg.configDir;
+      createHome = true;
+      group = "hass";
+      extraGroups = [ "dialout" ];
+      uid = config.ids.uids.hass;
+    };
+
+    users.groups.hass.gid = config.ids.gids.hass;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/ihaskell.nix b/nixpkgs/nixos/modules/services/misc/ihaskell.nix
new file mode 100644
index 000000000000..684a242d7385
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ihaskell.nix
@@ -0,0 +1,63 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.ihaskell;
+  ihaskell = pkgs.ihaskell.override {
+    packages = self: cfg.extraPackages self;
+  };
+
+in
+
+{
+  options = {
+    services.ihaskell = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Autostart an IHaskell notebook service.";
+      };
+
+      extraPackages = mkOption {
+        default = self: [];
+        example = literalExample ''
+          haskellPackages: [
+            haskellPackages.wreq
+            haskellPackages.lens
+          ]
+        '';
+        description = ''
+          Extra packages available to ghc when running ihaskell. The
+          value must be a function which receives the attrset defined
+          in <varname>haskellPackages</varname> as the sole argument.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.ihaskell = {
+      group = config.users.groups.ihaskell.name;
+      description = "IHaskell user";
+      home = "/var/lib/ihaskell";
+      createHome = true;
+      uid = config.ids.uids.ihaskell;
+    };
+
+    users.groups.ihaskell.gid = config.ids.gids.ihaskell;
+
+    systemd.services.ihaskell = {
+      description = "IHaskell notebook instance";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        User = config.users.users.ihaskell.name;
+        Group = config.users.groups.ihaskell.name;
+        ExecStart = "${pkgs.runtimeShell} -c \"cd $HOME;${ihaskell}/bin/ihaskell-notebook\"";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/irkerd.nix b/nixpkgs/nixos/modules/services/misc/irkerd.nix
new file mode 100644
index 000000000000..993d77ba424c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/irkerd.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.irkerd;
+  ports = [ 6659 ];
+in
+{
+  options.services.irkerd = {
+    enable = mkOption {
+      description = "Whether to enable irker, an IRC notification daemon.";
+      default = false;
+      type = types.bool;
+    };
+
+    openPorts = mkOption {
+      description = "Open ports in the firewall for irkerd";
+      default = false;
+      type = types.bool;
+    };
+
+    listenAddress = mkOption {
+      default = "localhost";
+      example = "0.0.0.0";
+      type = types.str;
+      description = ''
+        Specifies the bind address on which the irker daemon listens.
+        The default is localhost.
+
+        Irker authors strongly warn about the risks of running this on
+        a publicly accessible interface, so change this with caution.
+      '';
+    };
+
+    nick = mkOption {
+      default = "irker";
+      type = types.str;
+      description = "Nick to use for irker";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.irkerd = {
+      description = "Internet Relay Chat (IRC) notification daemon";
+      documentation = [ "man:irkerd(8)" "man:irkerhook(1)" "man:irk(1)" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.irker}/bin/irkerd -H ${cfg.listenAddress} -n ${cfg.nick}";
+        User = "irkerd";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.irker ];
+
+    users.users.irkerd = {
+      description = "Irker daemon user";
+      isSystemUser = true;
+      group = "irkerd";
+    };
+    users.groups.irkerd = {};
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openPorts ports;
+    networking.firewall.allowedUDPPorts = mkIf cfg.openPorts ports;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/jackett.nix b/nixpkgs/nixos/modules/services/misc/jackett.nix
new file mode 100644
index 000000000000..f2dc6635df93
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/jackett.nix
@@ -0,0 +1,82 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jackett;
+
+in
+{
+  options = {
+    services.jackett = {
+      enable = mkEnableOption "Jackett";
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/jackett/.config/Jackett";
+        description = "The directory where Jackett stores its data files.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open ports in the firewall for the Jackett web interface.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "jackett";
+        description = "User account under which Jackett runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "jackett";
+        description = "Group under which Jackett runs.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.jackett;
+        defaultText = "pkgs.jackett";
+        description = "Jackett package to use.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.jackett = {
+      description = "Jackett";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 9117 ];
+    };
+
+    users.users = mkIf (cfg.user == "jackett") {
+      jackett = {
+        group = cfg.group;
+        home = cfg.dataDir;
+        uid = config.ids.uids.jackett;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "jackett") {
+      jackett.gid = config.ids.gids.jackett;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/jellyfin.nix b/nixpkgs/nixos/modules/services/misc/jellyfin.nix
new file mode 100644
index 000000000000..6ecdfb57dc35
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/jellyfin.nix
@@ -0,0 +1,57 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.jellyfin;
+in
+{
+  options = {
+    services.jellyfin = {
+      enable = mkEnableOption "Jellyfin Media Server";
+
+      user = mkOption {
+        type = types.str;
+        default = "jellyfin";
+        description = "User account under which Jellyfin runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "jellyfin";
+        description = "Group under which jellyfin runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.jellyfin = {
+      description = "Jellyfin Media Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = rec {
+        User = cfg.user;
+        Group = cfg.group;
+        StateDirectory = "jellyfin";
+        CacheDirectory = "jellyfin";
+        ExecStart = "${pkgs.jellyfin}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users = mkIf (cfg.user == "jellyfin") {
+      jellyfin = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "jellyfin") {
+      jellyfin = {};
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ minijackson ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/leaps.nix b/nixpkgs/nixos/modules/services/misc/leaps.nix
new file mode 100644
index 000000000000..ef89d3e64d0c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/leaps.nix
@@ -0,0 +1,62 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.leaps;
+  stateDir = "/var/lib/leaps/";
+in
+{
+  options = {
+    services.leaps = {
+      enable = mkEnableOption "leaps";
+      port = mkOption {
+        type = types.int;
+        default = 8080;
+        description = "A port where leaps listens for incoming http requests";
+      };
+      address = mkOption {
+        default = "";
+        type = types.str;
+        example = "127.0.0.1";
+        description = "Hostname or IP-address to listen to. By default it will listen on all interfaces.";
+      };
+      path = mkOption {
+        default = "/";
+        type = types.path;
+        description = "Subdirectory used for reverse proxy setups";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users = {
+      users.leaps = {
+        uid             = config.ids.uids.leaps;
+        description     = "Leaps server user";
+        group           = "leaps";
+        home            = stateDir;
+        createHome      = true;
+      };
+
+      groups.leaps = {
+        gid = config.ids.gids.leaps;
+      };
+    };
+
+    systemd.services.leaps = {
+      description   = "leaps service";
+      wantedBy      = [ "multi-user.target" ];
+      after         = [ "network.target" ];
+
+      serviceConfig = {
+        User = "leaps";
+        Group = "leaps";
+        Restart = "on-failure";
+        WorkingDirectory = stateDir;
+        PrivateTmp = true;
+        ExecStart = "${pkgs.leaps}/bin/leaps -path ${toString cfg.path} -address ${cfg.address}:${toString cfg.port}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/lidarr.nix b/nixpkgs/nixos/modules/services/misc/lidarr.nix
new file mode 100644
index 000000000000..8ff1adadcf23
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/lidarr.nix
@@ -0,0 +1,89 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lidarr;
+in
+{
+  options = {
+    services.lidarr = {
+      enable = mkEnableOption "Lidarr";
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/lidarr/.config/Lidarr";
+        description = "The directory where Lidarr stores its data files.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.lidarr;
+        defaultText = "pkgs.lidarr";
+        description = "The Lidarr package to use";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for Lidarr
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "lidarr";
+        description = ''
+          User account under which Lidarr runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "lidarr";
+        description = ''
+          Group under which Lidarr runs.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.lidarr = {
+      description = "Lidarr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/Lidarr -nobrowser -data='${cfg.dataDir}'";
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 8686 ];
+    };
+
+    users.users = mkIf (cfg.user == "lidarr") {
+      lidarr = {
+        group = cfg.group;
+        home = "/var/lib/lidarr";
+        uid = config.ids.uids.lidarr;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "lidarr") {
+      lidarr = {
+        gid = config.ids.gids.lidarr;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/logkeys.nix b/nixpkgs/nixos/modules/services/misc/logkeys.nix
new file mode 100644
index 000000000000..0082db63a06a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/logkeys.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.logkeys;
+in {
+  options.services.logkeys = {
+    enable = mkEnableOption "logkeys service";
+
+    device = mkOption {
+      description = "Use the given device as keyboard input event device instead of /dev/input/eventX default.";
+      default = null;
+      type = types.nullOr types.str;
+      example = "/dev/input/event15";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.logkeys = {
+      description = "LogKeys Keylogger Daemon";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.logkeys}/bin/logkeys -s${lib.optionalString (cfg.device != null) " -d ${cfg.device}"}";
+        ExecStop = "${pkgs.logkeys}/bin/logkeys -k";
+        Type = "forking";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mame.nix b/nixpkgs/nixos/modules/services/misc/mame.nix
new file mode 100644
index 000000000000..c5d5e9e48371
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mame.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mame;
+  mame = "mame${lib.optionalString pkgs.stdenv.is64bit "64"}";
+in
+{
+  options = {
+    services.mame = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to setup TUN/TAP Ethernet interface for MAME emulator.
+        '';
+      };
+      user = mkOption {
+        type = types.str;
+        description = ''
+          User from which you run MAME binary.
+        '';
+      };
+      hostAddr = mkOption {
+        type = types.str;
+        description = ''
+          IP address of the host system. Usually an address of the main network
+          adapter or the adapter through which you get an internet connection.
+        '';
+        example = "192.168.31.156";
+      };
+      emuAddr = mkOption {
+        type = types.str;
+        description = ''
+          IP address of the guest system. The same you set inside guest OS under
+          MAME. Should be on the same subnet as <option>services.mame.hostAddr</option>.
+        '';
+        example = "192.168.31.155";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.mame ];
+
+    security.wrappers."${mame}" = {
+      source = "${pkgs.mame}/bin/${mame}";
+      capabilities = "cap_net_admin,cap_net_raw+eip";
+    };
+
+    systemd.services.mame = {
+      description = "MAME TUN/TAP Ethernet interface";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.iproute ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${pkgs.mame}/bin/taputil.sh -c ${cfg.user} ${cfg.emuAddr} ${cfg.hostAddr} -";
+        ExecStop = "${pkgs.mame}/bin/taputil.sh -d ${cfg.user}";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mathics.nix b/nixpkgs/nixos/modules/services/misc/mathics.nix
new file mode 100644
index 000000000000..c588a30d76cd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mathics.nix
@@ -0,0 +1,54 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mathics;
+
+in {
+  options = {
+    services.mathics = {
+      enable = mkEnableOption "Mathics notebook service";
+
+      external = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Listen on all interfaces, rather than just localhost?";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8000;
+        description = "TCP port to listen on.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.mathics = {
+      group = config.users.groups.mathics.name;
+      description = "Mathics user";
+      home = "/var/lib/mathics";
+      createHome = true;
+      uid = config.ids.uids.mathics;
+    };
+
+    users.groups.mathics.gid = config.ids.gids.mathics;
+
+    systemd.services.mathics = {
+      description = "Mathics notebook server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        User = config.users.users.mathics.name;
+        Group = config.users.groups.mathics.name;
+        ExecStart = concatStringsSep " " [
+          "${pkgs.mathics}/bin/mathicsserver"
+          "--port" (toString cfg.port)
+          (if cfg.external then "--external" else "")
+        ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix b/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix
new file mode 100644
index 000000000000..49c41ff637a8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/matrix-appservice-discord.nix
@@ -0,0 +1,162 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  dataDir = "/var/lib/matrix-appservice-discord";
+  registrationFile = "${dataDir}/discord-registration.yaml";
+  appDir = "${pkgs.matrix-appservice-discord}/lib/node_modules/matrix-appservice-discord";
+  cfg = config.services.matrix-appservice-discord;
+  # TODO: switch to configGen.json once RFC42 is implemented
+  settingsFile = pkgs.writeText "matrix-appservice-discord-settings.json" (builtins.toJSON cfg.settings);
+
+in {
+  options = {
+    services.matrix-appservice-discord = {
+      enable = mkEnableOption "a bridge between Matrix and Discord";
+
+      settings = mkOption rec {
+        # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
+        type = types.attrs;
+        apply = recursiveUpdate default;
+        default = {
+          database = {
+            filename = "${dataDir}/discord.db";
+
+            # TODO: remove those old config keys once the following issues are solved:
+            # * https://github.com/Half-Shot/matrix-appservice-discord/issues/490
+            # * https://github.com/Half-Shot/matrix-appservice-discord/issues/498
+            userStorePath = "${dataDir}/user-store.db";
+            roomStorePath = "${dataDir}/room-store.db";
+          };
+
+          # empty values necessary for registration file generation
+          # actual values defined in environmentFile
+          auth = {
+            clientID = "";
+            botToken = "";
+          };
+        };
+        example = literalExample ''
+          {
+            bridge = {
+              domain = "public-domain.tld";
+              homeserverUrl = "http://public-domain.tld:8008";
+            };
+          }
+        '';
+        description = ''
+          <filename>config.yaml</filename> configuration as a Nix attribute set.
+          </para>
+
+          <para>
+          Configuration options should match those described in
+          <link xlink:href="https://github.com/Half-Shot/matrix-appservice-discord/blob/master/config/config.sample.yaml">
+          config.sample.yaml</link>.
+          </para>
+
+          <para>
+          <option>config.bridge.domain</option> and <option>config.bridge.homeserverUrl</option>
+          should be set to match the public host name of the Matrix homeserver for webhooks and avatars to work.
+          </para>
+
+          <para>
+          Secret tokens should be specified using <option>environmentFile</option>
+          instead of this world-readable attribute set.
+        '';
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          File containing environment variables to be passed to the matrix-appservice-discord service,
+          in which secret tokens can be specified securely by defining values for
+          <literal>APPSERVICE_DISCORD_AUTH_CLIENT_I_D</literal> and
+          <literal>APPSERVICE_DISCORD_AUTH_BOT_TOKEN</literal>.
+        '';
+      };
+
+      url = mkOption {
+        type = types.str;
+        default = "http://localhost:${toString cfg.port}";
+        description = ''
+          The URL where the application service is listening for HS requests.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 9005; # from https://github.com/Half-Shot/matrix-appservice-discord/blob/master/package.json#L11
+        description = ''
+          Port number on which the bridge should listen for internal communication with the Matrix homeserver.
+        '';
+      };
+
+      localpart = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          The user_id localpart to assign to the AS.
+        '';
+      };
+
+      serviceDependencies = mkOption {
+        type = with types; listOf str;
+        default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        description = ''
+          List of Systemd services to require and wait for when starting the application service,
+          such as the Matrix homeserver if it's running on the same host.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.matrix-appservice-discord = {
+      description = "A bridge between Matrix and Discord.";
+
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
+      after = [ "network-online.target" ] ++ cfg.serviceDependencies;
+
+      preStart = ''
+        if [ ! -f '${registrationFile}' ]; then
+          ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+            --generate-registration \
+            --url=${escapeShellArg cfg.url} \
+            ${optionalString (cfg.localpart != null) "--localpart=${escapeShellArg cfg.localpart}"} \
+            --config='${settingsFile}' \
+            --file='${registrationFile}'
+        fi
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+
+        DynamicUser = true;
+        PrivateTmp = true;
+        WorkingDirectory = appDir;
+        StateDirectory = baseNameOf dataDir;
+        UMask = 0027;
+        EnvironmentFile = cfg.environmentFile;
+
+        ExecStart = ''
+          ${pkgs.matrix-appservice-discord}/bin/matrix-appservice-discord \
+            --file='${registrationFile}' \
+            --config='${settingsFile}' \
+            --port='${toString cfg.port}'
+        '';
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ pacien ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml b/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml
new file mode 100644
index 000000000000..d85bdd1208f9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/matrix-synapse-log_config.yaml
@@ -0,0 +1,25 @@
+version: 1
+
+# In systemd's journal, loglevel is implicitly stored, so let's omit it
+# from the message text.
+formatters:
+    journal_fmt:
+        format: '%(name)s: [%(request)s] %(message)s'
+
+filters:
+    context:
+        (): synapse.util.logcontext.LoggingContextFilter
+        request: ""
+
+handlers:
+    journal:
+        class: systemd.journal.JournalHandler
+        formatter: journal_fmt
+        filters: [context]
+        SYSLOG_IDENTIFIER: synapse
+
+root:
+    level: INFO
+    handlers: [journal]
+
+disable_existing_loggers: False
diff --git a/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix b/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix
new file mode 100644
index 000000000000..e982eb16fa70
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/matrix-synapse.nix
@@ -0,0 +1,732 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.matrix-synapse;
+  pg = config.services.postgresql;
+  usePostgresql = cfg.database_type == "psycopg2";
+  logConfigFile = pkgs.writeText "log_config.yaml" cfg.logConfig;
+  mkResource = r: ''{names: ${builtins.toJSON r.names}, compress: ${boolToString r.compress}}'';
+  mkListener = l: ''{port: ${toString l.port}, bind_address: "${l.bind_address}", type: ${l.type}, tls: ${boolToString l.tls}, x_forwarded: ${boolToString l.x_forwarded}, resources: [${concatStringsSep "," (map mkResource l.resources)}]}'';
+  pluginsEnv = cfg.package.python.buildEnv.override {
+    extraLibs = cfg.plugins;
+  };
+  configFile = pkgs.writeText "homeserver.yaml" ''
+${optionalString (cfg.tls_certificate_path != null) ''
+tls_certificate_path: "${cfg.tls_certificate_path}"
+''}
+${optionalString (cfg.tls_private_key_path != null) ''
+tls_private_key_path: "${cfg.tls_private_key_path}"
+''}
+${optionalString (cfg.tls_dh_params_path != null) ''
+tls_dh_params_path: "${cfg.tls_dh_params_path}"
+''}
+no_tls: ${boolToString cfg.no_tls}
+${optionalString (cfg.bind_port != null) ''
+bind_port: ${toString cfg.bind_port}
+''}
+${optionalString (cfg.unsecure_port != null) ''
+unsecure_port: ${toString cfg.unsecure_port}
+''}
+${optionalString (cfg.bind_host != null) ''
+bind_host: "${cfg.bind_host}"
+''}
+server_name: "${cfg.server_name}"
+pid_file: "/run/matrix-synapse.pid"
+${optionalString (cfg.public_baseurl != null) ''
+public_baseurl: "${cfg.public_baseurl}"
+''}
+listeners: [${concatStringsSep "," (map mkListener cfg.listeners)}]
+database: {
+  name: "${cfg.database_type}",
+  args: {
+    ${concatStringsSep ",\n    " (
+      mapAttrsToList (n: v: "\"${n}\": ${builtins.toJSON v}") cfg.database_args
+    )}
+  }
+}
+event_cache_size: "${cfg.event_cache_size}"
+verbose: ${cfg.verbose}
+log_config: "${logConfigFile}"
+rc_messages_per_second: ${cfg.rc_messages_per_second}
+rc_message_burst_count: ${cfg.rc_message_burst_count}
+federation_rc_window_size: ${cfg.federation_rc_window_size}
+federation_rc_sleep_limit: ${cfg.federation_rc_sleep_limit}
+federation_rc_sleep_delay: ${cfg.federation_rc_sleep_delay}
+federation_rc_reject_limit: ${cfg.federation_rc_reject_limit}
+federation_rc_concurrent: ${cfg.federation_rc_concurrent}
+media_store_path: "${cfg.dataDir}/media"
+uploads_path: "${cfg.dataDir}/uploads"
+max_upload_size: "${cfg.max_upload_size}"
+max_image_pixels: "${cfg.max_image_pixels}"
+dynamic_thumbnails: ${boolToString cfg.dynamic_thumbnails}
+url_preview_enabled: ${boolToString cfg.url_preview_enabled}
+${optionalString (cfg.url_preview_enabled == true) ''
+url_preview_ip_range_blacklist: ${builtins.toJSON cfg.url_preview_ip_range_blacklist}
+url_preview_ip_range_whitelist: ${builtins.toJSON cfg.url_preview_ip_range_whitelist}
+url_preview_url_blacklist: ${builtins.toJSON cfg.url_preview_url_blacklist}
+''}
+recaptcha_private_key: "${cfg.recaptcha_private_key}"
+recaptcha_public_key: "${cfg.recaptcha_public_key}"
+enable_registration_captcha: ${boolToString cfg.enable_registration_captcha}
+turn_uris: ${builtins.toJSON cfg.turn_uris}
+turn_shared_secret: "${cfg.turn_shared_secret}"
+enable_registration: ${boolToString cfg.enable_registration}
+${optionalString (cfg.registration_shared_secret != null) ''
+registration_shared_secret: "${cfg.registration_shared_secret}"
+''}
+recaptcha_siteverify_api: "https://www.google.com/recaptcha/api/siteverify"
+turn_user_lifetime: "${cfg.turn_user_lifetime}"
+user_creation_max_duration: ${cfg.user_creation_max_duration}
+bcrypt_rounds: ${cfg.bcrypt_rounds}
+allow_guest_access: ${boolToString cfg.allow_guest_access}
+
+account_threepid_delegates:
+  ${optionalString (cfg.account_threepid_delegates.email != null) "email: ${cfg.account_threepid_delegates.email}"}
+  ${optionalString (cfg.account_threepid_delegates.msisdn != null) "msisdn: ${cfg.account_threepid_delegates.msisdn}"}
+
+room_invite_state_types: ${builtins.toJSON cfg.room_invite_state_types}
+${optionalString (cfg.macaroon_secret_key != null) ''
+  macaroon_secret_key: "${cfg.macaroon_secret_key}"
+''}
+expire_access_token: ${boolToString cfg.expire_access_token}
+enable_metrics: ${boolToString cfg.enable_metrics}
+report_stats: ${boolToString cfg.report_stats}
+signing_key_path: "${cfg.dataDir}/homeserver.signing.key"
+key_refresh_interval: "${cfg.key_refresh_interval}"
+perspectives:
+  servers: {
+    ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
+    "${n}": {
+      "verify_keys": {
+        ${concatStringsSep "},\n" (mapAttrsToList (n: v: ''
+        "${n}": {
+          "key": "${v}"
+        }'') v)}
+      }
+    '') cfg.servers)}
+    }
+  }
+redaction_retention_period: ${toString cfg.redaction_retention_period}
+app_service_config_files: ${builtins.toJSON cfg.app_service_config_files}
+
+${cfg.extraConfig}
+'';
+
+  hasLocalPostgresDB = let args = cfg.database_args; in
+    usePostgresql && (!(args ? host) || (elem args.host [ "localhost" "127.0.0.1" "::1" ]));
+in {
+  options = {
+    services.matrix-synapse = {
+      enable = mkEnableOption "matrix.org synapse";
+      package = mkOption {
+        type = types.package;
+        default = pkgs.matrix-synapse;
+        defaultText = "pkgs.matrix-synapse";
+        description = ''
+          Overridable attribute of the matrix synapse server package to use.
+        '';
+      };
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        defaultText = "with config.services.matrix-synapse.package.plugins [ matrix-synapse-ldap3 matrix-synapse-pam ]";
+        description = ''
+          List of additional Matrix plugins to make available.
+        '';
+      };
+      no_tls = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Don't bind to the https port
+        '';
+      };
+      bind_port = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 8448;
+        description = ''
+          DEPRECATED: Use listeners instead.
+          The port to listen for HTTPS requests on.
+          For when matrix traffic is sent directly to synapse.
+        '';
+      };
+      unsecure_port = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 8008;
+        description = ''
+          DEPRECATED: Use listeners instead.
+          The port to listen for HTTP requests on.
+          For when matrix traffic passes through loadbalancer that unwraps TLS.
+        '';
+      };
+      bind_host = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          DEPRECATED: Use listeners instead.
+          Local interface to listen on.
+          The empty string will cause synapse to listen on all interfaces.
+        '';
+      };
+      tls_certificate_path = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "${cfg.dataDir}/homeserver.tls.crt";
+        description = ''
+          PEM encoded X509 certificate for TLS.
+          You can replace the self-signed certificate that synapse
+          autogenerates on launch with your own SSL certificate + key pair
+          if you like.  Any required intermediary certificates can be
+          appended after the primary certificate in hierarchical order.
+        '';
+      };
+      tls_private_key_path = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "${cfg.dataDir}/homeserver.tls.key";
+        description = ''
+          PEM encoded private key for TLS. Specify null if synapse is not
+          speaking TLS directly.
+        '';
+      };
+      tls_dh_params_path = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "${cfg.dataDir}/homeserver.tls.dh";
+        description = ''
+          PEM dh parameters for ephemeral keys
+        '';
+      };
+      server_name = mkOption {
+        type = types.str;
+        example = "example.com";
+        default = config.networking.hostName;
+        description = ''
+          The domain name of the server, with optional explicit port.
+          This is used by remote servers to connect to this server,
+          e.g. matrix.org, localhost:8080, etc.
+          This is also the last part of your UserID.
+        '';
+      };
+      public_baseurl = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "https://example.com:8448/";
+        description = ''
+          The public-facing base URL for the client API (not including _matrix/...)
+        '';
+      };
+      listeners = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            port = mkOption {
+              type = types.int;
+              example = 8448;
+              description = ''
+                The port to listen for HTTP(S) requests on.
+              '';
+            };
+            bind_address = mkOption {
+              type = types.str;
+              default = "";
+              example = "203.0.113.42";
+              description = ''
+                Local interface to listen on.
+                The empty string will cause synapse to listen on all interfaces.
+              '';
+            };
+            type = mkOption {
+              type = types.str;
+              default = "http";
+              description = ''
+                Type of listener.
+              '';
+            };
+            tls = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether to listen for HTTPS connections rather than HTTP.
+              '';
+            };
+            x_forwarded = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Use the X-Forwarded-For (XFF) header as the client IP and not the
+                actual client IP.
+              '';
+            };
+            resources = mkOption {
+              type = types.listOf (types.submodule {
+                options = {
+                  names = mkOption {
+                    type = types.listOf types.str;
+                    description = ''
+                      List of resources to host on this listener.
+                    '';
+                    example = ["client" "webclient" "federation"];
+                  };
+                  compress = mkOption {
+                    type = types.bool;
+                    description = ''
+                      Should synapse compress HTTP responses to clients that support it?
+                      This should be disabled if running synapse behind a load balancer
+                      that can do automatic compression.
+                    '';
+                  };
+                };
+              });
+              description = ''
+                List of HTTP resources to serve on this listener.
+              '';
+            };
+          };
+        });
+        default = [{
+          port = 8448;
+          bind_address = "";
+          type = "http";
+          tls = true;
+          x_forwarded = false;
+          resources = [
+            { names = ["client" "webclient"]; compress = true; }
+            { names = ["federation"]; compress = false; }
+          ];
+        }];
+        description = ''
+          List of ports that Synapse should listen on, their purpose and their configuration.
+        '';
+      };
+      verbose = mkOption {
+        type = types.str;
+        default = "0";
+        description = "Logging verbosity level.";
+      };
+      rc_messages_per_second = mkOption {
+        type = types.str;
+        default = "0.2";
+        description = "Number of messages a client can send per second";
+      };
+      rc_message_burst_count = mkOption {
+        type = types.str;
+        default = "10.0";
+        description = "Number of message a client can send before being throttled";
+      };
+      federation_rc_window_size = mkOption {
+        type = types.str;
+        default = "1000";
+        description = "The federation window size in milliseconds";
+      };
+      federation_rc_sleep_limit = mkOption {
+        type = types.str;
+        default = "10";
+        description = ''
+          The number of federation requests from a single server in a window
+          before the server will delay processing the request.
+        '';
+      };
+      federation_rc_sleep_delay = mkOption {
+        type = types.str;
+        default = "500";
+        description = ''
+          The duration in milliseconds to delay processing events from
+          remote servers by if they go over the sleep limit.
+        '';
+      };
+      federation_rc_reject_limit = mkOption {
+        type = types.str;
+        default = "50";
+        description = ''
+          The maximum number of concurrent federation requests allowed
+          from a single server
+        '';
+      };
+      federation_rc_concurrent = mkOption {
+        type = types.str;
+        default = "3";
+        description = "The number of federation requests to concurrently process from a single server";
+      };
+      database_type = mkOption {
+        type = types.enum [ "sqlite3" "psycopg2" ];
+        default = if versionAtLeast config.system.stateVersion "18.03"
+          then "psycopg2"
+          else "sqlite3";
+        description = ''
+          The database engine name. Can be sqlite or psycopg2.
+        '';
+      };
+      database_name = mkOption {
+        type = types.str;
+        default = "matrix-synapse";
+        description = "Database name.";
+      };
+      database_user = mkOption {
+        type = types.str;
+        default = "matrix-synapse";
+        description = "Database user name.";
+      };
+      database_args = mkOption {
+        type = types.attrs;
+        default = {
+          sqlite3 = { database = "${cfg.dataDir}/homeserver.db"; };
+          psycopg2 = {
+            user = cfg.database_user;
+            database = cfg.database_name;
+          };
+        }.${cfg.database_type};
+        description = ''
+          Arguments to pass to the engine.
+        '';
+      };
+      event_cache_size = mkOption {
+        type = types.str;
+        default = "10K";
+        description = "Number of events to cache in memory.";
+      };
+      url_preview_enabled = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Is the preview URL API enabled?  If enabled, you *must* specify an
+          explicit url_preview_ip_range_blacklist of IPs that the spider is
+          denied from accessing.
+        '';
+      };
+      url_preview_ip_range_blacklist = mkOption {
+        type = types.listOf types.str;
+        default = [
+          "127.0.0.0/8"
+          "10.0.0.0/8"
+          "172.16.0.0/12"
+          "192.168.0.0/16"
+          "100.64.0.0/10"
+          "169.254.0.0/16"
+          "::1/128"
+          "fe80::/64"
+          "fc00::/7"
+        ];
+        description = ''
+          List of IP address CIDR ranges that the URL preview spider is denied
+          from accessing.
+        '';
+      };
+      url_preview_ip_range_whitelist = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          List of IP address CIDR ranges that the URL preview spider is allowed
+          to access even if they are specified in
+          url_preview_ip_range_blacklist.
+        '';
+      };
+      url_preview_url_blacklist = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Optional list of URL matches that the URL preview spider is
+          denied from accessing.
+        '';
+      };
+      recaptcha_private_key = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          This Home Server's ReCAPTCHA private key.
+        '';
+      };
+      recaptcha_public_key = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          This Home Server's ReCAPTCHA public key.
+        '';
+      };
+      enable_registration_captcha = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enables ReCaptcha checks when registering, preventing signup
+          unless a captcha is answered. Requires a valid ReCaptcha
+          public/private key.
+        '';
+      };
+      turn_uris = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          The public URIs of the TURN server to give to clients
+        '';
+      };
+      turn_shared_secret = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          The shared secret used to compute passwords for the TURN server
+        '';
+      };
+      turn_user_lifetime = mkOption {
+        type = types.str;
+        default = "1h";
+        description = "How long generated TURN credentials last";
+      };
+      enable_registration = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable registration for new users.
+        '';
+      };
+      registration_shared_secret = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          If set, allows registration by anyone who also has the shared
+          secret, even if registration is otherwise disabled.
+        '';
+      };
+      enable_metrics = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable collection and rendering of performance metrics
+        '';
+      };
+      report_stats = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+        '';
+      };
+      servers = mkOption {
+        type = types.attrsOf (types.attrsOf types.str);
+        default = {
+          "matrix.org" = {
+            "ed25519:auto" = "Noi6WqcDj0QmPxCNQqgezwTlBKrfqehY1u2FyWP9uYw";
+          };
+        };
+        description = ''
+          The trusted servers to download signing keys from.
+        '';
+      };
+      max_upload_size = mkOption {
+        type = types.str;
+        default = "10M";
+        description = "The largest allowed upload size in bytes";
+      };
+      max_image_pixels = mkOption {
+        type = types.str;
+        default = "32M";
+        description = "Maximum number of pixels that will be thumbnailed";
+      };
+      dynamic_thumbnails = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to generate new thumbnails on the fly to precisely match
+          the resolution requested by the client. If true then whenever
+          a new resolution is requested by the client the server will
+          generate a new thumbnail. If false the server will pick a thumbnail
+          from a precalculated list.
+        '';
+      };
+      user_creation_max_duration = mkOption {
+        type = types.str;
+        default = "1209600000";
+        description = ''
+          Sets the expiry for the short term user creation in
+          milliseconds. The default value is two weeks.
+        '';
+      };
+      bcrypt_rounds = mkOption {
+        type = types.str;
+        default = "12";
+        description = ''
+          Set the number of bcrypt rounds used to generate password hash.
+          Larger numbers increase the work factor needed to generate the hash.
+        '';
+      };
+      allow_guest_access = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Allows users to register as guests without a password/email/etc, and
+          participate in rooms hosted on this server which have been made
+          accessible to anonymous users.
+        '';
+      };
+      account_threepid_delegates.email = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Delegate email sending to https://example.org
+        '';
+      };
+      account_threepid_delegates.msisdn = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Delegate SMS sending to this local process (https://localhost:8090)
+        '';
+      };
+      room_invite_state_types = mkOption {
+        type = types.listOf types.str;
+        default = ["m.room.join_rules" "m.room.canonical_alias" "m.room.avatar" "m.room.name"];
+        description = ''
+          A list of event types that will be included in the room_invite_state
+        '';
+      };
+      macaroon_secret_key = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Secret key for authentication tokens
+        '';
+      };
+      expire_access_token = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable access token expiration.
+        '';
+      };
+      key_refresh_interval = mkOption {
+        type = types.str;
+        default = "1d";
+        description = ''
+          How long key response published by this server is valid for.
+          Used to set the valid_until_ts in /key/v2 APIs.
+          Determines how quickly servers will query to check which keys
+          are still valid.
+        '';
+      };
+      app_service_config_files = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        description = ''
+          A list of application service config file to use
+        '';
+      };
+      redaction_retention_period = mkOption {
+        type = types.int;
+        default = 7;
+        description = ''
+          How long to keep redacted events in unredacted form in the database.
+        '';
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra config options for matrix-synapse.
+        '';
+      };
+      extraConfigFiles = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          Extra config files to include.
+
+          The configuration files will be included based on the command line
+          argument --config-path. This allows to configure secrets without
+          having to go through the Nix store, e.g. based on deployment keys if
+          NixOPS is in use.
+        '';
+      };
+      logConfig = mkOption {
+        type = types.lines;
+        default = readFile ./matrix-synapse-log_config.yaml;
+        description = ''
+          A yaml python logging config file
+        '';
+      };
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/matrix-synapse";
+        description = ''
+          The directory where matrix-synapse stores its stateful data such as
+          certificates, media and uploads.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = hasLocalPostgresDB -> config.services.postgresql.enable;
+        message = ''
+          Cannot deploy matrix-synapse with a configuration for a local postgresql database
+            and a missing postgresql service. Since 20.03 it's mandatory to manually configure the
+            database (please read the thread in https://github.com/NixOS/nixpkgs/pull/80447 for
+            further reference).
+
+            If you
+            - try to deploy a fresh synapse, you need to configure the database yourself. An example
+              for this can be found in <nixpkgs/nixos/tests/matrix-synapse.nix>
+            - update your existing matrix-synapse instance, you simply need to add `services.postgresql.enable = true`
+              to your configuration.
+
+          For further information about this update, please read the release-notes of 20.03 carefully.
+        '';
+      }
+    ];
+
+    users.users.matrix-synapse = { 
+        group = "matrix-synapse";
+        home = cfg.dataDir;
+        createHome = true;
+        shell = "${pkgs.bash}/bin/bash";
+        uid = config.ids.uids.matrix-synapse;
+      };
+
+    users.groups.matrix-synapse = {
+      gid = config.ids.gids.matrix-synapse;
+    };
+
+    systemd.services.matrix-synapse = {
+      description = "Synapse Matrix homeserver";
+      after = [ "network.target" ] ++ optional hasLocalPostgresDB "postgresql.service";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        ${cfg.package}/bin/homeserver \
+          --config-path ${configFile} \
+          --keys-directory ${cfg.dataDir} \
+          --generate-keys
+      '';
+      environment.PYTHONPATH = makeSearchPathOutput "lib" cfg.package.python.sitePackages [ pluginsEnv ];
+      serviceConfig = {
+        Type = "notify";
+        User = "matrix-synapse";
+        Group = "matrix-synapse";
+        WorkingDirectory = cfg.dataDir;
+        ExecStart = ''
+          ${cfg.package}/bin/homeserver \
+            ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
+            --keys-directory ${cfg.dataDir}
+        '';
+        ExecReload = "${pkgs.utillinux}/bin/kill -HUP $MAINPID";
+        Restart = "on-failure";
+      };
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "trusted_third_party_id_servers" ] ''
+      The `trusted_third_party_id_servers` option as been removed in `matrix-synapse` v1.4.0
+      as the behavior is now obsolete.
+    '')
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "create_local_database" ] ''
+      Database configuration must be done manually. An exemplary setup is demonstrated in
+      <nixpkgs/nixos/tests/matrix-synapse.nix>
+    '')
+    (mkRemovedOptionModule [ "services" "matrix-synapse" "web_client" ] "")
+  ];
+
+  meta.doc = ./matrix-synapse.xml;
+  meta.maintainers = teams.matrix.members;
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/matrix-synapse.xml b/nixpkgs/nixos/modules/services/misc/matrix-synapse.xml
new file mode 100644
index 000000000000..2f2ac27eeb9d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/matrix-synapse.xml
@@ -0,0 +1,225 @@
+<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-matrix">
+ <title>Matrix</title>
+ <para>
+  <link xlink:href="https://matrix.org/">Matrix</link> is an open standard for
+  interoperable, decentralised, real-time communication over IP. It can be used
+  to power Instant Messaging, VoIP/WebRTC signalling, Internet of Things
+  communication - or anywhere you need a standard HTTP API for publishing and
+  subscribing to data whilst tracking the conversation history.
+ </para>
+ <para>
+  This chapter will show you how to set up your own, self-hosted Matrix
+  homeserver using the Synapse reference homeserver, and how to serve your own
+  copy of the Riot web client. See the
+  <link xlink:href="https://matrix.org/docs/projects/try-matrix-now.html">Try
+  Matrix Now!</link> overview page for links to Riot Apps for Android and iOS,
+  desktop clients, as well as bridges to other networks and other projects
+  around Matrix.
+ </para>
+ <section xml:id="module-services-matrix-synapse">
+  <title>Synapse Homeserver</title>
+
+  <para>
+   <link xlink:href="https://github.com/matrix-org/synapse">Synapse</link> is
+   the reference homeserver implementation of Matrix from the core development
+   team at matrix.org. The following configuration example will set up a
+   synapse server for the <literal>example.org</literal> domain, served from
+   the host <literal>myhostname.example.org</literal>. For more information,
+   please refer to the
+   <link xlink:href="https://github.com/matrix-org/synapse#synapse-installation">
+   installation instructions of Synapse </link>.
+<programlisting>
+{ pkgs, ... }:
+let
+  fqdn =
+    let
+      join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
+    in join config.networking.hostName config.networking.domain;
+in {
+  networking = {
+    <link linkend="opt-networking.hostName">hostName</link> = "myhostname";
+    <link linkend="opt-networking.domain">domain</link> = "example.org";
+  };
+  <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
+
+  <link linkend="opt-services.postgresql.enable">services.postgresql.enable</link> = true;
+  <link linkend="opt-services.postgresql.initialScript">services.postgresql.initialScript</link> = pkgs.writeText "synapse-init.sql" ''
+    CREATE ROLE "matrix-synapse" WITH LOGIN PASSWORD 'synapse';
+    CREATE DATABASE "matrix-synapse" WITH OWNER "matrix-synapse"
+      TEMPLATE template0
+      LC_COLLATE = "C"
+      LC_CTYPE = "C";
+  '';
+
+  services.nginx = {
+    <link linkend="opt-services.nginx.enable">enable</link> = true;
+    # only recommendedProxySettings and recommendedGzipSettings are strictly required,
+    # but the rest make sense as well
+    <link linkend="opt-services.nginx.recommendedTlsSettings">recommendedTlsSettings</link> = true;
+    <link linkend="opt-services.nginx.recommendedOptimisation">recommendedOptimisation</link> = true;
+    <link linkend="opt-services.nginx.recommendedGzipSettings">recommendedGzipSettings</link> = true;
+    <link linkend="opt-services.nginx.recommendedProxySettings">recommendedProxySettings</link> = true;
+
+    <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
+      # This host section can be placed on a different host than the rest,
+      # i.e. to delegate from the host being accessible as ${config.networking.domain}
+      # to another host actually running the Matrix homeserver.
+      "${config.networking.domain}" = {
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/server".extraConfig</link> =
+          let
+            # use 443 instead of the default 8448 port to unite
+            # the client-server and server-server port for simplicity
+            server = { "m.server" = "${fqdn}:443"; };
+          in ''
+            add_header Content-Type application/json;
+            return 200 '${builtins.toJSON server}';
+          '';
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."= /.well-known/matrix/client".extraConfig</link> =
+          let
+            client = {
+              "m.homeserver" =  { "base_url" = "https://${fqdn}"; };
+              "m.identity_server" =  { "base_url" = "https://vector.im"; };
+            };
+          # ACAO required to allow riot-web on any URL to request this json file
+          in ''
+            add_header Content-Type application/json;
+            add_header Access-Control-Allow-Origin *;
+            return 200 '${builtins.toJSON client}';
+          '';
+      };
+
+      # Reverse proxy for Matrix client-server and server-server communication
+      ${fqdn} = {
+        <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
+        <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
+
+        # Or do a redirect instead of the 404, or whatever is appropriate for you.
+        # But do not put a Matrix Web client here! See the Riot Web section below.
+        <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.extraConfig">locations."/".extraConfig</link> = ''
+          return 404;
+        '';
+
+        # forward all Matrix API calls to the synapse Matrix homeserver
+        locations."/_matrix" = {
+          <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.proxyPass">proxyPass</link> = "http://[::1]:8008"; # without a trailing /
+        };
+      };
+    };
+  };
+  services.matrix-synapse = {
+    <link linkend="opt-services.matrix-synapse.enable">enable</link> = true;
+    <link linkend="opt-services.matrix-synapse.server_name">server_name</link> = config.networking.domain;
+    <link linkend="opt-services.matrix-synapse.listeners">listeners</link> = [
+      {
+        <link linkend="opt-services.matrix-synapse.listeners._.port">port</link> = 8008;
+        <link linkend="opt-services.matrix-synapse.listeners._.bind_address">bind_address</link> = "::1";
+        <link linkend="opt-services.matrix-synapse.listeners._.type">type</link> = "http";
+        <link linkend="opt-services.matrix-synapse.listeners._.tls">tls</link> = false;
+        <link linkend="opt-services.matrix-synapse.listeners._.x_forwarded">x_forwarded</link> = true;
+        <link linkend="opt-services.matrix-synapse.listeners._.resources">resources</link> = [
+          {
+            <link linkend="opt-services.matrix-synapse.listeners._.resources._.names">names</link> = [ "client" "federation" ];
+            <link linkend="opt-services.matrix-synapse.listeners._.resources._.compress">compress</link> = false;
+          }
+        ];
+      }
+    ];
+  };
+};
+</programlisting>
+  </para>
+
+  <para>
+   If the <code>A</code> and <code>AAAA</code> DNS records on
+   <literal>example.org</literal> do not point on the same host as the records
+   for <code>myhostname.example.org</code>, you can easily move the
+   <code>/.well-known</code> virtualHost section of the code to the host that
+   is serving <literal>example.org</literal>, while the rest stays on
+   <literal>myhostname.example.org</literal> with no other changes required.
+   This pattern also allows to seamlessly move the homeserver from
+   <literal>myhostname.example.org</literal> to
+   <literal>myotherhost.example.org</literal> by only changing the
+   <code>/.well-known</code> redirection target.
+  </para>
+
+  <para>
+   If you want to run a server with public registration by anybody, you can
+   then enable <literal><link linkend="opt-services.matrix-synapse.enable_registration">services.matrix-synapse.enable_registration</link> =
+   true;</literal>. Otherwise, or you can generate a registration secret with
+   <command>pwgen -s 64 1</command> and set it with
+   <option><link linkend="opt-services.matrix-synapse.registration_shared_secret">services.matrix-synapse.registration_shared_secret</link></option>. To
+   create a new user or admin, run the following after you have set the secret
+   and have rebuilt NixOS:
+<screen>
+<prompt>$ </prompt>nix run nixpkgs.matrix-synapse
+<prompt>$ </prompt>register_new_matrix_user -k <replaceable>your-registration-shared-secret</replaceable> http://localhost:8008
+<prompt>New user localpart: </prompt><replaceable>your-username</replaceable>
+<prompt>Password:</prompt>
+<prompt>Confirm password:</prompt>
+<prompt>Make admin [no]:</prompt>
+Success!
+</screen>
+   In the example, this would create a user with the Matrix Identifier
+   <literal>@your-username:example.org</literal>. Note that the registration
+   secret ends up in the nix store and therefore is world-readable by any user
+   on your machine, so it makes sense to only temporarily activate the
+   <link linkend="opt-services.matrix-synapse.registration_shared_secret">registration_shared_secret</link>
+   option until a better solution for NixOS is in place.
+  </para>
+ </section>
+ <section xml:id="module-services-matrix-riot-web">
+  <title>Riot Web Client</title>
+
+  <para>
+   <link xlink:href="https://github.com/vector-im/riot-web/">Riot Web</link> is
+   the reference web client for Matrix and developed by the core team at
+   matrix.org. The following snippet can be optionally added to the code before
+   to complete the synapse installation with a web client served at
+   <code>https://riot.myhostname.example.org</code> and
+   <code>https://riot.example.org</code>. Alternatively, you can use the hosted
+   copy at <link xlink:href="https://riot.im/app">https://riot.im/app</link>,
+   or use other web clients or native client applications. Due to the
+   <literal>/.well-known</literal> urls set up done above, many clients should
+   fill in the required connection details automatically when you enter your
+   Matrix Identifier. See
+   <link xlink:href="https://matrix.org/docs/projects/try-matrix-now.html">Try
+   Matrix Now!</link> for a list of existing clients and their supported
+   featureset.
+<programlisting>
+{
+  services.nginx.virtualHosts."riot.${fqdn}" = {
+    <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
+    <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true;
+    <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [
+      "riot.${config.networking.domain}"
+    ];
+
+    <link linkend="opt-services.nginx.virtualHosts._name_.root">root</link> = pkgs.riot-web.override {
+      conf = {
+        default_server_config."m.homeserver" = {
+          "base_url" = "${config.networking.domain}";
+          "server_name" = "${fqdn}";
+        };
+      };
+    };
+  };
+}
+</programlisting>
+  </para>
+
+  <para>
+   Note that the Riot developers do not recommend running Riot and your Matrix
+   homeserver on the same fully-qualified domain name for security reasons. In
+   the example, this means that you should not reuse the
+   <literal>myhostname.example.org</literal> virtualHost to also serve Riot,
+   but instead serve it on a different subdomain, like
+   <literal>riot.example.org</literal> in the example. See the
+   <link xlink:href="https://github.com/vector-im/riot-web#important-security-note">Riot
+   Important Security Notes</link> for more information on this subject.
+  </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix b/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix
new file mode 100644
index 000000000000..78a42fbb574b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mautrix-telegram.nix
@@ -0,0 +1,163 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  dataDir = "/var/lib/mautrix-telegram";
+  registrationFile = "${dataDir}/telegram-registration.yaml";
+  cfg = config.services.mautrix-telegram;
+  # TODO: switch to configGen.json once RFC42 is implemented
+  settingsFile = pkgs.writeText "mautrix-telegram-settings.json" (builtins.toJSON cfg.settings);
+
+in {
+  options = {
+    services.mautrix-telegram = {
+      enable = mkEnableOption "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge";
+
+      settings = mkOption rec {
+        # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
+        type = types.attrs;
+        apply = recursiveUpdate default;
+        default = {
+          appservice = rec {
+            database = "sqlite:///${dataDir}/mautrix-telegram.db";
+            hostname = "0.0.0.0";
+            port = 8080;
+            address = "http://localhost:${toString port}";
+          };
+
+          bridge = {
+            permissions."*" = "relaybot";
+            relaybot.whitelist = [ ];
+          };
+
+          logging = {
+            version = 1;
+
+            formatters.precise.format = "[%(levelname)s@%(name)s] %(message)s";
+
+            handlers.console = {
+              class = "logging.StreamHandler";
+              formatter = "precise";
+            };
+
+            loggers = {
+              mau.level = "INFO";
+              telethon.level = "INFO";
+
+              # prevent tokens from leaking in the logs:
+              # https://github.com/tulir/mautrix-telegram/issues/351
+              aiohttp.level = "WARNING";
+            };
+
+            # log to console/systemd instead of file
+            root = {
+              level = "INFO";
+              handlers = [ "console" ];
+            };
+          };
+        };
+        example = literalExample ''
+          {
+            homeserver = {
+              address = "http://localhost:8008";
+              domain = "public-domain.tld";
+            };
+
+            appservice.public = {
+              prefix = "/public";
+              external = "https://public-appservice-address/public";
+            };
+
+            bridge.permissions = {
+              "example.com" = "full";
+              "@admin:example.com" = "admin";
+            };
+          }
+        '';
+        description = ''
+          <filename>config.yaml</filename> configuration as a Nix attribute set.
+          Configuration options should match those described in
+          <link xlink:href="https://github.com/tulir/mautrix-telegram/blob/master/example-config.yaml">
+          example-config.yaml</link>.
+          </para>
+
+          <para>
+          Secret tokens should be specified using <option>environmentFile</option>
+          instead of this world-readable attribute set.
+        '';
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          File containing environment variables to be passed to the mautrix-telegram service,
+          in which secret tokens can be specified securely by defining values for
+          <literal>MAUTRIX_TELEGRAM_APPSERVICE_AS_TOKEN</literal>,
+          <literal>MAUTRIX_TELEGRAM_APPSERVICE_HS_TOKEN</literal>,
+          <literal>MAUTRIX_TELEGRAM_TELEGRAM_API_ID</literal>,
+          <literal>MAUTRIX_TELEGRAM_TELEGRAM_API_HASH</literal> and optionally
+          <literal>MAUTRIX_TELEGRAM_TELEGRAM_BOT_TOKEN</literal>.
+        '';
+      };
+
+      serviceDependencies = mkOption {
+        type = with types; listOf str;
+        default = optional config.services.matrix-synapse.enable "matrix-synapse.service";
+        description = ''
+          List of Systemd services to require and wait for when starting the application service.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.mautrix-telegram = {
+      description = "Mautrix-Telegram, a Matrix-Telegram hybrid puppeting/relaybot bridge.";
+
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ] ++ cfg.serviceDependencies;
+      after = [ "network-online.target" ] ++ cfg.serviceDependencies;
+
+      preStart = ''
+        # generate the appservice's registration file if absent
+        if [ ! -f '${registrationFile}' ]; then
+          ${pkgs.mautrix-telegram}/bin/mautrix-telegram \
+            --generate-registration \
+            --base-config='${pkgs.mautrix-telegram}/example-config.yaml' \
+            --config='${settingsFile}' \
+            --registration='${registrationFile}'
+        fi
+
+        # run automatic database init and migration scripts
+        ${pkgs.mautrix-telegram.alembic}/bin/alembic -x config='${settingsFile}' upgrade head
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+
+        DynamicUser = true;
+        PrivateTmp = true;
+        WorkingDirectory = pkgs.mautrix-telegram; # necessary for the database migration scripts to be found
+        StateDirectory = baseNameOf dataDir;
+        UMask = 0027;
+        EnvironmentFile = cfg.environmentFile;
+
+        ExecStart = ''
+          ${pkgs.mautrix-telegram}/bin/mautrix-telegram \
+            --config='${settingsFile}'
+        '';
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ pacien vskilet ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mbpfan.nix b/nixpkgs/nixos/modules/services/misc/mbpfan.nix
new file mode 100644
index 000000000000..e22d1ed61f99
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mbpfan.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mbpfan;
+  verbose = if cfg.verbose then "v" else "";
+
+in {
+  options.services.mbpfan = {
+    enable = mkEnableOption "mbpfan, fan controller daemon for Apple Macs and MacBooks";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.mbpfan;
+      defaultText = "pkgs.mbpfan";
+      description = ''
+        The package used for the mbpfan daemon.
+      '';
+    };
+
+    minFanSpeed = mkOption {
+      type = types.int;
+      default = 2000;
+      description = ''
+        The minimum fan speed.
+      '';
+    };
+
+    maxFanSpeed = mkOption {
+      type = types.int;
+      default = 6200;
+      description = ''
+        The maximum fan speed.
+      '';
+    };
+
+    lowTemp = mkOption {
+      type = types.int;
+      default = 63;
+      description = ''
+        The low temperature.
+      '';
+    };
+
+    highTemp = mkOption {
+      type = types.int;
+      default = 66;
+      description = ''
+        The high temperature.
+      '';
+    };
+
+    maxTemp = mkOption {
+      type = types.int;
+      default = 86;
+      description = ''
+        The maximum temperature.
+      '';
+    };
+
+    pollingInterval = mkOption {
+      type = types.int;
+      default = 7;
+      description = ''
+        The polling interval.
+      '';
+    };
+
+    verbose = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If true, sets the log level to verbose.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "coretemp" "applesmc" ];
+
+    environment = {
+      etc."mbpfan.conf".text = ''
+        [general]
+        min_fan_speed = ${toString cfg.minFanSpeed}
+        max_fan_speed = ${toString cfg.maxFanSpeed}
+        low_temp = ${toString cfg.lowTemp}
+        high_temp = ${toString cfg.highTemp}
+        max_temp = ${toString cfg.maxTemp}
+        polling_interval = ${toString cfg.pollingInterval}
+      '';
+      systemPackages = [ cfg.package ];
+    };
+
+    systemd.services.mbpfan = {
+      description = "A fan manager daemon for MacBook Pro";
+      wantedBy = [ "sysinit.target" ];
+      after = [ "syslog.target" "sysinit.target" ];
+      restartTriggers = [ config.environment.etc."mbpfan.conf".source ];
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${cfg.package}/bin/mbpfan -f${verbose}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        PIDFile = "/run/mbpfan.pid";
+        Restart = "always";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mediatomb.nix b/nixpkgs/nixos/modules/services/misc/mediatomb.nix
new file mode 100644
index 000000000000..529f584a201e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mediatomb.nix
@@ -0,0 +1,288 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  gid = config.ids.gids.mediatomb;
+  cfg = config.services.mediatomb;
+
+  mtConf = pkgs.writeText "config.xml" ''
+  <?xml version="1.0" encoding="UTF-8"?>
+  <config version="2" xmlns="http://mediatomb.cc/config/2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://mediatomb.cc/config/2 http://mediatomb.cc/config/2.xsd">
+    <server>
+      <ui enabled="yes" show-tooltips="yes">
+        <accounts enabled="no" session-timeout="30">
+          <account user="mediatomb" password="mediatomb"/>
+        </accounts>
+      </ui>
+      <name>${cfg.serverName}</name>
+      <udn>uuid:${cfg.uuid}</udn>
+      <home>${cfg.dataDir}</home>
+      <webroot>${pkgs.mediatomb}/share/mediatomb/web</webroot>
+      <storage>
+        <sqlite3 enabled="yes">
+          <database-file>mediatomb.db</database-file>
+        </sqlite3>
+      </storage>
+      <protocolInfo extend="${if cfg.ps3Support then "yes" else "no"}"/>
+      ${if cfg.dsmSupport then ''
+      <custom-http-headers>
+        <add header="X-User-Agent: redsonic"/>
+      </custom-http-headers>
+
+      <manufacturerURL>redsonic.com</manufacturerURL>
+      <modelNumber>105</modelNumber>
+      '' else ""}
+      ${if cfg.tg100Support then ''
+      <upnp-string-limit>101</upnp-string-limit>
+      '' else ""}
+      <extended-runtime-options>
+        <mark-played-items enabled="yes" suppress-cds-updates="yes">
+          <string mode="prepend">*</string>
+          <mark>
+            <content>video</content>
+          </mark>
+        </mark-played-items>
+      </extended-runtime-options>
+    </server>
+    <import hidden-files="no">
+      <scripting script-charset="UTF-8">
+        <common-script>${pkgs.mediatomb}/share/mediatomb/js/common.js</common-script>
+        <playlist-script>${pkgs.mediatomb}/share/mediatomb/js/playlists.js</playlist-script>
+        <virtual-layout type="builtin">
+          <import-script>${pkgs.mediatomb}/share/mediatomb/js/import.js</import-script>
+        </virtual-layout>
+      </scripting>
+      <mappings>
+        <extension-mimetype ignore-unknown="no">
+          <map from="mp3" to="audio/mpeg"/>
+          <map from="ogx" to="application/ogg"/>
+          <map from="ogv" to="video/ogg"/>
+          <map from="oga" to="audio/ogg"/>
+          <map from="ogg" to="audio/ogg"/>
+          <map from="ogm" to="video/ogg"/>
+          <map from="asf" to="video/x-ms-asf"/>
+          <map from="asx" to="video/x-ms-asf"/>
+          <map from="wma" to="audio/x-ms-wma"/>
+          <map from="wax" to="audio/x-ms-wax"/>
+          <map from="wmv" to="video/x-ms-wmv"/>
+          <map from="wvx" to="video/x-ms-wvx"/>
+          <map from="wm" to="video/x-ms-wm"/>
+          <map from="wmx" to="video/x-ms-wmx"/>
+          <map from="m3u" to="audio/x-mpegurl"/>
+          <map from="pls" to="audio/x-scpls"/>
+          <map from="flv" to="video/x-flv"/>
+          <map from="mkv" to="video/x-matroska"/>
+          <map from="mka" to="audio/x-matroska"/>
+          ${if cfg.ps3Support then ''
+          <map from="avi" to="video/divx"/>
+          '' else ""}
+          ${if cfg.dsmSupport then ''
+          <map from="avi" to="video/avi"/>
+          '' else ""}
+        </extension-mimetype>
+        <mimetype-upnpclass>
+          <map from="audio/*" to="object.item.audioItem.musicTrack"/>
+          <map from="video/*" to="object.item.videoItem"/>
+          <map from="image/*" to="object.item.imageItem"/>
+        </mimetype-upnpclass>
+        <mimetype-contenttype>
+          <treat mimetype="audio/mpeg" as="mp3"/>
+          <treat mimetype="application/ogg" as="ogg"/>
+          <treat mimetype="audio/ogg" as="ogg"/>
+          <treat mimetype="audio/x-flac" as="flac"/>
+          <treat mimetype="audio/x-ms-wma" as="wma"/>
+          <treat mimetype="audio/x-wavpack" as="wv"/>
+          <treat mimetype="image/jpeg" as="jpg"/>
+          <treat mimetype="audio/x-mpegurl" as="playlist"/>
+          <treat mimetype="audio/x-scpls" as="playlist"/>
+          <treat mimetype="audio/x-wav" as="pcm"/>
+          <treat mimetype="audio/L16" as="pcm"/>
+          <treat mimetype="video/x-msvideo" as="avi"/>
+          <treat mimetype="video/mp4" as="mp4"/>
+          <treat mimetype="audio/mp4" as="mp4"/>
+          <treat mimetype="application/x-iso9660" as="dvd"/>
+          <treat mimetype="application/x-iso9660-image" as="dvd"/>
+        </mimetype-contenttype>
+      </mappings>
+      <online-content>
+        <YouTube enabled="no" refresh="28800" update-at-start="no" purge-after="604800" racy-content="exclude" format="mp4" hd="no">
+          <favorites user="mediatomb"/>
+          <standardfeed feed="most_viewed" time-range="today"/>
+          <playlists user="mediatomb"/>
+          <uploads user="mediatomb"/>
+          <standardfeed feed="recently_featured" time-range="today"/>
+        </YouTube>
+      </online-content>
+    </import>
+    <transcoding enabled="${if cfg.transcoding then "yes" else "no"}">
+      <mimetype-profile-mappings>
+        <transcode mimetype="video/x-flv" using="vlcmpeg"/>
+        <transcode mimetype="application/ogg" using="vlcmpeg"/>
+        <transcode mimetype="application/ogg" using="oggflac2raw"/>
+        <transcode mimetype="audio/x-flac" using="oggflac2raw"/>
+      </mimetype-profile-mappings>
+      <profiles>
+        <profile name="oggflac2raw" enabled="no" type="external">
+          <mimetype>audio/L16</mimetype>
+          <accept-url>no</accept-url>
+          <first-resource>yes</first-resource>
+          <accept-ogg-theora>no</accept-ogg-theora>
+          <agent command="ogg123" arguments="-d raw -o byteorder:big -f %out %in"/>
+          <buffer size="1048576" chunk-size="131072" fill-size="262144"/>
+        </profile>
+        <profile name="vlcmpeg" enabled="no" type="external">
+          <mimetype>video/mpeg</mimetype>
+          <accept-url>yes</accept-url>
+          <first-resource>yes</first-resource>
+          <accept-ogg-theora>yes</accept-ogg-theora>
+          <agent command="vlc" arguments="-I dummy %in --sout #transcode{venc=ffmpeg,vcodec=mp2v,vb=4096,fps=25,aenc=ffmpeg,acodec=mpga,ab=192,samplerate=44100,channels=2}:standard{access=file,mux=ps,dst=%out} vlc:quit"/>
+          <buffer size="14400000" chunk-size="512000" fill-size="120000"/>
+        </profile>
+      </profiles>
+    </transcoding>
+  </config>
+  '';
+
+in {
+
+
+  ###### interface
+
+  options = {
+
+    services.mediatomb = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the mediatomb DLNA server.
+        '';
+      };
+
+      serverName = mkOption {
+        type = types.str;
+        default = "mediatomb";
+        description = ''
+          How to identify the server on the network.
+        '';
+      };
+
+      ps3Support = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable ps3 specific tweaks.
+          WARNING: incompatible with DSM 320 support.
+        '';
+      };
+
+      dsmSupport = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable D-Link DSM 320 specific tweaks.
+          WARNING: incompatible with ps3 support.
+        '';
+      };
+
+      tg100Support = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Telegent TG100 specific tweaks.
+        '';
+      };
+
+      transcoding = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable transcoding.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/mediatomb";
+        description = ''
+          The directory where mediatomb stores its state, data, etc.
+        '';
+      };
+
+      user = mkOption {
+        default = "mediatomb";
+        description = "User account under which mediatomb runs.";
+      };
+
+      group = mkOption {
+        default = "mediatomb";
+        description = "Group account under which mediatomb runs.";
+      };
+
+      port = mkOption {
+        default = 49152;
+        description = ''
+          The network port to listen on.
+        '';
+      };
+
+      interface = mkOption {
+        default = "";
+        description = ''
+          A specific interface to bind to.
+        '';
+      };
+
+      uuid = mkOption {
+        default = "fdfc8a4e-a3ad-4c1d-b43d-a2eedb03a687";
+        description = ''
+          A unique (on your network) to identify the server by.
+        '';
+      };
+
+      customCfg = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Allow mediatomb to create and use its own config file inside ${cfg.dataDir}.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.mediatomb = {
+      description = "MediaTomb media Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.mediatomb ];
+      serviceConfig.ExecStart = "${pkgs.mediatomb}/bin/mediatomb -p ${toString cfg.port} ${if cfg.interface!="" then "-e ${cfg.interface}" else ""} ${if cfg.customCfg then "" else "-c ${mtConf}"} -m ${cfg.dataDir}";
+      serviceConfig.User = "${cfg.user}";
+    };
+
+    users.groups = optionalAttrs (cfg.group == "mediatomb") {
+      mediatomb.gid = gid;
+    };
+
+    users.users = optionalAttrs (cfg.user == "mediatomb") {
+      mediatomb = {
+        isSystemUser = true;
+        group = cfg.group;
+        home = "${cfg.dataDir}";
+        createHome = true;
+        description = "Mediatomb DLNA Server User";
+      };
+    };
+
+    networking.firewall = {
+      allowedUDPPorts = [ 1900 cfg.port ];
+      allowedTCPPorts = [ cfg.port ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mesos-master.nix b/nixpkgs/nixos/modules/services/misc/mesos-master.nix
new file mode 100644
index 000000000000..572a9847e46c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mesos-master.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mesos.master;
+
+in {
+
+  options.services.mesos = {
+
+    master = {
+      enable = mkOption {
+        description = "Whether to enable the Mesos Master.";
+        default = false;
+        type = types.bool;
+      };
+
+      ip = mkOption {
+        description = "IP address to listen on.";
+        default = "0.0.0.0";
+        type = types.str;
+      };
+
+      port = mkOption {
+        description = "Mesos Master port";
+        default = 5050;
+        type = types.int;
+      };
+
+      advertiseIp = mkOption {
+        description = "IP address advertised to reach this master.";
+        default = null;
+        type = types.nullOr types.str;
+      };
+
+      advertisePort = mkOption {
+        description = "Port advertised to reach this Mesos master.";
+        default = null;
+        type = types.nullOr types.int;
+      };
+
+      zk = mkOption {
+        description = ''
+          ZooKeeper URL (used for leader election amongst masters).
+          May be one of:
+            zk://host1:port1,host2:port2,.../mesos
+            zk://username:password@host1:port1,host2:port2,.../mesos
+        '';
+        type = types.str;
+      };
+
+      workDir = mkOption {
+        description = "The Mesos work directory.";
+        default = "/var/lib/mesos/master";
+        type = types.str;
+      };
+
+      extraCmdLineOptions = mkOption {
+        description = ''
+          Extra command line options for Mesos Master.
+
+          See https://mesos.apache.org/documentation/latest/configuration/
+        '';
+        default = [ "" ];
+        type = types.listOf types.str;
+        example = [ "--credentials=VALUE" ];
+      };
+
+      quorum = mkOption {
+        description = ''
+          The size of the quorum of replicas when using 'replicated_log' based
+          registry. It is imperative to set this value to be a majority of
+          masters i.e., quorum > (number of masters)/2.
+
+          If 0 will fall back to --registry=in_memory.
+        '';
+        default = 0;
+        type = types.int;
+      };
+
+      logLevel = mkOption {
+        description = ''
+          The logging level used. Possible values:
+            'INFO', 'WARNING', 'ERROR'
+        '';
+        default = "INFO";
+        type = types.str;
+      };
+
+    };
+
+
+  };
+
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.workDir}' 0700 - - - -"
+    ];
+    systemd.services.mesos-master = {
+      description = "Mesos Master";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.mesos}/bin/mesos-master \
+            --ip=${cfg.ip} \
+            --port=${toString cfg.port} \
+            ${optionalString (cfg.advertiseIp != null) "--advertise_ip=${cfg.advertiseIp}"} \
+            ${optionalString (cfg.advertisePort  != null) "--advertise_port=${toString cfg.advertisePort}"} \
+            ${if cfg.quorum == 0
+              then "--registry=in_memory"
+              else "--zk=${cfg.zk} --registry=replicated_log --quorum=${toString cfg.quorum}"} \
+            --work_dir=${cfg.workDir} \
+            --logging_level=${cfg.logLevel} \
+            ${toString cfg.extraCmdLineOptions}
+        '';
+        Restart = "on-failure";
+      };
+    };
+  };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/misc/mesos-slave.nix b/nixpkgs/nixos/modules/services/misc/mesos-slave.nix
new file mode 100644
index 000000000000..170065d0065e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mesos-slave.nix
@@ -0,0 +1,220 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mesos.slave;
+
+  mkAttributes =
+    attrs: concatStringsSep ";" (mapAttrsToList
+                                   (k: v: "${k}:${v}")
+                                   (filterAttrs (k: v: v != null) attrs));
+  attribsArg = optionalString (cfg.attributes != {})
+                              "--attributes=${mkAttributes cfg.attributes}";
+
+  containerizersArg = concatStringsSep "," (
+    lib.unique (
+      cfg.containerizers ++ (optional cfg.withDocker "docker")
+    )
+  );
+
+  imageProvidersArg = concatStringsSep "," (
+    lib.unique (
+      cfg.imageProviders ++ (optional cfg.withDocker "docker")
+    )
+  );
+
+  isolationArg = concatStringsSep "," (
+    lib.unique (
+      cfg.isolation ++ (optionals cfg.withDocker [ "filesystem/linux" "docker/runtime"])
+    )
+  );
+
+in {
+
+  options.services.mesos = {
+    slave = {
+      enable = mkOption {
+        description = "Whether to enable the Mesos Slave.";
+        default = false;
+        type = types.bool;
+      };
+
+      ip = mkOption {
+        description = "IP address to listen on.";
+        default = "0.0.0.0";
+        type = types.str;
+      };
+
+      port = mkOption {
+        description = "Port to listen on.";
+        default = 5051;
+        type = types.int;
+      };
+
+      advertiseIp = mkOption {
+        description = "IP address advertised to reach this agent.";
+        default = null;
+        type = types.nullOr types.str;
+      };
+
+      advertisePort = mkOption {
+        description = "Port advertised to reach this agent.";
+        default = null;
+        type = types.nullOr types.int;
+      };
+
+      containerizers = mkOption {
+        description = ''
+          List of containerizer implementations to compose in order to provide
+          containerization. Available options are mesos and docker.
+          The order the containerizers are specified is the order they are tried.
+        '';
+        default = [ "mesos" ];
+        type = types.listOf types.str;
+      };
+
+      imageProviders = mkOption {
+        description = "List of supported image providers, e.g., APPC,DOCKER.";
+        default = [ ];
+        type = types.listOf types.str;
+      };
+
+      imageProvisionerBackend = mkOption {
+        description = ''
+          Strategy for provisioning container rootfs from images,
+          e.g., aufs, bind, copy, overlay.
+        '';
+        default = "copy";
+        type = types.str;
+      };
+
+      isolation = mkOption {
+        description = ''
+          Isolation mechanisms to use, e.g., posix/cpu,posix/mem, or
+          cgroups/cpu,cgroups/mem, or network/port_mapping, or `gpu/nvidia` for nvidia
+          specific gpu isolation.
+        '';
+        default = [ "posix/cpu" "posix/mem" ];
+        type = types.listOf types.str;
+      };
+
+      master = mkOption {
+        description = ''
+          May be one of:
+            zk://host1:port1,host2:port2,.../path
+            zk://username:password@host1:port1,host2:port2,.../path
+        '';
+        type = types.str;
+      };
+
+      withHadoop = mkOption {
+        description = "Add the HADOOP_HOME to the slave.";
+        default = false;
+        type = types.bool;
+      };
+
+      withDocker = mkOption {
+        description = "Enable the docker containerizer.";
+        default = config.virtualisation.docker.enable;
+        type = types.bool;
+      };
+
+      dockerRegistry = mkOption {
+        description = ''
+          The default url for pulling Docker images.
+          It could either be a Docker registry server url,
+          or a local path in which Docker image archives are stored.
+        '';
+        default = null;
+        type = types.nullOr (types.either types.str types.path);
+      };
+
+      workDir = mkOption {
+        description = "The Mesos work directory.";
+        default = "/var/lib/mesos/slave";
+        type = types.str;
+      };
+
+      extraCmdLineOptions = mkOption {
+        description = ''
+          Extra command line options for Mesos Slave.
+
+          See https://mesos.apache.org/documentation/latest/configuration/
+        '';
+        default = [ "" ];
+        type = types.listOf types.str;
+        example = [ "--gc_delay=3days" ];
+      };
+
+      logLevel = mkOption {
+        description = ''
+          The logging level used. Possible values:
+            'INFO', 'WARNING', 'ERROR'
+        '';
+        default = "INFO";
+        type = types.str;
+      };
+
+      attributes = mkOption {
+        description = ''
+          Machine attributes for the slave instance.
+
+          Use caution when changing this; you may need to manually reset slave
+          metadata before the slave can re-register.
+        '';
+        default = {};
+        type = types.attrsOf types.str;
+        example = { rack = "aa";
+                    host = "aabc123";
+                    os = "nixos"; };
+      };
+
+      executorEnvironmentVariables = mkOption {
+        description = ''
+          The environment variables that should be passed to the executor, and thus subsequently task(s).
+        '';
+        default = {
+          PATH = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
+        };
+        type = types.attrsOf types.str;
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.workDir}' 0701 - - - -"
+    ];
+    systemd.services.mesos-slave = {
+      description = "Mesos Slave";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ] ++ optionals cfg.withDocker [ "docker.service" ] ;
+      path = [ pkgs.runtimeShellPackage ];
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.mesos}/bin/mesos-slave \
+            --containerizers=${containerizersArg} \
+            --image_providers=${imageProvidersArg} \
+            --image_provisioner_backend=${cfg.imageProvisionerBackend} \
+            --isolation=${isolationArg} \
+            --ip=${cfg.ip} \
+            --port=${toString cfg.port} \
+            ${optionalString (cfg.advertiseIp != null) "--advertise_ip=${cfg.advertiseIp}"} \
+            ${optionalString (cfg.advertisePort  != null) "--advertise_port=${toString cfg.advertisePort}"} \
+            --master=${cfg.master} \
+            --work_dir=${cfg.workDir} \
+            --logging_level=${cfg.logLevel} \
+            ${attribsArg} \
+            ${optionalString cfg.withHadoop "--hadoop-home=${pkgs.hadoop}"} \
+            ${optionalString cfg.withDocker "--docker=${pkgs.docker}/libexec/docker/docker"} \
+            ${optionalString (cfg.dockerRegistry != null) "--docker_registry=${cfg.dockerRegistry}"} \
+            --executor_environment_variables=${lib.escapeShellArg (builtins.toJSON cfg.executorEnvironmentVariables)} \
+            ${toString cfg.extraCmdLineOptions}
+        '';
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/metabase.nix b/nixpkgs/nixos/modules/services/misc/metabase.nix
new file mode 100644
index 000000000000..e78100a046a2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/metabase.nix
@@ -0,0 +1,103 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.metabase;
+
+  inherit (lib) mkEnableOption mkIf mkOption;
+  inherit (lib) optional optionalAttrs types;
+
+  dataDir = "/var/lib/metabase";
+
+in {
+
+  options = {
+
+    services.metabase = {
+      enable = mkEnableOption "Metabase service";
+
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            IP address that Metabase should listen on.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 3000;
+          description = ''
+            Listen port for Metabase.
+          '';
+        };
+      };
+
+      ssl = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to enable SSL (https) support.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 8443;
+          description = ''
+            Listen port over SSL (https) for Metabase.
+          '';
+        };
+
+        keystore = mkOption {
+          type = types.nullOr types.path;
+          default = "${dataDir}/metabase.jks";
+          example = "/etc/secrets/keystore.jks";
+          description = ''
+            <link xlink:href="https://www.digitalocean.com/community/tutorials/java-keytool-essentials-working-with-java-keystores">Java KeyStore</link> file containing the certificates.
+          '';
+        };
+
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for Metabase.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.metabase = {
+      description = "Metabase server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      environment = {
+        MB_PLUGINS_DIR = "${dataDir}/plugins";
+        MB_DB_FILE = "${dataDir}/metabase.db";
+        MB_JETTY_HOST = cfg.listen.ip;
+        MB_JETTY_PORT = toString cfg.listen.port;
+      } // optionalAttrs (cfg.ssl.enable) {
+        MB_JETTY_SSL = true;
+        MB_JETTY_SSL_PORT = toString cfg.ssl.port;
+        MB_JETTY_SSL_KEYSTORE = cfg.ssl.keystore;
+      };
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = baseNameOf dataDir;
+        ExecStart = "${pkgs.metabase}/bin/metabase";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ] ++ optional cfg.ssl.enable cfg.ssl.port;
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/mwlib.nix b/nixpkgs/nixos/modules/services/misc/mwlib.nix
new file mode 100644
index 000000000000..6b41b552a86d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/mwlib.nix
@@ -0,0 +1,258 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mwlib;
+  pypkgs = pkgs.python27Packages;
+
+  inherit (pypkgs) python mwlib;
+
+  user = mkOption {
+    default = "nobody";
+    type = types.str;
+    description = "User to run as.";
+  };
+
+in
+{
+
+  options.services.mwlib = {
+
+    nserve = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to enable nserve. Nserve is a HTTP
+          server.  The Collection extension is talking to
+          that program directly.  Nserve uses at least
+          one qserve instance in order to distribute
+          and manage jobs.
+        '';
+      }; # nserve.enable
+
+      port = mkOption {
+        default = 8899;
+        type = types.int;
+        description = "Specify port to listen on.";
+      }; # nserve.port
+
+      address = mkOption {
+        default = "127.0.0.1";
+        type = types.str;
+        description = "Specify network interface to listen on.";
+      }; # nserve.address
+
+      qserve = mkOption {
+        default = [ "${cfg.qserve.address}:${toString cfg.qserve.port}" ];
+        type = types.listOf types.str;
+        description = "Register qserve instance.";
+      }; # nserve.qserve
+
+      inherit user;
+    }; # nserve
+
+    qserve = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          A job queue server used to distribute and manage
+          jobs. You should start one qserve instance
+          for each machine that is supposed to render pdf
+          files. Unless you’re operating the Wikipedia
+          installation, one machine should suffice.
+        '';
+      }; # qserve.enable
+
+      port = mkOption {
+        default = 14311;
+        type = types.int;
+        description = "Specify port to listen on.";
+      }; # qserve.port
+
+      address = mkOption {
+        default = "127.0.0.1";
+        type = types.str;
+        description = "Specify network interface to listen on.";
+      }; # qserve.address
+
+      datadir = mkOption {
+        default = "/var/lib/mwlib-qserve";
+        type = types.path;
+        description = "qserve data directory (FIXME: unused?)";
+      }; # qserve.datadir
+
+      allow = mkOption {
+        default = [ "127.0.0.1" ];
+        type = types.listOf types.str;
+        description = "List of allowed client IPs. Empty means any.";
+      }; # qserve.allow
+
+      inherit user;
+    }; # qserve
+
+    nslave = {
+      enable = mkOption {
+        default = cfg.qserve.enable;
+        type = types.bool;
+        description = ''
+          Pulls new jobs from exactly one qserve instance
+          and calls the zip and render programs
+          in order to download article collections and
+          convert them to different output formats. Nslave
+          uses a cache directory to store the generated
+          documents. Nslave also starts an internal http
+          server serving the content of the cache directory.
+        '';
+      }; # nslave.enable
+
+      cachedir = mkOption {
+        default = "/var/cache/mwlib-nslave";
+        type = types.path;
+        description = "Directory to store generated documents.";
+      }; # nslave.cachedir
+
+      numprocs = mkOption {
+        default = 10;
+        type = types.int;
+        description = "Number of parallel jobs to be executed.";
+      }; # nslave.numprocs
+
+      http = mkOption {
+        default = {};
+        description = ''
+          Internal http server serving the content of the cache directory.
+          You have to enable it, or use your own way for serving files
+          and set the http.url option accordingly.
+          '';
+        type = types.submodule ({
+          options = {
+            enable = mkOption {
+              default = true;
+              type = types.bool;
+              description = "Enable internal http server.";
+            }; # nslave.http.enable
+
+            port = mkOption {
+              default = 8898;
+              type = types.int;
+              description = "Port to listen to when serving files from cache.";
+            }; # nslave.http.port
+
+            address = mkOption {
+              default = "127.0.0.1";
+              type = types.str;
+              description = "Specify network interface to listen on.";
+            }; # nslave.http.address
+
+            url = mkOption {
+              default = "http://localhost:${toString cfg.nslave.http.port}/cache";
+              type = types.str;
+              description = ''
+                Specify URL for accessing generated files from cache.
+                The Collection extension of Mediawiki won't be able to
+                download files without it.
+                '';
+            }; # nslave.http.url
+          };
+        }); # types.submodule
+      }; # nslave.http
+
+      inherit user;
+    }; # nslave
+
+  }; # options.services
+
+  config = {
+
+    systemd.services.mwlib-nserve = mkIf cfg.nserve.enable
+    {
+      description = "mwlib network interface";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "mwlib-qserve.service" ];
+
+      serviceConfig = {
+        ExecStart = concatStringsSep " " (
+          [
+            "${mwlib}/bin/nserve"
+            "--port ${toString cfg.nserve.port}"
+            "--interface ${cfg.nserve.address}"
+          ] ++ cfg.nserve.qserve
+        );
+        User = cfg.nserve.user;
+      };
+    }; # systemd.services.mwlib-nserve
+
+    systemd.services.mwlib-qserve = mkIf cfg.qserve.enable
+    {
+      description = "mwlib job queue server";
+
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        mkdir -pv '${cfg.qserve.datadir}'
+        chown -Rc ${cfg.qserve.user}:`id -ng ${cfg.qserve.user}` '${cfg.qserve.datadir}'
+        chmod -Rc u=rwX,go= '${cfg.qserve.datadir}'
+      '';
+
+      serviceConfig = {
+        ExecStart = concatStringsSep " " (
+          [
+            "${mwlib}/bin/mw-qserve"
+            "-p ${toString cfg.qserve.port}"
+            "-i ${cfg.qserve.address}"
+            "-d ${cfg.qserve.datadir}"
+          ] ++ map (a: "-a ${a}") cfg.qserve.allow
+        );
+        User = cfg.qserve.user;
+        PermissionsStartOnly = true;
+      };
+    }; # systemd.services.mwlib-qserve
+
+    systemd.services.mwlib-nslave = mkIf cfg.nslave.enable
+    {
+      description = "mwlib worker";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        mkdir -pv '${cfg.nslave.cachedir}'
+        chown -Rc ${cfg.nslave.user}:`id -ng ${cfg.nslave.user}` '${cfg.nslave.cachedir}'
+        chmod -Rc u=rwX,go= '${cfg.nslave.cachedir}'
+      '';
+
+      path = with pkgs; [ imagemagick pdftk ];
+      environment = {
+        PYTHONPATH = concatMapStringsSep ":"
+          (m: "${pypkgs.${m}}/lib/${python.libPrefix}/site-packages")
+          [ "mwlib-rl" "mwlib-ext" "pygments" "pyfribidi" ];
+      };
+
+      serviceConfig = {
+        ExecStart = concatStringsSep " " (
+          [
+            "${mwlib}/bin/nslave"
+            "--cachedir ${cfg.nslave.cachedir}"
+            "--numprocs ${toString cfg.nslave.numprocs}"
+            "--url ${cfg.nslave.http.url}"
+          ] ++ (
+            if cfg.nslave.http.enable then
+            [
+              "--serve-files-port ${toString cfg.nslave.http.port}"
+              "--serve-files-address ${cfg.nslave.http.address}"
+            ] else
+            [
+              "--no-serve-files"
+            ]
+          ));
+        User = cfg.nslave.user;
+        PermissionsStartOnly = true;
+      };
+    }; # systemd.services.mwlib-nslave
+
+  }; # config
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nix-daemon.nix b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
new file mode 100644
index 000000000000..0b3d7f3f03c3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nix-daemon.nix
@@ -0,0 +1,544 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.nix;
+
+  nix = cfg.package.out;
+
+  nixVersion = getVersion nix;
+
+  isNix23 = versionAtLeast nixVersion "2.3pre";
+
+  makeNixBuildUser = nr: {
+    name  = "nixbld${toString nr}";
+    value = {
+      description = "Nix build user ${toString nr}";
+
+      /* For consistency with the setgid(2), setuid(2), and setgroups(2)
+         calls in `libstore/build.cc', don't add any supplementary group
+         here except "nixbld".  */
+      uid = builtins.add config.ids.uids.nixbld nr;
+      group = "nixbld";
+      extraGroups = [ "nixbld" ];
+    };
+  };
+
+  nixbldUsers = listToAttrs (map makeNixBuildUser (range 1 cfg.nrBuildUsers));
+
+  nixConf =
+    assert versionAtLeast nixVersion "2.2";
+    pkgs.runCommand "nix.conf" { preferLocalBuild = true; extraOptions = cfg.extraOptions; } (
+      ''
+        cat > $out <<END
+        # WARNING: this file is generated from the nix.* options in
+        # your NixOS configuration, typically
+        # /etc/nixos/configuration.nix.  Do not edit it!
+        build-users-group = nixbld
+        max-jobs = ${toString (cfg.maxJobs)}
+        cores = ${toString (cfg.buildCores)}
+        sandbox = ${if (builtins.isBool cfg.useSandbox) then boolToString cfg.useSandbox else cfg.useSandbox}
+        extra-sandbox-paths = ${toString cfg.sandboxPaths}
+        substituters = ${toString cfg.binaryCaches}
+        trusted-substituters = ${toString cfg.trustedBinaryCaches}
+        trusted-public-keys = ${toString cfg.binaryCachePublicKeys}
+        auto-optimise-store = ${boolToString cfg.autoOptimiseStore}
+        require-sigs = ${if cfg.requireSignedBinaryCaches then "true" else "false"}
+        trusted-users = ${toString cfg.trustedUsers}
+        allowed-users = ${toString cfg.allowedUsers}
+        ${optionalString (!cfg.distributedBuilds) ''
+          builders =
+        ''}
+        system-features = ${toString cfg.systemFeatures}
+        ${optionalString isNix23 ''
+          sandbox-fallback = false
+        ''}
+        $extraOptions
+        END
+      '' + optionalString cfg.checkConfig (
+            if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then ''
+              echo "Ignore nix.checkConfig when cross-compiling"
+            '' else ''
+              echo "Checking that Nix can read nix.conf..."
+              ln -s $out ./nix.conf
+              NIX_CONF_DIR=$PWD ${cfg.package}/bin/nix show-config ${optionalString isNix23 "--no-net --option experimental-features nix-command"} >/dev/null
+            '')
+      );
+
+in
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ])
+    (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ])
+  ];
+
+  ###### interface
+
+  options = {
+
+    nix = {
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.nix;
+        defaultText = "pkgs.nix";
+        description = ''
+          This option specifies the Nix package instance to use throughout the system.
+        '';
+      };
+
+      maxJobs = mkOption {
+        type = types.either types.int (types.enum ["auto"]);
+        default = "auto";
+        example = 64;
+        description = ''
+          This option defines the maximum number of jobs that Nix will try to
+          build in parallel. The default is auto, which means it will use all
+          available logical cores. It is recommend to set it to the total
+          number of logical cores in your system (e.g., 16 for two CPUs with 4
+          cores each and hyper-threading).
+        '';
+      };
+
+      autoOptimiseStore = mkOption {
+        type = types.bool;
+        default = false;
+        example = true;
+        description = ''
+         If set to true, Nix automatically detects files in the store that have
+         identical contents, and replaces them with hard links to a single copy.
+         This saves disk space. If set to false (the default), you can still run
+         nix-store --optimise to get rid of duplicate files.
+        '';
+      };
+
+      buildCores = mkOption {
+        type = types.int;
+        default = 0;
+        example = 64;
+        description = ''
+          This option defines the maximum number of concurrent tasks during
+          one build. It affects, e.g., -j option for make.
+          The special value 0 means that the builder should use all
+          available CPU cores in the system. Some builds may become
+          non-deterministic with this option; use with care! Packages will
+          only be affected if enableParallelBuilding is set for them.
+        '';
+      };
+
+      useSandbox = mkOption {
+        type = types.either types.bool (types.enum ["relaxed"]);
+        default = true;
+        description = "
+          If set, Nix will perform builds in a sandboxed environment that it
+          will set up automatically for each build. This prevents impurities
+          in builds by disallowing access to dependencies outside of the Nix
+          store by using network and mount namespaces in a chroot environment.
+          This is enabled by default even though it has a possible performance
+          impact due to the initial setup time of a sandbox for each build. It
+          doesn't affect derivation hashes, so changing this option will not
+          trigger a rebuild of packages.
+        ";
+      };
+
+      sandboxPaths = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "/dev" "/proc" ];
+        description =
+          ''
+            Directories from the host filesystem to be included
+            in the sandbox.
+          '';
+      };
+
+      extraOptions = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          keep-outputs = true
+          keep-derivations = true
+        '';
+        description = "Additional text appended to <filename>nix.conf</filename>.";
+      };
+
+      distributedBuilds = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to distribute builds to the machines listed in
+          <option>nix.buildMachines</option>.
+        '';
+      };
+
+      daemonNiceLevel = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Nix daemon process priority. This priority propagates to build processes.
+          0 is the default Unix process priority, 19 is the lowest.
+        '';
+      };
+
+      daemonIONiceLevel = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Nix daemon process I/O priority. This priority propagates to build processes.
+          0 is the default Unix process I/O priority, 7 is the lowest.
+        '';
+      };
+
+      buildMachines = mkOption {
+        type = types.listOf types.attrs;
+        default = [];
+        example = literalExample ''
+          [ { hostName = "voila.labs.cs.uu.nl";
+              sshUser = "nix";
+              sshKey = "/root/.ssh/id_buildfarm";
+              system = "powerpc-darwin";
+              maxJobs = 1;
+            }
+            { hostName = "linux64.example.org";
+              sshUser = "buildfarm";
+              sshKey = "/root/.ssh/id_buildfarm";
+              system = "x86_64-linux";
+              maxJobs = 2;
+              speedFactor = 2;
+              supportedFeatures = [ "kvm" ];
+              mandatoryFeatures = [ "perf" ];
+            }
+          ]
+        '';
+        description = ''
+          This option lists the machines to be used if distributed
+          builds are enabled (see
+          <option>nix.distributedBuilds</option>).  Nix will perform
+          derivations on those machines via SSH by copying the inputs
+          to the Nix store on the remote machine, starting the build,
+          then copying the output back to the local Nix store.  Each
+          element of the list should be an attribute set containing
+          the machine's host name (<varname>hostname</varname>), the
+          user name to be used for the SSH connection
+          (<varname>sshUser</varname>), the Nix system type
+          (<varname>system</varname>, e.g.,
+          <literal>"i686-linux"</literal>), the maximum number of
+          jobs to be run in parallel on that machine
+          (<varname>maxJobs</varname>), the path to the SSH private
+          key to be used to connect (<varname>sshKey</varname>), a
+          list of supported features of the machine
+          (<varname>supportedFeatures</varname>) and a list of
+          mandatory features of the machine
+          (<varname>mandatoryFeatures</varname>). The SSH private key
+          should not have a passphrase, and the corresponding public
+          key should be added to
+          <filename>~<replaceable>sshUser</replaceable>/authorized_keys</filename>
+          on the remote machine.
+        '';
+      };
+
+      # Environment variables for running Nix.
+      envVars = mkOption {
+        type = types.attrs;
+        internal = true;
+        default = {};
+        description = "Environment variables used by Nix.";
+      };
+
+      nrBuildUsers = mkOption {
+        type = types.int;
+        description = ''
+          Number of <literal>nixbld</literal> user accounts created to
+          perform secure concurrent builds.  If you receive an error
+          message saying that “all build users are currently in use”,
+          you should increase this value.
+        '';
+      };
+
+      readOnlyStore = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If set, NixOS will enforce the immutability of the Nix store
+          by making <filename>/nix/store</filename> a read-only bind
+          mount.  Nix will automatically make the store writable when
+          needed.
+        '';
+      };
+
+      binaryCaches = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          List of binary cache URLs used to obtain pre-built binaries
+          of Nix packages.
+
+          By default https://cache.nixos.org/ is added,
+          to override it use <literal>lib.mkForce []</literal>.
+        '';
+      };
+
+      trustedBinaryCaches = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "https://hydra.nixos.org/" ];
+        description = ''
+          List of binary cache URLs that non-root users can use (in
+          addition to those specified using
+          <option>nix.binaryCaches</option>) by passing
+          <literal>--option binary-caches</literal> to Nix commands.
+        '';
+      };
+
+      requireSignedBinaryCaches = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If enabled (the default), Nix will only download binaries from binary caches if
+          they are cryptographically signed with any of the keys listed in
+          <option>nix.binaryCachePublicKeys</option>. If disabled, signatures are neither
+          required nor checked, so it's strongly recommended that you use only
+          trustworthy caches and https to prevent man-in-the-middle attacks.
+        '';
+      };
+
+      binaryCachePublicKeys = mkOption {
+        type = types.listOf types.str;
+        example = [ "hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=" ];
+        description = ''
+          List of public keys used to sign binary caches. If
+          <option>nix.requireSignedBinaryCaches</option> is enabled,
+          then Nix will use a binary from a binary cache if and only
+          if it is signed by <emphasis>any</emphasis> of the keys
+          listed here. By default, only the key for
+          <uri>cache.nixos.org</uri> is included.
+        '';
+      };
+
+      trustedUsers = mkOption {
+        type = types.listOf types.str;
+        default = [ "root" ];
+        example = [ "root" "alice" "@wheel" ];
+        description = ''
+          A list of names of users that have additional rights when
+          connecting to the Nix daemon, such as the ability to specify
+          additional binary caches, or to import unsigned NARs. You
+          can also specify groups by prefixing them with
+          <literal>@</literal>; for instance,
+          <literal>@wheel</literal> means all users in the wheel
+          group.
+        '';
+      };
+
+      allowedUsers = mkOption {
+        type = types.listOf types.str;
+        default = [ "*" ];
+        example = [ "@wheel" "@builders" "alice" "bob" ];
+        description = ''
+          A list of names of users (separated by whitespace) that are
+          allowed to connect to the Nix daemon. As with
+          <option>nix.trustedUsers</option>, you can specify groups by
+          prefixing them with <literal>@</literal>. Also, you can
+          allow all users by specifying <literal>*</literal>. The
+          default is <literal>*</literal>. Note that trusted users are
+          always allowed to connect.
+        '';
+      };
+
+      nixPath = mkOption {
+        type = types.listOf types.str;
+        default =
+          [
+            "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
+            "nixos-config=/etc/nixos/configuration.nix"
+            "/nix/var/nix/profiles/per-user/root/channels"
+          ];
+        description = ''
+          The default Nix expression search path, used by the Nix
+          evaluator to look up paths enclosed in angle brackets
+          (e.g. <literal>&lt;nixpkgs&gt;</literal>).
+        '';
+      };
+
+      systemFeatures = mkOption {
+        type = types.listOf types.str;
+        example = [ "kvm" "big-parallel" "gccarch-skylake" ];
+        description = ''
+          The supported features of a machine
+        '';
+      };
+
+      checkConfig = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If enabled (the default), checks that Nix can parse the generated nix.conf.
+        '';
+      };
+
+      registry = mkOption {
+        type = types.attrsOf (types.submodule (
+          let
+            inputAttrs = types.attrsOf (types.oneOf [types.str types.int types.bool types.package]);
+          in
+          { config, name, ... }:
+          { options = {
+              from = mkOption {
+                type = inputAttrs;
+                example = { type = "indirect"; id = "nixpkgs"; };
+                description = "The flake reference to be rewritten.";
+              };
+              to = mkOption {
+                type = inputAttrs;
+                example = { type = "github"; owner = "my-org"; repo = "my-nixpkgs"; };
+                description = "The flake reference to which <option>from></option> is to be rewritten.";
+              };
+              flake = mkOption {
+                type = types.unspecified;
+                default = null;
+                example = literalExample "nixpkgs";
+                description = ''
+                  The flake input to which <option>from></option> is to be rewritten.
+                '';
+              };
+              exact = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  Whether the <option>from</option> reference needs to match exactly. If set,
+                  a <option>from</option> reference like <literal>nixpkgs</literal> does not
+                  match with a reference like <literal>nixpkgs/nixos-20.03</literal>.
+                '';
+              };
+            };
+            config = {
+              from = mkDefault { type = "indirect"; id = name; };
+              to = mkIf (config.flake != null)
+                ({ type = "path";
+                   path = config.flake.outPath;
+                 } // lib.filterAttrs
+                   (n: v: n == "lastModified" || n == "rev" || n == "revCount" || n == "narHash")
+                   config.flake);
+            };
+          }
+        ));
+        default = {};
+        description = ''
+          A system-wide flake registry.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = {
+
+    nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
+    nix.binaryCaches = [ "https://cache.nixos.org/" ];
+
+    environment.systemPackages =
+      [ nix
+        pkgs.nix-info
+      ]
+      ++ optional (config.programs.bash.enableCompletion && !versionAtLeast nixVersion "2.4pre") pkgs.nix-bash-completions;
+
+    environment.etc."nix/nix.conf".source = nixConf;
+
+    environment.etc."nix/registry.json".text = builtins.toJSON {
+      version = 2;
+      flakes = mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry;
+    };
+
+    # List of machines for distributed Nix builds in the format
+    # expected by build-remote.pl.
+    environment.etc."nix/machines" =
+      { enable = cfg.buildMachines != [];
+        text =
+          concatMapStrings (machine:
+            "${if machine ? sshUser then "${machine.sshUser}@" else ""}${machine.hostName} "
+            + machine.system or (concatStringsSep "," machine.systems)
+            + " ${machine.sshKey or "-"} ${toString machine.maxJobs or 1} "
+            + toString (machine.speedFactor or 1)
+            + " "
+            + concatStringsSep "," (machine.mandatoryFeatures or [] ++ machine.supportedFeatures or [])
+            + " "
+            + concatStringsSep "," machine.mandatoryFeatures or []
+            + "\n"
+          ) cfg.buildMachines;
+      };
+
+    systemd.packages = [ nix ];
+
+    systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
+
+    systemd.services.nix-daemon =
+      { path = [ nix pkgs.utillinux config.programs.ssh.package ]
+          ++ optionals cfg.distributedBuilds [ pkgs.gzip ];
+
+        environment = cfg.envVars
+          // { CURL_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; }
+          // config.networking.proxy.envVars;
+
+        unitConfig.RequiresMountsFor = "/nix/store";
+
+        serviceConfig =
+          { Nice = cfg.daemonNiceLevel;
+            IOSchedulingPriority = cfg.daemonIONiceLevel;
+            LimitNOFILE = 4096;
+          };
+
+        restartTriggers = [ nixConf ];
+      };
+
+    # Set up the environment variables for running Nix.
+    environment.sessionVariables = cfg.envVars //
+      { NIX_PATH = cfg.nixPath;
+      };
+
+    environment.extraInit =
+      ''
+        if [ -e "$HOME/.nix-defexpr/channels" ]; then
+          export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}"
+        fi
+      '';
+
+    nix.nrBuildUsers = mkDefault (lib.max 32 (if cfg.maxJobs == "auto" then 0 else cfg.maxJobs));
+
+    users.users = nixbldUsers;
+
+    services.xserver.displayManager.hiddenUsers = attrNames nixbldUsers;
+
+    system.activationScripts.nix = stringAfter [ "etc" "users" ]
+      ''
+        install -m 0755 -d /nix/var/nix/{gcroots,profiles}/per-user
+
+        # Subscribe the root user to the NixOS channel by default.
+        if [ ! -e "/root/.nix-channels" ]; then
+            echo "${config.system.defaultChannel} nixos" > "/root/.nix-channels"
+        fi
+      '';
+
+    nix.systemFeatures = mkDefault (
+      [ "nixos-test" "benchmark" "big-parallel" "kvm" ] ++
+      optionals (pkgs.stdenv.isx86_64 && pkgs.hostPlatform.platform ? gcc.arch) (
+        # a x86_64 builder can run code for `platform.gcc.arch` and minor architectures:
+        [ "gccarch-${pkgs.hostPlatform.platform.gcc.arch}" ] ++ {
+          sandybridge    = [ "gccarch-westmere" ];
+          ivybridge      = [ "gccarch-westmere" "gccarch-sandybridge" ];
+          haswell        = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" ];
+          broadwell      = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" "gccarch-haswell" ];
+          skylake        = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" "gccarch-haswell" "gccarch-broadwell" ];
+          skylake-avx512 = [ "gccarch-westmere" "gccarch-sandybridge" "gccarch-ivybridge" "gccarch-haswell" "gccarch-broadwell" "gccarch-skylake" ];
+        }.${pkgs.hostPlatform.platform.gcc.arch} or []
+      )
+    );
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nix-gc.nix b/nixpkgs/nixos/modules/services/misc/nix-gc.nix
new file mode 100644
index 000000000000..12bed05757ad
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nix-gc.nix
@@ -0,0 +1,61 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.nix.gc;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    nix.gc = {
+
+      automatic = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Automatically run the garbage collector at a specific time.";
+      };
+
+      dates = mkOption {
+        default = "03:15";
+        type = types.str;
+        description = ''
+          Specification (in the format described by
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>) of the time at
+          which the garbage collector will run.
+        '';
+      };
+
+      options = mkOption {
+        default = "";
+        example = "--max-freed $((64 * 1024**3))";
+        type = types.str;
+        description = ''
+          Options given to <filename>nix-collect-garbage</filename> when the
+          garbage collector is run automatically.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = {
+
+    systemd.services.nix-gc =
+      { description = "Nix Garbage Collector";
+        script = "exec ${config.nix.package.out}/bin/nix-collect-garbage ${cfg.options}";
+        startAt = optional cfg.automatic cfg.dates;
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nix-optimise.nix b/nixpkgs/nixos/modules/services/misc/nix-optimise.nix
new file mode 100644
index 000000000000..e02026d5f76c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nix-optimise.nix
@@ -0,0 +1,51 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.nix.optimise;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    nix.optimise = {
+
+      automatic = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Automatically run the nix store optimiser at a specific time.";
+      };
+
+      dates = mkOption {
+        default = ["03:45"];
+        type = types.listOf types.str;
+        description = ''
+          Specification (in the format described by
+          <citerefentry><refentrytitle>systemd.time</refentrytitle>
+          <manvolnum>7</manvolnum></citerefentry>) of the time at
+          which the optimiser will run.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = {
+
+    systemd.services.nix-optimise =
+      { description = "Nix Store Optimiser";
+        # No point this if the nix daemon (and thus the nix store) is outside
+        unitConfig.ConditionPathIsReadWrite = "/nix/var/nix/daemon-socket";
+        serviceConfig.ExecStart = "${config.nix.package}/bin/nix-store --optimise";
+        startAt = optionals cfg.automatic cfg.dates;
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix b/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix
new file mode 100644
index 000000000000..7ce3841be2f5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nix-ssh-serve.nix
@@ -0,0 +1,61 @@
+{ config, lib, ... }:
+
+with lib;
+let cfg = config.nix.sshServe;
+    command =
+      if cfg.protocol == "ssh"
+        then "nix-store --serve"
+      else "nix-daemon --stdio";
+in {
+  options = {
+
+    nix.sshServe = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable serving the Nix store as a remote store via SSH.";
+      };
+
+      keys = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "ssh-dss AAAAB3NzaC1k... alice@example.org" ];
+        description = "A list of SSH public keys allowed to access the binary cache via SSH.";
+      };
+
+      protocol = mkOption {
+        type = types.enum [ "ssh" "ssh-ng" ];
+        default = "ssh";
+        description = "The specific Nix-over-SSH protocol to use.";
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.nix-ssh = {
+      description = "Nix SSH store user";
+      uid = config.ids.uids.nix-ssh;
+      useDefaultShell = true;
+    };
+
+    services.openssh.enable = true;
+
+    services.openssh.extraConfig = ''
+      Match User nix-ssh
+        AllowAgentForwarding no
+        AllowTcpForwarding no
+        PermitTTY no
+        PermitTunnel no
+        X11Forwarding no
+        ForceCommand ${config.nix.package.out}/bin/${command}
+      Match All
+    '';
+
+    users.users.nix-ssh.openssh.authorizedKeys.keys = cfg.keys;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/novacomd.nix b/nixpkgs/nixos/modules/services/misc/novacomd.nix
new file mode 100644
index 000000000000..7cfc68d2b673
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/novacomd.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.novacomd;
+
+in {
+
+  options = {
+    services.novacomd = {
+      enable = mkEnableOption "Novacom service for connecting to WebOS devices";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.webos.novacom ];
+
+    systemd.services.novacomd = {
+      description = "Novacom WebOS daemon";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.webos.novacomd}/sbin/novacomd";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ dtzWill ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/nzbget.nix b/nixpkgs/nixos/modules/services/misc/nzbget.nix
new file mode 100644
index 000000000000..715ec891cd68
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/nzbget.nix
@@ -0,0 +1,99 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nzbget;
+  pkg = pkgs.nzbget;
+  stateDir = "/var/lib/nzbget";
+  configFile = "${stateDir}/nzbget.conf";
+  configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${value}") nixosOpts);
+
+  nixosOpts = {
+    # allows nzbget to run as a "simple" service
+    OutputMode = "loggable";
+    # use journald for logging
+    WriteLog = "none";
+    ErrorTarget = "screen";
+    WarningTarget = "screen";
+    InfoTarget = "screen";
+    DetailTarget = "screen";
+    # required paths
+    ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf";
+    WebDir = "${pkg}/share/nzbget/webui";
+    # nixos handles package updates
+    UpdateCheck = "none";
+  };
+
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "misc" "nzbget" "configFile" ] "The configuration of nzbget is now managed by users through the web interface.")
+    (mkRemovedOptionModule [ "services" "misc" "nzbget" "dataDir" ] "The data directory for nzbget is now /var/lib/nzbget.")
+    (mkRemovedOptionModule [ "services" "misc" "nzbget" "openFirewall" ] "The port used by nzbget is managed through the web interface so you should adjust your firewall rules accordingly.")
+  ];
+
+  # interface
+
+  options = {
+    services.nzbget = {
+      enable = mkEnableOption "NZBGet";
+
+      user = mkOption {
+        type = types.str;
+        default = "nzbget";
+        description = "User account under which NZBGet runs";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nzbget";
+        description = "Group under which NZBGet runs";
+      };
+    };
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.nzbget = {
+      description = "NZBGet Daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [
+        unrar
+        p7zip
+      ];
+      preStart = ''
+        if [ ! -f ${configFile} ]; then
+          ${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile}
+        fi
+      '';
+
+      serviceConfig = {
+        StateDirectory = "nzbget";
+        StateDirectoryMode = "0750";
+        User = cfg.user;
+        Group = cfg.group;
+        UMask = "0002";
+        Restart = "on-failure";
+        ExecStart = "${pkg}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}";
+        ExecStop = "${pkg}/bin/nzbget --quit";
+      };
+    };
+
+    users.users = mkIf (cfg.user == "nzbget") {
+      nzbget = {
+        home = stateDir;
+        group = cfg.group;
+        uid = config.ids.uids.nzbget;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "nzbget") {
+      nzbget = {
+        gid = config.ids.gids.nzbget;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/octoprint.nix b/nixpkgs/nixos/modules/services/misc/octoprint.nix
new file mode 100644
index 000000000000..7a71d2c8c6aa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/octoprint.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.octoprint;
+
+  baseConfig = {
+    plugins.curalegacy.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine";
+    server.host = cfg.host;
+    server.port = cfg.port;
+    webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg";
+  };
+
+  fullConfig = recursiveUpdate cfg.extraConfig baseConfig;
+
+  cfgUpdate = pkgs.writeText "octoprint-config.yaml" (builtins.toJSON fullConfig);
+
+  pluginsEnv = package.python.withPackages (ps: [ps.octoprint] ++ (cfg.plugins ps));
+
+  package = pkgs.octoprint;
+
+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 {
+        default = plugins: [];
+        defaultText = "plugins: []";
+        example = literalExample "plugins: [ m3d-fio ]";
+        description = "Additional plugins.";
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        description = "Extra options which are added to OctoPrint's YAML configuration file.";
+      };
+
+    };
+
+  };
+
+  ##### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = optionalAttrs (cfg.user == "octoprint") {
+      octoprint = {
+        group = cfg.group;
+        uid = config.ids.uids.octoprint;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "octoprint") {
+      octoprint.gid = config.ids.gids.octoprint;
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.octoprint = {
+      description = "OctoPrint, web interface for 3D printers";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pluginsEnv ];
+
+      preStart = ''
+        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
+      '';
+
+      serviceConfig = {
+        ExecStart = "${pluginsEnv}/bin/octoprint serve -b ${cfg.stateDir}";
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/osrm.nix b/nixpkgs/nixos/modules/services/misc/osrm.nix
new file mode 100644
index 000000000000..79c347ab7e0e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/osrm.nix
@@ -0,0 +1,86 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.osrm;
+in
+
+{
+  options.services.osrm = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable the OSRM service.";
+    };
+
+    address = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      description = "IP address on which the web server will listen.";
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 5000;
+      description = "Port on which the web server will run.";
+    };
+
+    threads = mkOption {
+      type = types.int;
+      default = 4;
+      description = "Number of threads to use.";
+    };
+
+    algorithm = mkOption {
+      type = types.enum [ "CH" "CoreCH" "MLD" ];
+      default = "MLD";
+      description = "Algorithm to use for the data. Must be one of CH, CoreCH, MLD";
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "--max-table-size 1000" "--max-matching-size 1000" ];
+      description = "Extra command line arguments passed to osrm-routed";
+    };
+
+    dataFile = mkOption {
+      type = types.path;
+      example = "/var/lib/osrm/berlin-latest.osrm";
+      description = "Data file location";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.osrm = {
+      group = config.users.users.osrm.name;
+      description = "OSRM user";
+      createHome = false;
+      isSystemUser = true;
+    };
+
+    users.groups.osrm = { };
+
+    systemd.services.osrm = {
+      description = "OSRM service";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User = config.users.users.osrm.name;
+        ExecStart = ''
+          ${pkgs.osrm-backend}/bin/osrm-routed \
+            --ip ${cfg.address} \
+            --port ${toString cfg.port} \
+            --threads ${toString cfg.threads} \
+            --algorithm ${cfg.algorithm} \
+            ${toString cfg.extraFlags} \
+            ${cfg.dataFile}
+        '';
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/packagekit.nix b/nixpkgs/nixos/modules/services/misc/packagekit.nix
new file mode 100644
index 000000000000..325c4e84e0d8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/packagekit.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.packagekit;
+
+  packagekitConf = ''
+    [Daemon]
+    DefaultBackend=${cfg.backend}
+    KeepCache=false
+  '';
+
+  vendorConf = ''
+    [PackagesNotFound]
+    DefaultUrl=https://github.com/NixOS/nixpkgs
+    CodecUrl=https://github.com/NixOS/nixpkgs
+    HardwareUrl=https://github.com/NixOS/nixpkgs
+    FontUrl=https://github.com/NixOS/nixpkgs
+    MimeUrl=https://github.com/NixOS/nixpkgs
+  '';
+
+in
+
+{
+
+  options = {
+
+    services.packagekit = {
+      enable = mkEnableOption
+        ''
+          PackageKit provides a cross-platform D-Bus abstraction layer for
+          installing software. Software utilizing PackageKit can install
+          software regardless of the package manager.
+        '';
+
+      # TODO: integrate with PolicyKit if the nix backend matures to the point
+      # where it will require elevated permissions
+      backend = mkOption {
+        type = types.enum [ "test_nop" ];
+        default = "test_nop";
+        description = ''
+          PackageKit supports multiple different backends and <literal>auto</literal> which
+          should do the right thing.
+          </para>
+          <para>
+          On NixOS however, we do not have a backend compatible with nix 2.0
+          (refer to <link xlink:href="https://github.com/NixOS/nix/issues/233">this issue</link> so we have to force
+          it to <literal>test_nop</literal> for now.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.dbus.packages = with pkgs; [ packagekit ];
+
+    systemd.packages = with pkgs; [ packagekit ];
+
+    environment.etc."PackageKit/PackageKit.conf".text = packagekitConf;
+    environment.etc."PackageKit/Vendor.conf".text = vendorConf;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/paperless.nix b/nixpkgs/nixos/modules/services/misc/paperless.nix
new file mode 100644
index 000000000000..bfaf760fb836
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/paperless.nix
@@ -0,0 +1,183 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.paperless;
+
+  defaultUser = "paperless";
+
+  manage = cfg.package.withConfig {
+    config = {
+      PAPERLESS_CONSUMPTION_DIR = cfg.consumptionDir;
+      PAPERLESS_INLINE_DOC = "true";
+      PAPERLESS_DISABLE_LOGIN = "true";
+    } // cfg.extraConfig;
+    inherit (cfg) dataDir ocrLanguages;
+    paperlessPkg = cfg.package;
+  };
+in
+{
+  options.services.paperless = {
+    enable = mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Enable Paperless.
+
+        When started, the Paperless database is automatically created if it doesn't
+        exist and updated if the Paperless package has changed.
+        Both tasks are achieved by running a Django migration.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/paperless";
+      description = "Directory to store the Paperless data.";
+    };
+
+    consumptionDir = mkOption {
+      type = types.str;
+      default = "${cfg.dataDir}/consume";
+      defaultText = "\${dataDir}/consume";
+      description = "Directory from which new documents are imported.";
+    };
+
+    consumptionDirIsPublic = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Whether all users can write to the consumption dir.";
+    };
+
+    ocrLanguages = mkOption {
+      type = with types; nullOr (listOf str);
+      default = null;
+      description = ''
+        Languages available for OCR via Tesseract, specified as
+        <literal>ISO 639-2/T</literal> language codes.
+        If unset, defaults to all available languages.
+      '';
+      example = [ "eng" "spa" "jpn" ];
+    };
+
+    address = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = "Server listening address.";
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 28981;
+      description = "Server port to listen on.";
+    };
+
+    extraConfig = mkOption {
+      type = types.attrs;
+      default = {};
+      description = ''
+        Extra paperless config options.
+
+        The config values are evaluated as double-quoted Bash string literals.
+
+        See <literal>paperless-src/paperless.conf.example</literal> for available options.
+
+        To enable user authentication, set <literal>PAPERLESS_DISABLE_LOGIN = "false"</literal>
+        and run the shell command <literal>$dataDir/paperless-manage createsuperuser</literal>.
+
+        To define secret options without storing them in /nix/store, use the following pattern:
+        <literal>PAPERLESS_PASSPHRASE = "$(&lt; /etc/my_passphrase_file)"</literal>
+      '';
+      example = literalExample ''
+        {
+          PAPERLESS_OCR_LANGUAGE = "deu";
+        }
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = defaultUser;
+      description = "User under which Paperless runs.";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.paperless;
+      defaultText = "pkgs.paperless";
+      description = "The Paperless package to use.";
+    };
+
+    manage = mkOption {
+      type = types.package;
+      readOnly = true;
+      default = manage;
+      description = ''
+        A script to manage the Paperless instance.
+        It wraps Django's manage.py and is also available at
+        <literal>$dataDir/manage-paperless</literal>
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${config.users.users.${cfg.user}.group} - -"
+    ] ++ (optional cfg.consumptionDirIsPublic
+      "d '${cfg.consumptionDir}' 777 - - - -"
+      # If the consumption dir is not created here, it's automatically created by
+      # 'manage' with the default permissions.
+    );
+
+    systemd.services.paperless-consumer = {
+      description = "Paperless document consumer";
+      serviceConfig = {
+        User = cfg.user;
+        ExecStart = "${manage} document_consumer";
+        Restart = "always";
+      };
+      after = [ "systemd-tmpfiles-setup.service" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        if [[ $(readlink ${cfg.dataDir}/paperless-manage) != ${manage} ]]; then
+          ln -sf ${manage} ${cfg.dataDir}/paperless-manage
+        fi
+
+        ${manage.setupEnv}
+        # Auto-migrate on first run or if the package has changed
+        versionFile="$PAPERLESS_DBDIR/src-version"
+        if [[ $(cat "$versionFile" 2>/dev/null) != ${cfg.package} ]]; then
+          python $paperlessSrc/manage.py migrate
+          echo ${cfg.package} > "$versionFile"
+        fi
+      '';
+    };
+
+    systemd.services.paperless-server = {
+      description = "Paperless document server";
+      serviceConfig = {
+        User = cfg.user;
+        ExecStart = "${manage} runserver --noreload ${cfg.address}:${toString cfg.port}";
+        Restart = "always";
+      };
+      # Bind to `paperless-consumer` so that the server never runs
+      # during migrations
+      bindsTo = [ "paperless-consumer.service" ];
+      after = [ "paperless-consumer.service" ];
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    users = optionalAttrs (cfg.user == defaultUser) {
+      users.${defaultUser} = {
+        group = defaultUser;
+        uid = config.ids.uids.paperless;
+        home = cfg.dataDir;
+      };
+
+      groups.${defaultUser} = {
+        gid = config.ids.gids.paperless;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/parsoid.nix b/nixpkgs/nixos/modules/services/misc/parsoid.nix
new file mode 100644
index 000000000000..09b7f977bfbf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/parsoid.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.parsoid;
+
+  parsoid = pkgs.nodePackages.parsoid;
+
+  confTree = {
+    worker_heartbeat_timeout = 300000;
+    logging = { level = "info"; };
+    services = [{
+      module = "lib/index.js";
+      entrypoint = "apiServiceWorker";
+      conf = {
+        mwApis = map (x: if isAttrs x then x else { uri = x; }) cfg.wikis;
+        serverInterface = cfg.interface;
+        serverPort = cfg.port;
+      };
+    }];
+  };
+
+  confFile = pkgs.writeText "config.yml" (builtins.toJSON (recursiveUpdate confTree cfg.extraConfig));
+
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] "Use services.parsoid.wikis instead")
+  ];
+
+  ##### interface
+
+  options = {
+
+    services.parsoid = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Parsoid -- bidirectional
+          wikitext parser.
+        '';
+      };
+
+      wikis = mkOption {
+        type = types.listOf (types.either types.str types.attrs);
+        example = [ "http://localhost/api.php" ];
+        description = ''
+          Used MediaWiki API endpoints.
+        '';
+      };
+
+      workers = mkOption {
+        type = types.int;
+        default = 2;
+        description = ''
+          Number of Parsoid workers.
+        '';
+      };
+
+      interface = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = ''
+          Interface to listen on.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8000;
+        description = ''
+          Port to listen on.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        description = ''
+          Extra configuration to add to parsoid configuration.
+        '';
+      };
+
+    };
+
+  };
+
+  ##### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.parsoid = {
+      description = "Bidirectional wikitext parser";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${parsoid}/lib/node_modules/parsoid/bin/server.js -c ${confFile} -n ${toString cfg.workers}";
+
+        DynamicUser = true;
+        User = "parsoid";
+        Group = "parsoid";
+
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        #MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        RemoveIPC = true;
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/plex.nix b/nixpkgs/nixos/modules/services/misc/plex.nix
new file mode 100644
index 000000000000..7efadf1b9bb1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/plex.nix
@@ -0,0 +1,151 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.plex;
+in
+{
+  options = {
+    services.plex = {
+      enable = mkEnableOption "Plex Media Server";
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/plex";
+        description = ''
+          The directory where Plex stores its data files.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the media server.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "plex";
+        description = ''
+          User account under which Plex runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "plex";
+        description = ''
+          Group under which Plex runs.
+        '';
+      };
+
+      managePlugins = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If set to true, this option will cause all of the symlinks in Plex's
+          plugin directory to be removed and symlinks for paths specified in
+          <option>extraPlugins</option> to be added.
+        '';
+      };
+
+      extraPlugins = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          A list of paths to extra plugin bundles to install in Plex's plugin
+          directory. Every time the systemd unit for Plex starts up, all of the
+          symlinks in Plex's plugin directory will be cleared and this module
+          will symlink all of the paths specified here to that directory. If
+          this behavior is undesired, set <option>managePlugins</option> to
+          false.
+        '';
+      };
+
+      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.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # Most of this is just copied from the RPM package's systemd service file.
+    systemd.services.plex = {
+      description = "Plex Media Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+
+        # Run the pre-start script with full permissions (the "!" prefix) so it
+        # can create the data directory if necessary.
+        ExecStartPre = let
+          preStartScript = pkgs.writeScript "plex-run-prestart" ''
+            #!${pkgs.bash}/bin/bash
+
+            # Create data directory if it doesn't exist
+            if ! test -d "$PLEX_DATADIR"; then
+              echo "Creating initial Plex data directory in: $PLEX_DATADIR"
+              install -d -m 0755 -o "${cfg.user}" -g "${cfg.group}" "$PLEX_DATADIR"
+            fi
+         '';
+        in
+          "!${preStartScript}";
+
+        ExecStart = "${cfg.package}/bin/plexmediaserver";
+        KillSignal = "SIGQUIT";
+        Restart = "on-failure";
+      };
+
+      environment = {
+        # Configuration for our FHS userenv script
+        PLEX_DATADIR=cfg.dataDir;
+        PLEX_PLUGINS=concatMapStringsSep ":" builtins.toString cfg.extraPlugins;
+
+        # The following variables should be set by the FHS userenv script:
+        #   PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR
+        #   PLEX_MEDIA_SERVER_HOME
+
+        # Allow access to GPU acceleration; the Plex LD_LIBRARY_PATH is added
+        # by the FHS userenv script.
+        LD_LIBRARY_PATH="/run/opengl-driver/lib";
+
+        PLEX_MEDIA_SERVER_MAX_PLUGIN_PROCS="6";
+        PLEX_MEDIA_SERVER_TMPDIR="/tmp";
+        PLEX_MEDIA_SERVER_USE_SYSLOG="true";
+        LC_ALL="en_US.UTF-8";
+        LANG="en_US.UTF-8";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 32400 3005 8324 32469 ];
+      allowedUDPPorts = [ 1900 5353 32410 32412 32413 32414 ];
+    };
+
+    users.users = mkIf (cfg.user == "plex") {
+      plex = {
+        group = cfg.group;
+        uid = config.ids.uids.plex;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "plex") {
+      plex = {
+        gid = config.ids.gids.plex;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/pykms.nix b/nixpkgs/nixos/modules/services/misc/pykms.nix
new file mode 100644
index 000000000000..d6aeae48ccb6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/pykms.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pykms;
+  libDir = "/var/lib/pykms";
+
+in {
+  meta.maintainers = with lib.maintainers; [ peterhoeg ];
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "pykms" "verbose" ] "Use services.pykms.logLevel instead")
+  ];
+
+  options = {
+    services.pykms = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the PyKMS service.";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = "The IP address on which to listen.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 1688;
+        description = "The port on which to listen.";
+      };
+
+      openFirewallPort = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether the listening port should be opened automatically.";
+      };
+
+      memoryLimit = mkOption {
+        type = types.str;
+        default = "64M";
+        description = "How much memory to use at most.";
+      };
+
+      logLevel = mkOption {
+        type = types.enum [ "CRITICAL" "ERROR" "WARNING" "INFO" "DEBUG" "MINI" ];
+        default = "INFO";
+        description = "How much to log";
+      };
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Additional arguments";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewallPort [ cfg.port ];
+
+    systemd.services.pykms = {
+      description = "Python KMS";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      # python programs with DynamicUser = true require HOME to be set
+      environment.HOME = libDir;
+      serviceConfig = with pkgs; {
+        DynamicUser = true;
+        StateDirectory = baseNameOf libDir;
+        ExecStartPre = "${getBin pykms}/libexec/create_pykms_db.sh ${libDir}/clients.db";
+        ExecStart = lib.concatStringsSep " " ([
+          "${getBin pykms}/bin/server"
+          "--logfile STDOUT"
+          "--loglevel ${cfg.logLevel}"
+        ] ++ cfg.extraArgs ++ [
+          cfg.listenAddress
+          (toString cfg.port)
+        ]);
+        ProtectHome = "tmpfs";
+        WorkingDirectory = libDir;
+        SyslogIdentifier = "pykms";
+        Restart = "on-failure";
+        MemoryLimit = cfg.memoryLimit;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/radarr.nix b/nixpkgs/nixos/modules/services/misc/radarr.nix
new file mode 100644
index 000000000000..74444e24043f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/radarr.nix
@@ -0,0 +1,75 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.radarr;
+
+in
+{
+  options = {
+    services.radarr = {
+      enable = mkEnableOption "Radarr";
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/radarr/.config/Radarr";
+        description = "The directory where Radarr stores its data files.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open ports in the firewall for the Radarr web interface.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "radarr";
+        description = "User account under which Radarr runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "radarr";
+        description = "Group under which Radarr runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.radarr = {
+      description = "Radarr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${pkgs.radarr}/bin/Radarr -nobrowser -data='${cfg.dataDir}'";
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 7878 ];
+    };
+
+    users.users = mkIf (cfg.user == "radarr") {
+      radarr = {
+        group = cfg.group;
+        home = cfg.dataDir;
+        uid = config.ids.uids.radarr;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "radarr") {
+      radarr.gid = config.ids.gids.radarr;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/redmine.nix b/nixpkgs/nixos/modules/services/misc/redmine.nix
new file mode 100644
index 000000000000..0e71cf925692
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/redmine.nix
@@ -0,0 +1,395 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption types;
+  inherit (lib) concatStringsSep literalExample mapAttrsToList;
+  inherit (lib) optional optionalAttrs optionalString singleton versionAtLeast;
+
+  cfg = config.services.redmine;
+
+  bundle = "${cfg.package}/share/redmine/bin/bundle";
+
+  databaseYml = pkgs.writeText "database.yml" ''
+    production:
+      adapter: ${cfg.database.type}
+      database: ${cfg.database.name}
+      host: ${if (cfg.database.type == "postgresql" && cfg.database.socket != null) then cfg.database.socket else cfg.database.host}
+      port: ${toString cfg.database.port}
+      username: ${cfg.database.user}
+      password: #dbpass#
+      ${optionalString (cfg.database.type == "mysql2" && cfg.database.socket != null) "socket: ${cfg.database.socket}"}
+  '';
+
+  configurationYml = pkgs.writeText "configuration.yml" ''
+    default:
+      scm_subversion_command: ${pkgs.subversion}/bin/svn
+      scm_mercurial_command: ${pkgs.mercurial}/bin/hg
+      scm_git_command: ${pkgs.gitAndTools.git}/bin/git
+      scm_cvs_command: ${pkgs.cvs}/bin/cvs
+      scm_bazaar_command: ${pkgs.breezy}/bin/bzr
+      scm_darcs_command: ${pkgs.darcs}/bin/darcs
+
+    ${cfg.extraConfig}
+  '';
+
+  additionalEnvironment = pkgs.writeText "additional_environment.rb" ''
+    config.logger = Logger.new("${cfg.stateDir}/log/production.log", 14, 1048576)
+    config.logger.level = Logger::INFO
+
+    ${cfg.extraEnv}
+  '';
+
+  unpackTheme = unpack "theme";
+  unpackPlugin = unpack "plugin";
+  unpack = id: (name: source:
+    pkgs.stdenv.mkDerivation {
+      name = "redmine-${id}-${name}";
+      buildInputs = [ pkgs.unzip ];
+      buildCommand = ''
+        mkdir -p $out
+        cd $out
+        unpackFile ${source}
+      '';
+  });
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql2";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "postgresql";
+
+in
+
+{
+  options = {
+    services.redmine = {
+      enable = mkEnableOption "Redmine";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.redmine;
+        description = "Which Redmine package to use.";
+        example = "pkgs.redmine.override { ruby = pkgs.ruby_2_7; }";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "redmine";
+        description = "User under which Redmine is ran.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "redmine";
+        description = "Group under which Redmine is ran.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 3000;
+        description = "Port on which Redmine is ran.";
+      };
+
+      stateDir = mkOption {
+        type = types.str;
+        default = "/var/lib/redmine";
+        description = "The state directory, logs and plugins are stored here.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration in configuration.yml.
+
+          See <link xlink:href="https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration"/>
+          for details.
+        '';
+        example = literalExample ''
+          email_delivery:
+            delivery_method: smtp
+            smtp_settings:
+              address: mail.example.com
+              port: 25
+        '';
+      };
+
+      extraEnv = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration in additional_environment.rb.
+
+          See <link xlink:href="https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example"/>
+          for details.
+        '';
+        example = literalExample ''
+          config.logger.level = Logger::DEBUG
+        '';
+      };
+
+      themes = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        description = "Set of themes.";
+        example = literalExample ''
+          {
+            dkuk-redmine_alex_skin = builtins.fetchurl {
+              url = "https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip";
+              sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+            };
+          }
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        description = "Set of plugins.";
+        example = literalExample ''
+          {
+            redmine_env_auth = builtins.fetchurl {
+              url = "https://github.com/Intera/redmine_env_auth/archive/0.6.zip";
+              sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak";
+            };
+          }
+        '';
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql2" "postgresql" ];
+          example = "postgresql";
+          default = "mysql2";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = if cfg.database.type == "postgresql" then 5432 else 3306;
+          defaultText = "3306";
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "redmine";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "redmine";
+          description = "Database user.";
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            The password corresponding to <option>database.user</option>.
+            Warning: this is stored in cleartext in the Nix store!
+            Use <option>database.passwordFile</option> instead.
+          '';
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/redmine-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default =
+            if mysqlLocal then "/run/mysqld/mysqld.sock"
+            else if pgsqlLocal then "/run/postgresql"
+            else null;
+          defaultText = "/run/mysqld/mysqld.sock";
+          example = "/run/mysqld/mysqld.sock";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Create the database and database user locally.";
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.passwordFile != null || cfg.database.password != "" || cfg.database.socket != null;
+        message = "one of services.redmine.database.socket, services.redmine.database.passwordFile, or services.redmine.database.password must be set";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.user == cfg.user;
+        message = "services.redmine.database.user must be set to ${cfg.user} if services.redmine.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+        message = "services.redmine.database.socket must be set if services.redmine.database.createLocally is set to true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.host == "localhost";
+        message = "services.redmine.database.host must be set to localhost if services.redmine.database.createLocally is set to true";
+      }
+    ];
+
+    services.mysql = mkIf mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.postgresql = mkIf pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    # create symlinks for the basic directory layout the redmine package expects
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/cache' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/config' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/files' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/log' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/plugins' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/public' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/public/plugin_assets' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/public/themes' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/tmp' 0750 ${cfg.user} ${cfg.group} - -"
+
+      "d /run/redmine - - - - -"
+      "d /run/redmine/public - - - - -"
+      "L+ /run/redmine/config - - - - ${cfg.stateDir}/config"
+      "L+ /run/redmine/files - - - - ${cfg.stateDir}/files"
+      "L+ /run/redmine/log - - - - ${cfg.stateDir}/log"
+      "L+ /run/redmine/plugins - - - - ${cfg.stateDir}/plugins"
+      "L+ /run/redmine/public/plugin_assets - - - - ${cfg.stateDir}/public/plugin_assets"
+      "L+ /run/redmine/public/themes - - - - ${cfg.stateDir}/public/themes"
+      "L+ /run/redmine/tmp - - - - ${cfg.stateDir}/tmp"
+    ];
+
+    systemd.services.redmine = {
+      after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+      wantedBy = [ "multi-user.target" ];
+      environment.RAILS_ENV = "production";
+      environment.RAILS_CACHE = "${cfg.stateDir}/cache";
+      environment.REDMINE_LANG = "en";
+      environment.SCHEMA = "${cfg.stateDir}/cache/schema.db";
+      path = with pkgs; [
+        imagemagick
+        breezy
+        cvs
+        darcs
+        gitAndTools.git
+        mercurial
+        subversion
+      ];
+      preStart = ''
+        rm -rf "${cfg.stateDir}/plugins/"*
+        rm -rf "${cfg.stateDir}/public/themes/"*
+
+        # start with a fresh config directory
+        # the config directory is copied instead of linked as some mutable data is stored in there
+        find "${cfg.stateDir}/config" ! -name "secret_token.rb" -type f -exec rm -f {} +
+        cp -r ${cfg.package}/share/redmine/config.dist/* "${cfg.stateDir}/config/"
+
+        chmod -R u+w "${cfg.stateDir}/config"
+
+        # link in the application configuration
+        ln -fs ${configurationYml} "${cfg.stateDir}/config/configuration.yml"
+
+        # link in the additional environment configuration
+        ln -fs ${additionalEnvironment} "${cfg.stateDir}/config/additional_environment.rb"
+
+
+        # link in all user specified themes
+        for theme in ${concatStringsSep " " (mapAttrsToList unpackTheme cfg.themes)}; do
+          ln -fs $theme/* "${cfg.stateDir}/public/themes"
+        done
+
+        # link in redmine provided themes
+        ln -sf ${cfg.package}/share/redmine/public/themes.dist/* "${cfg.stateDir}/public/themes/"
+
+
+        # link in all user specified plugins
+        for plugin in ${concatStringsSep " " (mapAttrsToList unpackPlugin cfg.plugins)}; do
+          ln -fs $plugin/* "${cfg.stateDir}/plugins/''${plugin##*-redmine-plugin-}"
+        done
+
+
+        # handle database.passwordFile & permissions
+        DBPASS=$(head -n1 ${cfg.database.passwordFile})
+        cp -f ${databaseYml} "${cfg.stateDir}/config/database.yml"
+        sed -e "s,#dbpass#,$DBPASS,g" -i "${cfg.stateDir}/config/database.yml"
+        chmod 440 "${cfg.stateDir}/config/database.yml"
+
+
+        # generate a secret token if required
+        if ! test -e "${cfg.stateDir}/config/initializers/secret_token.rb"; then
+          ${bundle} exec rake generate_secret_token
+          chmod 440 "${cfg.stateDir}/config/initializers/secret_token.rb"
+        fi
+
+        # execute redmine required commands prior to starting the application
+        ${bundle} exec rake db:migrate
+        ${bundle} exec rake redmine:plugins:migrate
+        ${bundle} exec rake redmine:load_default_data
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        TimeoutSec = "300";
+        WorkingDirectory = "${cfg.package}/share/redmine";
+        ExecStart="${bundle} exec rails server webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'";
+      };
+
+    };
+
+    users.users = optionalAttrs (cfg.user == "redmine") {
+      redmine = {
+        group = cfg.group;
+        home = cfg.stateDir;
+        uid = config.ids.uids.redmine;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "redmine") {
+      redmine.gid = config.ids.gids.redmine;
+    };
+
+    warnings = optional (cfg.database.password != "")
+      ''config.services.redmine.database.password will be stored as plaintext
+      in the Nix store. Use database.passwordFile instead.'';
+
+    # Create database passwordFile default when password is configured.
+    services.redmine.database.passwordFile =
+      (mkDefault (toString (pkgs.writeTextFile {
+        name = "redmine-database-password";
+        text = cfg.database.password;
+      })));
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix b/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix
new file mode 100644
index 000000000000..9fab462f7e3b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ripple-data-api.nix
@@ -0,0 +1,193 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.rippleDataApi;
+
+  deployment_env_config = builtins.toJSON {
+    production = {
+      port = toString cfg.port;
+      maxSockets = 150;
+      batchSize = 100;
+      startIndex = 32570;
+      rippleds = cfg.rippleds;
+      redis = {
+        enable = cfg.redis.enable;
+        host = cfg.redis.host;
+        port = cfg.redis.port;
+        options.auth_pass = null;
+      };
+    };
+  };
+
+  db_config = builtins.toJSON {
+    production = {
+      username = optional (cfg.couchdb.pass != "") cfg.couchdb.user;
+      password = optional (cfg.couchdb.pass != "") cfg.couchdb.pass;
+      host = cfg.couchdb.host;
+      port = cfg.couchdb.port;
+      database = cfg.couchdb.db;
+      protocol = "http";
+    };
+  };
+
+in {
+  options = {
+    services.rippleDataApi = {
+      enable = mkEnableOption "ripple data api";
+
+      port = mkOption {
+        description = "Ripple data api port";
+        default = 5993;
+        type = types.int;
+      };
+
+      importMode = mkOption {
+        description = "Ripple data api import mode.";
+        default = "liveOnly";
+        type = types.enum ["live" "liveOnly"];
+      };
+
+      minLedger = mkOption {
+        description = "Ripple data api minimal ledger to fetch.";
+        default = null;
+        type = types.nullOr types.int;
+      };
+
+      maxLedger = mkOption {
+        description = "Ripple data api maximal ledger to fetch.";
+        default = null;
+        type = types.nullOr types.int;
+      };
+
+      redis = {
+        enable = mkOption {
+          description = "Whether to enable caching of ripple data to redis.";
+          default = true;
+          type = types.bool;
+        };
+
+        host = mkOption {
+          description = "Ripple data api redis host.";
+          default = "localhost";
+          type = types.str;
+        };
+
+        port = mkOption {
+          description = "Ripple data api redis port.";
+          default = 5984;
+          type = types.int;
+        };
+      };
+
+      couchdb = {
+        host = mkOption {
+          description = "Ripple data api couchdb host.";
+          default = "localhost";
+          type = types.str;
+        };
+
+        port = mkOption {
+          description = "Ripple data api couchdb port.";
+          default = 5984;
+          type = types.int;
+        };
+
+        db = mkOption {
+          description = "Ripple data api couchdb database.";
+          default = "rippled";
+          type = types.str;
+        };
+
+        user = mkOption {
+          description = "Ripple data api couchdb username.";
+          default = "rippled";
+          type = types.str;
+        };
+
+        pass = mkOption {
+          description = "Ripple data api couchdb password.";
+          default = "";
+          type = types.str;
+        };
+
+        create = mkOption {
+          description = "Whether to create couchdb database needed by ripple data api.";
+          type = types.bool;
+          default = true;
+        };
+      };
+
+      rippleds = mkOption {
+        description = "List of rippleds to be used by ripple data api.";
+        default = [
+          "http://s_east.ripple.com:51234"
+          "http://s_west.ripple.com:51234"
+        ];
+        type = types.listOf types.str;
+      };
+    };
+  };
+
+  config = mkIf (cfg.enable) {
+    services.couchdb.enable = mkDefault true;
+    services.couchdb.bindAddress = mkDefault "0.0.0.0";
+    services.redis.enable = mkDefault true;
+
+    systemd.services.ripple-data-api = {
+      after = [ "couchdb.service" "redis.service" "ripple-data-api-importer.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        NODE_ENV = "production";
+        DEPLOYMENT_ENVS_CONFIG = pkgs.writeText "deployment.environment.json" deployment_env_config;
+        DB_CONFIG = pkgs.writeText "db.config.json" db_config;
+      };
+
+      serviceConfig = {
+        ExecStart = "${pkgs.ripple-data-api}/bin/api";
+        Restart = "always";
+        User = "ripple-data-api";
+      };
+    };
+
+    systemd.services.ripple-data-importer = {
+      after = [ "couchdb.service" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.curl ];
+
+      environment = {
+        NODE_ENV = "production";
+        DEPLOYMENT_ENVS_CONFIG = pkgs.writeText "deployment.environment.json" deployment_env_config;
+        DB_CONFIG = pkgs.writeText "db.config.json" db_config;
+        LOG_FILE = "/dev/null";
+      };
+
+      serviceConfig = let
+        importMode =
+          if cfg.minLedger != null && cfg.maxLedger != null then
+            "${toString cfg.minLedger} ${toString cfg.maxLedger}"
+          else
+            cfg.importMode;
+      in {
+        ExecStart = "${pkgs.ripple-data-api}/bin/importer ${importMode} debug";
+        Restart = "always";
+        User = "ripple-data-api";
+      };
+
+      preStart = mkMerge [
+        (mkIf (cfg.couchdb.create) ''
+          HOST="http://${optionalString (cfg.couchdb.pass != "") "${cfg.couchdb.user}:${cfg.couchdb.pass}@"}${cfg.couchdb.host}:${toString cfg.couchdb.port}"
+          curl -X PUT $HOST/${cfg.couchdb.db} || true
+        '')
+        "${pkgs.ripple-data-api}/bin/update-views"
+      ];
+    };
+
+    users.users.ripple-data-api =
+      { description = "Ripple data api user";
+        uid = config.ids.uids.ripple-data-api;
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/rippled.nix b/nixpkgs/nixos/modules/services/misc/rippled.nix
new file mode 100644
index 000000000000..ef34e3a779f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/rippled.nix
@@ -0,0 +1,431 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.rippled;
+
+  b2i = val: if val then "1" else "0";
+
+  dbCfg = db: ''
+    type=${db.type}
+    path=${db.path}
+    ${optionalString (db.compression != null) ("compression=${b2i db.compression}") }
+    ${optionalString (db.onlineDelete != null) ("online_delete=${toString db.onlineDelete}")}
+    ${optionalString (db.advisoryDelete != null) ("advisory_delete=${b2i db.advisoryDelete}")}
+    ${db.extraOpts}
+  '';
+
+  rippledCfg = ''
+    [server]
+    ${concatMapStringsSep "\n" (n: "port_${n}") (attrNames cfg.ports)}
+
+    ${concatMapStrings (p: ''
+    [port_${p.name}]
+    ip=${p.ip}
+    port=${toString p.port}
+    protocol=${concatStringsSep "," p.protocol}
+    ${optionalString (p.user != "") "user=${p.user}"}
+    ${optionalString (p.password != "") "user=${p.password}"}
+    admin=${concatStringsSep "," p.admin}
+    ${optionalString (p.ssl.key != null) "ssl_key=${p.ssl.key}"}
+    ${optionalString (p.ssl.cert != null) "ssl_cert=${p.ssl.cert}"}
+    ${optionalString (p.ssl.chain != null) "ssl_chain=${p.ssl.chain}"}
+    '') (attrValues cfg.ports)}
+
+    [database_path]
+    ${cfg.databasePath}
+
+    [node_db]
+    ${dbCfg cfg.nodeDb}
+
+    ${optionalString (cfg.tempDb != null) ''
+    [temp_db]
+    ${dbCfg cfg.tempDb}''}
+
+    ${optionalString (cfg.importDb != null) ''
+    [import_db]
+    ${dbCfg cfg.importDb}''}
+
+    [ips]
+    ${concatStringsSep "\n" cfg.ips}
+
+    [ips_fixed]
+    ${concatStringsSep "\n" cfg.ipsFixed}
+
+    [validators]
+    ${concatStringsSep "\n" cfg.validators}
+
+    [node_size]
+    ${cfg.nodeSize}
+
+    [ledger_history]
+    ${toString cfg.ledgerHistory}
+
+    [fetch_depth]
+    ${toString cfg.fetchDepth}
+
+    [validation_quorum]
+    ${toString cfg.validationQuorum}
+
+    [sntp_servers]
+    ${concatStringsSep "\n" cfg.sntpServers}
+
+    ${optionalString cfg.statsd.enable ''
+    [insight]
+    server=statsd
+    address=${cfg.statsd.address}
+    prefix=${cfg.statsd.prefix}
+    ''}
+
+    [rpc_startup]
+    { "command": "log_level", "severity": "${cfg.logLevel}" }
+  '' + cfg.extraConfig;
+
+  portOptions = { name, ...}: {
+    options = {
+      name = mkOption {
+        internal = true;
+        default = name;
+      };
+
+      ip = mkOption {
+        default = "127.0.0.1";
+        description = "Ip where rippled listens.";
+        type = types.str;
+      };
+
+      port = mkOption {
+        description = "Port where rippled listens.";
+        type = types.int;
+      };
+
+      protocol = mkOption {
+        description = "Protocols expose by rippled.";
+        type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]);
+      };
+
+      user = mkOption {
+        description = "When set, these credentials will be required on HTTP/S requests.";
+        type = types.str;
+        default = "";
+      };
+
+      password = mkOption {
+        description = "When set, these credentials will be required on HTTP/S requests.";
+        type = types.str;
+        default = "";
+      };
+
+      admin = mkOption {
+        description = "A comma-separated list of admin IP addresses.";
+        type = types.listOf types.str;
+        default = ["127.0.0.1"];
+      };
+
+      ssl = {
+        key = mkOption {
+          description = ''
+            Specifies the filename holding the SSL key in PEM format.
+          '';
+          default = null;
+          type = types.nullOr types.path;
+        };
+
+        cert = mkOption {
+          description = ''
+            Specifies the path to the SSL certificate file in PEM format.
+            This is not needed if the chain includes it.
+          '';
+          default = null;
+          type = types.nullOr types.path;
+        };
+
+        chain = mkOption {
+          description = ''
+            If you need a certificate chain, specify the path to the
+            certificate chain here. The chain may include the end certificate.
+          '';
+          default = null;
+          type = types.nullOr types.path;
+        };
+      };
+    };
+  };
+
+  dbOptions = {
+    options = {
+      type = mkOption {
+        description = "Rippled database type.";
+        type = types.enum ["rocksdb" "nudb"];
+        default = "rocksdb";
+      };
+
+      path = mkOption {
+        description = "Location to store the database.";
+        type = types.path;
+        default = cfg.databasePath;
+      };
+
+      compression = mkOption {
+        description = "Whether to enable snappy compression.";
+        type = types.nullOr types.bool;
+        default = null;
+      };
+
+      onlineDelete = mkOption {
+        description = "Enable automatic purging of older ledger information.";
+        type = types.nullOr (types.addCheck types.int (v: v > 256));
+        default = cfg.ledgerHistory;
+      };
+
+      advisoryDelete = mkOption {
+        description = ''
+          If set, then require administrative RPC call "can_delete"
+          to enable online deletion of ledger records.
+        '';
+        type = types.nullOr types.bool;
+        default = null;
+      };
+
+      extraOpts = mkOption {
+        description = "Extra database options.";
+        type = types.lines;
+        default = "";
+      };
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.rippled = {
+      enable = mkEnableOption "rippled";
+
+      package = mkOption {
+        description = "Which rippled package to use.";
+        type = types.package;
+        default = pkgs.rippled;
+        defaultText = "pkgs.rippled";
+      };
+
+      ports = mkOption {
+        description = "Ports exposed by rippled";
+        type = with types; attrsOf (submodule portOptions);
+        default = {
+          rpc = {
+            port = 5005;
+            admin = ["127.0.0.1"];
+            protocol = ["http"];
+          };
+
+          peer = {
+            port = 51235;
+            ip = "0.0.0.0";
+            protocol = ["peer"];
+          };
+
+          ws_public = {
+            port = 5006;
+            ip = "0.0.0.0";
+            protocol = ["ws" "wss"];
+          };
+        };
+      };
+
+      nodeDb = mkOption {
+        description = "Rippled main database options.";
+        type = with types; nullOr (submodule dbOptions);
+        default = {
+          type = "rocksdb";
+          extraOpts = ''
+            open_files=2000
+            filter_bits=12
+            cache_mb=256
+            file_size_pb=8
+            file_size_mult=2;
+          '';
+        };
+      };
+
+      tempDb = mkOption {
+        description = "Rippled temporary database options.";
+        type = with types; nullOr (submodule dbOptions);
+        default = null;
+      };
+
+      importDb = mkOption {
+        description = "Settings for performing a one-time import.";
+        type = with types; nullOr (submodule dbOptions);
+        default = null;
+      };
+
+      nodeSize = mkOption {
+        description = ''
+          Rippled size of the node you are running.
+          "tiny", "small", "medium", "large", and "huge"
+        '';
+        type = types.enum ["tiny" "small" "medium" "large" "huge"];
+        default = "small";
+      };
+
+      ips = mkOption {
+        description = ''
+          List of hostnames or ips where the Ripple protocol is served.
+          For a starter list, you can either copy entries from:
+          https://ripple.com/ripple.txt or if you prefer you can let it
+           default to r.ripple.com 51235
+
+          A port may optionally be specified after adding a space to the
+          address. By convention, if known, IPs are listed in from most
+          to least trusted.
+        '';
+        type = types.listOf types.str;
+        default = ["r.ripple.com 51235"];
+      };
+
+      ipsFixed = mkOption {
+        description = ''
+          List of IP addresses or hostnames to which rippled should always
+          attempt to maintain peer connections with. This is useful for
+          manually forming private networks, for example to configure a
+          validation server that connects to the Ripple network through a
+          public-facing server, or for building a set of cluster peers.
+
+          A port may optionally be specified after adding a space to the address
+        '';
+        type = types.listOf types.str;
+        default = [];
+      };
+
+      validators = mkOption {
+        description = ''
+          List of nodes to always accept as validators. Nodes are specified by domain
+          or public key.
+        '';
+        type = types.listOf types.str;
+        default = [
+          "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7  RL1"
+          "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj  RL2"
+          "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C  RL3"
+          "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS  RL4"
+          "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA  RL5"
+        ];
+      };
+
+      databasePath = mkOption {
+        description = ''
+          Path to the ripple database.
+        '';
+        type = types.path;
+        default = "/var/lib/rippled";
+      };
+
+      validationQuorum = mkOption {
+        description = ''
+          The minimum number of trusted validations a ledger must have before
+          the server considers it fully validated.
+        '';
+        type = types.int;
+        default = 3;
+      };
+
+      ledgerHistory = mkOption {
+        description = ''
+          The number of past ledgers to acquire on server startup and the minimum
+          to maintain while running.
+        '';
+        type = types.either types.int (types.enum ["full"]);
+        default = 1296000; # 1 month
+      };
+
+      fetchDepth = mkOption {
+        description = ''
+          The number of past ledgers to serve to other peers that request historical
+          ledger data (or "full" for no limit).
+        '';
+        type = types.either types.int (types.enum ["full"]);
+        default = "full";
+      };
+
+      sntpServers = mkOption {
+        description = ''
+          IP address or domain of NTP servers to use for time synchronization.;
+        '';
+        type = types.listOf types.str;
+        default = [
+          "time.windows.com"
+          "time.apple.com"
+          "time.nist.gov"
+          "pool.ntp.org"
+        ];
+      };
+
+      logLevel = mkOption {
+        description = "Logging verbosity.";
+        type = types.enum ["debug" "error" "info"];
+        default = "error";
+      };
+
+      statsd = {
+        enable = mkEnableOption "statsd monitoring for rippled";
+
+        address = mkOption {
+          description = "The UDP address and port of the listening StatsD server.";
+          default = "127.0.0.1:8125";
+          type = types.str;
+        };
+
+        prefix = mkOption {
+          description = "A string prepended to each collected metric.";
+          default = "";
+          type = types.str;
+        };
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        description = ''
+          Extra lines to be added verbatim to the rippled.cfg configuration file.
+        '';
+      };
+
+      config = mkOption {
+        internal = true;
+        default = pkgs.writeText "rippled.conf" rippledCfg;
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.rippled =
+      { description = "Ripple server user";
+        uid = config.ids.uids.rippled;
+        home = cfg.databasePath;
+        createHome = true;
+      };
+
+    systemd.services.rippled = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}";
+        User = "rippled";
+        Restart = "on-failure";
+        LimitNOFILE=10000;
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/safeeyes.nix b/nixpkgs/nixos/modules/services/misc/safeeyes.nix
new file mode 100644
index 000000000000..6ecb0d13187c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/safeeyes.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.safeeyes;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.safeeyes = {
+
+      enable = mkEnableOption "the safeeyes OSGi service";
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.user.services.safeeyes = {
+      description = "Safeeyes";
+
+      wantedBy = [ "graphical-session.target" ];
+      partOf   = [ "graphical-session.target" ];
+
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.safeeyes}/bin/safeeyes
+        '';
+        Restart = "on-failure";
+        RestartSec = 3;
+        StartLimitInterval = 350;
+        StartLimitBurst = 10;
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/serviio.nix b/nixpkgs/nixos/modules/services/misc/serviio.nix
new file mode 100644
index 000000000000..0ead6a816918
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/serviio.nix
@@ -0,0 +1,87 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.serviio;
+
+  serviioStart = pkgs.writeScript "serviio.sh" ''
+    #!${pkgs.bash}/bin/sh
+
+    SERVIIO_HOME=${pkgs.serviio}
+
+    # Setup the classpath
+    SERVIIO_CLASS_PATH="$SERVIIO_HOME/lib/*:$SERVIIO_HOME/config"
+
+    # Setup Serviio specific properties
+    JAVA_OPTS="-Djava.net.preferIPv4Stack=true -Djava.awt.headless=true -Dorg.restlet.engine.loggerFacadeClass=org.restlet.ext.slf4j.Slf4jLoggerFacade
+               -Dderby.system.home=${cfg.dataDir}/library -Dserviio.home=${cfg.dataDir} -Dffmpeg.location=${pkgs.ffmpeg}/bin/ffmpeg -Ddcraw.location=${pkgs.dcraw}/bin/dcraw"
+
+    # Execute the JVM in the foreground
+    exec ${pkgs.jre}/bin/java -Xmx512M -Xms20M -XX:+UseG1GC -XX:GCTimeRatio=1 -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 $JAVA_OPTS -classpath "$SERVIIO_CLASS_PATH" org.serviio.MediaServer "$@"
+  '';
+
+in {
+
+  ###### interface
+  options = {
+    services.serviio = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Serviio Media Server.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/serviio";
+        description = ''
+          The directory where serviio stores its state, data, etc.
+        '';
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.serviio = {
+      description = "Serviio Media Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.serviio ];
+      serviceConfig = {
+        User = "serviio";
+        Group = "serviio";
+        ExecStart = "${serviioStart}";
+        ExecStop = "${serviioStart} -stop";
+      };
+    };
+
+    users.users.serviio =
+      { group = "serviio";
+        home = cfg.dataDir;
+        description = "Serviio Media Server User";
+        createHome = true;
+        isSystemUser = true;
+      };
+
+    users.groups.serviio = { };
+
+    networking.firewall = {
+      allowedTCPPorts = [
+        8895  # serve UPnP responses
+        23423 # console
+        23424 # mediabrowser
+      ];
+      allowedUDPPorts = [
+        1900 # UPnP service discovey
+      ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/sickbeard.nix b/nixpkgs/nixos/modules/services/misc/sickbeard.nix
new file mode 100644
index 000000000000..a32dbfa3108f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sickbeard.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  name = "sickbeard";
+
+  cfg = config.services.sickbeard;
+  sickbeard = cfg.package;
+
+in
+{
+
+  ###### interface
+
+  options = {
+    services.sickbeard = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the sickbeard server.";
+      };
+      package = mkOption {
+        type = types.package;
+        default = pkgs.sickbeard;
+        example = literalExample "pkgs.sickrage";
+        description =''
+          Enable <literal>pkgs.sickrage</literal> or <literal>pkgs.sickgear</literal>
+          as an alternative to SickBeard
+        '';
+      };
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/${name}";
+        description = "Path where to store data files.";
+      };
+      configFile = mkOption {
+        type = types.path;
+        default = "${cfg.dataDir}/config.ini";
+        description = "Path to config file.";
+      };
+      port = mkOption {
+        type = types.ints.u16;
+        default = 8081;
+        description = "Port to bind to.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = name;
+        description = "User to run the service as";
+      };
+      group = mkOption {
+        type = types.str;
+        default = name;
+        description = "Group to run the service as";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = optionalAttrs (cfg.user == name) {
+      ${name} = {
+        uid = config.ids.uids.sickbeard;
+        group = cfg.group;
+        description = "sickbeard user";
+        home = cfg.dataDir;
+        createHome = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == name) {
+      ${name}.gid = config.ids.gids.sickbeard;
+    };
+
+    systemd.services.sickbeard = {
+      description = "Sickbeard Server";
+      wantedBy    = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${sickbeard}/SickBeard.py --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/siproxd.nix b/nixpkgs/nixos/modules/services/misc/siproxd.nix
new file mode 100644
index 000000000000..ae7b27de8e70
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/siproxd.nix
@@ -0,0 +1,179 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.siproxd;
+
+  conf = ''
+    daemonize = 0
+    rtp_proxy_enable = 1
+    user = siproxd
+    if_inbound  = ${cfg.ifInbound}
+    if_outbound = ${cfg.ifOutbound}
+    sip_listen_port = ${toString cfg.sipListenPort}
+    rtp_port_low    = ${toString cfg.rtpPortLow}
+    rtp_port_high   = ${toString cfg.rtpPortHigh}
+    rtp_dscp        = ${toString cfg.rtpDscp}
+    sip_dscp        = ${toString cfg.sipDscp}
+    ${optionalString (cfg.hostsAllowReg != []) "hosts_allow_reg = ${concatStringsSep "," cfg.hostsAllowReg}"}
+    ${optionalString (cfg.hostsAllowSip != []) "hosts_allow_sip = ${concatStringsSep "," cfg.hostsAllowSip}"}
+    ${optionalString (cfg.hostsDenySip != []) "hosts_deny_sip  = ${concatStringsSep "," cfg.hostsDenySip}"}
+    ${if (cfg.passwordFile != "") then "proxy_auth_pwfile = ${cfg.passwordFile}" else ""}
+    ${cfg.extraConfig}
+  '';
+
+  confFile = builtins.toFile "siproxd.conf" conf;
+
+in
+{
+  ##### interface
+
+  options = {
+
+    services.siproxd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Siproxd SIP 
+	  proxy/masquerading daemon.
+        '';
+      };
+
+      ifInbound = mkOption {
+        type = types.str;
+        example = "eth0";
+        description = "Local network interface";
+      };
+
+      ifOutbound = mkOption {
+        type = types.str;
+        example = "ppp0";
+        description = "Public network interface";
+      };
+
+      hostsAllowReg = mkOption {
+        type = types.listOf types.str;
+	default = [ ];
+        example = [ "192.168.1.0/24" "192.168.2.0/24" ];
+	description = ''
+          Acess control list for incoming SIP registrations.
+        '';
+      };
+
+      hostsAllowSip = mkOption {
+        type = types.listOf types.str;
+	default = [ ];
+        example = [ "123.45.0.0/16" "123.46.0.0/16" ];
+	description = ''
+          Acess control list for incoming SIP traffic.
+        '';
+      };
+
+      hostsDenySip = mkOption {
+        type = types.listOf types.str;
+	default = [ ];
+        example = [ "10.0.0.0/8" "11.0.0.0/8" ];
+	description = ''
+          Acess control list for denying incoming
+	   SIP registrations and traffic.
+        '';
+      };
+
+      sipListenPort = mkOption {
+        type = types.int;
+        default = 5060;
+        description = ''
+	  Port to listen for incoming SIP messages.
+        '';
+      };
+
+      rtpPortLow = mkOption {
+        type = types.int;
+        default = 7070;
+        description = ''
+         Bottom of UDP port range for incoming and outgoing RTP traffic
+        '';
+      };
+
+      rtpPortHigh = mkOption {
+        type = types.int;
+        default = 7089;
+        description = ''
+         Top of UDP port range for incoming and outgoing RTP traffic
+        '';
+      };
+
+      rtpTimeout = mkOption {
+        type = types.int;
+        default = 300;
+        description = ''
+          Timeout for an RTP stream. If for the specified 
+          number of seconds no data is relayed on an active
+          stream, it is considered dead and will be killed.
+        '';
+      };
+
+      rtpDscp = mkOption {
+        type = types.int;
+        default = 46;
+        description = ''
+          DSCP (differentiated services) value to be assigned
+          to RTP packets. Allows QOS aware routers to handle 
+          different types traffic with different priorities.
+        '';
+      };
+
+      sipDscp = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          DSCP (differentiated services) value to be assigned
+          to SIP packets. Allows QOS aware routers to handle 
+          different types traffic with different priorities.
+        '';
+      };
+
+      passwordFile = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Path to per-user password file.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration to add to siproxd configuration.
+        '';
+      };
+
+    };
+
+  };
+
+  ##### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.siproxyd = {
+      uid = config.ids.uids.siproxd;
+    };
+
+    systemd.services.siproxd = {
+      description = "SIP proxy/masquerading daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.siproxd}/sbin/siproxd -c ${confFile}";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/snapper.nix b/nixpkgs/nixos/modules/services/misc/snapper.nix
new file mode 100644
index 000000000000..6f3aaa973a04
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/snapper.nix
@@ -0,0 +1,152 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.snapper;
+in
+
+{
+  options.services.snapper = {
+
+    snapshotInterval = mkOption {
+      type = types.str;
+      default = "hourly";
+      description = ''
+        Snapshot interval.
+
+        The format is described in
+        <citerefentry><refentrytitle>systemd.time</refentrytitle>
+        <manvolnum>7</manvolnum></citerefentry>.
+      '';
+    };
+
+    cleanupInterval = mkOption {
+      type = types.str;
+      default = "1d";
+      description = ''
+        Cleanup interval.
+
+        The format is described in
+        <citerefentry><refentrytitle>systemd.time</refentrytitle>
+        <manvolnum>7</manvolnum></citerefentry>.
+      '';
+    };
+
+    filters = mkOption {
+      type = types.nullOr types.lines;
+      default = null;
+      description = ''
+        Global display difference filter. See man:snapper(8) for more details.
+      '';
+    };
+
+    configs = mkOption {
+      default = { };
+      example = literalExample {
+        home = {
+          subvolume = "/home";
+          extraConfig = ''
+            ALLOW_USERS="alice"
+          '';
+        };
+      };
+
+      description = ''
+        Subvolume configuration
+      '';
+
+      type = types.attrsOf (types.submodule {
+        options = {
+          subvolume = mkOption {
+            type = types.path;
+            description = ''
+              Path of the subvolume or mount point.
+              This path is a subvolume and has to contain a subvolume named
+              .snapshots.
+              See also man:snapper(8) section PERMISSIONS.
+            '';
+          };
+
+          fstype = mkOption {
+            type = types.enum [ "btrfs" ];
+            default = "btrfs";
+            description = ''
+              Filesystem type. Only btrfs is stable and tested.
+            '';
+          };
+
+          extraConfig = mkOption {
+            type = types.lines;
+            default = "";
+            description = ''
+              Additional configuration next to SUBVOLUME and FSTYPE.
+              See man:snapper-configs(5).
+            '';
+          };
+        };
+      });
+    };
+  };
+
+  config = mkIf (cfg.configs != {}) (let
+    documentation = [ "man:snapper(8)" "man:snapper-configs(5)" ];
+  in {
+
+    environment = {
+
+      systemPackages = [ pkgs.snapper ];
+
+      # Note: snapper/config-templates/default is only needed for create-config
+      #       which is not the NixOS way to configure.
+      etc = {
+
+        "sysconfig/snapper".text = ''
+          SNAPPER_CONFIGS="${lib.concatStringsSep " " (builtins.attrNames cfg.configs)}"
+        '';
+
+      }
+      // (mapAttrs' (name: subvolume: nameValuePair "snapper/configs/${name}" ({
+        text = ''
+          ${subvolume.extraConfig}
+          FSTYPE="${subvolume.fstype}"
+          SUBVOLUME="${subvolume.subvolume}"
+        '';
+      })) cfg.configs)
+      // (lib.optionalAttrs (cfg.filters != null) {
+        "snapper/filters/default.txt".text = cfg.filters;
+      });
+
+    };
+
+    services.dbus.packages = [ pkgs.snapper ];
+
+    systemd.services.snapper-timeline = {
+      description = "Timeline of Snapper Snapshots";
+      inherit documentation;
+      serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --timeline";
+    };
+
+    systemd.timers.snapper-timeline = {
+      description = "Timeline of Snapper Snapshots";
+      inherit documentation;
+      wantedBy = [ "basic.target" ];
+      timerConfig.OnCalendar = cfg.snapshotInterval;
+    };
+
+    systemd.services.snapper-cleanup = {
+      description = "Cleanup of Snapper Snapshots";
+      inherit documentation;
+      serviceConfig.ExecStart = "${pkgs.snapper}/lib/snapper/systemd-helper --cleanup";
+    };
+
+    systemd.timers.snapper-cleanup = {
+      description = "Cleanup of Snapper Snapshots";
+      inherit documentation;
+      wantedBy = [ "basic.target" ];
+      timerConfig.OnBootSec = "10m";
+      timerConfig.OnUnitActiveSec = cfg.cleanupInterval;
+    };
+  });
+}
+
diff --git a/nixpkgs/nixos/modules/services/misc/sonarr.nix b/nixpkgs/nixos/modules/services/misc/sonarr.nix
new file mode 100644
index 000000000000..77c7f0582d0b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sonarr.nix
@@ -0,0 +1,76 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sonarr;
+in
+{
+  options = {
+    services.sonarr = {
+      enable = mkEnableOption "Sonarr";
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/sonarr/.config/NzbDrone";
+        description = "The directory where Sonarr stores its data files.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Sonarr web interface
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "sonarr";
+        description = "User account under which Sonaar runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "sonarr";
+        description = "Group under which Sonaar runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.sonarr = {
+      description = "Sonarr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${pkgs.sonarr}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'";
+        Restart = "on-failure";
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 8989 ];
+    };
+
+    users.users = mkIf (cfg.user == "sonarr") {
+      sonarr = {
+        group = cfg.group;
+        home = cfg.dataDir;
+        uid = config.ids.uids.sonarr;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "sonarr") {
+      sonarr.gid = config.ids.gids.sonarr;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix b/nixpkgs/nixos/modules/services/misc/spice-vdagentd.nix
new file mode 100644
index 000000000000..2dd9fcf68ab0
--- /dev/null
+++ b/nixpkgs/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 "/run/spice-vdagentd/"
+      '';
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.spice-vdagent}/bin/spice-vdagentd";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/ssm-agent.nix b/nixpkgs/nixos/modules/services/misc/ssm-agent.nix
new file mode 100644
index 000000000000..f7c05deeecb5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/ssm-agent.nix
@@ -0,0 +1,45 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+let
+  cfg = config.services.ssm-agent;
+
+  # The SSM agent doesn't pay attention to our /etc/os-release yet, and the lsb-release tool
+  # in nixpkgs doesn't seem to work properly on NixOS, so let's just fake the two fields SSM
+  # looks for. See https://github.com/aws/amazon-ssm-agent/issues/38 for upstream fix.
+  fake-lsb-release = pkgs.writeScriptBin "lsb_release" ''
+    #!${pkgs.runtimeShell}
+
+    case "$1" in
+      -i) echo "nixos";;
+      -r) echo "${config.system.nixos.version}";;
+    esac
+  '';
+in {
+  options.services.ssm-agent = {
+    enable = mkEnableOption "AWS SSM agent";
+
+    package = mkOption {
+      type = types.path;
+      description = "The SSM agent package to use";
+      default = pkgs.ssm-agent;
+      defaultText = "pkgs.ssm-agent";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.ssm-agent = {
+      inherit (cfg.package.meta) description;
+      after    = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ fake-lsb-release ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/agent";
+        KillMode = "process";
+        Restart = "on-failure";
+        RestartSec = "15min";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/sssd.nix b/nixpkgs/nixos/modules/services/misc/sssd.nix
new file mode 100644
index 000000000000..3da99a3b38c1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sssd.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.sssd;
+  nscd = config.services.nscd;
+in {
+  options = {
+    services.sssd = {
+      enable = mkEnableOption "the System Security Services Daemon";
+
+      config = mkOption {
+        type = types.lines;
+        description = "Contents of <filename>sssd.conf</filename>.";
+        default = ''
+          [sssd]
+          config_file_version = 2
+          services = nss, pam
+          domains = shadowutils
+
+          [nss]
+
+          [pam]
+
+          [domain/shadowutils]
+          id_provider = proxy
+          proxy_lib_name = files
+          auth_provider = proxy
+          proxy_pam_target = sssd-shadowutils
+          proxy_fast_alias = True
+        '';
+      };
+
+      sshAuthorizedKeysIntegration = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to make sshd look up authorized keys from SSS.
+          For this to work, the <literal>ssh</literal> SSS service must be enabled in the sssd configuration.
+        '';
+      };
+    };
+  };
+  config = mkMerge [
+    (mkIf cfg.enable {
+      systemd.services.sssd = {
+        description = "System Security Services Daemon";
+        wantedBy    = [ "multi-user.target" ];
+        before = [ "systemd-user-sessions.service" "nss-user-lookup.target" ];
+        after = [ "network-online.target" "nscd.service" ];
+        requires = [ "network-online.target" "nscd.service" ];
+        wants = [ "nss-user-lookup.target" ];
+        restartTriggers = [
+          config.environment.etc."nscd.conf".source
+          config.environment.etc."sssd/sssd.conf".source
+        ];
+        script = ''
+          export LDB_MODULES_PATH+="''${LDB_MODULES_PATH+:}${pkgs.ldb}/modules/ldb:${pkgs.sssd}/modules/ldb"
+          mkdir -p /var/lib/sss/{pubconf,db,mc,pipes,gpo_cache,secrets} /var/lib/sss/pipes/private /var/lib/sss/pubconf/krb5.include.d
+          ${pkgs.sssd}/bin/sssd -D
+        '';
+        serviceConfig = {
+          Type = "forking";
+          PIDFile = "/run/sssd.pid";
+        };
+      };
+
+      environment.etc."sssd/sssd.conf" = {
+        text = cfg.config;
+        mode = "0400";
+      };
+
+      system.nssModules = pkgs.sssd;
+      system.nssDatabases = {
+        group = [ "sss" ];
+        passwd = [ "sss" ];
+        services = [ "sss" ];
+        shadow = [ "sss" ];
+      };
+      services.dbus.packages = [ pkgs.sssd ];
+    })
+
+    (mkIf cfg.sshAuthorizedKeysIntegration {
+    # Ugly: sshd refuses to start if a store path is given because /nix/store is group-writable.
+    # So indirect by a symlink.
+    environment.etc."ssh/authorized_keys_command" = {
+      mode = "0755";
+      text = ''
+        #!/bin/sh
+        exec ${pkgs.sssd}/bin/sss_ssh_authorizedkeys "$@"
+      '';
+    };
+    services.openssh.authorizedKeysCommand = "/etc/ssh/authorized_keys_command";
+    services.openssh.authorizedKeysCommandUser = "nobody";
+  })];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/subsonic.nix b/nixpkgs/nixos/modules/services/misc/subsonic.nix
new file mode 100644
index 000000000000..152917d345cc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/subsonic.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.subsonic; in {
+  options = {
+    services.subsonic = {
+      enable = mkEnableOption "Subsonic daemon";
+
+      home = mkOption {
+        type = types.path;
+        default = "/var/lib/subsonic";
+        description = ''
+          The directory where Subsonic will create files.
+          Make sure it is writable.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = ''
+          The host name or IP address on which to bind Subsonic.
+          Only relevant if you have multiple network interfaces and want
+          to make Subsonic available on only one of them. The default value
+          will bind Subsonic to all available network interfaces.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 4040;
+        description = ''
+          The port on which Subsonic will listen for
+          incoming HTTP traffic. Set to 0 to disable.
+        '';
+      };
+
+      httpsPort = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          The port on which Subsonic will listen for
+          incoming HTTPS traffic. Set to 0 to disable.
+        '';
+      };
+
+      contextPath = mkOption {
+        type = types.path;
+        default = "/";
+        description = ''
+          The context path, i.e., the last part of the Subsonic
+          URL. Typically '/' or '/subsonic'. Default '/'
+        '';
+      };
+
+      maxMemory = mkOption {
+        type = types.int;
+        default = 100;
+        description = ''
+          The memory limit (max Java heap size) in megabytes.
+          Default: 100
+        '';
+      };
+
+      defaultMusicFolder = mkOption {
+        type = types.path;
+        default = "/var/music";
+        description = ''
+          Configure Subsonic to use this folder for music.  This option
+          only has effect the first time Subsonic is started.
+        '';
+      };
+
+      defaultPodcastFolder = mkOption {
+        type = types.path;
+        default = "/var/music/Podcast";
+        description = ''
+          Configure Subsonic to use this folder for Podcasts.  This option
+          only has effect the first time Subsonic is started.
+        '';
+      };
+
+      defaultPlaylistFolder = mkOption {
+        type = types.path;
+        default = "/var/playlists";
+        description = ''
+          Configure Subsonic to use this folder for playlists.  This option
+          only has effect the first time Subsonic is started.
+        '';
+      };
+
+      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
+          ${cfg.home}/transcoders.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.subsonic = {
+      description = "Personal media streamer";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        ${pkgs.jre}/bin/java -Xmx${toString cfg.maxMemory}m \
+          -Dsubsonic.home=${cfg.home} \
+          -Dsubsonic.host=${cfg.listenAddress} \
+          -Dsubsonic.port=${toString cfg.port} \
+          -Dsubsonic.httpsPort=${toString cfg.httpsPort} \
+          -Dsubsonic.contextPath=${cfg.contextPath} \
+          -Dsubsonic.defaultMusicFolder=${cfg.defaultMusicFolder} \
+          -Dsubsonic.defaultPodcastFolder=${cfg.defaultPodcastFolder} \
+          -Dsubsonic.defaultPlaylistFolder=${cfg.defaultPlaylistFolder} \
+          -Djava.awt.headless=true \
+          -verbose:gc \
+          -jar ${pkgs.subsonic}/subsonic-booter-jar-with-dependencies.jar
+      '';
+
+      preStart = ''
+        # Formerly this module set cfg.home to /var/subsonic. Try to move
+        # /var/subsonic to cfg.home.
+        oldHome="/var/subsonic"
+        if [ "${cfg.home}" != "$oldHome" ] &&
+                ! [ -e "${cfg.home}" ] &&
+                [ -d "$oldHome" ] &&
+                [ $(${pkgs.coreutils}/bin/stat -c %u "$oldHome") -eq \
+                    ${toString config.users.users.subsonic.uid} ]; then
+            logger Moving "$oldHome" to "${cfg.home}"
+            ${pkgs.coreutils}/bin/mv -T "$oldHome" "${cfg.home}"
+        fi
+
+        # Install transcoders.
+        ${pkgs.coreutils}/bin/rm -rf ${cfg.home}/transcode ; \
+        ${pkgs.coreutils}/bin/mkdir -p ${cfg.home}/transcode ; \
+        ${pkgs.bash}/bin/bash -c ' \
+          for exe in "$@"; do \
+            ${pkgs.coreutils}/bin/ln -sf "$exe" ${cfg.home}/transcode; \
+          done' IGNORED_FIRST_ARG ${toString cfg.transcoders}
+      '';
+      serviceConfig = {
+        # Needed for Subsonic to find subsonic.war.
+        WorkingDirectory = "${pkgs.subsonic}";
+        Restart = "always";
+        User = "subsonic";
+        UMask = "0022";
+      };
+    };
+
+    users.users.subsonic = {
+      description = "Subsonic daemon user";
+      home = cfg.home;
+      createHome = true;
+      group = "subsonic";
+      uid = config.ids.uids.subsonic;
+    };
+
+    users.groups.subsonic.gid = config.ids.gids.subsonic;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/sundtek.nix b/nixpkgs/nixos/modules/services/misc/sundtek.nix
new file mode 100644
index 000000000000..e3234518c940
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sundtek.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sundtek;
+
+in
+{
+  options.services.sundtek = {
+    enable = mkEnableOption "Sundtek driver";
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.sundtek ];
+
+    systemd.services.sundtek = {
+      description = "Sundtek driver";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = ''
+          ${pkgs.sundtek}/bin/mediasrv -d -v -p ${pkgs.sundtek}/bin ;\
+          ${pkgs.sundtek}/bin/mediaclient --start --wait-for-devices
+          '';
+        ExecStop = "${pkgs.sundtek}/bin/mediaclient --shutdown";
+        RemainAfterExit = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/svnserve.nix b/nixpkgs/nixos/modules/services/misc/svnserve.nix
new file mode 100644
index 000000000000..3335ed09d40e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/svnserve.nix
@@ -0,0 +1,45 @@
+# SVN server
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.svnserve;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.svnserve = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable svnserve to serve Subversion repositories through the SVN protocol.";
+      };
+
+      svnBaseDir = mkOption {
+        default = "/repos";
+	description = "Base directory from which Subversion repositories are accessed.";
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.svnserve = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = "mkdir -p ${cfg.svnBaseDir}";
+      script = "${pkgs.subversion.out}/bin/svnserve -r ${cfg.svnBaseDir} -d --foreground --pid-file=/run/svnserve.pid";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/synergy.nix b/nixpkgs/nixos/modules/services/misc/synergy.nix
new file mode 100644
index 000000000000..5b7cf3ac46c3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/synergy.nix
@@ -0,0 +1,124 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfgC = config.services.synergy.client;
+  cfgS = config.services.synergy.server;
+
+in
+
+{
+  ###### interface
+
+  options = {
+
+    services.synergy = {
+
+      # !!! All these option descriptions needs to be cleaned up.
+
+      client = {
+        enable = mkEnableOption "the Synergy client (receive keyboard and mouse events from a Synergy server)";
+
+        screenName = mkOption {
+          default = "";
+          description = ''
+            Use the given name instead of the hostname to identify
+            ourselves to the server.
+          '';
+        };
+        serverAddress = mkOption {
+          description = ''
+            The server address is of the form: [hostname][:port].  The
+            hostname must be the address or hostname of the server.  The
+            port overrides the default port, 24800.
+          '';
+        };
+        autoStart = mkOption {
+          default = true;
+          type = types.bool;
+          description = "Whether the Synergy client should be started automatically.";
+        };
+      };
+
+      server = {
+        enable = mkEnableOption "the Synergy server (send keyboard and mouse events)";
+
+        configFile = mkOption {
+          default = "/etc/synergy-server.conf";
+          description = "The Synergy server configuration file.";
+        };
+        screenName = mkOption {
+          default = "";
+          description = ''
+            Use the given name instead of the hostname to identify
+            this screen in the configuration.
+          '';
+        };
+        address = mkOption {
+          default = "";
+          description = "Address on which to listen for clients.";
+        };
+        autoStart = mkOption {
+          default = true;
+          type = types.bool;
+          description = "Whether the Synergy server should be started automatically.";
+        };
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkMerge [
+    (mkIf cfgC.enable {
+      systemd.user.services.synergy-client = {
+        after = [ "network.target" "graphical-session.target" ];
+        description = "Synergy client";
+        wantedBy = optional cfgC.autoStart "graphical-session.target";
+        path = [ pkgs.synergy ];
+        serviceConfig.ExecStart = ''${pkgs.synergy}/bin/synergyc -f ${optionalString (cfgC.screenName != "") "-n ${cfgC.screenName}"} ${cfgC.serverAddress}'';
+        serviceConfig.Restart = "on-failure";
+      };
+    })
+    (mkIf cfgS.enable {
+      systemd.user.services.synergy-server = {
+        after = [ "network.target" "graphical-session.target" ];
+        description = "Synergy server";
+        wantedBy = optional cfgS.autoStart "graphical-session.target";
+        path = [ pkgs.synergy ];
+        serviceConfig.ExecStart = ''${pkgs.synergy}/bin/synergys -c ${cfgS.configFile} -f ${optionalString (cfgS.address != "") "-a ${cfgS.address}"} ${optionalString (cfgS.screenName != "") "-n ${cfgS.screenName}" }'';
+        serviceConfig.Restart = "on-failure";
+      };
+    })
+  ];
+
+}
+
+/* SYNERGY SERVER example configuration file
+section: screens
+  laptop:
+  dm:
+  win:
+end
+section: aliases
+    laptop:
+      192.168.5.5
+    dm:
+      192.168.5.78
+    win:
+      192.168.5.54
+end
+section: links
+   laptop:
+       left = dm
+   dm:
+       right = laptop
+       left = win
+  win:
+      right = dm
+end
+*/
diff --git a/nixpkgs/nixos/modules/services/misc/sysprof.nix b/nixpkgs/nixos/modules/services/misc/sysprof.nix
new file mode 100644
index 000000000000..ab91a8b586a2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/sysprof.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, ... }:
+
+{
+  options = {
+    services.sysprof = {
+      enable = lib.mkEnableOption "sysprof profiling daemon";
+    };
+  };
+
+  config = lib.mkIf config.services.sysprof.enable {
+    environment.systemPackages = [ pkgs.sysprof ];
+
+    services.dbus.packages = [ pkgs.sysprof ];
+
+    systemd.packages = [ pkgs.sysprof ];
+  };
+
+  meta.maintainers = pkgs.sysprof.meta.maintainers;
+}
diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/default.nix b/nixpkgs/nixos/modules/services/misc/taskserver/default.nix
new file mode 100644
index 000000000000..a894caed1a34
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/taskserver/default.nix
@@ -0,0 +1,568 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.taskserver;
+
+  taskd = "${pkgs.taskserver}/bin/taskd";
+
+  mkManualPkiOption = desc: mkOption {
+    type = types.nullOr types.path;
+    default = null;
+    description = desc + ''
+      <note><para>
+      Setting this option will prevent automatic CA creation and handling.
+      </para></note>
+    '';
+  };
+
+  manualPkiOptions = {
+    ca.cert = mkManualPkiOption ''
+      Fully qualified path to the CA certificate.
+    '';
+
+    server.cert = mkManualPkiOption ''
+      Fully qualified path to the server certificate.
+    '';
+
+    server.crl = mkManualPkiOption ''
+      Fully qualified path to the server certificate revocation list.
+    '';
+
+    server.key = mkManualPkiOption ''
+      Fully qualified path to the server key.
+    '';
+  };
+
+  mkAutoDesc = preamble: ''
+    ${preamble}
+
+    <note><para>
+    This option is for the automatically handled CA and will be ignored if any
+    of the <option>services.taskserver.pki.manual.*</option> options are set.
+    </para></note>
+  '';
+
+  mkExpireOption = desc: mkOption {
+    type = types.nullOr types.int;
+    default = null;
+    example = 365;
+    apply = val: if val == null then -1 else val;
+    description = mkAutoDesc ''
+      The expiration time of ${desc} in days or <literal>null</literal> for no
+      expiration time.
+    '';
+  };
+
+  autoPkiOptions = {
+    bits = mkOption {
+      type = types.int;
+      default = 4096;
+      example = 2048;
+      description = mkAutoDesc "The bit size for generated keys.";
+    };
+
+    expiration = {
+      ca = mkExpireOption "the CA certificate";
+      server = mkExpireOption "the server certificate";
+      client = mkExpireOption "client certificates";
+      crl = mkExpireOption "the certificate revocation list (CRL)";
+    };
+  };
+
+  needToCreateCA = let
+    notFound = path: let
+      dotted = concatStringsSep "." path;
+    in throw "Can't find option definitions for path `${dotted}'.";
+    findPkiDefinitions = path: attrs: let
+      mkSublist = key: val: let
+        newPath = path ++ singleton key;
+      in if isOption val
+         then attrByPath newPath (notFound newPath) cfg.pki.manual
+         else findPkiDefinitions newPath val;
+    in flatten (mapAttrsToList mkSublist attrs);
+  in all (x: x == null) (findPkiDefinitions [] manualPkiOptions);
+
+  orgOptions = { ... }: {
+    options.users = mkOption {
+      type = types.uniq (types.listOf types.str);
+      default = [];
+      example = [ "alice" "bob" ];
+      description = ''
+        A list of user names that belong to the organization.
+      '';
+    };
+
+    options.groups = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "workers" "slackers" ];
+      description = ''
+        A list of group names that belong to the organization.
+      '';
+    };
+  };
+
+  certtool = "${pkgs.gnutls.bin}/bin/certtool";
+
+  nixos-taskserver = pkgs.pythonPackages.buildPythonApplication {
+    name = "nixos-taskserver";
+
+    src = pkgs.runCommand "nixos-taskserver-src" { preferLocalBuild = true; } ''
+      mkdir -p "$out"
+      cat "${pkgs.substituteAll {
+        src = ./helper-tool.py;
+        inherit taskd certtool;
+        inherit (cfg) dataDir user group fqdn;
+        certBits = cfg.pki.auto.bits;
+        clientExpiration = cfg.pki.auto.expiration.client;
+        crlExpiration = cfg.pki.auto.expiration.crl;
+        isAutoConfig = if needToCreateCA then "True" else "False";
+      }}" > "$out/main.py"
+      cat > "$out/setup.py" <<EOF
+      from setuptools import setup
+      setup(name="nixos-taskserver",
+            py_modules=["main"],
+            install_requires=["Click"],
+            entry_points="[console_scripts]\\nnixos-taskserver=main:cli")
+      EOF
+    '';
+
+    propagatedBuildInputs = [ pkgs.pythonPackages.click ];
+  };
+
+in {
+  options = {
+    services.taskserver = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Taskwarrior server.
+
+          More instructions about NixOS in conjuction with Taskserver can be
+          found in the NixOS manual at
+          <olink targetdoc="manual" targetptr="module-taskserver"/>.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "taskd";
+        description = "User for Taskserver.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "taskd";
+        description = "Group for Taskserver.";
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/taskserver";
+        description = "Data directory for Taskserver.";
+      };
+
+      ciphers = mkOption {
+        type = types.nullOr (types.separatedString ":");
+        default = null;
+        example = "NORMAL:-VERS-SSL3.0";
+        description = let
+          url = "https://gnutls.org/manual/html_node/Priority-Strings.html";
+        in ''
+          List of GnuTLS ciphers to use. See the GnuTLS documentation about
+          priority strings at <link xlink:href="${url}"/> for full details.
+        '';
+      };
+
+      organisations = mkOption {
+        type = types.attrsOf (types.submodule orgOptions);
+        default = {};
+        example.myShinyOrganisation.users = [ "alice" "bob" ];
+        example.myShinyOrganisation.groups = [ "staff" "outsiders" ];
+        example.yetAnotherOrganisation.users = [ "foo" "bar" ];
+        description = ''
+          An attribute set where the keys name the organisation and the values
+          are a set of lists of <option>users</option> and
+          <option>groups</option>.
+        '';
+      };
+
+      confirmation = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Determines whether certain commands are confirmed.
+        '';
+      };
+
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Logs debugging information.
+        '';
+      };
+
+      extensions = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Fully qualified path of the Taskserver extension scripts.
+          Currently there are none.
+        '';
+      };
+
+      ipLog = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Logs the IP addresses of incoming requests.
+        '';
+      };
+
+      queueSize = mkOption {
+        type = types.int;
+        default = 10;
+        description = ''
+          Size of the connection backlog, see <citerefentry>
+            <refentrytitle>listen</refentrytitle>
+            <manvolnum>2</manvolnum>
+          </citerefentry>.
+        '';
+      };
+
+      requestLimit = mkOption {
+        type = types.int;
+        default = 1048576;
+        description = ''
+          Size limit of incoming requests, in bytes.
+        '';
+      };
+
+      allowedClientIDs = mkOption {
+        type = with types; either str (listOf str);
+        default = [];
+        example = [ "[Tt]ask [2-9]+" ];
+        description = ''
+          A list of regular expressions that are matched against the reported
+          client id (such as <literal>task 2.3.0</literal>).
+
+          The values <literal>all</literal> or <literal>none</literal> have
+          special meaning. Overidden by any entry in the option
+          <option>services.taskserver.disallowedClientIDs</option>.
+        '';
+      };
+
+      disallowedClientIDs = mkOption {
+        type = with types; either str (listOf str);
+        default = [];
+        example = [ "[Tt]ask [2-9]+" ];
+        description = ''
+          A list of regular expressions that are matched against the reported
+          client id (such as <literal>task 2.3.0</literal>).
+
+          The values <literal>all</literal> or <literal>none</literal> have
+          special meaning. Any entry here overrides those in
+          <option>services.taskserver.allowedClientIDs</option>.
+        '';
+      };
+
+      listenHost = mkOption {
+        type = types.str;
+        default = "localhost";
+        example = "::";
+        description = ''
+          The address (IPv4, IPv6 or DNS) to listen on.
+
+          If the value is something else than <literal>localhost</literal> the
+          port defined by <option>listenPort</option> is automatically added to
+          <option>networking.firewall.allowedTCPPorts</option>.
+        '';
+      };
+
+      listenPort = mkOption {
+        type = types.int;
+        default = 53589;
+        description = ''
+          Port number of the Taskserver.
+        '';
+      };
+
+      fqdn = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          The fully qualified domain name of this server, which is also used
+          as the common name in the certificates.
+        '';
+      };
+
+      trust = mkOption {
+        type = types.enum [ "allow all" "strict" ];
+        default = "strict";
+        description = ''
+          Determines how client certificates are validated.
+
+          The value <literal>allow all</literal> performs no client
+          certificate validation. This is not recommended. The value
+          <literal>strict</literal> causes the client certificate to be
+          validated against a CA.
+        '';
+      };
+
+      pki.manual = manualPkiOptions;
+      pki.auto = autoPkiOptions;
+
+      config = mkOption {
+        type = types.attrs;
+        example.client.cert = "/tmp/debugging.cert";
+        description = ''
+          Configuration options to pass to Taskserver.
+
+          The options here are the same as described in <citerefentry>
+            <refentrytitle>taskdrc</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>, but with one difference:
+
+          The <literal>server</literal> option is
+          <literal>server.listen</literal> here, because the
+          <literal>server</literal> option would collide with other options
+          like <literal>server.cert</literal> and we would run in a type error
+          (attribute set versus string).
+
+          Nix types like integers or booleans are automatically converted to
+          the right values Taskserver would expect.
+        '';
+        apply = let
+          mkKey = path: if path == ["server" "listen"] then "server"
+                        else concatStringsSep "." path;
+          recurse = path: attrs: let
+            mapper = name: val: let
+              newPath = path ++ [ name ];
+              scalar = if val == true then "true"
+                       else if val == false then "false"
+                       else toString val;
+            in if isAttrs val then recurse newPath val
+               else [ "${mkKey newPath}=${scalar}" ];
+          in concatLists (mapAttrsToList mapper attrs);
+        in recurse [];
+      };
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule ["services" "taskserver" "extraConfig"] ''
+      This option was removed in favor of `services.taskserver.config` with
+      different semantics (it's now a list of attributes instead of lines).
+
+      Please look up the documentation of `services.taskserver.config' to get
+      more information about the new way to pass additional configuration
+      options.
+    '')
+  ];
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+      environment.systemPackages = [ nixos-taskserver ];
+
+      users.users = optionalAttrs (cfg.user == "taskd") {
+        taskd = {
+          uid = config.ids.uids.taskd;
+          description = "Taskserver user";
+          group = cfg.group;
+        };
+      };
+
+      users.groups = optionalAttrs (cfg.group == "taskd") {
+        taskd.gid = config.ids.gids.taskd;
+      };
+
+      services.taskserver.config = {
+        # systemd related
+        daemon = false;
+        log = "-";
+
+        # logging
+        debug = cfg.debug;
+        ip.log = cfg.ipLog;
+
+        # general
+        ciphers = cfg.ciphers;
+        confirmation = cfg.confirmation;
+        extensions = cfg.extensions;
+        queue.size = cfg.queueSize;
+        request.limit = cfg.requestLimit;
+
+        # client
+        client.allow = cfg.allowedClientIDs;
+        client.deny = cfg.disallowedClientIDs;
+
+        # server
+        trust = cfg.trust;
+        server = {
+          listen = "${cfg.listenHost}:${toString cfg.listenPort}";
+        } // (if needToCreateCA then {
+          cert = "${cfg.dataDir}/keys/server.cert";
+          key = "${cfg.dataDir}/keys/server.key";
+          crl = "${cfg.dataDir}/keys/server.crl";
+        } else {
+          cert = "${cfg.pki.manual.server.cert}";
+          key = "${cfg.pki.manual.server.key}";
+          ${mapNullable (_: "crl") cfg.pki.manual.server.crl} = "${cfg.pki.manual.server.crl}";
+        });
+
+        ca.cert = if needToCreateCA then "${cfg.dataDir}/keys/ca.cert"
+                  else "${cfg.pki.manual.ca.cert}";
+      };
+
+      systemd.services.taskserver-init = {
+        wantedBy = [ "taskserver.service" ];
+        before = [ "taskserver.service" ];
+        description = "Initialize Taskserver Data Directory";
+
+        preStart = ''
+          mkdir -m 0770 -p "${cfg.dataDir}"
+          chown "${cfg.user}:${cfg.group}" "${cfg.dataDir}"
+        '';
+
+        script = ''
+          ${taskd} init
+          touch "${cfg.dataDir}/.is_initialized"
+        '';
+
+        environment.TASKDDATA = cfg.dataDir;
+
+        unitConfig.ConditionPathExists = "!${cfg.dataDir}/.is_initialized";
+
+        serviceConfig.Type = "oneshot";
+        serviceConfig.User = cfg.user;
+        serviceConfig.Group = cfg.group;
+        serviceConfig.PermissionsStartOnly = true;
+        serviceConfig.PrivateNetwork = true;
+        serviceConfig.PrivateDevices = true;
+        serviceConfig.PrivateTmp = true;
+      };
+
+      systemd.services.taskserver = {
+        description = "Taskwarrior Server";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        environment.TASKDDATA = cfg.dataDir;
+
+        preStart = let
+          jsonOrgs = builtins.toJSON cfg.organisations;
+          jsonFile = pkgs.writeText "orgs.json" jsonOrgs;
+          helperTool = "${nixos-taskserver}/bin/nixos-taskserver";
+        in "${helperTool} process-json '${jsonFile}'";
+
+        serviceConfig = {
+          ExecStart = let
+            mkCfgFlag = flag: escapeShellArg "--${flag}";
+            cfgFlags = concatMapStringsSep " " mkCfgFlag cfg.config;
+          in "@${taskd} taskd server ${cfgFlags}";
+          ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID";
+          Restart = "on-failure";
+          PermissionsStartOnly = true;
+          PrivateTmp = true;
+          PrivateDevices = true;
+          User = cfg.user;
+          Group = cfg.group;
+        };
+      };
+    })
+    (mkIf (cfg.enable && needToCreateCA) {
+      systemd.services.taskserver-ca = {
+        wantedBy = [ "taskserver.service" ];
+        after = [ "taskserver-init.service" ];
+        before = [ "taskserver.service" ];
+        description = "Initialize CA for TaskServer";
+        serviceConfig.Type = "oneshot";
+        serviceConfig.UMask = "0077";
+        serviceConfig.PrivateNetwork = true;
+        serviceConfig.PrivateTmp = true;
+
+        script = ''
+          silent_certtool() {
+            if ! output="$("${certtool}" "$@" 2>&1)"; then
+              echo "GNUTLS certtool invocation failed with output:" >&2
+              echo "$output" >&2
+            fi
+          }
+
+          mkdir -m 0700 -p "${cfg.dataDir}/keys"
+          chown root:root "${cfg.dataDir}/keys"
+
+          if [ ! -e "${cfg.dataDir}/keys/ca.key" ]; then
+            silent_certtool -p \
+              --bits ${toString cfg.pki.auto.bits} \
+              --outfile "${cfg.dataDir}/keys/ca.key"
+            silent_certtool -s \
+              --template "${pkgs.writeText "taskserver-ca.template" ''
+                cn = ${cfg.fqdn}
+                expiration_days = ${toString cfg.pki.auto.expiration.ca}
+                cert_signing_key
+                ca
+              ''}" \
+              --load-privkey "${cfg.dataDir}/keys/ca.key" \
+              --outfile "${cfg.dataDir}/keys/ca.cert"
+
+            chgrp "${cfg.group}" "${cfg.dataDir}/keys/ca.cert"
+            chmod g+r "${cfg.dataDir}/keys/ca.cert"
+          fi
+
+          if [ ! -e "${cfg.dataDir}/keys/server.key" ]; then
+            silent_certtool -p \
+              --bits ${toString cfg.pki.auto.bits} \
+              --outfile "${cfg.dataDir}/keys/server.key"
+
+            silent_certtool -c \
+              --template "${pkgs.writeText "taskserver-cert.template" ''
+                cn = ${cfg.fqdn}
+                expiration_days = ${toString cfg.pki.auto.expiration.server}
+                tls_www_server
+                encryption_key
+                signing_key
+              ''}" \
+              --load-ca-privkey "${cfg.dataDir}/keys/ca.key" \
+              --load-ca-certificate "${cfg.dataDir}/keys/ca.cert" \
+              --load-privkey "${cfg.dataDir}/keys/server.key" \
+              --outfile "${cfg.dataDir}/keys/server.cert"
+
+            chgrp "${cfg.group}" \
+              "${cfg.dataDir}/keys/server.key" \
+              "${cfg.dataDir}/keys/server.cert"
+
+            chmod g+r \
+              "${cfg.dataDir}/keys/server.key" \
+              "${cfg.dataDir}/keys/server.cert"
+          fi
+
+          if [ ! -e "${cfg.dataDir}/keys/server.crl" ]; then
+            silent_certtool --generate-crl \
+              --template "${pkgs.writeText "taskserver-crl.template" ''
+                expiration_days = ${toString cfg.pki.auto.expiration.crl}
+              ''}" \
+              --load-ca-privkey "${cfg.dataDir}/keys/ca.key" \
+              --load-ca-certificate "${cfg.dataDir}/keys/ca.cert" \
+              --outfile "${cfg.dataDir}/keys/server.crl"
+
+            chgrp "${cfg.group}" "${cfg.dataDir}/keys/server.crl"
+            chmod g+r "${cfg.dataDir}/keys/server.crl"
+          fi
+
+          chmod go+x "${cfg.dataDir}/keys"
+        '';
+      };
+    })
+    (mkIf (cfg.enable && cfg.listenHost != "localhost") {
+      networking.firewall.allowedTCPPorts = [ cfg.listenPort ];
+    })
+  ];
+
+  meta.doc = ./doc.xml;
+}
diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml b/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml
new file mode 100644
index 000000000000..5656bb85b373
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/taskserver/doc.xml
@@ -0,0 +1,135 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    version="5.0"
+    xml:id="module-taskserver">
+ <title>Taskserver</title>
+ <para>
+  Taskserver is the server component of
+  <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a free and
+  open source todo list application.
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://taskwarrior.org/docs/#taskd"/>
+ </para>
+ <section xml:id="module-services-taskserver-configuration">
+  <title>Configuration</title>
+
+  <para>
+   Taskserver does all of its authentication via TLS using client certificates,
+   so you either need to roll your own CA or purchase a certificate from a
+   known CA, which allows creation of client certificates. These certificates
+   are usually advertised as <quote>server certificates</quote>.
+  </para>
+
+  <para>
+   So in order to make it easier to handle your own CA, there is a helper tool
+   called <command>nixos-taskserver</command> which manages the custom CA along
+   with Taskserver organisations, users and groups.
+  </para>
+
+  <para>
+   While the client certificates in Taskserver only authenticate whether a user
+   is allowed to connect, every user has its own UUID which identifies it as an
+   entity.
+  </para>
+
+  <para>
+   With <command>nixos-taskserver</command> the client certificate is created
+   along with the UUID of the user, so it handles all of the credentials needed
+   in order to setup the Taskwarrior client to work with a Taskserver.
+  </para>
+ </section>
+ <section xml:id="module-services-taskserver-nixos-taskserver-tool">
+  <title>The nixos-taskserver tool</title>
+
+  <para>
+   Because Taskserver by default only provides scripts to setup users
+   imperatively, the <command>nixos-taskserver</command> tool is used for
+   addition and deletion of organisations along with users and groups defined
+   by <xref linkend="opt-services.taskserver.organisations"/> and as well for
+   imperative set up.
+  </para>
+
+  <para>
+   The tool is designed to not interfere if the command is used to manually set
+   up some organisations, users or groups.
+  </para>
+
+  <para>
+   For example if you add a new organisation using <command>nixos-taskserver
+   org add foo</command>, the organisation is not modified and deleted no
+   matter what you define in
+   <option>services.taskserver.organisations</option>, even if you're adding
+   the same organisation in that option.
+  </para>
+
+  <para>
+   The tool is modelled to imitate the official <command>taskd</command>
+   command, documentation for each subcommand can be shown by using the
+   <option>--help</option> switch.
+  </para>
+ </section>
+ <section xml:id="module-services-taskserver-declarative-ca-management">
+  <title>Declarative/automatic CA management</title>
+
+  <para>
+   Everything is done according to what you specify in the module options,
+   however in order to set up a Taskwarrior client for synchronisation with a
+   Taskserver instance, you have to transfer the keys and certificates to the
+   client machine.
+  </para>
+
+  <para>
+   This is done using <command>nixos-taskserver user export $orgname
+   $username</command> which is printing a shell script fragment to stdout
+   which can either be used verbatim or adjusted to import the user on the
+   client machine.
+  </para>
+
+  <para>
+   For example, let's say you have the following configuration:
+<screen>
+{
+  <xref linkend="opt-services.taskserver.enable"/> = true;
+  <xref linkend="opt-services.taskserver.fqdn"/> = "server";
+  <xref linkend="opt-services.taskserver.listenHost"/> = "::";
+  <link linkend="opt-services.taskserver.organisations._name_.users">services.taskserver.organisations.my-company.users</link> = [ "alice" ];
+}
+</screen>
+   This creates an organisation called <literal>my-company</literal> with the
+   user <literal>alice</literal>.
+  </para>
+
+  <para>
+   Now in order to import the <literal>alice</literal> user to another machine
+   <literal>alicebox</literal>, all we need to do is something like this:
+<screen>
+<prompt>$ </prompt>ssh server nixos-taskserver user export my-company alice | sh
+</screen>
+   Of course, if no SSH daemon is available on the server you can also copy
+   &amp; paste it directly into a shell.
+  </para>
+
+  <para>
+   After this step the user should be set up and you can start synchronising
+   your tasks for the first time with <command>task sync init</command> on
+   <literal>alicebox</literal>.
+  </para>
+
+  <para>
+   Subsequent synchronisation requests merely require the command <command>task
+   sync</command> after that stage.
+  </para>
+ </section>
+ <section xml:id="module-services-taskserver-manual-ca-management">
+  <title>Manual CA management</title>
+
+  <para>
+   If you set any options within
+   <link linkend="opt-services.taskserver.pki.manual.ca.cert">service.taskserver.pki.manual</link>.*,
+   <command>nixos-taskserver</command> won't issue certificates, but you can
+   still use it for adding or removing user accounts.
+  </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py b/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py
new file mode 100644
index 000000000000..22a3d8d5311b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/taskserver/helper-tool.py
@@ -0,0 +1,688 @@
+import grp
+import json
+import pwd
+import os
+import re
+import string
+import subprocess
+import sys
+
+from contextlib import contextmanager
+from shutil import rmtree
+from tempfile import NamedTemporaryFile
+
+import click
+
+IS_AUTO_CONFIG = @isAutoConfig@ # NOQA
+CERTTOOL_COMMAND = "@certtool@"
+CERT_BITS = "@certBits@"
+CLIENT_EXPIRATION = "@clientExpiration@"
+CRL_EXPIRATION = "@crlExpiration@"
+
+TASKD_COMMAND = "@taskd@"
+TASKD_DATA_DIR = "@dataDir@"
+TASKD_USER = "@user@"
+TASKD_GROUP = "@group@"
+FQDN = "@fqdn@"
+
+CA_KEY = os.path.join(TASKD_DATA_DIR, "keys", "ca.key")
+CA_CERT = os.path.join(TASKD_DATA_DIR, "keys", "ca.cert")
+CRL_FILE = os.path.join(TASKD_DATA_DIR, "keys", "server.crl")
+
+RE_CONFIGUSER = re.compile(r'^\s*user\s*=(.*)$')
+RE_USERKEY = re.compile(r'New user key: (.+)$', re.MULTILINE)
+
+
+def lazyprop(fun):
+    """
+    Decorator which only evaluates the specified function when accessed.
+    """
+    name = '_lazy_' + fun.__name__
+
+    @property
+    def _lazy(self):
+        val = getattr(self, name, None)
+        if val is None:
+            val = fun(self)
+            setattr(self, name, val)
+        return val
+
+    return _lazy
+
+
+class TaskdError(OSError):
+    pass
+
+
+def run_as_taskd_user():
+    uid = pwd.getpwnam(TASKD_USER).pw_uid
+    gid = grp.getgrnam(TASKD_GROUP).gr_gid
+    os.setgid(gid)
+    os.setuid(uid)
+
+
+def taskd_cmd(cmd, *args, **kwargs):
+    """
+    Invoke taskd with the specified command with the privileges of the 'taskd'
+    user and 'taskd' group.
+
+    If 'capture_stdout' is passed as a keyword argument with the value True,
+    the return value are the contents the command printed to stdout.
+    """
+    capture_stdout = kwargs.pop("capture_stdout", False)
+    fun = subprocess.check_output if capture_stdout else subprocess.check_call
+    return fun(
+        [TASKD_COMMAND, cmd, "--data", TASKD_DATA_DIR] + list(args),
+        preexec_fn=run_as_taskd_user,
+        **kwargs
+    )
+
+
+def certtool_cmd(*args, **kwargs):
+    """
+    Invoke certtool from GNUTLS and return the output of the command.
+
+    The provided arguments are added to the certtool command and keyword
+    arguments are added to subprocess.check_output().
+
+    Note that this will suppress all output of certtool and it will only be
+    printed whenever there is an unsuccessful return code.
+    """
+    return subprocess.check_output(
+        [CERTTOOL_COMMAND] + list(args),
+        preexec_fn=lambda: os.umask(0077),
+        stderr=subprocess.STDOUT,
+        **kwargs
+    )
+
+
+def label(msg):
+    if sys.stdout.isatty() or sys.stderr.isatty():
+        sys.stderr.write(msg + "\n")
+
+
+def mkpath(*args):
+    return os.path.join(TASKD_DATA_DIR, "orgs", *args)
+
+
+def mark_imperative(*path):
+    """
+    Mark the specified path as being imperatively managed by creating an empty
+    file called ".imperative", so that it doesn't interfere with the
+    declarative configuration.
+    """
+    open(os.path.join(mkpath(*path), ".imperative"), 'a').close()
+
+
+def is_imperative(*path):
+    """
+    Check whether the given path is marked as imperative, see mark_imperative()
+    for more information.
+    """
+    full_path = []
+    for component in path:
+        full_path.append(component)
+        if os.path.exists(os.path.join(mkpath(*full_path), ".imperative")):
+            return True
+    return False
+
+
+def fetch_username(org, key):
+    for line in open(mkpath(org, "users", key, "config"), "r"):
+        match = RE_CONFIGUSER.match(line)
+        if match is None:
+            continue
+        return match.group(1).strip()
+    return None
+
+
+@contextmanager
+def create_template(contents):
+    """
+    Generate a temporary file with the specified contents as a list of strings
+    and yield its path as the context.
+    """
+    template = NamedTemporaryFile(mode="w", prefix="certtool-template")
+    template.writelines(map(lambda l: l + "\n", contents))
+    template.flush()
+    yield template.name
+    template.close()
+
+
+def generate_key(org, user):
+    if not IS_AUTO_CONFIG:
+        msg = "Automatic PKI handling is disabled, you need to " \
+              "manually issue a client certificate for user {}.\n"
+        sys.stderr.write(msg.format(user))
+        return
+
+    basedir = os.path.join(TASKD_DATA_DIR, "keys", org, user)
+    if os.path.exists(basedir):
+        raise OSError("Keyfile directory for {} already exists.".format(user))
+
+    privkey = os.path.join(basedir, "private.key")
+    pubcert = os.path.join(basedir, "public.cert")
+
+    try:
+        os.makedirs(basedir, mode=0700)
+
+        certtool_cmd("-p", "--bits", CERT_BITS, "--outfile", privkey)
+
+        template_data = [
+            "organization = {0}".format(org),
+            "cn = {}".format(FQDN),
+            "expiration_days = {}".format(CLIENT_EXPIRATION),
+            "tls_www_client",
+            "encryption_key",
+            "signing_key"
+        ]
+
+        with create_template(template_data) as template:
+            certtool_cmd(
+                "-c",
+                "--load-privkey", privkey,
+                "--load-ca-privkey", CA_KEY,
+                "--load-ca-certificate", CA_CERT,
+                "--template", template,
+                "--outfile", pubcert
+            )
+    except:
+        rmtree(basedir)
+        raise
+
+
+def revoke_key(org, user):
+    basedir = os.path.join(TASKD_DATA_DIR, "keys", org, user)
+    if not os.path.exists(basedir):
+        raise OSError("Keyfile directory for {} doesn't exist.".format(user))
+
+    pubcert = os.path.join(basedir, "public.cert")
+
+    expiration = "expiration_days = {}".format(CRL_EXPIRATION)
+
+    with create_template([expiration]) as template:
+        oldcrl = NamedTemporaryFile(mode="wb", prefix="old-crl")
+        oldcrl.write(open(CRL_FILE, "rb").read())
+        oldcrl.flush()
+        certtool_cmd(
+            "--generate-crl",
+            "--load-crl", oldcrl.name,
+            "--load-ca-privkey", CA_KEY,
+            "--load-ca-certificate", CA_CERT,
+            "--load-certificate", pubcert,
+            "--template", template,
+            "--outfile", CRL_FILE
+        )
+        oldcrl.close()
+    rmtree(basedir)
+
+
+def is_key_line(line, match):
+    return line.startswith("---") and line.lstrip("- ").startswith(match)
+
+
+def getkey(*args):
+    path = os.path.join(TASKD_DATA_DIR, "keys", *args)
+    buf = []
+    for line in open(path, "r"):
+        if len(buf) == 0:
+            if is_key_line(line, "BEGIN"):
+                buf.append(line)
+            continue
+
+        buf.append(line)
+
+        if is_key_line(line, "END"):
+            return ''.join(buf)
+    raise IOError("Unable to get key from {}.".format(path))
+
+
+def mktaskkey(cfg, path, keydata):
+    heredoc = 'cat > "{}" <<EOF\n{}EOF'.format(path, keydata)
+    cmd = 'task config taskd.{} -- "{}"'.format(cfg, path)
+    return heredoc + "\n" + cmd
+
+
+class User(object):
+    def __init__(self, org, name, key):
+        self.__org = org
+        self.name = name
+        self.key = key
+
+    def export(self):
+        credentials = '/'.join([self.__org, self.name, self.key])
+        allow_unquoted = string.ascii_letters + string.digits + "/-_."
+        if not all((c in allow_unquoted) for c in credentials):
+            credentials = "'" + credentials.replace("'", r"'\''") + "'"
+
+        script = []
+
+        if IS_AUTO_CONFIG:
+            pubcert = getkey(self.__org, self.name, "public.cert")
+            privkey = getkey(self.__org, self.name, "private.key")
+            cacert = getkey("ca.cert")
+
+            keydir = "${TASKDATA:-$HOME/.task}/keys"
+
+            script += [
+                "umask 0077",
+                'mkdir -p "{}"'.format(keydir),
+                mktaskkey("certificate", os.path.join(keydir, "public.cert"),
+                          pubcert),
+                mktaskkey("key", os.path.join(keydir, "private.key"), privkey),
+                mktaskkey("ca", os.path.join(keydir, "ca.cert"), cacert)
+            ]
+
+        script.append(
+            "task config taskd.credentials -- {}".format(credentials)
+        )
+
+        return "\n".join(script) + "\n"
+
+
+class Group(object):
+    def __init__(self, org, name):
+        self.__org = org
+        self.name = name
+
+
+class Organisation(object):
+    def __init__(self, name, ignore_imperative):
+        self.name = name
+        self.ignore_imperative = ignore_imperative
+
+    def add_user(self, name):
+        """
+        Create a new user along with a certificate and key.
+
+        Returns a 'User' object or None if the user already exists.
+        """
+        if self.ignore_imperative and is_imperative(self.name):
+            return None
+        if name not in self.users.keys():
+            output = taskd_cmd("add", "user", self.name, name,
+                               capture_stdout=True)
+            key = RE_USERKEY.search(output)
+            if key is None:
+                msg = "Unable to find key while creating user {}."
+                raise TaskdError(msg.format(name))
+
+            generate_key(self.name, name)
+            newuser = User(self.name, name, key.group(1))
+            self._lazy_users[name] = newuser
+            return newuser
+        return None
+
+    def del_user(self, name):
+        """
+        Delete a user and revoke its keys.
+        """
+        if name in self.users.keys():
+            user = self.get_user(name)
+            if self.ignore_imperative and \
+               is_imperative(self.name, "users", user.key):
+                return
+
+            # Work around https://bug.tasktools.org/browse/TD-40:
+            rmtree(mkpath(self.name, "users", user.key))
+
+            revoke_key(self.name, name)
+            del self._lazy_users[name]
+
+    def add_group(self, name):
+        """
+        Create a new group.
+
+        Returns a 'Group' object or None if the group already exists.
+        """
+        if self.ignore_imperative and is_imperative(self.name):
+            return None
+        if name not in self.groups.keys():
+            taskd_cmd("add", "group", self.name, name)
+            newgroup = Group(self.name, name)
+            self._lazy_groups[name] = newgroup
+            return newgroup
+        return None
+
+    def del_group(self, name):
+        """
+        Delete a group.
+        """
+        if name in self.users.keys():
+            if self.ignore_imperative and \
+               is_imperative(self.name, "groups", name):
+                return
+            taskd_cmd("remove", "group", self.name, name)
+            del self._lazy_groups[name]
+
+    def get_user(self, name):
+        return self.users.get(name)
+
+    @lazyprop
+    def users(self):
+        result = {}
+        for key in os.listdir(mkpath(self.name, "users")):
+            user = fetch_username(self.name, key)
+            if user is not None:
+                result[user] = User(self.name, user, key)
+        return result
+
+    def get_group(self, name):
+        return self.groups.get(name)
+
+    @lazyprop
+    def groups(self):
+        result = {}
+        for group in os.listdir(mkpath(self.name, "groups")):
+            result[group] = Group(self.name, group)
+        return result
+
+
+class Manager(object):
+    def __init__(self, ignore_imperative=False):
+        """
+        Instantiates an organisations manager.
+
+        If ignore_imperative is True, all actions that modify data are checked
+        whether they're created imperatively and if so, they will result in no
+        operation.
+        """
+        self.ignore_imperative = ignore_imperative
+
+    def add_org(self, name):
+        """
+        Create a new organisation.
+
+        Returns an 'Organisation' object or None if the organisation already
+        exists.
+        """
+        if name not in self.orgs.keys():
+            taskd_cmd("add", "org", name)
+            neworg = Organisation(name, self.ignore_imperative)
+            self._lazy_orgs[name] = neworg
+            return neworg
+        return None
+
+    def del_org(self, name):
+        """
+        Delete and revoke keys of an organisation with all its users and
+        groups.
+        """
+        org = self.get_org(name)
+        if org is not None:
+            if self.ignore_imperative and is_imperative(name):
+                return
+            for user in org.users.keys():
+                org.del_user(user)
+            for group in org.groups.keys():
+                org.del_group(group)
+            taskd_cmd("remove", "org", name)
+            del self._lazy_orgs[name]
+
+    def get_org(self, name):
+        return self.orgs.get(name)
+
+    @lazyprop
+    def orgs(self):
+        result = {}
+        for org in os.listdir(mkpath()):
+            result[org] = Organisation(org, self.ignore_imperative)
+        return result
+
+
+class OrganisationType(click.ParamType):
+    name = 'organisation'
+
+    def convert(self, value, param, ctx):
+        org = Manager().get_org(value)
+        if org is None:
+            self.fail("Organisation {} does not exist.".format(value))
+        return org
+
+ORGANISATION = OrganisationType()
+
+
+@click.group()
+@click.pass_context
+def cli(ctx):
+    """
+    Manage Taskserver users and certificates
+    """
+    if not IS_AUTO_CONFIG:
+        return
+    for path in (CA_KEY, CA_CERT, CRL_FILE):
+        if not os.path.exists(path):
+            msg = "CA setup not done or incomplete, missing file {}."
+            ctx.fail(msg.format(path))
+
+
+@cli.group("org")
+def org_cli():
+    """
+    Manage organisations
+    """
+    pass
+
+
+@cli.group("user")
+def user_cli():
+    """
+    Manage users
+    """
+    pass
+
+
+@cli.group("group")
+def group_cli():
+    """
+    Manage groups
+    """
+    pass
+
+
+@user_cli.command("list")
+@click.argument("organisation", type=ORGANISATION)
+def list_users(organisation):
+    """
+    List all users belonging to the specified organisation.
+    """
+    label("The following users exists for {}:".format(organisation.name))
+    for user in organisation.users.values():
+        sys.stdout.write(user.name + "\n")
+
+
+@group_cli.command("list")
+@click.argument("organisation", type=ORGANISATION)
+def list_groups(organisation):
+    """
+    List all users belonging to the specified organisation.
+    """
+    label("The following users exists for {}:".format(organisation.name))
+    for group in organisation.groups.values():
+        sys.stdout.write(group.name + "\n")
+
+
+@org_cli.command("list")
+def list_orgs():
+    """
+    List available organisations
+    """
+    label("The following organisations exist:")
+    for org in Manager().orgs:
+        sys.stdout.write(org.name + "\n")
+
+
+@user_cli.command("getkey")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("user")
+def get_uuid(organisation, user):
+    """
+    Get the UUID of the specified user belonging to the specified organisation.
+    """
+    userobj = organisation.get_user(user)
+    if userobj is None:
+        msg = "User {} doesn't exist in organisation {}."
+        sys.exit(msg.format(userobj.name, organisation.name))
+
+    label("User {} has the following UUID:".format(userobj.name))
+    sys.stdout.write(user.key + "\n")
+
+
+@user_cli.command("export")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("user")
+def export_user(organisation, user):
+    """
+    Export user of the specified organisation as a series of shell commands
+    that can be used on the client side to easily import the certificates.
+
+    Note that the private key will be exported as well, so use this with care!
+    """
+    userobj = organisation.get_user(user)
+    if userobj is None:
+        msg = "User {} doesn't exist in organisation {}."
+        sys.exit(msg.format(user, organisation.name))
+
+    sys.stdout.write(userobj.export())
+
+
+@org_cli.command("add")
+@click.argument("name")
+def add_org(name):
+    """
+    Create an organisation with the specified name.
+    """
+    if os.path.exists(mkpath(name)):
+        msg = "Organisation with name {} already exists."
+        sys.exit(msg.format(name))
+
+    taskd_cmd("add", "org", name)
+    mark_imperative(name)
+
+
+@org_cli.command("remove")
+@click.argument("name")
+def del_org(name):
+    """
+    Delete the organisation with the specified name.
+
+    All of the users and groups will be deleted as well and client certificates
+    will be revoked.
+    """
+    Manager().del_org(name)
+    msg = ("Organisation {} deleted. Be sure to restart the Taskserver"
+           " using 'systemctl restart taskserver.service' in order for"
+           " the certificate revocation to apply.")
+    click.echo(msg.format(name), err=True)
+
+
+@user_cli.command("add")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("user")
+def add_user(organisation, user):
+    """
+    Create a user for the given organisation along with a client certificate
+    and print the key of the new user.
+
+    The client certificate along with it's public key can be shown via the
+    'user export' subcommand.
+    """
+    userobj = organisation.add_user(user)
+    if userobj is None:
+        msg = "User {} already exists in organisation {}."
+        sys.exit(msg.format(user, organisation))
+    else:
+        mark_imperative(organisation.name, "users", userobj.key)
+
+
+@user_cli.command("remove")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("user")
+def del_user(organisation, user):
+    """
+    Delete a user from the given organisation.
+
+    This will also revoke the client certificate of the given user.
+    """
+    organisation.del_user(user)
+    msg = ("User {} deleted. Be sure to restart the Taskserver using"
+           " 'systemctl restart taskserver.service' in order for the"
+           " certificate revocation to apply.")
+    click.echo(msg.format(user), err=True)
+
+
+@group_cli.command("add")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("group")
+def add_group(organisation, group):
+    """
+    Create a group for the given organisation.
+    """
+    groupobj = organisation.add_group(group)
+    if groupobj is None:
+        msg = "Group {} already exists in organisation {}."
+        sys.exit(msg.format(group, organisation))
+    else:
+        mark_imperative(organisation.name, "groups", groupobj.name)
+
+
+@group_cli.command("remove")
+@click.argument("organisation", type=ORGANISATION)
+@click.argument("group")
+def del_group(organisation, group):
+    """
+    Delete a group from the given organisation.
+    """
+    organisation.del_group(group)
+    click("Group {} deleted.".format(group), err=True)
+
+
+def add_or_delete(old, new, add_fun, del_fun):
+    """
+    Given an 'old' and 'new' list, figure out the intersections and invoke
+    'add_fun' against every element that is not in the 'old' list and 'del_fun'
+    against every element that is not in the 'new' list.
+
+    Returns a tuple where the first element is the list of elements that were
+    added and the second element consisting of elements that were deleted.
+    """
+    old_set = set(old)
+    new_set = set(new)
+    to_delete = old_set - new_set
+    to_add = new_set - old_set
+    for elem in to_delete:
+        del_fun(elem)
+    for elem in to_add:
+        add_fun(elem)
+    return to_add, to_delete
+
+
+@cli.command("process-json")
+@click.argument('json-file', type=click.File('rb'))
+def process_json(json_file):
+    """
+    Create and delete users, groups and organisations based on a JSON file.
+
+    The structure of this file is exactly the same as the
+    'services.taskserver.organisations' option of the NixOS module and is used
+    for declaratively adding and deleting users.
+
+    Hence this subcommand is not recommended outside of the scope of the NixOS
+    module.
+    """
+    data = json.load(json_file)
+
+    mgr = Manager(ignore_imperative=True)
+    add_or_delete(mgr.orgs.keys(), data.keys(), mgr.add_org, mgr.del_org)
+
+    for org in mgr.orgs.values():
+        if is_imperative(org.name):
+            continue
+        add_or_delete(org.users.keys(), data[org.name]['users'],
+                      org.add_user, org.del_user)
+        add_or_delete(org.groups.keys(), data[org.name]['groups'],
+                      org.add_group, org.del_group)
+
+
+if __name__ == '__main__':
+    cli()
diff --git a/nixpkgs/nixos/modules/services/misc/tautulli.nix b/nixpkgs/nixos/modules/services/misc/tautulli.nix
new file mode 100644
index 000000000000..aded33629f1c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/tautulli.nix
@@ -0,0 +1,81 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.tautulli;
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "plexpy" ] [ "services" "tautulli" ])
+  ];
+
+  options = {
+    services.tautulli = {
+      enable = mkEnableOption "Tautulli Plex Monitor";
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/plexpy";
+        description = "The directory where Tautulli stores its data files.";
+      };
+
+      configFile = mkOption {
+        type = types.str;
+        default = "/var/lib/plexpy/config.ini";
+        description = "The location of Tautulli's config file.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8181;
+        description = "TCP port where Tautulli listens.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "plexpy";
+        description = "User account under which Tautulli runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nogroup";
+        description = "Group under which Tautulli runs.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.tautulli;
+        defaultText = "pkgs.tautulli";
+        description = ''
+          The Tautulli package to use.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.tautulli = {
+      description = "Tautulli Plex Monitor";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        GuessMainPID = "false";
+        ExecStart = "${cfg.package}/bin/tautulli --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port} --pidfile ${cfg.dataDir}/tautulli.pid --nolaunch";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users = mkIf (cfg.user == "plexpy") {
+      plexpy = { group = cfg.group; uid = config.ids.uids.plexpy; };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix b/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix
new file mode 100644
index 000000000000..2adc08f6cfed
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/tiddlywiki.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.tiddlywiki;
+  listenParams = concatStrings (mapAttrsToList (n: v: " '${n}=${toString v}' ") cfg.listenOptions);
+  exe = "${pkgs.nodePackages.tiddlywiki}/lib/node_modules/.bin/tiddlywiki";
+  name = "tiddlywiki";
+  dataDir = "/var/lib/" + name;
+
+in {
+
+  options.services.tiddlywiki = {
+
+    enable = mkEnableOption "TiddlyWiki nodejs server";
+
+    listenOptions = mkOption {
+      type = types.attrs;
+      default = {};
+      example = {
+        credentials = "../credentials.csv";
+        readers="(authenticated)";
+        port = 3456;
+      };
+      description = ''
+        Parameters passed to <literal>--listen</literal> command.
+        Refer to <link xlink:href="https://tiddlywiki.com/#WebServer"/>
+        for details on supported values.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd = {
+      services.tiddlywiki = {
+        description = "TiddlyWiki nodejs server";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          Type = "simple";
+          Restart = "on-failure";
+          DynamicUser = true;
+          StateDirectory = name;
+          ExecStartPre = "-${exe} ${dataDir} --init server";
+          ExecStart = "${exe} ${dataDir} --listen ${listenParams}";
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/misc/tzupdate.nix b/nixpkgs/nixos/modules/services/misc/tzupdate.nix
new file mode 100644
index 000000000000..570982ced29a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/tzupdate.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.tzupdate;
+in {
+  options.services.tzupdate = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable the tzupdate timezone updating service. This provides
+        a one-shot service which can be activated with systemctl to 
+        update the timezone.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # We need to have imperative time zone management for this to work.
+    # This will give users an error if they have set an explicit time
+    # zone, which is better than silently overriding it.
+    time.timeZone = null; 
+
+    # We provide a one-shot service which can be manually run. We could
+    # provide a service that runs on startup, but it's tricky to get
+    # a service to run after you have *internet* access.
+    systemd.services.tzupdate = {
+      description = "tzupdate timezone update service";
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        # We could link directly into pkgs.tzdata, but at least timedatectl seems
+        # to expect the symlink to point directly to a file in etc.
+        # Setting the "debian timezone file" to point at /dev/null stops it doing anything.
+        ExecStart = "${pkgs.tzupdate}/bin/tzupdate -z /etc/zoneinfo -d /dev/null";
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.michaelpj ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/uhub.nix b/nixpkgs/nixos/modules/services/misc/uhub.nix
new file mode 100644
index 000000000000..d1b388310280
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/uhub.nix
@@ -0,0 +1,180 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.uhub;
+
+  uhubPkg = pkgs.uhub.override { tlsSupport = cfg.enableTLS; };
+
+  pluginConfig = ""
+  + optionalString cfg.plugins.authSqlite.enable ''
+    plugin ${uhubPkg.mod_auth_sqlite}/mod_auth_sqlite.so "file=${cfg.plugins.authSqlite.file}"
+  ''
+  + optionalString cfg.plugins.logging.enable ''
+    plugin ${uhubPkg.mod_logging}/mod_logging.so ${if cfg.plugins.logging.syslog then "syslog=true" else "file=${cfg.plugins.logging.file}"}
+  ''
+  + optionalString cfg.plugins.welcome.enable ''
+    plugin ${uhubPkg.mod_welcome}/mod_welcome.so "motd=${pkgs.writeText "motd.txt"  cfg.plugins.welcome.motd} rules=${pkgs.writeText "rules.txt" cfg.plugins.welcome.rules}"
+  ''
+  + optionalString cfg.plugins.history.enable ''
+    plugin ${uhubPkg.mod_chat_history}/mod_chat_history.so "history_max=${toString cfg.plugins.history.max} history_default=${toString cfg.plugins.history.default} history_connect=${toString cfg.plugins.history.connect}"
+  '';
+
+  uhubConfigFile = pkgs.writeText "uhub.conf" ''
+    file_acl=${pkgs.writeText "users.conf" cfg.aclConfig}
+    file_plugins=${pkgs.writeText "plugins.conf" pluginConfig}
+    server_bind_addr=${cfg.address}
+    server_port=${toString cfg.port}
+    ${lib.optionalString cfg.enableTLS "tls_enable=yes"}
+    ${cfg.hubConfig}
+  '';
+
+in
+
+{
+  options = {
+
+    services.uhub = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the uhub ADC hub.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 1511;
+        description = "TCP port to bind the hub to.";
+      };
+
+      address = mkOption {
+        type = types.str;
+        default = "any";
+        description = "Address to bind the hub to.";
+      };
+
+      enableTLS = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable TLS support.";
+      };
+
+      hubConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Contents of uhub configuration file.";
+      };
+
+      aclConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Contents of user ACL configuration file.";
+      };
+
+      plugins = {
+
+        authSqlite = {
+          enable = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether to enable the Sqlite authentication database plugin";
+          };
+          file = mkOption {
+            type = types.path;
+            example = "/var/db/uhub-users";
+            description = "Path to user database. Use the uhub-passwd utility to create the database and add/remove users.";
+          };
+        };
+
+        logging = {
+          enable = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether to enable the logging plugin.";
+          };
+          file = mkOption {
+            type = types.str;
+            default = "";
+            description = "Path of log file.";
+          };
+          syslog = mkOption {
+            type = types.bool;
+            default = false;
+            description = "If true then the system log is used instead of writing to file.";
+          };
+        };
+
+        welcome = {
+          enable = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether to enable the welcome plugin.";
+          };
+          motd = mkOption {
+            default = "";
+            type = types.lines;
+            description = ''
+              Welcome message displayed to clients after connecting
+              and with the <literal>!motd</literal> command.
+            '';
+          };
+          rules = mkOption {
+            default = "";
+            type = types.lines;
+            description = ''
+              Rules message, displayed to clients with the <literal>!rules</literal> command.
+            '';
+          };
+        };
+
+        history = {
+          enable = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether to enable the history plugin.";
+          };
+          max = mkOption {
+            type = types.int;
+            default = 200;
+            description = "The maximum number of messages to keep in history";
+          };
+          default = mkOption {
+            type = types.int;
+            default = 10;
+            description = "When !history is provided without arguments, then this default number of messages are returned.";
+          };
+          connect = mkOption {
+            type = types.int;
+            default = 5;
+            description = "The number of chat history messages to send when users connect (0 = do not send any history).";
+          };
+        };
+
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users = {
+      users.uhub.uid = config.ids.uids.uhub;
+      groups.uhub.gid = config.ids.gids.uhub;
+    };
+
+    systemd.services.uhub = {
+      description = "high performance peer-to-peer hub for the ADC network";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "notify";
+        ExecStart  = "${uhubPkg}/bin/uhub -c ${uhubConfigFile} -u uhub -g uhub -L";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/misc/weechat.nix b/nixpkgs/nixos/modules/services/misc/weechat.nix
new file mode 100644
index 000000000000..c6ff540ea12f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/weechat.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.weechat;
+in
+
+{
+  options.services.weechat = {
+    enable = mkEnableOption "weechat";
+    root = mkOption {
+      description = "Weechat state directory.";
+      type = types.str;
+      default = "/var/lib/weechat";
+    };
+    sessionName = mkOption {
+      description = "Name of the `screen' session for weechat.";
+      default = "weechat-screen";
+      type = types.str;
+    };
+    binary = mkOption {
+      description = "Binary to execute (by default \${weechat}/bin/weechat).";
+      example = literalExample ''
+        ''${pkgs.weechat}/bin/weechat-headless
+      '';
+      default = "${pkgs.weechat}/bin/weechat";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users = {
+      groups.weechat = {};
+      users.weechat = {
+        createHome = true;
+        group = "weechat";
+        home = cfg.root;
+        isSystemUser = true;
+      };
+    };
+
+    systemd.services.weechat = {
+      environment.WEECHAT_HOME = cfg.root;
+      serviceConfig = {
+        User = "weechat";
+        Group = "weechat";
+        RemainAfterExit = "yes";
+      };
+      script = "exec ${config.security.wrapperDir}/screen -Dm -S ${cfg.sessionName} ${cfg.binary}";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network.target" ];
+    };
+
+    security.wrappers.screen.source = "${pkgs.screen}/bin/screen";
+  };
+
+  meta.doc = ./weechat.xml;
+}
diff --git a/nixpkgs/nixos/modules/services/misc/weechat.xml b/nixpkgs/nixos/modules/services/misc/weechat.xml
new file mode 100644
index 000000000000..7255edfb9da3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/weechat.xml
@@ -0,0 +1,66 @@
+<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-weechat">
+ <title>WeeChat</title>
+ <para>
+  <link xlink:href="https://weechat.org/">WeeChat</link> is a fast and
+  extensible IRC client.
+ </para>
+ <section xml:id="module-services-weechat-basic-usage">
+  <title>Basic Usage</title>
+
+  <para>
+   By default, the module creates a
+   <literal><link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</link></literal>
+   unit which runs the chat client in a detached
+   <literal><link xlink:href="https://www.gnu.org/software/screen/">screen</link></literal>
+   session.
+  </para>
+
+  <para>
+   This can be done by enabling the <literal>weechat</literal> service:
+<programlisting>
+{ ... }:
+
+{
+  <link linkend="opt-services.weechat.enable">services.weechat.enable</link> = true;
+}
+</programlisting>
+  </para>
+
+  <para>
+   The service is managed by a dedicated user named <literal>weechat</literal>
+   in the state directory <literal>/var/lib/weechat</literal>.
+  </para>
+ </section>
+ <section xml:id="module-services-weechat-reattach">
+  <title>Re-attaching to WeeChat</title>
+
+  <para>
+   WeeChat runs in a screen session owned by a dedicated user. To explicitly
+   allow your another user to attach to this session, the
+   <literal>screenrc</literal> needs to be tweaked by adding
+   <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link>
+   support:
+<programlisting>
+{
+  <link linkend="opt-programs.screen.screenrc">programs.screen.screenrc</link> = ''
+    multiuser on
+    acladd normal_user
+  '';
+}
+</programlisting>
+   Now, the session can be re-attached like this:
+<programlisting>
+screen -x weechat/weechat-screen
+</programlisting>
+  </para>
+
+  <para>
+   <emphasis>The session name can be changed using
+   <link linkend="opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis>
+  </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/misc/xmr-stak.nix b/nixpkgs/nixos/modules/services/misc/xmr-stak.nix
new file mode 100644
index 000000000000..a87878c31e0d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/xmr-stak.nix
@@ -0,0 +1,93 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xmr-stak;
+
+  pkg = pkgs.xmr-stak.override {
+    inherit (cfg) openclSupport cudaSupport;
+  };
+
+in
+
+{
+  options = {
+    services.xmr-stak = {
+      enable = mkEnableOption "xmr-stak miner";
+      openclSupport = mkEnableOption "support for OpenCL (AMD/ATI graphics cards)";
+      cudaSupport = mkEnableOption "support for CUDA (NVidia graphics cards)";
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "--noCPU" "--currency monero" ];
+        description = "List of parameters to pass to xmr-stak.";
+      };
+
+      configFiles = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        example = literalExample ''
+          {
+            "config.txt" = '''
+              "verbose_level" : 4,
+              "h_print_time" : 60,
+              "tls_secure_algo" : true,
+            ''';
+            "pools.txt" = '''
+              "currency" : "monero7",
+              "pool_list" :
+              [ { "pool_address" : "pool.supportxmr.com:443",
+                  "wallet_address" : "my-wallet-address",
+                  "rig_id" : "",
+                  "pool_password" : "nixos",
+                  "use_nicehash" : false,
+                  "use_tls" : true,
+                  "tls_fingerprint" : "",
+                  "pool_weight" : 23
+                },
+              ],
+            ''';
+          }
+        '';
+        description = ''
+          Content of config files like config.txt, pools.txt or cpu.txt.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.xmr-stak = {
+      wantedBy = [ "multi-user.target" ];
+      bindsTo = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      environment = mkIf cfg.cudaSupport {
+        LD_LIBRARY_PATH = "${pkgs.linuxPackages_latest.nvidia_x11}/lib";
+      };
+
+      preStart = concatStrings (flip mapAttrsToList cfg.configFiles (fn: content: ''
+        ln -sf '${pkgs.writeText "xmr-stak-${fn}" content}' '${fn}'
+      ''));
+
+      serviceConfig = let rootRequired = cfg.openclSupport || cfg.cudaSupport; in {
+        ExecStart = "${pkg}/bin/xmr-stak ${concatStringsSep " " cfg.extraArgs}";
+        # xmr-stak generates cpu and/or gpu configuration files
+        WorkingDirectory = "/tmp";
+        PrivateTmp = true;
+        DynamicUser = !rootRequired;
+        LimitMEMLOCK = toString (1024*1024);
+      };
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule ["services" "xmr-stak" "configText"] ''
+      This option was removed in favour of `services.xmr-stak.configFiles`
+      because the new config file `pools.txt` was introduced. You are
+      now able to define all other config files like cpu.txt or amd.txt.
+    '')
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/zoneminder.nix b/nixpkgs/nixos/modules/services/misc/zoneminder.nix
new file mode 100644
index 000000000000..d9d34b7fac9b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/zoneminder.nix
@@ -0,0 +1,370 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.zoneminder;
+  fpm = config.services.phpfpm.pools.zoneminder;
+  pkg = pkgs.zoneminder;
+
+  dirName = pkg.dirName;
+
+  user = "zoneminder";
+  group = {
+    nginx = config.services.nginx.group;
+    none  = user;
+  }.${cfg.webserver};
+
+  useNginx = cfg.webserver == "nginx";
+
+  defaultDir = "/var/lib/${user}";
+  home = if useCustomDir then cfg.storageDir else defaultDir;
+
+  useCustomDir = cfg.storageDir != null;
+
+  zms = "/cgi-bin/zms";
+
+  dirs = dirList: [ dirName ] ++ map (e: "${dirName}/${e}") dirList;
+
+  cacheDirs = [ "swap" ];
+  libDirs   = [ "events" "exports" "images" "sounds" ];
+
+  dirStanzas = baseDir:
+    lib.concatStringsSep "\n" (map (e:
+      "ZM_DIR_${lib.toUpper e}=${baseDir}/${e}"
+      ) libDirs);
+
+  defaultsFile = pkgs.writeText "60-defaults.conf" ''
+    # 01-system-paths.conf
+    ${dirStanzas home}
+    ZM_PATH_ARP=${lib.getBin pkgs.nettools}/bin/arp
+    ZM_PATH_LOGS=/var/log/${dirName}
+    ZM_PATH_MAP=/dev/shm
+    ZM_PATH_SOCKS=/run/${dirName}
+    ZM_PATH_SWAP=/var/cache/${dirName}/swap
+    ZM_PATH_ZMS=${zms}
+
+    # 02-multiserver.conf
+    ZM_SERVER_HOST=
+
+    # Database
+    ZM_DB_TYPE=mysql
+    ZM_DB_HOST=${cfg.database.host}
+    ZM_DB_NAME=${cfg.database.name}
+    ZM_DB_USER=${cfg.database.username}
+    ZM_DB_PASS=${cfg.database.password}
+
+    # Web
+    ZM_WEB_USER=${user}
+    ZM_WEB_GROUP=${group}
+  '';
+
+  configFile = pkgs.writeText "80-nixos.conf" ''
+    # You can override defaults here
+
+    ${cfg.extraConfig}
+  '';
+
+in {
+  options = {
+    services.zoneminder = with lib; {
+      enable = lib.mkEnableOption ''
+        ZoneMinder
+        </para><para>
+        If you intend to run the database locally, you should set
+        `config.services.zoneminder.database.createLocally` to true. Otherwise,
+        when set to `false` (the default), you will have to create the database
+        and database user as well as populate the database yourself.
+        Additionally, you will need to run `zmupdate.pl` yourself when
+        upgrading to a newer version.
+      '';
+
+      webserver = mkOption {
+        type = types.enum [ "nginx" "none" ];
+        default = "nginx";
+        description = ''
+          The webserver to configure for the PHP frontend.
+          </para>
+          <para>
+
+          Set it to `none` if you want to configure it yourself. PRs are welcome
+          for support for other web servers.
+        '';
+      };
+
+      hostname = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          The hostname on which to listen.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8095;
+        description = ''
+          The port on which to listen.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open the firewall port(s).
+        '';
+      };
+
+      database = {
+        createLocally = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Create the database and database user locally.
+          '';
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = ''
+            Hostname hosting the database.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "zm";
+          description = ''
+            Name of database.
+          '';
+        };
+
+        username = mkOption {
+          type = types.str;
+          default = "zmuser";
+          description = ''
+            Username for accessing the database.
+          '';
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "zmpass";
+          description = ''
+            Username for accessing the database.
+            Not used if <literal>createLocally</literal> is set.
+          '';
+        };
+      };
+
+      cameras = mkOption {
+        type = types.int;
+        default = 1;
+        description = ''
+          Set this to the number of cameras you expect to support.
+        '';
+      };
+
+      storageDir = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/storage/tank";
+        description = ''
+          ZoneMinder can generate quite a lot of data, so in case you don't want
+          to use the default ${home}, you can override the path here.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional configuration added verbatim to the configuration file.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.username == user;
+        message = "services.zoneminder.database.username must be set to ${user} if services.zoneminder.database.createLocally is set true";
+      }
+    ];
+
+    environment.etc = {
+      "zoneminder/60-defaults.conf".source = defaultsFile;
+      "zoneminder/80-nixos.conf".source    = configFile;
+    };
+
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
+      cfg.port
+      6802 # zmtrigger
+    ];
+
+    services = {
+      fcgiwrap = lib.mkIf useNginx {
+        enable = true;
+        preforkProcesses = cfg.cameras;
+        inherit user group;
+      };
+
+      mysql = lib.mkIf cfg.database.createLocally {
+        enable = true;
+        package = lib.mkDefault pkgs.mariadb;
+        ensureDatabases = [ cfg.database.name ];
+        ensureUsers = [{
+          name = cfg.database.username;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }];
+      };
+
+      nginx = lib.mkIf useNginx {
+        enable = true;
+        virtualHosts = {
+          ${cfg.hostname} = {
+            default = true;
+            root = "${pkg}/share/zoneminder/www";
+            listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ];
+            extraConfig = let
+              fcgi = config.services.fcgiwrap;
+            in ''
+              index index.php;
+
+              location / {
+                try_files $uri $uri/ /index.php?$args =404;
+
+                rewrite ^/skins/.*/css/fonts/(.*)$ /fonts/$1 permanent;
+
+                location ~ /api/(css|img|ico) {
+                  rewrite ^/api(.+)$ /api/app/webroot/$1 break;
+                  try_files $uri $uri/ =404;
+                }
+
+                location ~ \.(gif|ico|jpg|jpeg|png)$ {
+                  access_log off;
+                  expires 30d;
+                }
+
+                location /api {
+                  rewrite ^/api(.+)$ /api/app/webroot/index.php?p=$1 last;
+                }
+
+                location /cgi-bin {
+                  gzip off;
+
+                  include ${pkgs.nginx}/conf/fastcgi_params;
+                  fastcgi_param SCRIPT_FILENAME ${pkg}/libexec/zoneminder/${zms};
+                  fastcgi_param HTTP_PROXY "";
+                  fastcgi_intercept_errors on;
+
+                  fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress};
+                }
+
+                location /cache/ {
+                  alias /var/cache/${dirName}/;
+                }
+
+                location ~ \.php$ {
+                  try_files $uri =404;
+                  fastcgi_index index.php;
+
+                  include ${pkgs.nginx}/conf/fastcgi_params;
+                  fastcgi_param SCRIPT_FILENAME $request_filename;
+                  fastcgi_param HTTP_PROXY "";
+
+                  fastcgi_pass unix:${fpm.socket};
+                }
+              }
+            '';
+          };
+        };
+      };
+
+      phpfpm = lib.mkIf useNginx {
+        pools.zoneminder = {
+          inherit user group;
+          phpPackage = pkgs.php.withExtensions ({ enabled, all }: enabled ++ [ all.apcu ]);
+          phpOptions = ''
+            date.timezone = "${config.time.timeZone}"
+          '';
+          settings = lib.mapAttrs (name: lib.mkDefault) {
+            "listen.owner" = user;
+            "listen.group" = group;
+            "listen.mode" = "0660";
+
+            "pm" = "dynamic";
+            "pm.start_servers" = 1;
+            "pm.min_spare_servers" = 1;
+            "pm.max_spare_servers" = 2;
+            "pm.max_requests" = 500;
+            "pm.max_children" = 5;
+            "pm.status_path" = "/$pool-status";
+            "ping.path" = "/$pool-ping";
+          };
+        };
+      };
+    };
+
+    systemd.services = {
+      zoneminder = with pkgs; {
+        inherit (zoneminder.meta) description;
+        documentation = [ "https://zoneminder.readthedocs.org/en/latest/" ];
+        path = [
+          coreutils
+          procps
+          psmisc
+        ];
+        after = [ "nginx.service" ] ++ lib.optional cfg.database.createLocally "mysql.service";
+        wantedBy = [ "multi-user.target" ];
+        restartTriggers = [ defaultsFile configFile ];
+        preStart = lib.optionalString useCustomDir ''
+          install -dm775 -o ${user} -g ${group} ${cfg.storageDir}/{${lib.concatStringsSep "," libDirs}}
+        '' + lib.optionalString cfg.database.createLocally ''
+          if ! test -e "/var/lib/${dirName}/db-created"; then
+            ${config.services.mysql.package}/bin/mysql < ${pkg}/share/zoneminder/db/zm_create.sql
+            touch "/var/lib/${dirName}/db-created"
+          fi
+
+          ${zoneminder}/bin/zmupdate.pl -nointeractive
+        '';
+        serviceConfig = {
+          User = user;
+          Group = group;
+          SupplementaryGroups = [ "video" ];
+          ExecStart  = "${zoneminder}/bin/zmpkg.pl start";
+          ExecStop   = "${zoneminder}/bin/zmpkg.pl stop";
+          ExecReload = "${zoneminder}/bin/zmpkg.pl restart";
+          PIDFile = "/run/${dirName}/zm.pid";
+          Type = "forking";
+          Restart = "on-failure";
+          RestartSec = "10s";
+          CacheDirectory = dirs cacheDirs;
+          RuntimeDirectory = dirName;
+          ReadWriteDirectories = lib.mkIf useCustomDir [ cfg.storageDir ];
+          StateDirectory = dirs (if useCustomDir then [] else libDirs);
+          LogsDirectory = dirName;
+          PrivateTmp = true;
+          ProtectSystem = "strict";
+          ProtectKernelTunables = true;
+          SystemCallArchitectures = "native";
+          NoNewPrivileges = true;
+        };
+      };
+    };
+
+    users.groups.${user} = {
+      gid = config.ids.gids.zoneminder;
+    };
+
+    users.users.${user} = {
+      uid = config.ids.uids.zoneminder;
+      group = user;
+      inherit home;
+      inherit (pkgs.zoneminder.meta) description;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ peterhoeg ];
+}
diff --git a/nixpkgs/nixos/modules/services/misc/zookeeper.nix b/nixpkgs/nixos/modules/services/misc/zookeeper.nix
new file mode 100644
index 000000000000..f6af7c75ebae
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/misc/zookeeper.nix
@@ -0,0 +1,155 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.zookeeper;
+
+  zookeeperConfig = ''
+    dataDir=${cfg.dataDir}
+    clientPort=${toString cfg.port}
+    autopurge.purgeInterval=${toString cfg.purgeInterval}
+    ${cfg.extraConf}
+    ${cfg.servers}
+  '';
+
+  configDir = pkgs.buildEnv {
+    name = "zookeeper-conf";
+    paths = [
+      (pkgs.writeTextDir "zoo.cfg" zookeeperConfig)
+      (pkgs.writeTextDir "log4j.properties" cfg.logging)
+    ];
+  };
+
+in {
+
+  options.services.zookeeper = {
+    enable = mkOption {
+      description = "Whether to enable Zookeeper.";
+      default = false;
+      type = types.bool;
+    };
+
+    port = mkOption {
+      description = "Zookeeper Client port.";
+      default = 2181;
+      type = types.int;
+    };
+
+    id = mkOption {
+      description = "Zookeeper ID.";
+      default = 0;
+      type = types.int;
+    };
+
+    purgeInterval = mkOption {
+      description = ''
+        The time interval in hours for which the purge task has to be triggered. Set to a positive integer (1 and above) to enable the auto purging.
+      '';
+      default = 1;
+      type = types.int;
+    };
+
+    extraConf = mkOption {
+      description = "Extra configuration for Zookeeper.";
+      type = types.lines;
+      default = ''
+        initLimit=5
+        syncLimit=2
+        tickTime=2000
+      '';
+    };
+
+    servers = mkOption {
+      description = "All Zookeeper Servers.";
+      default = "";
+      type = types.lines;
+      example = ''
+        server.0=host0:2888:3888
+        server.1=host1:2888:3888
+        server.2=host2:2888:3888
+      '';
+    };
+
+    logging = mkOption {
+      description = "Zookeeper logging configuration.";
+      default = ''
+        zookeeper.root.logger=INFO, CONSOLE
+        log4j.rootLogger=INFO, CONSOLE
+        log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+        log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+        log4j.appender.CONSOLE.layout.ConversionPattern=[myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n
+      '';
+      type = types.lines;
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/zookeeper";
+      description = ''
+        Data directory for Zookeeper
+      '';
+    };
+
+    extraCmdLineOptions = mkOption {
+      description = "Extra command line options for the Zookeeper launcher.";
+      default = [ "-Dcom.sun.management.jmxremote" "-Dcom.sun.management.jmxremote.local.only=true" ];
+      type = types.listOf types.str;
+      example = [ "-Djava.net.preferIPv4Stack=true" "-Dcom.sun.management.jmxremote" "-Dcom.sun.management.jmxremote.local.only=true" ];
+    };
+
+    preferIPv4 = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Add the -Djava.net.preferIPv4Stack=true flag to the Zookeeper server.
+      '';
+    };
+
+    package = mkOption {
+      description = "The zookeeper package to use";
+      default = pkgs.zookeeper;
+      defaultText = "pkgs.zookeeper";
+      type = types.package;
+    };
+
+  };
+
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [cfg.package];
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 zookeeper - - -"
+      "Z '${cfg.dataDir}' 0700 zookeeper - - -"
+    ];
+
+    systemd.services.zookeeper = {
+      description = "Zookeeper Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      environment = { ZOOCFGDIR = configDir; };
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.jre}/bin/java \
+            -cp "${cfg.package}/lib/*:${cfg.package}/${cfg.package.name}.jar:${configDir}" \
+            ${escapeShellArgs cfg.extraCmdLineOptions} \
+            -Dzookeeper.datadir.autocreate=false \
+            ${optionalString cfg.preferIPv4 "-Djava.net.preferIPv4Stack=true"} \
+            org.apache.zookeeper.server.quorum.QuorumPeerMain \
+            ${configDir}/zoo.cfg
+        '';
+        User = "zookeeper";
+      };
+      preStart = ''
+        echo "${toString cfg.id}" > ${cfg.dataDir}/myid
+      '';
+    };
+
+    users.users.zookeeper = {
+      uid = config.ids.uids.zookeeper;
+      description = "Zookeeper daemon user";
+      home = cfg.dataDir;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/alerta.nix b/nixpkgs/nixos/modules/services/monitoring/alerta.nix
new file mode 100644
index 000000000000..34f2d41706a5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/alerta.nix
@@ -0,0 +1,115 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.alerta;
+
+  alertaConf = pkgs.writeTextFile {
+    name = "alertad.conf";
+    text = ''
+      DATABASE_URL = '${cfg.databaseUrl}'
+      DATABASE_NAME = '${cfg.databaseName}'
+      LOG_FILE = '${cfg.logDir}/alertad.log'
+      LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+      CORS_ORIGINS = [ ${concatMapStringsSep ", " (s: "\"" + s + "\"") cfg.corsOrigins} ];
+      AUTH_REQUIRED = ${if cfg.authenticationRequired then "True" else "False"}
+      SIGNUP_ENABLED = ${if cfg.signupEnabled then "True" else "False"}
+      ${cfg.extraConfig}
+    '';
+  };
+in
+{
+  options.services.alerta = {
+    enable = mkEnableOption "alerta";
+
+    port = mkOption {
+      type = types.int;
+      default = 5000;
+      description = "Port of Alerta";
+    };
+
+    bind = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      example = literalExample "0.0.0.0";
+      description = "Address to bind to. The default is to bind to all addresses";
+    };
+
+    logDir = mkOption {
+      type = types.path;
+      description = "Location where the logfiles are stored";
+      default = "/var/log/alerta";
+    };
+
+    databaseUrl = mkOption {
+      type = types.str;
+      description = "URL of the MongoDB or PostgreSQL database to connect to";
+      default = "mongodb://localhost";
+      example = "mongodb://localhost";
+    };
+
+    databaseName = mkOption {
+      type = types.str;
+      description = "Name of the database instance to connect to";
+      default = "monitoring";
+      example = "monitoring";
+    };
+
+    corsOrigins = mkOption {
+      type = types.listOf types.str;
+      description = "List of URLs that can access the API for Cross-Origin Resource Sharing (CORS)";
+      example = [ "http://localhost" "http://localhost:5000" ];
+      default = [ "http://localhost" "http://localhost:5000" ];
+    };
+
+    authenticationRequired = mkOption {
+      type = types.bool;
+      description = "Whether users must authenticate when using the web UI or command-line tool";
+      default = false;
+    };
+
+    signupEnabled = mkOption {
+      type = types.bool;
+      description = "Whether to prevent sign-up of new users via the web UI";
+      default = true;
+    };
+
+    extraConfig = mkOption {
+      description = "These lines go into alertad.conf verbatim.";
+      default = "";
+      type = types.lines;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logDir}' - alerta alerta - -"
+    ];
+
+    systemd.services.alerta = {
+      description = "Alerta Monitoring System";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      environment = {
+        ALERTA_SVR_CONF_FILE = alertaConf;
+      };
+      serviceConfig = {
+        ExecStart = "${pkgs.python36Packages.alerta-server}/bin/alertad run --port ${toString cfg.port} --host ${cfg.bind}";
+        User = "alerta";
+        Group = "alerta";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.python36Packages.alerta ];
+
+    users.users.alerta = {
+      uid = config.ids.uids.alerta;
+      description = "Alerta user";
+    };
+
+    users.groups.alerta = {
+      gid = config.ids.gids.alerta;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix b/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix
new file mode 100644
index 000000000000..75218aa1d46b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/apcupsd.nix
@@ -0,0 +1,191 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.apcupsd;
+
+  configFile = pkgs.writeText "apcupsd.conf" ''
+    ## apcupsd.conf v1.1 ##
+    # apcupsd complains if the first line is not like above.
+    ${cfg.configText}
+    SCRIPTDIR ${toString scriptDir}
+  '';
+
+  # List of events from "man apccontrol"
+  eventList = [
+    "annoyme"
+    "battattach"
+    "battdetach"
+    "changeme"
+    "commfailure"
+    "commok"
+    "doreboot"
+    "doshutdown"
+    "emergency"
+    "failing"
+    "killpower"
+    "loadlimit"
+    "mainsback"
+    "onbattery"
+    "offbattery"
+    "powerout"
+    "remotedown"
+    "runlimit"
+    "timeout"
+    "startselftest"
+    "endselftest"
+  ];
+
+  shellCmdsForEventScript = eventname: commands: ''
+    echo "#!${pkgs.runtimeShell}" > "$out/${eventname}"
+    echo '${commands}' >> "$out/${eventname}"
+    chmod a+x "$out/${eventname}"
+  '';
+
+  eventToShellCmds = event: if builtins.hasAttr event cfg.hooks then (shellCmdsForEventScript event (builtins.getAttr event cfg.hooks)) else "";
+
+  scriptDir = pkgs.runCommand "apcupsd-scriptdir" { preferLocalBuild = true; } (''
+    mkdir "$out"
+    # Copy SCRIPTDIR from apcupsd package
+    cp -r ${pkgs.apcupsd}/etc/apcupsd/* "$out"/
+    # Make the files writeable (nix will unset the write bits afterwards)
+    chmod u+w "$out"/*
+    # Remove the sample event notification scripts, because they don't work
+    # anyways (they try to send mail to "root" with the "mail" command)
+    (cd "$out" && rm changeme commok commfailure onbattery offbattery)
+    # Remove the sample apcupsd.conf file (we're generating our own)
+    rm "$out/apcupsd.conf"
+    # Set the SCRIPTDIR= line in apccontrol to the dir we're creating now
+    sed -i -e "s|^SCRIPTDIR=.*|SCRIPTDIR=$out|" "$out/apccontrol"
+    '' + concatStringsSep "\n" (map eventToShellCmds eventList)
+
+  );
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.apcupsd = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to enable the APC UPS daemon. apcupsd monitors your UPS and
+          permits orderly shutdown of your computer in the event of a power
+          failure. User manual: http://www.apcupsd.com/manual/manual.html.
+          Note that apcupsd runs as root (to allow shutdown of computer).
+          You can check the status of your UPS with the "apcaccess" command.
+        '';
+      };
+
+      configText = mkOption {
+        default = ''
+          UPSTYPE usb
+          NISIP 127.0.0.1
+          BATTERYLEVEL 50
+          MINUTES 5
+        '';
+        type = types.lines;
+        description = ''
+          Contents of the runtime configuration file, apcupsd.conf. The default
+          settings makes apcupsd autodetect USB UPSes, limit network access to
+          localhost and shutdown the system when the battery level is below 50
+          percent, or when the UPS has calculated that it has 5 minutes or less
+          of remaining power-on time. See man apcupsd.conf for details.
+        '';
+      };
+
+      hooks = mkOption {
+        default = {};
+        example = {
+          doshutdown = ''# shell commands to notify that the computer is shutting down'';
+        };
+        type = types.attrsOf types.lines;
+        description = ''
+          Each attribute in this option names an apcupsd event and the string
+          value it contains will be executed in a shell, in response to that
+          event (prior to the default action). See "man apccontrol" for the
+          list of events and what they represent.
+
+          A hook script can stop apccontrol from doing its default action by
+          exiting with value 99. Do not do this unless you know what you're
+          doing.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [ {
+      assertion = let hooknames = builtins.attrNames cfg.hooks; in all (x: elem x eventList) hooknames;
+      message = ''
+        One (or more) attribute names in services.apcupsd.hooks are invalid.
+        Current attribute names: ${toString (builtins.attrNames cfg.hooks)}
+        Valid attribute names  : ${toString eventList}
+      '';
+    } ];
+
+    # Give users access to the "apcaccess" tool
+    environment.systemPackages = [ pkgs.apcupsd ];
+
+    # NOTE 1: apcupsd runs as root because it needs permission to run
+    # "shutdown"
+    #
+    # NOTE 2: When apcupsd calls "wall", it prints an error because stdout is
+    # not connected to a tty (it is connected to the journal):
+    #   wall: cannot get tty name: Inappropriate ioctl for device
+    # The message still gets through.
+    systemd.services.apcupsd = {
+      description = "APC UPS Daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = "mkdir -p /run/apcupsd/";
+      serviceConfig = {
+        ExecStart = "${pkgs.apcupsd}/bin/apcupsd -b -f ${configFile} -d1";
+        # TODO: When apcupsd has initiated a shutdown, systemd always ends up
+        # waiting for it to stop ("A stop job is running for UPS daemon"). This
+        # is weird, because in the journal one can clearly see that apcupsd has
+        # received the SIGTERM signal and has already quit (or so it seems).
+        # This reduces the wait time from 90 seconds (default) to just 5. Then
+        # systemd kills it with SIGKILL.
+        TimeoutStopSec = 5;
+      };
+      unitConfig.Documentation = "man:apcupsd(8)";
+    };
+
+    # A special service to tell the UPS to power down/hibernate just before the
+    # computer shuts down. (The UPS has a built in delay before it actually
+    # shuts off power.) Copied from here:
+    # http://forums.opensuse.org/english/get-technical-help-here/applications/479499-apcupsd-systemd-killpower-issues.html
+    systemd.services.apcupsd-killpower = {
+      description = "APC UPS Kill Power";
+      after = [ "shutdown.target" ]; # append umount.target?
+      before = [ "final.target" ];
+      wantedBy = [ "shutdown.target" ];
+      unitConfig = {
+        ConditionPathExists = "/run/apcupsd/powerfail";
+        DefaultDependencies = "no";
+      };
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.apcupsd}/bin/apcupsd --killpower -f ${configFile}";
+        TimeoutSec = "infinity";
+        StandardOutput = "tty";
+        RemainAfterExit = "yes";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/arbtt.nix b/nixpkgs/nixos/modules/services/monitoring/arbtt.nix
new file mode 100644
index 000000000000..b41a3c7b5016
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/arbtt.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.arbtt;
+in {
+  options = {
+    services.arbtt = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the arbtt statistics capture service.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.haskellPackages.arbtt;
+        defaultText = "pkgs.haskellPackages.arbtt";
+        example = literalExample "pkgs.haskellPackages.arbtt";
+        description = ''
+          The package to use for the arbtt binaries.
+        '';
+      };
+
+      logFile = mkOption {
+        type = types.str;
+        default = "%h/.arbtt/capture.log";
+        example = "/home/username/.arbtt-capture.log";
+        description = ''
+          The log file for captured samples.
+        '';
+      };
+
+      sampleRate = mkOption {
+        type = types.int;
+        default = 60;
+        example = 120;
+        description = ''
+          The sampling interval in seconds.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.arbtt = {
+      description = "arbtt statistics capture service";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${cfg.package}/bin/arbtt-capture --logfile=${cfg.logFile} --sample-rate=${toString cfg.sampleRate}";
+        Restart = "always";
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.michaelpj ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/bosun.nix b/nixpkgs/nixos/modules/services/monitoring/bosun.nix
new file mode 100644
index 000000000000..04e9da1c81a3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/bosun.nix
@@ -0,0 +1,166 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.bosun;
+
+  configFile = pkgs.writeText "bosun.conf" ''
+    ${optionalString (cfg.opentsdbHost !=null) "tsdbHost = ${cfg.opentsdbHost}"}
+    ${optionalString (cfg.influxHost !=null) "influxHost = ${cfg.influxHost}"}
+    httpListen = ${cfg.listenAddress}
+    stateFile = ${cfg.stateFile}
+    ledisDir = ${cfg.ledisDir}
+    checkFrequency = ${cfg.checkFrequency}
+
+    ${cfg.extraConfig}
+  '';
+
+in {
+
+  options = {
+
+    services.bosun = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run bosun.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.bosun;
+        defaultText = "pkgs.bosun";
+        example = literalExample "pkgs.bosun";
+        description = ''
+          bosun binary to use.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "bosun";
+        description = ''
+          User account under which bosun runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "bosun";
+        description = ''
+          Group account under which bosun runs.
+        '';
+      };
+
+      opentsdbHost = mkOption {
+        type = types.nullOr types.str;
+        default = "localhost:4242";
+        description = ''
+          Host and port of the OpenTSDB database that stores bosun data.
+          To disable opentsdb you can pass null as parameter.
+        '';
+      };
+
+      influxHost = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "localhost:8086";
+        description = ''
+           Host and port of the influxdb database.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = ":8070";
+        description = ''
+          The host address and port that bosun's web interface will listen on.
+        '';
+      };
+
+      stateFile = mkOption {
+        type = types.path;
+        default = "/var/lib/bosun/bosun.state";
+        description = ''
+          Path to bosun's state file.
+        '';
+      };
+
+      ledisDir = mkOption {
+        type = types.path;
+        default = "/var/lib/bosun/ledis_data";
+        description = ''
+          Path to bosun's ledis data dir
+        '';
+      };
+
+      checkFrequency = mkOption {
+        type = types.str;
+        default = "5m";
+        description = ''
+          Bosun's check frequency
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration options for Bosun. You should describe your
+          desired templates, alerts, macros, etc through this configuration
+          option.
+
+          A detailed description of the supported syntax can be found at-spi2-atk
+          http://bosun.org/configuration.html
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.bosun = {
+      description = "bosun metrics collector (part of Bosun)";
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        mkdir -p "$(dirname "${cfg.stateFile}")";
+        touch "${cfg.stateFile}"
+        touch "${cfg.stateFile}.tmp"
+
+        mkdir -p "${cfg.ledisDir}";
+
+        if [ "$(id -u)" = 0 ]; then
+          chown ${cfg.user}:${cfg.group} "${cfg.stateFile}"
+          chown ${cfg.user}:${cfg.group} "${cfg.stateFile}.tmp"
+          chown ${cfg.user}:${cfg.group} "${cfg.ledisDir}"
+        fi
+      '';
+
+      serviceConfig = {
+        PermissionsStartOnly = true;
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = ''
+          ${cfg.package}/bin/bosun -c ${configFile}
+        '';
+      };
+    };
+
+    users.users.bosun = {
+      description = "bosun user";
+      group = "bosun";
+      uid = config.ids.uids.bosun;
+    };
+
+    users.groups.bosun.gid = config.ids.gids.bosun;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix b/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix
new file mode 100644
index 000000000000..655a6934a266
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/cadvisor.nix
@@ -0,0 +1,140 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cadvisor;
+
+in {
+  options = {
+    services.cadvisor = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to enable cadvisor service.";
+      };
+
+      listenAddress = mkOption {
+        default = "127.0.0.1";
+        type = types.str;
+        description = "Cadvisor listening host";
+      };
+
+      port = mkOption {
+        default = 8080;
+        type = types.int;
+        description = "Cadvisor listening port";
+      };
+
+      storageDriver = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        example = "influxdb";
+        description = "Cadvisor storage driver.";
+      };
+
+      storageDriverHost = mkOption {
+        default = "localhost:8086";
+        type = types.str;
+        description = "Cadvisor storage driver host.";
+      };
+
+      storageDriverDb = mkOption {
+        default = "root";
+        type = types.str;
+        description = "Cadvisord storage driver database name.";
+      };
+
+      storageDriverUser = mkOption {
+        default = "root";
+        type = types.str;
+        description = "Cadvisor storage driver username.";
+      };
+
+      storageDriverPassword = mkOption {
+        default = "root";
+        type = types.str;
+        description = ''
+          Cadvisor storage driver password.
+
+          Warning: this password is stored in the world-readable Nix store. It's
+          recommended to use the <option>storageDriverPasswordFile</option> option
+          since that gives you control over the security of the password.
+          <option>storageDriverPasswordFile</option> also takes precedence over <option>storageDriverPassword</option>.
+        '';
+      };
+
+      storageDriverPasswordFile = mkOption {
+        type = types.str;
+        description = ''
+          File that contains the cadvisor storage driver password.
+
+          <option>storageDriverPasswordFile</option> takes precedence over <option>storageDriverPassword</option>
+
+          Warning: when <option>storageDriverPassword</option> is non-empty this defaults to a file in the
+          world-readable Nix store that contains the value of <option>storageDriverPassword</option>.
+
+          It's recommended to override this with a path not in the Nix store.
+          Tip: use <link xlink:href='https://nixos.org/nixops/manual/#idm140737318306400'>nixops key management</link>
+        '';
+      };
+
+      storageDriverSecure = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Cadvisor storage driver, enable secure communication.";
+      };
+
+      extraOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Additional cadvisor options.
+          
+          See <link xlink:href='https://github.com/google/cadvisor/blob/master/docs/runtime_options.md'/> for available options.
+        '';
+      };
+    };
+  };
+
+  config = mkMerge [
+    { services.cadvisor.storageDriverPasswordFile = mkIf (cfg.storageDriverPassword != "") (
+        mkDefault (toString (pkgs.writeTextFile {
+          name = "cadvisor-storage-driver-password";
+          text = cfg.storageDriverPassword;
+        }))
+      );
+    }
+
+    (mkIf cfg.enable {
+      systemd.services.cadvisor = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" "docker.service" "influxdb.service" ];
+
+        postStart = mkBefore ''
+          until ${pkgs.curl.bin}/bin/curl -s -o /dev/null 'http://${cfg.listenAddress}:${toString cfg.port}/containers/'; do
+            sleep 1;
+          done
+        '';
+
+        script = ''
+          exec ${pkgs.cadvisor}/bin/cadvisor \
+            -logtostderr=true \
+            -listen_ip="${cfg.listenAddress}" \
+            -port="${toString cfg.port}" \
+            ${escapeShellArgs cfg.extraOptions} \
+            ${optionalString (cfg.storageDriver != null) ''
+              -storage_driver "${cfg.storageDriver}" \
+              -storage_driver_user "${cfg.storageDriverHost}" \
+              -storage_driver_db "${cfg.storageDriverDb}" \
+              -storage_driver_user "${cfg.storageDriverUser}" \
+              -storage_driver_password "$(cat "${cfg.storageDriverPasswordFile}")" \
+              ${optionalString cfg.storageDriverSecure "-storage_driver_secure"}
+            ''}
+        '';
+
+        serviceConfig.TimeoutStartSec=300;
+      };
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/collectd.nix b/nixpkgs/nixos/modules/services/monitoring/collectd.nix
new file mode 100644
index 000000000000..ef3663c62e04
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/collectd.nix
@@ -0,0 +1,138 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.collectd;
+
+  conf = pkgs.writeText "collectd.conf" ''
+    BaseDir "${cfg.dataDir}"
+    AutoLoadPlugin ${boolToString cfg.autoLoadPlugin}
+    Hostname "${config.networking.hostName}"
+
+    LoadPlugin syslog
+    <Plugin "syslog">
+      LogLevel "info"
+      NotifyLevel "OKAY"
+    </Plugin>
+
+    ${concatStrings (mapAttrsToList (plugin: pluginConfig: ''
+      LoadPlugin ${plugin}
+      <Plugin "${plugin}">
+      ${pluginConfig}
+      </Plugin>
+    '') cfg.plugins)}
+
+    ${concatMapStrings (f: ''
+      Include "${f}"
+    '') cfg.include}
+
+    ${cfg.extraConfig}
+  '';
+
+  package =
+    if cfg.buildMinimalPackage
+    then minimalPackage
+    else cfg.package;
+
+  minimalPackage = cfg.package.override {
+    enabledPlugins = [ "syslog" ] ++ builtins.attrNames cfg.plugins;
+  };
+
+in {
+  options.services.collectd = with types; {
+    enable = mkEnableOption "collectd agent";
+
+    package = mkOption {
+      default = pkgs.collectd;
+      defaultText = "pkgs.collectd";
+      description = ''
+        Which collectd package to use.
+      '';
+      type = types.package;
+    };
+
+    buildMinimalPackage = mkOption {
+      default = false;
+      description = ''
+        Build a minimal collectd package with only the configured `services.collectd.plugins`
+      '';
+      type = types.bool;
+    };
+
+    user = mkOption {
+      default = "collectd";
+      description = ''
+        User under which to run collectd.
+      '';
+      type = nullOr str;
+    };
+
+    dataDir = mkOption {
+      default = "/var/lib/collectd";
+      description = ''
+        Data directory for collectd agent.
+      '';
+      type = path;
+    };
+
+    autoLoadPlugin = mkOption {
+      default = false;
+      description = ''
+        Enable plugin autoloading.
+      '';
+      type = bool;
+    };
+
+    include = mkOption {
+      default = [];
+      description = ''
+        Additional paths to load config from.
+      '';
+      type = listOf str;
+    };
+
+    plugins = mkOption {
+      default = {};
+      example = { cpu = ""; memory = ""; network = "Server 192.168.1.1 25826"; };
+      description = ''
+        Attribute set of plugin names to plugin config segments
+      '';
+      type = types.attrsOf types.str;
+    };
+
+    extraConfig = mkOption {
+      default = "";
+      description = ''
+        Extra configuration for collectd.
+      '';
+      type = lines;
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} - - -"
+    ];
+
+    systemd.services.collectd = {
+      description = "Collectd Monitoring Agent";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${package}/sbin/collectd -C ${conf} -f";
+        User = cfg.user;
+        Restart = "on-failure";
+        RestartSec = 3;
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == "collectd") {
+      collectd = {
+        isSystemUser = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix b/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix
new file mode 100644
index 000000000000..88ca3a9227d2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/das_watchdog.nix
@@ -0,0 +1,34 @@
+# A general watchdog for the linux operating system that should run in the
+# background at all times to ensure a realtime process won't hang the machine
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) das_watchdog;
+
+in {
+  ###### interface
+
+  options = {
+    services.das_watchdog.enable = mkEnableOption "realtime watchdog";
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.das_watchdog.enable {
+    environment.systemPackages = [ das_watchdog ];
+    systemd.services.das_watchdog = {
+      description = "Watchdog to ensure a realtime process won't hang the machine";
+      after = [ "multi-user.target" "sound.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = "root";
+        Type = "simple";
+        ExecStart = "${das_watchdog}/bin/das_watchdog";
+        RemainAfterExit = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix b/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix
new file mode 100644
index 000000000000..f1cb890794e1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/datadog-agent.nix
@@ -0,0 +1,272 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.datadog-agent;
+
+  ddConf = {
+    dd_url              = "https://app.datadoghq.com";
+    skip_ssl_validation = false;
+    confd_path          = "/etc/datadog-agent/conf.d";
+    additional_checksd  = "/etc/datadog-agent/checks.d";
+    use_dogstatsd       = true;
+  }
+  // optionalAttrs (cfg.logLevel != null) { log_level = cfg.logLevel; }
+  // optionalAttrs (cfg.hostname != null) { inherit (cfg) hostname; }
+  // optionalAttrs (cfg.tags != null ) { tags = concatStringsSep ", " cfg.tags; }
+  // optionalAttrs (cfg.enableLiveProcessCollection) { process_config = { enabled = "true"; }; }
+  // optionalAttrs (cfg.enableTraceAgent) { apm_config = { enabled = true; }; }
+  // cfg.extraConfig;
+
+  # Generate Datadog configuration files for each configured checks.
+  # This works because check configurations have predictable paths,
+  # and because JSON is a valid subset of YAML.
+  makeCheckConfigs = entries: mapAttrs' (name: conf: {
+    name = "datadog-agent/conf.d/${name}.d/conf.yaml";
+    value.source = pkgs.writeText "${name}-check-conf.yaml" (builtins.toJSON conf);
+  }) entries;
+
+  defaultChecks = {
+    disk = cfg.diskCheck;
+    network = cfg.networkCheck;
+  };
+
+  # Assemble all check configurations and the top-level agent
+  # configuration.
+  etcfiles = with pkgs; with builtins;
+  { "datadog-agent/datadog.yaml" = {
+      source = writeText "datadog.yaml" (toJSON ddConf);
+    };
+  } // makeCheckConfigs (cfg.checks // defaultChecks);
+
+  # Apply the configured extraIntegrations to the provided agent
+  # package. See the documentation of `dd-agent/integrations-core.nix`
+  # for detailed information on this.
+  datadogPkg = cfg.package.override {
+    pythonPackages = pkgs.datadog-integrations-core cfg.extraIntegrations;
+  };
+in {
+  options.services.datadog-agent = {
+    enable = mkOption {
+      description = ''
+        Whether to enable the datadog-agent v6 monitoring service
+      '';
+      default = false;
+      type = types.bool;
+    };
+
+    package = mkOption {
+      default = pkgs.datadog-agent;
+      defaultText = "pkgs.datadog-agent";
+      description = ''
+        Which DataDog v6 agent package to use. Note that the provided
+        package is expected to have an overridable `pythonPackages`-attribute
+        which configures the Python environment with the Datadog
+        checks.
+      '';
+      type = types.package;
+    };
+
+    apiKeyFile = mkOption {
+      description = ''
+        Path to a file containing the Datadog API key to associate the
+        agent with your account.
+      '';
+      example = "/run/keys/datadog_api_key";
+      type = types.path;
+    };
+
+    tags = mkOption {
+      description = "The tags to mark this Datadog agent";
+      example = [ "test" "service" ];
+      default = null;
+      type = types.nullOr (types.listOf types.str);
+    };
+
+    hostname = mkOption {
+      description = "The hostname to show in the Datadog dashboard (optional)";
+      default = null;
+      example = "mymachine.mydomain";
+      type = types.nullOr types.str;
+    };
+
+    logLevel = mkOption {
+      description = "Logging verbosity.";
+      default = null;
+      type = types.nullOr (types.enum ["DEBUG" "INFO" "WARN" "ERROR"]);
+    };
+
+    extraIntegrations = mkOption {
+      default = {};
+      type    = types.attrs;
+
+      description = ''
+        Extra integrations from the Datadog core-integrations
+        repository that should be built and included.
+
+        By default the included integrations are disk, mongo, network,
+        nginx and postgres.
+
+        To include additional integrations the name of the derivation
+        and a function to filter its dependencies from the Python
+        package set must be provided.
+      '';
+
+      example = {
+        ntp = (pythonPackages: [ pythonPackages.ntplib ]);
+      };
+    };
+
+    extraConfig = mkOption {
+      default = {};
+      type = types.attrs;
+      description = ''
+        Extra configuration options that will be merged into the
+        main config file <filename>datadog.yaml</filename>.
+      '';
+     };
+
+    enableLiveProcessCollection = mkOption {
+      description = ''
+        Whether to enable the live process collection agent.
+      '';
+      default = false;
+      type = types.bool;
+    };
+
+    enableTraceAgent = mkOption {
+      description = ''
+        Whether to enable the trace agent.
+      '';
+      default = false;
+      type = types.bool;
+    };
+
+    checks = mkOption {
+      description = ''
+        Configuration for all Datadog checks. Keys of this attribute
+        set will be used as the name of the check to create the
+        appropriate configuration in `conf.d/$check.d/conf.yaml`.
+
+        The configuration is converted into JSON from the plain Nix
+        language configuration, meaning that you should write
+        configuration adhering to Datadog's documentation - but in Nix
+        language.
+
+        Refer to the implementation of this module (specifically the
+        definition of `defaultChecks`) for an example.
+
+        Note: The 'disk' and 'network' check are configured in
+        separate options because they exist by default. Attempting to
+        override their configuration here will have no effect.
+      '';
+
+      example = {
+        http_check = {
+          init_config = null; # sic!
+          instances = [
+            {
+              name = "some-service";
+              url = "http://localhost:1337/healthz";
+              tags = [ "some-service" ];
+            }
+          ];
+        };
+      };
+
+      default = {};
+
+      # sic! The structure of the values is up to the check, so we can
+      # not usefully constrain the type further.
+      type = with types; attrsOf attrs;
+    };
+
+    diskCheck = mkOption {
+      description = "Disk check config";
+      type = types.attrs;
+      default = {
+        init_config = {};
+        instances = [ { use_mount = "false"; } ];
+      };
+    };
+
+    networkCheck = mkOption {
+      description = "Network check config";
+      type = types.attrs;
+      default = {
+        init_config = {};
+        # Network check only supports one configured instance
+        instances = [ { collect_connection_state = false;
+          excluded_interfaces = [ "lo" "lo0" ]; } ];
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ datadogPkg pkgs.sysstat pkgs.procps pkgs.iproute ];
+
+    users.users.datadog = {
+      description = "Datadog Agent User";
+      uid = config.ids.uids.datadog;
+      group = "datadog";
+      home = "/var/log/datadog/";
+      createHome = true;
+    };
+
+    users.groups.datadog.gid = config.ids.gids.datadog;
+
+    systemd.services = let
+      makeService = attrs: recursiveUpdate {
+        path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.iproute ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = "datadog";
+          Group = "datadog";
+          Restart = "always";
+          RestartSec = 2;
+        };
+        restartTriggers = [ datadogPkg ] ++  map (x: x.source) (attrValues etcfiles);
+      } attrs;
+    in {
+      datadog-agent = makeService {
+        description = "Datadog agent monitor";
+        preStart = ''
+          chown -R datadog: /etc/datadog-agent
+          rm -f /etc/datadog-agent/auth_token
+        '';
+        script = ''
+          export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
+          exec ${datadogPkg}/bin/agent run -c /etc/datadog-agent/datadog.yaml
+        '';
+        serviceConfig.PermissionsStartOnly = true;
+      };
+
+      dd-jmxfetch = lib.mkIf (lib.hasAttr "jmx" cfg.checks) (makeService {
+        description = "Datadog JMX Fetcher";
+        path = [ datadogPkg pkgs.python pkgs.sysstat pkgs.procps pkgs.jdk ];
+        serviceConfig.ExecStart = "${datadogPkg}/bin/dd-jmxfetch";
+      });
+
+      datadog-process-agent = lib.mkIf cfg.enableLiveProcessCollection (makeService {
+        description = "Datadog Live Process Agent";
+        path = [ ];
+        script = ''
+          export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
+          ${pkgs.datadog-process-agent}/bin/agent --config /etc/datadog-agent/datadog.yaml
+        '';
+      });
+
+      datadog-trace-agent = lib.mkIf cfg.enableTraceAgent (makeService {
+        description = "Datadog Trace Agent";
+        path = [ ];
+        script = ''
+          export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
+          ${datadogPkg}/bin/trace-agent -config /etc/datadog-agent/datadog.yaml
+        '';
+      });
+
+    };
+
+    environment.etc = etcfiles;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix
new file mode 100644
index 000000000000..045128197421
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent-defaults.nix
@@ -0,0 +1,8 @@
+# Generated using update-dd-agent-default, please re-run after updating dd-agent. DO NOT EDIT MANUALLY.
+[
+  "auto_conf"
+  "agent_metrics.yaml.default"
+  "disk.yaml.default"
+  "network.yaml.default"
+  "ntp.yaml.default"
+]
diff --git a/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
new file mode 100644
index 000000000000..e91717fb2054
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
@@ -0,0 +1,236 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dd-agent;
+
+  ddConf = pkgs.writeText "datadog.conf" ''
+    [Main]
+    dd_url: https://app.datadoghq.com
+    skip_ssl_validation: no
+    api_key: ${cfg.api_key}
+    ${optionalString (cfg.hostname != null) "hostname: ${cfg.hostname}"}
+
+    collector_log_file: /var/log/datadog/collector.log
+    forwarder_log_file: /var/log/datadog/forwarder.log
+    dogstatsd_log_file: /var/log/datadog/dogstatsd.log
+    pup_log_file:       /var/log/datadog/pup.log
+
+    # proxy_host: my-proxy.com
+    # proxy_port: 3128
+    # proxy_user: user
+    # proxy_password: password
+
+    # tags: mytag0, mytag1
+    ${optionalString (cfg.tags != null ) "tags: ${concatStringsSep ", " cfg.tags }"}
+
+    # collect_ec2_tags: no
+    # recent_point_threshold: 30
+    # use_mount: no
+    # listen_port: 17123
+    # graphite_listen_port: 17124
+    # non_local_traffic: no
+    # use_curl_http_client: False
+    # bind_host: localhost
+
+    # use_pup: no
+    # pup_port: 17125
+    # pup_interface: localhost
+    # pup_url: http://localhost:17125
+
+    # dogstatsd_port : 8125
+    # dogstatsd_interval : 10
+    # dogstatsd_normalize : yes
+    # statsd_forward_host: address_of_own_statsd_server
+    # statsd_forward_port: 8125
+
+    # device_blacklist_re: .*\/dev\/mapper\/lxc-box.*
+
+    # ganglia_host: localhost
+    # ganglia_port: 8651
+  '';
+
+  diskConfig = pkgs.writeText "disk.yaml" ''
+    init_config:
+
+    instances:
+      - use_mount: no
+  '';
+
+  networkConfig = pkgs.writeText "network.yaml" ''
+    init_config:
+
+    instances:
+      # Network check only supports one configured instance
+      - collect_connection_state: false
+        excluded_interfaces:
+          - lo
+          - lo0
+  '';
+
+  postgresqlConfig = pkgs.writeText "postgres.yaml" cfg.postgresqlConfig;
+  nginxConfig = pkgs.writeText "nginx.yaml" cfg.nginxConfig;
+  mongoConfig = pkgs.writeText "mongo.yaml" cfg.mongoConfig;
+  jmxConfig = pkgs.writeText "jmx.yaml" cfg.jmxConfig;
+  processConfig = pkgs.writeText "process.yaml" cfg.processConfig;
+
+  etcfiles =
+    let
+      defaultConfd = import ./dd-agent-defaults.nix;
+    in
+      listToAttrs (map (f: {
+        name = "dd-agent/conf.d/${f}";
+        value.source = "${pkgs.dd-agent}/agent/conf.d-system/${f}";
+      }) defaultConfd) //
+      {
+        "dd-agent/datadog.conf".source = ddConf;
+        "dd-agent/conf.d/disk.yaml".source = diskConfig;
+        "dd-agent/conf.d/network.yaml".source = networkConfig;
+      } //
+      (optionalAttrs (cfg.postgresqlConfig != null)
+      {
+        "dd-agent/conf.d/postgres.yaml".source = postgresqlConfig;
+      }) //
+      (optionalAttrs (cfg.nginxConfig != null)
+      {
+        "dd-agent/conf.d/nginx.yaml".source = nginxConfig;
+      }) //
+      (optionalAttrs (cfg.mongoConfig != null)
+      { 
+        "dd-agent/conf.d/mongo.yaml".source = mongoConfig;
+      }) //
+      (optionalAttrs (cfg.processConfig != null)
+      { 
+        "dd-agent/conf.d/process.yaml".source = processConfig;
+      }) //
+      (optionalAttrs (cfg.jmxConfig != null)
+      {
+        "dd-agent/conf.d/jmx.yaml".source = jmxConfig;
+      });
+
+in {
+  options.services.dd-agent = {
+    enable = mkOption {
+      description = ''
+        Whether to enable the dd-agent v5 monitoring service.
+        For datadog-agent v6, see <option>services.datadog-agent.enable</option>.
+      '';
+      default = false;
+      type = types.bool;
+    };
+
+    api_key = mkOption {
+      description = ''
+        The Datadog API key to associate the agent with your account.
+
+        Warning: this key is stored in cleartext within the world-readable
+        Nix store! Consider using the new v6
+        <option>services.datadog-agent</option> module instead.
+      '';
+      example = "ae0aa6a8f08efa988ba0a17578f009ab";
+      type = types.str;
+    };
+
+    tags = mkOption {
+      description = "The tags to mark this Datadog agent";
+      example = [ "test" "service" ];
+      default = null;
+      type = types.nullOr (types.listOf types.str);
+    };
+
+    hostname = mkOption {
+      description = "The hostname to show in the Datadog dashboard (optional)";
+      default = null;
+      example = "mymachine.mydomain";
+      type = types.nullOr types.str;
+    };
+
+    postgresqlConfig = mkOption {
+      description = "Datadog PostgreSQL integration configuration";
+      default = null;
+      type = types.nullOr types.lines;
+    };
+
+    nginxConfig = mkOption {
+      description = "Datadog nginx integration configuration";
+      default = null;
+      type = types.nullOr types.lines;
+    };
+
+    mongoConfig = mkOption {
+      description = "MongoDB integration configuration";
+      default = null;
+      type = types.nullOr types.lines;
+    };
+
+    jmxConfig = mkOption {
+      description = "JMX integration configuration";
+      default = null;
+      type = types.nullOr types.lines;
+    };
+
+    processConfig = mkOption {
+      description = ''
+        Process integration configuration
+        See <link xlink:href="https://docs.datadoghq.com/integrations/process/"/>
+      '';
+      default = null;
+      type = types.nullOr types.lines;
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.dd-agent pkgs.sysstat pkgs.procps ];
+
+    users.users.datadog = {
+      description = "Datadog Agent User";
+      uid = config.ids.uids.datadog;
+      group = "datadog";
+      home = "/var/log/datadog/";
+      createHome = true;
+    };
+
+    users.groups.datadog.gid = config.ids.gids.datadog;
+
+    systemd.services = let
+      makeService = attrs: recursiveUpdate {
+        path = [ pkgs.dd-agent pkgs.python pkgs.sysstat pkgs.procps pkgs.gohai ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = "datadog";
+          Group = "datadog";
+          Restart = "always";
+          RestartSec = 2;
+          PrivateTmp = true;
+        };
+        restartTriggers = [ pkgs.dd-agent ddConf diskConfig networkConfig postgresqlConfig nginxConfig mongoConfig jmxConfig processConfig ];
+      } attrs;
+    in {
+      dd-agent = makeService {
+        description = "Datadog agent monitor";
+        serviceConfig.ExecStart = "${pkgs.dd-agent}/bin/dd-agent foreground";
+      };
+
+      dogstatsd = makeService {
+        description = "Datadog statsd";
+        environment.TMPDIR = "/run/dogstatsd";
+        serviceConfig = {
+          ExecStart = "${pkgs.dd-agent}/bin/dogstatsd start";
+          Type = "forking";
+          PIDFile = "/run/dogstatsd/dogstatsd.pid";
+          RuntimeDirectory = "dogstatsd";
+        };
+      };
+
+      dd-jmxfetch = lib.mkIf (cfg.jmxConfig != null) {
+        description = "Datadog JMX Fetcher";
+        path = [ pkgs.dd-agent pkgs.python pkgs.sysstat pkgs.procps pkgs.jdk ];
+        serviceConfig.ExecStart = "${pkgs.dd-agent}/bin/dd-jmxfetch";
+      };
+    };
+
+    environment.etc = etcfiles;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults b/nixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults
new file mode 100755
index 000000000000..76724173171a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/dd-agent/update-dd-agent-defaults
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+dd=$(nix-build --no-out-link -A dd-agent ../../../..)
+echo '# Generated using update-dd-agent-default, please re-run after updating dd-agent. DO NOT EDIT MANUALLY.' > dd-agent-defaults.nix
+echo '[' >> dd-agent-defaults.nix
+echo '  "auto_conf"' >> dd-agent-defaults.nix
+for f in $(find $dd/agent/conf.d-system -maxdepth 1 -type f | grep -v '\.example' | sort); do
+  echo "  \"$(basename $f)\"" >> dd-agent-defaults.nix
+done
+echo ']' >> dd-agent-defaults.nix
diff --git a/nixpkgs/nixos/modules/services/monitoring/do-agent.nix b/nixpkgs/nixos/modules/services/monitoring/do-agent.nix
new file mode 100644
index 000000000000..2d3fe2f79768
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/do-agent.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.do-agent;
+in
+{
+  options.services.do-agent = {
+    enable = mkEnableOption "do-agent, the DigitalOcean droplet metrics agent";
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.do-agent ];
+
+    systemd.services.do-agent = {
+      description = "DigitalOcean Droplet Metrics Agent";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.do-agent}/bin/do-agent --syslog";
+        Restart = "always";
+        OOMScoreAdjust = -900;
+        SyslogIdentifier = "DigitalOceanAgent";
+        PrivateTmp = "yes";
+        ProtectSystem = "full";
+        ProtectHome = "yes";
+        NoNewPrivileges = "yes";
+        DynamicUser = "yes";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix b/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix
new file mode 100644
index 000000000000..9b65c76ce02e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/fusion-inventory.nix
@@ -0,0 +1,63 @@
+# Fusion Inventory daemon.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.fusionInventory;
+
+  configFile = pkgs.writeText "fusion_inventory.conf" ''
+    server = ${concatStringsSep ", " cfg.servers}
+
+    logger = stderr
+
+    ${cfg.extraConfig}
+  '';
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.fusionInventory = {
+
+      enable = mkEnableOption "Fusion Inventory Agent";
+
+      servers = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          The urls of the OCS/GLPI servers to connect to.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Configuration that is injected verbatim into the configuration file.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.fusion-inventory = {
+      description = "FusionInventory user";
+      isSystemUser = true;
+    };
+
+    systemd.services.fusion-inventory = {
+      description = "Fusion Inventory Agent";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.fusionInventory}/bin/fusioninventory-agent --conf-file=${configFile} --daemon --no-fork";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix b/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix
new file mode 100644
index 000000000000..893c15d568bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.grafana_reporter;
+
+in {
+  options.services.grafana_reporter = {
+    enable = mkEnableOption "grafana_reporter";
+
+    grafana = {
+      protocol = mkOption {
+        description = "Grafana protocol.";
+        default = "http";
+        type = types.enum ["http" "https"];
+      };
+      addr = mkOption {
+        description = "Grafana address.";
+        default = "127.0.0.1";
+        type = types.str;
+      };
+      port = mkOption {
+        description = "Grafana port.";
+        default = 3000;
+        type = types.int;
+      };
+
+    };
+    addr = mkOption {
+      description = "Listening address.";
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    port = mkOption {
+      description = "Listening port.";
+      default = 8686;
+      type = types.int;
+    };
+
+    templateDir = mkOption {
+      description = "Optional template directory to use custom tex templates";
+      default = "${pkgs.grafana_reporter}";
+      type = types.str;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.grafana_reporter = {
+      description = "Grafana Reporter Service Daemon";
+      wantedBy = ["multi-user.target"];
+      after = ["network.target"];
+      serviceConfig = let
+        args = lib.concatStringsSep " " [
+          "-proto ${cfg.grafana.protocol}://"
+          "-ip ${cfg.grafana.addr}:${toString cfg.grafana.port}"
+          "-port :${toString cfg.port}"
+          "-templates ${cfg.templateDir}"
+        ];
+      in {
+        ExecStart = "${pkgs.grafana_reporter}/bin/grafana-reporter ${args}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/grafana.nix b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
new file mode 100644
index 000000000000..b0c81a46d4d8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/grafana.nix
@@ -0,0 +1,559 @@
+{ options, config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.grafana;
+  opt = options.services.grafana;
+
+  envOptions = {
+    PATHS_DATA = cfg.dataDir;
+    PATHS_PLUGINS = "${cfg.dataDir}/plugins";
+    PATHS_LOGS = "${cfg.dataDir}/log";
+
+    SERVER_PROTOCOL = cfg.protocol;
+    SERVER_HTTP_ADDR = cfg.addr;
+    SERVER_HTTP_PORT = cfg.port;
+    SERVER_DOMAIN = cfg.domain;
+    SERVER_ROOT_URL = cfg.rootUrl;
+    SERVER_STATIC_ROOT_PATH = cfg.staticRootPath;
+    SERVER_CERT_FILE = cfg.certFile;
+    SERVER_CERT_KEY = cfg.certKey;
+
+    DATABASE_TYPE = cfg.database.type;
+    DATABASE_HOST = cfg.database.host;
+    DATABASE_NAME = cfg.database.name;
+    DATABASE_USER = cfg.database.user;
+    DATABASE_PASSWORD = cfg.database.password;
+    DATABASE_PATH = cfg.database.path;
+    DATABASE_CONN_MAX_LIFETIME = cfg.database.connMaxLifetime;
+
+    SECURITY_ADMIN_USER = cfg.security.adminUser;
+    SECURITY_ADMIN_PASSWORD = cfg.security.adminPassword;
+    SECURITY_SECRET_KEY = cfg.security.secretKey;
+
+    USERS_ALLOW_SIGN_UP = boolToString cfg.users.allowSignUp;
+    USERS_ALLOW_ORG_CREATE = boolToString cfg.users.allowOrgCreate;
+    USERS_AUTO_ASSIGN_ORG = boolToString cfg.users.autoAssignOrg;
+    USERS_AUTO_ASSIGN_ORG_ROLE = cfg.users.autoAssignOrgRole;
+
+    AUTH_ANONYMOUS_ENABLED = boolToString cfg.auth.anonymous.enable;
+    AUTH_ANONYMOUS_ORG_NAME = cfg.auth.anonymous.org_name;
+    AUTH_ANONYMOUS_ORG_ROLE = cfg.auth.anonymous.org_role;
+
+    ANALYTICS_REPORTING_ENABLED = boolToString cfg.analytics.reporting.enable;
+
+    SMTP_ENABLED = boolToString cfg.smtp.enable;
+    SMTP_HOST = cfg.smtp.host;
+    SMTP_USER = cfg.smtp.user;
+    SMTP_PASSWORD = cfg.smtp.password;
+    SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
+  } // cfg.extraOptions;
+
+  datasourceConfiguration = {
+    apiVersion = 1;
+    datasources = cfg.provision.datasources;
+  };
+
+  datasourceFile = pkgs.writeText "datasource.yaml" (builtins.toJSON datasourceConfiguration);
+
+  dashboardConfiguration = {
+    apiVersion = 1;
+    providers = cfg.provision.dashboards;
+  };
+
+  dashboardFile = pkgs.writeText "dashboard.yaml" (builtins.toJSON dashboardConfiguration);
+
+  provisionConfDir =  pkgs.runCommand "grafana-provisioning" { } ''
+    mkdir -p $out/{datasources,dashboards}
+    ln -sf ${datasourceFile} $out/datasources/datasource.yaml
+    ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml
+  '';
+
+  # Get a submodule without any embedded metadata:
+  _filter = x: filterAttrs (k: v: k != "_module") x;
+
+  # http://docs.grafana.org/administration/provisioning/#datasources
+  grafanaTypes.datasourceConfig = types.submodule {
+    options = {
+      name = mkOption {
+        type = types.str;
+        description = "Name of the datasource. Required";
+      };
+      type = mkOption {
+        type = types.enum ["graphite" "prometheus" "cloudwatch" "elasticsearch" "influxdb" "opentsdb" "mysql" "mssql" "postgres" "loki"];
+        description = "Datasource type. Required";
+      };
+      access = mkOption {
+        type = types.enum ["proxy" "direct"];
+        default = "proxy";
+        description = "Access mode. proxy or direct (Server or Browser in the UI). Required";
+      };
+      orgId = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Org id. will default to orgId 1 if not specified";
+      };
+      url = mkOption {
+        type = types.str;
+        description = "Url of the datasource";
+      };
+      password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Database password, if used";
+      };
+      user = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Database user, if used";
+      };
+      database = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Database name, if used";
+      };
+      basicAuth = mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = "Enable/disable basic auth";
+      };
+      basicAuthUser = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Basic auth username";
+      };
+      basicAuthPassword = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Basic auth password";
+      };
+      withCredentials = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable/disable with credentials headers";
+      };
+      isDefault = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Mark as default datasource. Max one per org";
+      };
+      jsonData = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Datasource specific configuration";
+      };
+      secureJsonData = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Datasource specific secure configuration";
+      };
+      version = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Version";
+      };
+      editable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Allow users to edit datasources from the UI.";
+      };
+    };
+  };
+
+  # http://docs.grafana.org/administration/provisioning/#dashboards
+  grafanaTypes.dashboardConfig = types.submodule {
+    options = {
+      name = mkOption {
+        type = types.str;
+        default = "default";
+        description = "Provider name";
+      };
+      orgId = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Organization ID";
+      };
+      folder = mkOption {
+        type = types.str;
+        default = "";
+        description = "Add dashboards to the specified folder";
+      };
+      type = mkOption {
+        type = types.str;
+        default = "file";
+        description = "Dashboard provider type";
+      };
+      disableDeletion = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable deletion when JSON file is removed";
+      };
+      updateIntervalSeconds = mkOption {
+        type = types.int;
+        default = 10;
+        description = "How often Grafana will scan for changed dashboards";
+      };
+      options = {
+        path = mkOption {
+          type = types.path;
+          description = "Path grafana will watch for dashboards";
+        };
+      };
+    };
+  };
+in {
+  options.services.grafana = {
+    enable = mkEnableOption "grafana";
+
+    protocol = mkOption {
+      description = "Which protocol to listen.";
+      default = "http";
+      type = types.enum ["http" "https" "socket"];
+    };
+
+    addr = mkOption {
+      description = "Listening address.";
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    port = mkOption {
+      description = "Listening port.";
+      default = 3000;
+      type = types.int;
+    };
+
+    domain = mkOption {
+      description = "The public facing domain name used to access grafana from a browser.";
+      default = "localhost";
+      type = types.str;
+    };
+
+    rootUrl = mkOption {
+      description = "Full public facing url.";
+      default = "%(protocol)s://%(domain)s:%(http_port)s/";
+      type = types.str;
+    };
+
+    certFile = mkOption {
+      description = "Cert file for ssl.";
+      default = "";
+      type = types.str;
+    };
+
+    certKey = mkOption {
+      description = "Cert key for ssl.";
+      default = "";
+      type = types.str;
+    };
+
+    staticRootPath = mkOption {
+      description = "Root path for static assets.";
+      default = "${cfg.package}/share/grafana/public";
+      type = types.str;
+    };
+
+    package = mkOption {
+      description = "Package to use.";
+      default = pkgs.grafana;
+      defaultText = "pkgs.grafana";
+      type = types.package;
+    };
+
+    dataDir = mkOption {
+      description = "Data directory.";
+      default = "/var/lib/grafana";
+      type = types.path;
+    };
+
+    database = {
+      type = mkOption {
+        description = "Database type.";
+        default = "sqlite3";
+        type = types.enum ["mysql" "sqlite3" "postgres"];
+      };
+
+      host = mkOption {
+        description = "Database host.";
+        default = "127.0.0.1:3306";
+        type = types.str;
+      };
+
+      name = mkOption {
+        description = "Database name.";
+        default = "grafana";
+        type = types.str;
+      };
+
+      user = mkOption {
+        description = "Database user.";
+        default = "root";
+        type = types.str;
+      };
+
+      password = mkOption {
+        description = ''
+          Database password.
+          This option is mutual exclusive with the passwordFile option.
+        '';
+        default = "";
+        type = types.str;
+      };
+
+      passwordFile = mkOption {
+        description = ''
+          File that containts the database password.
+          This option is mutual exclusive with the password option.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      path = mkOption {
+        description = "Database path.";
+        default = "${cfg.dataDir}/data/grafana.db";
+        type = types.path;
+      };
+
+      connMaxLifetime = mkOption {
+        description = ''
+          Sets the maximum amount of time (in seconds) a connection may be reused.
+          For MySQL this setting should be shorter than the `wait_timeout' variable.
+        '';
+        default = "unlimited";
+        example = 14400;
+        type = types.either types.int (types.enum [ "unlimited" ]);
+      };
+    };
+
+    provision = {
+      enable = mkEnableOption "provision";
+      datasources = mkOption {
+        description = "Grafana datasources configuration";
+        default = [];
+        type = types.listOf grafanaTypes.datasourceConfig;
+        apply = x: map _filter x;
+      };
+      dashboards = mkOption {
+        description = "Grafana dashboard configuration";
+        default = [];
+        type = types.listOf grafanaTypes.dashboardConfig;
+        apply = x: map _filter x;
+      };
+    };
+
+    security = {
+      adminUser = mkOption {
+        description = "Default admin username.";
+        default = "admin";
+        type = types.str;
+      };
+
+      adminPassword = mkOption {
+        description = ''
+          Default admin password.
+          This option is mutual exclusive with the adminPasswordFile option.
+        '';
+        default = "admin";
+        type = types.str;
+      };
+
+      adminPasswordFile = mkOption {
+        description = ''
+          Default admin password.
+          This option is mutual exclusive with the <literal>adminPassword</literal> option.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      secretKey = mkOption {
+        description = "Secret key used for signing.";
+        default = "SW2YcwTIb9zpOOhoPsMm";
+        type = types.str;
+      };
+
+      secretKeyFile = mkOption {
+        description = "Secret key used for signing.";
+        default = null;
+        type = types.nullOr types.path;
+      };
+    };
+
+    smtp = {
+      enable = mkEnableOption "smtp";
+      host = mkOption {
+        description = "Host to connect to";
+        default = "localhost:25";
+        type = types.str;
+      };
+      user = mkOption {
+        description = "User used for authentication";
+        default = "";
+        type = types.str;
+      };
+      password = mkOption {
+        description = ''
+          Password used for authentication.
+          This option is mutual exclusive with the passwordFile option.
+        '';
+        default = "";
+        type = types.str;
+      };
+      passwordFile = mkOption {
+        description = ''
+          Password used for authentication.
+          This option is mutual exclusive with the password option.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+      fromAddress = mkOption {
+        description = "Email address used for sending";
+        default = "admin@grafana.localhost";
+        type = types.str;
+      };
+    };
+
+    users = {
+      allowSignUp = mkOption {
+        description = "Disable user signup / registration";
+        default = false;
+        type = types.bool;
+      };
+
+      allowOrgCreate = mkOption {
+        description = "Whether user is allowed to create organizations.";
+        default = false;
+        type = types.bool;
+      };
+
+      autoAssignOrg = mkOption {
+        description = "Whether to automatically assign new users to default org.";
+        default = true;
+        type = types.bool;
+      };
+
+      autoAssignOrgRole = mkOption {
+        description = "Default role new users will be auto assigned.";
+        default = "Viewer";
+        type = types.enum ["Viewer" "Editor"];
+      };
+    };
+
+    auth.anonymous = {
+      enable = mkOption {
+        description = "Whether to allow anonymous access";
+        default = false;
+        type = types.bool;
+      };
+      org_name = mkOption {
+        description = "Which organization to allow anonymous access to";
+        default = "Main Org.";
+        type = types.str;
+      };
+      org_role = mkOption {
+        description = "Which role anonymous users have in the organization";
+        default = "Viewer";
+        type = types.str;
+      };
+
+    };
+
+    analytics.reporting = {
+      enable = mkOption {
+        description = "Whether to allow anonymous usage reporting to stats.grafana.net";
+        default = true;
+        type = types.bool;
+      };
+    };
+
+    extraOptions = mkOption {
+      description = ''
+        Extra configuration options passed as env variables as specified in
+        <link xlink:href="http://docs.grafana.org/installation/configuration/">documentation</link>,
+        but without GF_ prefix
+      '';
+      default = {};
+      type = with types; attrsOf (either str path);
+    };
+  };
+
+  config = mkIf cfg.enable {
+    warnings = flatten [
+      (optional (
+        cfg.database.password != opt.database.password.default ||
+        cfg.security.adminPassword != opt.security.adminPassword.default
+      ) "Grafana passwords will be stored as plaintext in the Nix store!")
+      (optional (
+        any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources
+      ) "Datasource passwords will be stored as plaintext in the Nix store!")
+    ];
+
+    environment.systemPackages = [ cfg.package ];
+
+    assertions = [
+      {
+        assertion = cfg.database.password != opt.database.password.default -> cfg.database.passwordFile == null;
+        message = "Cannot set both password and passwordFile";
+      }
+      {
+        assertion = cfg.security.adminPassword != opt.security.adminPassword.default -> cfg.security.adminPasswordFile == null;
+        message = "Cannot set both adminPassword and adminPasswordFile";
+      }
+      {
+        assertion = cfg.security.secretKey != opt.security.secretKey.default -> cfg.security.secretKeyFile == null;
+        message = "Cannot set both secretKey and secretKeyFile";
+      }
+      {
+        assertion = cfg.smtp.password != opt.smtp.password.default -> cfg.smtp.passwordFile == null;
+        message = "Cannot set both password and passwordFile";
+      }
+    ];
+
+    systemd.services.grafana = {
+      description = "Grafana Service Daemon";
+      wantedBy = ["multi-user.target"];
+      after = ["networking.target"];
+      environment = {
+        QT_QPA_PLATFORM = "offscreen";
+      } // mapAttrs' (n: v: nameValuePair "GF_${n}" (toString v)) envOptions;
+      script = ''
+        ${optionalString (cfg.database.passwordFile != null) ''
+          export GF_DATABASE_PASSWORD="$(cat ${escapeShellArg cfg.database.passwordFile})"
+        ''}
+        ${optionalString (cfg.security.adminPasswordFile != null) ''
+          export GF_SECURITY_ADMIN_PASSWORD="$(cat ${escapeShellArg cfg.security.adminPasswordFile})"
+        ''}
+        ${optionalString (cfg.security.secretKeyFile != null) ''
+          export GF_SECURITY_SECRET_KEY="$(cat ${escapeShellArg cfg.security.secretKeyFile})"
+        ''}
+        ${optionalString (cfg.smtp.passwordFile != null) ''
+          export GF_SMTP_PASSWORD="$(cat ${escapeShellArg cfg.smtp.passwordFile})"
+        ''}
+        ${optionalString cfg.provision.enable ''
+          export GF_PATHS_PROVISIONING=${provisionConfDir};
+        ''}
+        exec ${cfg.package}/bin/grafana-server -homepath ${cfg.dataDir}
+      '';
+      serviceConfig = {
+        WorkingDirectory = cfg.dataDir;
+        User = "grafana";
+      };
+      preStart = ''
+        ln -fs ${cfg.package}/share/grafana/conf ${cfg.dataDir}
+        ln -fs ${cfg.package}/share/grafana/tools ${cfg.dataDir}
+      '';
+    };
+
+    users.users.grafana = {
+      uid = config.ids.uids.grafana;
+      description = "Grafana user";
+      home = cfg.dataDir;
+      createHome = true;
+      group = "grafana";
+    };
+    users.groups.grafana = {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/graphite.nix b/nixpkgs/nixos/modules/services/monitoring/graphite.nix
new file mode 100644
index 000000000000..64d9d61950da
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/graphite.nix
@@ -0,0 +1,570 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.graphite;
+  writeTextOrNull = f: t: mapNullable (pkgs.writeTextDir f) t;
+
+  dataDir = cfg.dataDir;
+  staticDir = cfg.dataDir + "/static";
+
+  graphiteLocalSettingsDir = pkgs.runCommand "graphite_local_settings" {
+      inherit graphiteLocalSettings;
+      preferLocalBuild = true;
+    } ''
+    mkdir -p $out
+    ln -s $graphiteLocalSettings $out/graphite_local_settings.py
+  '';
+
+  graphiteLocalSettings = pkgs.writeText "graphite_local_settings.py" (
+    "STATIC_ROOT = '${staticDir}'\n" +
+    optionalString (config.time.timeZone != null) "TIME_ZONE = '${config.time.timeZone}'\n"
+    + cfg.web.extraConfig
+  );
+
+  graphiteApiConfig = pkgs.writeText "graphite-api.yaml" ''
+    search_index: ${dataDir}/index
+    ${optionalString (config.time.timeZone != null) ''time_zone: ${config.time.timeZone}''}
+    ${optionalString (cfg.api.finders != []) ''finders:''}
+    ${concatMapStringsSep "\n" (f: "  - " + f.moduleName) cfg.api.finders}
+    ${optionalString (cfg.api.functions != []) ''functions:''}
+    ${concatMapStringsSep "\n" (f: "  - " + f) cfg.api.functions}
+    ${cfg.api.extraConfig}
+  '';
+
+  seyrenConfig = {
+    SEYREN_URL = cfg.seyren.seyrenUrl;
+    MONGO_URL = cfg.seyren.mongoUrl;
+    GRAPHITE_URL = cfg.seyren.graphiteUrl;
+  } // cfg.seyren.extraConfig;
+
+  configDir = pkgs.buildEnv {
+    name = "graphite-config";
+    paths = lists.filter (el: el != null) [
+      (writeTextOrNull "carbon.conf" cfg.carbon.config)
+      (writeTextOrNull "storage-aggregation.conf" cfg.carbon.storageAggregation)
+      (writeTextOrNull "storage-schemas.conf" cfg.carbon.storageSchemas)
+      (writeTextOrNull "blacklist.conf" cfg.carbon.blacklist)
+      (writeTextOrNull "whitelist.conf" cfg.carbon.whitelist)
+      (writeTextOrNull "rewrite-rules.conf" cfg.carbon.rewriteRules)
+      (writeTextOrNull "relay-rules.conf" cfg.carbon.relayRules)
+      (writeTextOrNull "aggregation-rules.conf" cfg.carbon.aggregationRules)
+    ];
+  };
+
+  carbonOpts = name: with config.ids; ''
+    --nodaemon --syslog --prefix=${name} --pidfile /run/${name}/${name}.pid ${name}
+  '';
+
+  carbonEnv = {
+    PYTHONPATH = let
+      cenv = pkgs.python3.buildEnv.override {
+        extraLibs = [ pkgs.python3Packages.carbon ];
+      };
+    in "${cenv}/${pkgs.python3.sitePackages}";
+    GRAPHITE_ROOT = dataDir;
+    GRAPHITE_CONF_DIR = configDir;
+    GRAPHITE_STORAGE_DIR = dataDir;
+  };
+
+in {
+
+  imports = [
+    (mkRemovedOptionModule ["services" "graphite" "pager"] "")
+  ];
+
+  ###### interface
+
+  options.services.graphite = {
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/db/graphite";
+      description = ''
+        Data directory for graphite.
+      '';
+    };
+
+    web = {
+      enable = mkOption {
+        description = "Whether to enable graphite web frontend.";
+        default = false;
+        type = types.bool;
+      };
+
+      listenAddress = mkOption {
+        description = "Graphite web frontend listen address.";
+        default = "127.0.0.1";
+        type = types.str;
+      };
+
+      port = mkOption {
+        description = "Graphite web frontend port.";
+        default = 8080;
+        type = types.int;
+      };
+
+      extraConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Graphite webapp settings. See:
+          <link xlink:href="http://graphite.readthedocs.io/en/latest/config-local-settings.html"/>
+        '';
+      };
+    };
+
+    api = {
+      enable = mkOption {
+        description = ''
+          Whether to enable graphite api. Graphite api is lightweight alternative
+          to graphite web, with api and without dashboard. It's advised to use
+          grafana as alternative dashboard and influxdb as alternative to
+          graphite carbon.
+
+          For more information visit
+          <link xlink:href="https://graphite-api.readthedocs.org/en/latest/"/>
+        '';
+        default = false;
+        type = types.bool;
+      };
+
+      finders = mkOption {
+        description = "List of finder plugins to load.";
+        default = [];
+        example = literalExample "[ pkgs.python3Packages.influxgraph ]";
+        type = types.listOf types.package;
+      };
+
+      functions = mkOption {
+        description = "List of functions to load.";
+        default = [
+          "graphite_api.functions.SeriesFunctions"
+          "graphite_api.functions.PieFunctions"
+        ];
+        type = types.listOf types.str;
+      };
+
+      listenAddress = mkOption {
+        description = "Graphite web service listen address.";
+        default = "127.0.0.1";
+        type = types.str;
+      };
+
+      port = mkOption {
+        description = "Graphite api service port.";
+        default = 8080;
+        type = types.int;
+      };
+
+      package = mkOption {
+        description = "Package to use for graphite api.";
+        default = pkgs.python3Packages.graphite_api;
+        defaultText = "pkgs.python3Packages.graphite_api";
+        type = types.package;
+      };
+
+      extraConfig = mkOption {
+        description = "Extra configuration for graphite api.";
+        default = ''
+          whisper:
+            directories:
+                - ${dataDir}/whisper
+        '';
+        example = ''
+          allowed_origins:
+            - dashboard.example.com
+          cheat_times: true
+          influxdb:
+            host: localhost
+            port: 8086
+            user: influxdb
+            pass: influxdb
+            db: metrics
+          cache:
+            CACHE_TYPE: 'filesystem'
+            CACHE_DIR: '/tmp/graphite-api-cache'
+        '';
+        type = types.lines;
+      };
+    };
+
+    carbon = {
+      config = mkOption {
+        description = "Content of carbon configuration file.";
+        default = ''
+          [cache]
+          # Listen on localhost by default for security reasons
+          UDP_RECEIVER_INTERFACE = 127.0.0.1
+          PICKLE_RECEIVER_INTERFACE = 127.0.0.1
+          LINE_RECEIVER_INTERFACE = 127.0.0.1
+          CACHE_QUERY_INTERFACE = 127.0.0.1
+          # Do not log every update
+          LOG_UPDATES = False
+          LOG_CACHE_HITS = False
+        '';
+        type = types.str;
+      };
+
+      enableCache = mkOption {
+        description = "Whether to enable carbon cache, the graphite storage daemon.";
+        default = false;
+        type = types.bool;
+      };
+
+      storageAggregation = mkOption {
+        description = "Defines how to aggregate data to lower-precision retentions.";
+        default = null;
+        type = types.nullOr types.str;
+        example = ''
+          [all_min]
+          pattern = \.min$
+          xFilesFactor = 0.1
+          aggregationMethod = min
+        '';
+      };
+
+      storageSchemas = mkOption {
+        description = "Defines retention rates for storing metrics.";
+        default = "";
+        type = types.nullOr types.str;
+        example = ''
+          [apache_busyWorkers]
+          pattern = ^servers\.www.*\.workers\.busyWorkers$
+          retentions = 15s:7d,1m:21d,15m:5y
+        '';
+      };
+
+      blacklist = mkOption {
+        description = "Any metrics received which match one of the experssions will be dropped.";
+        default = null;
+        type = types.nullOr types.str;
+        example = "^some\\.noisy\\.metric\\.prefix\\..*";
+      };
+
+      whitelist = mkOption {
+        description = "Only metrics received which match one of the experssions will be persisted.";
+        default = null;
+        type = types.nullOr types.str;
+        example = ".*";
+      };
+
+      rewriteRules = mkOption {
+        description = ''
+          Regular expression patterns that can be used to rewrite metric names
+          in a search and replace fashion.
+        '';
+        default = null;
+        type = types.nullOr types.str;
+        example = ''
+          [post]
+          _sum$ =
+          _avg$ =
+        '';
+      };
+
+      enableRelay = mkOption {
+        description = "Whether to enable carbon relay, the carbon replication and sharding service.";
+        default = false;
+        type = types.bool;
+      };
+
+      relayRules = mkOption {
+        description = "Relay rules are used to send certain metrics to a certain backend.";
+        default = null;
+        type = types.nullOr types.str;
+        example = ''
+          [example]
+          pattern = ^mydata\.foo\..+
+          servers = 10.1.2.3, 10.1.2.4:2004, myserver.mydomain.com
+        '';
+      };
+
+      enableAggregator = mkOption {
+        description = "Whether to enable carbon aggregator, the carbon buffering service.";
+        default = false;
+        type = types.bool;
+      };
+
+      aggregationRules = mkOption {
+        description = "Defines if and how received metrics will be aggregated.";
+        default = null;
+        type = types.nullOr types.str;
+        example = ''
+          <env>.applications.<app>.all.requests (60) = sum <env>.applications.<app>.*.requests
+          <env>.applications.<app>.all.latency (60) = avg <env>.applications.<app>.*.latency
+        '';
+      };
+    };
+
+    seyren = {
+      enable = mkOption {
+        description = "Whether to enable seyren service.";
+        default = false;
+        type = types.bool;
+      };
+
+      port = mkOption {
+        description = "Seyren listening port.";
+        default = 8081;
+        type = types.int;
+      };
+
+      seyrenUrl = mkOption {
+        default = "http://localhost:${toString cfg.seyren.port}/";
+        description = "Host where seyren is accessible.";
+        type = types.str;
+      };
+
+      graphiteUrl = mkOption {
+        default = "http://${cfg.web.listenAddress}:${toString cfg.web.port}";
+        description = "Host where graphite service runs.";
+        type = types.str;
+      };
+
+      mongoUrl = mkOption {
+        default = "mongodb://${config.services.mongodb.bind_ip}:27017/seyren";
+        description = "Mongodb connection string.";
+        type = types.str;
+      };
+
+      extraConfig = mkOption {
+        default = {};
+        description = ''
+          Extra seyren configuration. See
+          <link xlink:href='https://github.com/scobal/seyren#config' />
+        '';
+        type = types.attrsOf types.str;
+        example = literalExample ''
+          {
+            GRAPHITE_USERNAME = "user";
+            GRAPHITE_PASSWORD = "pass";
+          }
+        '';
+      };
+    };
+
+    beacon = {
+      enable = mkEnableOption "graphite beacon";
+
+      config = mkOption {
+        description = "Graphite beacon configuration.";
+        default = {};
+        type = types.attrs;
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkMerge [
+    (mkIf cfg.carbon.enableCache {
+      systemd.services.carbonCache = let name = "carbon-cache"; in {
+        description = "Graphite Data Storage Backend";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        environment = carbonEnv;
+        serviceConfig = {
+          RuntimeDirectory = name;
+          ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
+          User = "graphite";
+          Group = "graphite";
+          PermissionsStartOnly = true;
+          PIDFile="/run/${name}/${name}.pid";
+        };
+        preStart = ''
+          install -dm0700 -o graphite -g graphite ${cfg.dataDir}
+          install -dm0700 -o graphite -g graphite ${cfg.dataDir}/whisper
+        '';
+      };
+    })
+
+    (mkIf cfg.carbon.enableAggregator {
+      systemd.services.carbonAggregator = let name = "carbon-aggregator"; in {
+        enable = cfg.carbon.enableAggregator;
+        description = "Carbon Data Aggregator";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        environment = carbonEnv;
+        serviceConfig = {
+          RuntimeDirectory = name;
+          ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
+          User = "graphite";
+          Group = "graphite";
+          PIDFile="/run/${name}/${name}.pid";
+        };
+      };
+    })
+
+    (mkIf cfg.carbon.enableRelay {
+      systemd.services.carbonRelay = let name = "carbon-relay"; in {
+        description = "Carbon Data Relay";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        environment = carbonEnv;
+        serviceConfig = {
+          RuntimeDirectory = name;
+          ExecStart = "${pkgs.python3Packages.twisted}/bin/twistd ${carbonOpts name}";
+          User = "graphite";
+          Group = "graphite";
+          PIDFile="/run/${name}/${name}.pid";
+        };
+      };
+    })
+
+    (mkIf (cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay) {
+      environment.systemPackages = [
+        pkgs.python3Packages.carbon
+      ];
+    })
+
+    (mkIf cfg.web.enable ({
+      systemd.services.graphiteWeb = {
+        description = "Graphite Web Interface";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        path = [ pkgs.perl ];
+        environment = {
+          PYTHONPATH = let
+              penv = pkgs.python3.buildEnv.override {
+                extraLibs = [
+                  pkgs.python3Packages.graphite-web
+                ];
+              };
+              penvPack = "${penv}/${pkgs.python3.sitePackages}";
+            in concatStringsSep ":" [
+                 "${graphiteLocalSettingsDir}"
+                 "${penvPack}"
+                 # explicitly adding pycairo in path because it cannot be imported via buildEnv
+                 "${pkgs.python3Packages.pycairo}/${pkgs.python3.sitePackages}"
+               ];
+          DJANGO_SETTINGS_MODULE = "graphite.settings";
+          GRAPHITE_SETTINGS_MODULE = "graphite_local_settings";
+          GRAPHITE_CONF_DIR = configDir;
+          GRAPHITE_STORAGE_DIR = dataDir;
+          LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib";
+        };
+        serviceConfig = {
+          ExecStart = ''
+            ${pkgs.python3Packages.waitress-django}/bin/waitress-serve-django \
+              --host=${cfg.web.listenAddress} --port=${toString cfg.web.port}
+          '';
+          User = "graphite";
+          Group = "graphite";
+          PermissionsStartOnly = true;
+        };
+        preStart = ''
+          if ! test -e ${dataDir}/db-created; then
+            mkdir -p ${dataDir}/{whisper/,log/webapp/}
+            chmod 0700 ${dataDir}/{whisper/,log/webapp/}
+
+            ${pkgs.python3Packages.django}/bin/django-admin.py migrate --noinput
+
+            chown -R graphite:graphite ${dataDir}
+
+            touch ${dataDir}/db-created
+          fi
+
+          # Only collect static files when graphite_web changes.
+          if ! [ "${dataDir}/current_graphite_web" -ef "${pkgs.python3Packages.graphite-web}" ]; then
+            mkdir -p ${staticDir}
+            ${pkgs.python3Packages.django}/bin/django-admin.py collectstatic  --noinput --clear
+            chown -R graphite:graphite ${staticDir}
+            ln -sfT "${pkgs.python3Packages.graphite-web}" "${dataDir}/current_graphite_web"
+          fi
+        '';
+      };
+
+      environment.systemPackages = [ pkgs.python3Packages.graphite-web ];
+    }))
+
+    (mkIf cfg.api.enable {
+      systemd.services.graphiteApi = {
+        description = "Graphite Api Interface";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        environment = {
+          PYTHONPATH = let
+              aenv = pkgs.python3.buildEnv.override {
+                extraLibs = [ cfg.api.package pkgs.cairo pkgs.python3Packages.cffi ] ++ cfg.api.finders;
+              };
+            in "${aenv}/${pkgs.python3.sitePackages}";
+          GRAPHITE_API_CONFIG = graphiteApiConfig;
+          LD_LIBRARY_PATH = "${pkgs.cairo.out}/lib";
+        };
+        serviceConfig = {
+          ExecStart = ''
+            ${pkgs.python3Packages.waitress}/bin/waitress-serve \
+            --host=${cfg.api.listenAddress} --port=${toString cfg.api.port} \
+            graphite_api.app:app
+          '';
+          User = "graphite";
+          Group = "graphite";
+          PermissionsStartOnly = true;
+        };
+        preStart = ''
+          if ! test -e ${dataDir}/db-created; then
+            mkdir -p ${dataDir}/cache/
+            chmod 0700 ${dataDir}/cache/
+
+            chown graphite:graphite ${cfg.dataDir}
+            chown -R graphite:graphite ${cfg.dataDir}/cache
+
+            touch ${dataDir}/db-created
+          fi
+        '';
+      };
+    })
+
+    (mkIf cfg.seyren.enable {
+      systemd.services.seyren = {
+        description = "Graphite Alerting Dashboard";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" "mongodb.service" ];
+        environment = seyrenConfig;
+        serviceConfig = {
+          ExecStart = "${pkgs.seyren}/bin/seyren -httpPort ${toString cfg.seyren.port}";
+          WorkingDirectory = dataDir;
+          User = "graphite";
+          Group = "graphite";
+        };
+        preStart = ''
+          if ! test -e ${dataDir}/db-created; then
+            mkdir -p ${dataDir}
+            chown graphite:graphite ${dataDir}
+          fi
+        '';
+      };
+
+      services.mongodb.enable = mkDefault true;
+    })
+
+    (mkIf cfg.beacon.enable {
+      systemd.services.graphite-beacon = {
+        description = "Grpahite Beacon Alerting Daemon";
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = ''
+            ${pkgs.python3Packages.graphite_beacon}/bin/graphite-beacon \
+              --config=${pkgs.writeText "graphite-beacon.json" (builtins.toJSON cfg.beacon.config)}
+          '';
+          User = "graphite";
+          Group = "graphite";
+        };
+      };
+    })
+
+    (mkIf (
+      cfg.carbon.enableCache || cfg.carbon.enableAggregator || cfg.carbon.enableRelay ||
+      cfg.web.enable || cfg.api.enable ||
+      cfg.seyren.enable || cfg.beacon.enable
+     ) {
+      users.users.graphite = {
+        uid = config.ids.uids.graphite;
+        description = "Graphite daemon user";
+        home = dataDir;
+      };
+      users.groups.graphite.gid = config.ids.gids.graphite;
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/hdaps.nix b/nixpkgs/nixos/modules/services/monitoring/hdaps.nix
new file mode 100644
index 000000000000..2cad3b84d847
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/hdaps.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.hdapsd;
+  hdapsd = [ pkgs.hdapsd ];
+in
+{
+  options = {
+    services.hdapsd.enable = mkEnableOption
+      ''
+        Hard Drive Active Protection System Daemon,
+        devices are detected and managed automatically by udev and systemd
+      '';
+  };
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "hdapsd" ];
+    services.udev.packages = hdapsd;
+    systemd.packages = hdapsd;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/heapster.nix b/nixpkgs/nixos/modules/services/monitoring/heapster.nix
new file mode 100644
index 000000000000..0a9dfa12eaa5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/heapster.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.heapster;
+in {
+  options.services.heapster = {
+    enable = mkOption {
+      description = "Whether to enable heapster monitoring";
+      default = false;
+      type = types.bool;
+    };
+
+    source = mkOption {
+      description = "Heapster metric source";
+      example = "kubernetes:https://kubernetes.default";
+      type = types.str;
+    };
+
+    sink = mkOption {
+      description = "Heapster metic sink";
+      example = "influxdb:http://localhost:8086";
+      type = types.str;
+    };
+
+    extraOpts = mkOption {
+      description = "Heapster extra options";
+      default = "";
+      type = types.separatedString " ";
+    };
+
+    package = mkOption {
+      description = "Package to use by heapster";
+      default = pkgs.heapster;
+      defaultText = "pkgs.heapster";
+      type = types.package;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.heapster = {
+      wantedBy = ["multi-user.target"];
+      after = ["cadvisor.service" "kube-apiserver.service"];
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/heapster --source=${cfg.source} --sink=${cfg.sink} ${cfg.extraOpts}";
+        User = "heapster";
+      };
+    };
+
+    users.users.heapster = {
+      uid = config.ids.uids.heapster;
+      description = "Heapster user";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/incron.nix b/nixpkgs/nixos/modules/services/monitoring/incron.nix
new file mode 100644
index 000000000000..1789fd9f2051
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/incron.nix
@@ -0,0 +1,98 @@
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.incron;
+
+in
+
+{
+  options = {
+
+    services.incron = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the incron daemon.
+
+          Note that commands run under incrontab only support common Nix profiles for the <envar>PATH</envar> provided variable.
+        '';
+      };
+
+      allow = mkOption {
+        type = types.nullOr (types.listOf types.str);
+        default = null;
+        description = ''
+          Users allowed to use incrontab.
+
+          If empty then no user will be allowed to have their own incrontab.
+          If <literal>null</literal> then will defer to <option>deny</option>.
+          If both <option>allow</option> and <option>deny</option> are null
+          then all users will be allowed to have their own incrontab.
+        '';
+      };
+
+      deny = mkOption {
+        type = types.nullOr (types.listOf types.str);
+        default = null;
+        description = "Users forbidden from using incrontab.";
+      };
+
+      systab = mkOption {
+        type = types.lines;
+        default = "";
+        description = "The system incrontab contents.";
+        example = ''
+          /var/mail IN_CLOSE_WRITE abc $@/$#
+          /tmp IN_ALL_EVENTS efg $@/$# $&
+        '';
+      };
+
+      extraPackages = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = literalExample "[ pkgs.rsync ]";
+        description = "Extra packages available to the system incrontab.";
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    warnings = optional (cfg.allow != null && cfg.deny != null)
+      ''If `services.incron.allow` is set then `services.incron.deny` will be ignored.'';
+
+    environment.systemPackages = [ pkgs.incron ];
+
+    security.wrappers.incrontab.source = "${pkgs.incron}/bin/incrontab";
+
+    # incron won't read symlinks
+    environment.etc."incron.d/system" = {
+      mode = "0444";
+      text = cfg.systab;
+    };
+    environment.etc."incron.allow" = mkIf (cfg.allow != null) {
+      text = concatStringsSep "\n" cfg.allow;
+    };
+    environment.etc."incron.deny" = mkIf (cfg.deny != null) {
+      text = concatStringsSep "\n" cfg.deny;
+    };
+
+    systemd.services.incron = {
+      description = "File System Events Scheduler";
+      wantedBy = [ "multi-user.target" ];
+      path = cfg.extraPackages;
+      serviceConfig.PIDFile = "/run/incrond.pid";
+      serviceConfig.ExecStartPre = "${pkgs.coreutils}/bin/mkdir -m 710 -p /var/spool/incron";
+      serviceConfig.ExecStart = "${pkgs.incron}/bin/incrond --foreground";
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix b/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix
new file mode 100644
index 000000000000..9b4ff3c56124
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/kapacitor.nix
@@ -0,0 +1,191 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.kapacitor;
+
+  kapacitorConf = pkgs.writeTextFile {
+    name = "kapacitord.conf";
+    text = ''
+      hostname="${config.networking.hostName}"
+      data_dir="${cfg.dataDir}"
+
+      [http]
+        bind-address = "${cfg.bind}:${toString cfg.port}"
+        log-enabled = false
+        auth-enabled = false
+
+      [task]
+        dir = "${cfg.dataDir}/tasks"
+        snapshot-interval = "${cfg.taskSnapshotInterval}"
+
+      [replay]
+        dir = "${cfg.dataDir}/replay"
+
+      [storage]
+        boltdb = "${cfg.dataDir}/kapacitor.db"
+
+      ${optionalString (cfg.loadDirectory != null) ''
+        [load]
+          enabled = true
+          dir = "${cfg.loadDirectory}"
+      ''}
+
+      ${optionalString (cfg.defaultDatabase.enable) ''
+        [[influxdb]]
+          name = "default"
+          enabled = true
+          default = true
+          urls = [ "${cfg.defaultDatabase.url}" ]
+          username = "${cfg.defaultDatabase.username}"
+          password = "${cfg.defaultDatabase.password}"
+      ''}
+
+      ${optionalString (cfg.alerta.enable) ''
+        [alerta]
+          enabled = true
+          url = "${cfg.alerta.url}"
+          token = "${cfg.alerta.token}"
+          environment = "${cfg.alerta.environment}"
+          origin = "${cfg.alerta.origin}"
+      ''}
+
+      ${cfg.extraConfig}
+    '';
+  };
+in
+{
+  options.services.kapacitor = {
+    enable = mkEnableOption "kapacitor";
+
+    dataDir = mkOption {
+      type = types.path;
+      example = "/var/lib/kapacitor";
+      default = "/var/lib/kapacitor";
+      description = "Location where Kapacitor stores its state";
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 9092;
+      description = "Port of Kapacitor";
+    };
+
+    bind = mkOption {
+      type = types.str;
+      default = "";
+      example = literalExample "0.0.0.0";
+      description = "Address to bind to. The default is to bind to all addresses";
+    };
+
+    extraConfig = mkOption {
+      description = "These lines go into kapacitord.conf verbatim.";
+      default = "";
+      type = types.lines;
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "kapacitor";
+      description = "User account under which Kapacitor runs";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "kapacitor";
+      description = "Group under which Kapacitor runs";
+    };
+
+    taskSnapshotInterval = mkOption {
+      type = types.str;
+      description = "Specifies how often to snapshot the task state  (in InfluxDB time units)";
+      default = "1m0s";
+      example = "1m0s";
+    };
+
+    loadDirectory = mkOption {
+      type = types.nullOr types.path;
+      description = "Directory where to load services from, such as tasks, templates and handlers (or null to disable service loading on startup)";
+      default = null;
+    };
+
+    defaultDatabase = {
+      enable = mkEnableOption "kapacitor.defaultDatabase";
+
+      url = mkOption {
+        description = "The URL to an InfluxDB server that serves as the default database";
+        example = "http://localhost:8086";
+        type = types.str;
+      };
+
+      username = mkOption {
+        description = "The username to connect to the remote InfluxDB server";
+        type = types.str;
+      };
+
+      password = mkOption {
+        description = "The password to connect to the remote InfluxDB server";
+        type = types.str;
+      };
+    };
+
+    alerta = {
+      enable = mkEnableOption "kapacitor alerta integration";
+
+      url = mkOption {
+        description = "The URL to the Alerta REST API";
+        default = "http://localhost:5000";
+        example = "http://localhost:5000";
+        type = types.str;
+      };
+
+      token = mkOption {
+        description = "Default Alerta authentication token";
+        type = types.str;
+        default = "";
+      };
+
+      environment = mkOption {
+        description = "Default Alerta environment";
+        type = types.str;
+        default = "Production";
+      };
+
+      origin = mkOption {
+        description = "Default origin of alert";
+        type = types.str;
+        default = "kapacitor";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.kapacitor ];
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.kapacitor = {
+      description = "Kapacitor Real-Time Stream Processing Engine";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.kapacitor}/bin/kapacitord -config ${kapacitorConf}";
+        User = "kapacitor";
+        Group = "kapacitor";
+      };
+    };
+
+    users.users.kapacitor = {
+      uid = config.ids.uids.kapacitor;
+      description = "Kapacitor user";
+      home = cfg.dataDir;
+    };
+
+    users.groups.kapacitor = {
+      gid = config.ids.gids.kapacitor;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/loki.nix b/nixpkgs/nixos/modules/services/monitoring/loki.nix
new file mode 100644
index 000000000000..f4eec7e0d284
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/loki.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) escapeShellArgs literalExample mkEnableOption mkIf mkOption types;
+
+  cfg = config.services.loki;
+
+  prettyJSON = conf:
+    pkgs.runCommand "loki-config.json" { } ''
+      echo '${builtins.toJSON conf}' | ${pkgs.jq}/bin/jq 'del(._module)' > $out
+    '';
+
+in {
+  options.services.loki = {
+    enable = mkEnableOption "loki";
+
+    user = mkOption {
+      type = types.str;
+      default = "loki";
+      description = ''
+        User under which the Loki service runs.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "loki";
+      description = ''
+        Group under which the Loki service runs.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/loki";
+      description = ''
+        Specify the directory for Loki.
+      '';
+    };
+
+    configuration = mkOption {
+      type = types.attrs;
+      default = {};
+      description = ''
+        Specify the configuration for Loki in Nix.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Specify a configuration file that Loki should use.
+      '';
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = literalExample [ "--server.http-listen-port=3101" ];
+      description = ''
+        Specify a list of additional command line flags,
+        which get escaped and are then passed to Loki.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [{
+      assertion = (
+        (cfg.configuration == {} -> cfg.configFile != null) &&
+        (cfg.configFile != null -> cfg.configuration == {})
+      );
+      message  = ''
+        Please specify either
+        'services.loki.configuration' or
+        'services.loki.configFile'.
+      '';
+    }];
+
+    users.groups.${cfg.group} = { };
+    users.users.${cfg.user} = {
+      description = "Loki Service User";
+      group = cfg.group;
+      home = cfg.dataDir;
+      createHome = true;
+      isSystemUser = true;
+    };
+
+    systemd.services.loki = {
+      description = "Loki Service Daemon";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = let
+        conf = if cfg.configFile == null
+               then prettyJSON cfg.configuration
+               else cfg.configFile;
+      in
+      {
+        ExecStart = "${pkgs.grafana-loki}/bin/loki --config.file=${conf} ${escapeShellArgs cfg.extraFlags}";
+        User = cfg.user;
+        Restart = "always";
+        PrivateTmp = true;
+        ProtectHome = true;
+        ProtectSystem = "full";
+        DevicePolicy = "closed";
+        NoNewPrivileges = true;
+        WorkingDirectory = cfg.dataDir;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/longview.nix b/nixpkgs/nixos/modules/services/monitoring/longview.nix
new file mode 100644
index 000000000000..9c38956f9ba8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/longview.nix
@@ -0,0 +1,160 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.longview;
+
+  runDir = "/run/longview";
+  configsDir = "${runDir}/longview.d";
+
+in {
+  options = {
+
+    services.longview = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If enabled, system metrics will be sent to Linode LongView.
+        '';
+      };
+
+      apiKey = mkOption {
+        type = types.str;
+        default = "";
+        example = "01234567-89AB-CDEF-0123456789ABCDEF";
+        description = ''
+          Longview API key. To get this, look in Longview settings which
+          are found at https://manager.linode.com/longview/.
+
+          Warning: this secret is stored in the world-readable Nix store!
+          Use <option>apiKeyFile</option> instead.
+        '';
+      };
+
+      apiKeyFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/longview-api-key";
+        description = ''
+          A file containing the Longview API key.
+          To get this, look in Longview settings which
+          are found at https://manager.linode.com/longview/.
+
+          <option>apiKeyFile</option> takes precedence over <option>apiKey</option>.
+        '';
+      };
+
+      apacheStatusUrl = mkOption {
+        type = types.str;
+        default = "";
+        example = "http://127.0.0.1/server-status";
+        description = ''
+          The Apache status page URL. If provided, Longview will
+          gather statistics from this location. This requires Apache
+          mod_status to be loaded and enabled.
+        '';
+      };
+
+      nginxStatusUrl = mkOption {
+        type = types.str;
+        default = "";
+        example = "http://127.0.0.1/nginx_status";
+        description = ''
+          The Nginx status page URL. Longview will gather statistics
+          from this URL. This requires the Nginx stub_status module to
+          be enabled and configured at the given location.
+        '';
+      };
+
+      mysqlUser = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          The user for connecting to the MySQL database. If provided,
+          Longview will connect to MySQL and collect statistics about
+          queries, etc. This user does not need to have been granted
+          any extra privileges.
+        '';
+      };
+
+      mysqlPassword = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          The password corresponding to <option>mysqlUser</option>.
+          Warning: this is stored in cleartext in the Nix store!
+          Use <option>mysqlPasswordFile</option> instead.
+        '';
+      };
+
+      mysqlPasswordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/dbpassword";
+        description = ''
+          A file containing the password corresponding to <option>mysqlUser</option>.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.longview =
+      { description = "Longview Metrics Collection";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig.Type = "forking";
+        serviceConfig.ExecStop = "-${pkgs.coreutils}/bin/kill -TERM $MAINPID";
+        serviceConfig.ExecReload = "-${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        serviceConfig.PIDFile = "${runDir}/longview.pid";
+        serviceConfig.ExecStart = "${pkgs.longview}/bin/longview";
+        preStart = ''
+          umask 077
+          mkdir -p ${configsDir}
+        '' + (optionalString (cfg.apiKeyFile != null) ''
+          cp --no-preserve=all "${cfg.apiKeyFile}" ${runDir}/longview.key
+        '') + (optionalString (cfg.apacheStatusUrl != "") ''
+          cat > ${configsDir}/Apache.conf <<EOF
+          location ${cfg.apacheStatusUrl}?auto
+          EOF
+        '') + (optionalString (cfg.mysqlUser != "" && cfg.mysqlPasswordFile != null) ''
+          cat > ${configsDir}/MySQL.conf <<EOF
+          username ${cfg.mysqlUser}
+          password `head -n1 "${cfg.mysqlPasswordFile}"`
+          EOF
+        '') + (optionalString (cfg.nginxStatusUrl != "") ''
+          cat > ${configsDir}/Nginx.conf <<EOF
+          location ${cfg.nginxStatusUrl}
+          EOF
+        '');
+      };
+
+    warnings = let warn = k: optional (cfg.${k} != "")
+                 "config.services.longview.${k} is insecure. Use ${k}File instead.";
+               in concatMap warn [ "apiKey" "mysqlPassword" ];
+
+    assertions = [
+      { assertion = cfg.apiKeyFile != null;
+        message = "Longview needs an API key configured";
+      }
+    ];
+
+    # Create API key file if not configured.
+    services.longview.apiKeyFile = mkIf (cfg.apiKey != "")
+      (mkDefault (toString (pkgs.writeTextFile {
+        name = "longview.key";
+        text = cfg.apiKey;
+      })));
+
+    # Create MySQL password file if not configured.
+    services.longview.mysqlPasswordFile = mkDefault (toString (pkgs.writeTextFile {
+      name = "mysql-password-file";
+      text = cfg.mysqlPassword;
+    }));
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/monit.nix b/nixpkgs/nixos/modules/services/monitoring/monit.nix
new file mode 100644
index 000000000000..ca9352272174
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/monit.nix
@@ -0,0 +1,46 @@
+{config, pkgs, lib, ...}:
+
+with lib;
+
+let
+  cfg = config.services.monit;
+in
+
+{
+  options.services.monit = {
+
+    enable = mkEnableOption "Monit";
+
+    config = mkOption {
+      type = types.lines;
+      default = "";
+      description = "monitrc content";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.monit ];
+
+    environment.etc.monitrc = {
+      text = cfg.config;
+      mode = "0400";
+    };
+
+    systemd.services.monit = {
+      description = "Pro-active monitoring utility for unix systems";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.monit}/bin/monit -I -c /etc/monitrc";
+        ExecStop = "${pkgs.monit}/bin/monit -c /etc/monitrc quit";
+        ExecReload = "${pkgs.monit}/bin/monit -c /etc/monitrc reload";
+        KillMode = "process";
+        Restart = "always";
+      };
+      restartTriggers = [ config.environment.etc.monitrc.source ];
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/munin.nix b/nixpkgs/nixos/modules/services/monitoring/munin.nix
new file mode 100644
index 000000000000..1ebf7ee6a761
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/munin.nix
@@ -0,0 +1,402 @@
+{ config, lib, pkgs, ... }:
+
+# TODO: support munin-async
+# TODO: LWP/Pg perl libs aren't recognized
+
+# TODO: support fastcgi
+# http://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
+# spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
+# spawn-fcgi -s /run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
+# https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
+# nginx https://munin.readthedocs.org/en/latest/example/webserver/nginx.html
+
+
+with lib;
+
+let
+  nodeCfg = config.services.munin-node;
+  cronCfg = config.services.munin-cron;
+
+  muninConf = pkgs.writeText "munin.conf"
+    ''
+      dbdir     /var/lib/munin
+      htmldir   /var/www/munin
+      logdir    /var/log/munin
+      rundir    /run/munin
+
+      ${lib.optionalString (cronCfg.extraCSS != "") "staticdir ${customStaticDir}"}
+
+      ${cronCfg.extraGlobalConfig}
+
+      ${cronCfg.hosts}
+    '';
+
+  nodeConf = pkgs.writeText "munin-node.conf"
+    ''
+      log_level 3
+      log_file Sys::Syslog
+      port 4949
+      host *
+      background 0
+      user root
+      group root
+      host_name ${config.networking.hostName}
+      setsid 0
+
+      # wrapped plugins by makeWrapper being with dots
+      ignore_file ^\.
+
+      allow ^::1$
+      allow ^127\.0\.0\.1$
+
+      ${nodeCfg.extraConfig}
+    '';
+
+  pluginConf = pkgs.writeText "munin-plugin-conf"
+    ''
+      [hddtemp_smartctl]
+      user root
+      group root
+
+      [meminfo]
+      user root
+      group root
+
+      [ipmi*]
+      user root
+      group root
+
+      [munin*]
+      env.UPDATE_STATSFILE /var/lib/munin/munin-update.stats
+
+      ${nodeCfg.extraPluginConfig}
+    '';
+
+  pluginConfDir = pkgs.stdenv.mkDerivation {
+    name = "munin-plugin-conf.d";
+    buildCommand = ''
+      mkdir $out
+      ln -s ${pluginConf} $out/nixos-config
+    '';
+  };
+
+  # Copy one Munin plugin into the Nix store with a specific name.
+  # This is suitable for use with plugins going directly into /etc/munin/plugins,
+  # i.e. munin.extraPlugins.
+  internOnePlugin = name: path:
+    "cp -a '${path}' '${name}'";
+
+  # Copy an entire tree of Munin plugins into a single directory in the Nix
+  # store, with no renaming.
+  # This is suitable for use with munin-node-configure --suggest, i.e.
+  # munin.extraAutoPlugins.
+  internManyPlugins = name: path:
+    "find '${path}' -type f -perm /a+x -exec cp -a -t . '{}' '+'";
+
+  # Use the appropriate intern-fn to copy the plugins into the store and patch
+  # them afterwards in an attempt to get them to run on NixOS.
+  internAndFixPlugins = name: intern-fn: paths:
+    pkgs.runCommand name {} ''
+      mkdir -p "$out"
+      cd "$out"
+      ${lib.concatStringsSep "\n"
+          (lib.attrsets.mapAttrsToList intern-fn paths)}
+      chmod -R u+w .
+      find . -type f -exec sed -E -i '
+        s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
+      ' '{}' '+'
+    '';
+
+  # TODO: write a derivation for munin-contrib, so that for contrib plugins
+  # you can just refer to them by name rather than needing to include a copy
+  # of munin-contrib in your nixos configuration.
+  extraPluginDir = internAndFixPlugins "munin-extra-plugins.d"
+    internOnePlugin nodeCfg.extraPlugins;
+
+  extraAutoPluginDir = internAndFixPlugins "munin-extra-auto-plugins.d"
+    internManyPlugins
+    (builtins.listToAttrs
+      (map
+        (path: { name = baseNameOf path; value = path; })
+        nodeCfg.extraAutoPlugins));
+
+  customStaticDir = pkgs.runCommand "munin-custom-static-data" {} ''
+    cp -a "${pkgs.munin}/etc/opt/munin/static" "$out"
+    cd "$out"
+    chmod -R u+w .
+    echo "${cronCfg.extraCSS}" >> style.css
+    echo "${cronCfg.extraCSS}" >> style-new.css
+  '';
+in
+
+{
+
+  options = {
+
+    services.munin-node = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enable Munin Node agent. Munin node listens on 0.0.0.0 and
+          by default accepts connections only from 127.0.0.1 for security reasons.
+
+          See <link xlink:href='http://guide.munin-monitoring.org/en/latest/architecture/index.html' />.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          <filename>munin-node.conf</filename> extra configuration. See
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html' />
+        '';
+      };
+
+      extraPluginConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          <filename>plugin-conf.d</filename> extra plugin configuration. See
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/plugin/use.html' />
+        '';
+        example = ''
+          [fail2ban_*]
+          user root
+        '';
+      };
+
+      extraPlugins = mkOption {
+        default = {};
+        type = with types; attrsOf path;
+        description = ''
+          Additional Munin plugins to activate. Keys are the name of the plugin
+          symlink, values are the path to the underlying plugin script. You
+          can use the same plugin script multiple times (e.g. for wildcard
+          plugins).
+
+          Note that these plugins do not participate in autoconfiguration. If
+          you want to autoconfigure additional plugins, use
+          <option>services.munin-node.extraAutoPlugins</option>.
+
+          Plugins enabled in this manner take precedence over autoconfigured
+          plugins.
+
+          Plugins will be copied into the Nix store, and it will attempt to
+          modify them to run properly by fixing hardcoded references to
+          <literal>/bin</literal>, <literal>/usr/bin</literal>,
+          <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+        '';
+        example = literalExample ''
+          {
+            zfs_usage_bigpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
+            zfs_usage_smallpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
+            zfs_list = /src/munin-contrib/plugins/zfs/zfs_list;
+          };
+        '';
+      };
+
+      extraAutoPlugins = mkOption {
+        default = [];
+        type = with types; listOf path;
+        description = ''
+          Additional Munin plugins to autoconfigure, using
+          <literal>munin-node-configure --suggest</literal>. These should be
+          the actual paths to the plugin files (or directories containing them),
+          not just their names.
+
+          If you want to manually enable individual plugins instead, use
+          <option>services.munin-node.extraPlugins</option>.
+
+          Note that only plugins that have the 'autoconfig' capability will do
+          anything if listed here, since plugins that cannot autoconfigure
+          won't be automatically enabled by
+          <literal>munin-node-configure</literal>.
+
+          Plugins will be copied into the Nix store, and it will attempt to
+          modify them to run properly by fixing hardcoded references to
+          <literal>/bin</literal>, <literal>/usr/bin</literal>,
+          <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+        '';
+        example = literalExample ''
+          [
+            /src/munin-contrib/plugins/zfs
+            /src/munin-contrib/plugins/ssh
+          ];
+        '';
+      };
+
+      disabledPlugins = mkOption {
+        # TODO: figure out why Munin isn't writing the log file and fix it.
+        # In the meantime this at least suppresses a useless graph full of
+        # NaNs in the output.
+        default = [ "munin_stats" ];
+        type = with types; listOf str;
+        description = ''
+          Munin plugins to disable, even if
+          <literal>munin-node-configure --suggest</literal> tries to enable
+          them. To disable a wildcard plugin, use an actual wildcard, as in
+          the example.
+
+          munin_stats is disabled by default as it tries to read
+          <literal>/var/log/munin/munin-update.log</literal> for timing
+          information, and the NixOS build of Munin does not write this file.
+        '';
+        example = [ "diskstats" "zfs_usage_*" ];
+      };
+    };
+
+    services.munin-cron = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enable munin-cron. Takes care of all heavy lifting to collect data from
+          nodes and draws graphs to html. Runs munin-update, munin-limits,
+          munin-graphs and munin-html in that order.
+
+          HTML output is in <filename>/var/www/munin/</filename>, configure your
+          favourite webserver to serve static files.
+        '';
+      };
+
+      extraGlobalConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          <filename>munin.conf</filename> extra global configuration.
+          See <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />.
+          Useful to setup notifications, see
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/tutorial/alert.html' />
+        '';
+        example = ''
+          contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
+        '';
+      };
+
+      hosts = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Definitions of hosts of nodes to collect data from. Needs at least one
+          host for cron to succeed. See
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />
+        '';
+        example = ''
+          [''${config.networking.hostName}]
+          address localhost
+        '';
+      };
+
+      extraCSS = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Custom styling for the HTML that munin-cron generates. This will be
+          appended to the CSS files used by munin-cron and will thus take
+          precedence over the builtin styles.
+        '';
+        example = ''
+          /* A simple dark theme. */
+          html, body { background: #222222; }
+          #header, #footer { background: #333333; }
+          img.i, img.iwarn, img.icrit, img.iunkn {
+            filter: invert(100%) hue-rotate(-30deg);
+          }
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkMerge [ (mkIf (nodeCfg.enable || cronCfg.enable)  {
+
+    environment.systemPackages = [ pkgs.munin ];
+
+    users.users.munin = {
+      description = "Munin monitoring user";
+      group = "munin";
+      uid = config.ids.uids.munin;
+      home = "/var/lib/munin";
+    };
+
+    users.groups.munin = {
+      gid = config.ids.gids.munin;
+    };
+
+  }) (mkIf nodeCfg.enable {
+
+    systemd.services.munin-node = {
+      description = "Munin Node";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [ munin smartmontools "/run/current-system/sw" "/run/wrappers" ];
+      environment.MUNIN_LIBDIR = "${pkgs.munin}/lib";
+      environment.MUNIN_PLUGSTATE = "/run/munin";
+      environment.MUNIN_LOGDIR = "/var/log/munin";
+      preStart = ''
+        echo "Updating munin plugins..."
+
+        mkdir -p /etc/munin/plugins
+        rm -rf /etc/munin/plugins/*
+
+        # Autoconfigure builtin plugins
+        ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${pkgs.munin}/lib/plugins --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
+
+        # Autoconfigure extra plugins
+        ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${extraAutoPluginDir} --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
+
+        ${lib.optionalString (nodeCfg.extraPlugins != {}) ''
+            # Link in manually enabled plugins
+            ln -f -s -t /etc/munin/plugins ${extraPluginDir}/*
+          ''}
+
+        ${lib.optionalString (nodeCfg.disabledPlugins != []) ''
+            # Disable plugins
+            cd /etc/munin/plugins
+            rm -f ${toString nodeCfg.disabledPlugins}
+          ''}
+      '';
+      serviceConfig = {
+        ExecStart = "${pkgs.munin}/sbin/munin-node --config ${nodeConf} --servicedir /etc/munin/plugins/ --sconfdir=${pluginConfDir}";
+      };
+    };
+
+    # munin_stats plugin breaks as of 2.0.33 when this doesn't exist
+    systemd.tmpfiles.rules = [ "d /run/munin 0755 munin munin -" ];
+
+  }) (mkIf cronCfg.enable {
+
+    # Munin is hardcoded to use DejaVu Mono and the graphs come out wrong if
+    # it's not available.
+    fonts.fonts = [ pkgs.dejavu_fonts ];
+
+    systemd.timers.munin-cron = {
+      description = "batch Munin master programs";
+      wantedBy = [ "timers.target" ];
+      timerConfig.OnCalendar = "*:0/5";
+    };
+
+    systemd.services.munin-cron = {
+      description = "batch Munin master programs";
+      unitConfig.Documentation = "man:munin-cron(8)";
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = "munin";
+        ExecStart = "${pkgs.munin}/bin/munin-cron --config ${muninConf}";
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d /run/munin 0755 munin munin -"
+      "d /var/log/munin 0755 munin munin -"
+      "d /var/www/munin 0755 munin munin -"
+      "d /var/lib/munin 0755 munin munin -"
+    ];
+  })];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/nagios.nix b/nixpkgs/nixos/modules/services/monitoring/nagios.nix
new file mode 100644
index 000000000000..9ac6869068f2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/nagios.nix
@@ -0,0 +1,212 @@
+# Nagios system/network monitoring daemon.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nagios;
+
+  nagiosState = "/var/lib/nagios";
+  nagiosLogDir = "/var/log/nagios";
+  urlPath = "/nagios";
+
+  nagiosObjectDefs = cfg.objectDefs;
+
+  nagiosObjectDefsDir = pkgs.runCommand "nagios-objects" {
+      inherit nagiosObjectDefs;
+      preferLocalBuild = true;
+    } "mkdir -p $out; ln -s $nagiosObjectDefs $out/";
+
+  nagiosCfgFile = let
+    default = {
+      log_file="${nagiosLogDir}/current";
+      log_archive_path="${nagiosLogDir}/archive";
+      status_file="${nagiosState}/status.dat";
+      object_cache_file="${nagiosState}/objects.cache";
+      temp_file="${nagiosState}/nagios.tmp";
+      lock_file="/run/nagios.lock";
+      state_retention_file="${nagiosState}/retention.dat";
+      query_socket="${nagiosState}/nagios.qh";
+      check_result_path="${nagiosState}";
+      command_file="${nagiosState}/nagios.cmd";
+      cfg_dir="${nagiosObjectDefsDir}";
+      nagios_user="nagios";
+      nagios_group="nagios";
+      illegal_macro_output_chars="`~$&|'\"<>";
+      retain_state_information="1";
+    };
+    lines = mapAttrsToList (key: value: "${key}=${value}") (default // cfg.extraConfig);
+    content = concatStringsSep "\n" lines;
+    file = pkgs.writeText "nagios.cfg" content;
+    validated =  pkgs.runCommand "nagios-checked.cfg" {preferLocalBuild=true;} ''
+      cp ${file} nagios.cfg
+      # nagios checks the existence of /var/lib/nagios, but
+      # it does not exists in the build sandbox, so we fake it
+      mkdir lib
+      lib=$(readlink -f lib)
+      sed -i s@=${nagiosState}@=$lib@ nagios.cfg
+      ${pkgs.nagios}/bin/nagios -v nagios.cfg && cp ${file} $out
+    '';
+    defaultCfgFile = if cfg.validateConfig then validated else file;
+  in
+  if cfg.mainConfigFile == null then defaultCfgFile else cfg.mainConfigFile;
+
+  # Plain configuration for the Nagios web-interface with no
+  # authentication.
+  nagiosCGICfgFile = pkgs.writeText "nagios.cgi.conf"
+    ''
+      main_config_file=${cfg.mainConfigFile}
+      use_authentication=0
+      url_html_path=${urlPath}
+    '';
+
+  extraHttpdConfig =
+    ''
+      ScriptAlias ${urlPath}/cgi-bin ${pkgs.nagios}/sbin
+
+      <Directory "${pkgs.nagios}/sbin">
+        Options ExecCGI
+        Require all granted
+        SetEnv NAGIOS_CGI_CONFIG ${cfg.cgiConfigFile}
+      </Directory>
+
+      Alias ${urlPath} ${pkgs.nagios}/share
+
+      <Directory "${pkgs.nagios}/share">
+        Options None
+        Require all granted
+      </Directory>
+    '';
+
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "nagios" "urlPath" ] "The urlPath option has been removed as it is hard coded to /nagios in the nagios package.")
+  ];
+
+  meta.maintainers = with lib.maintainers; [ symphorien ];
+
+  options = {
+    services.nagios = {
+      enable = mkEnableOption "<link xlink:href='http://www.nagios.org/'>Nagios</link> to monitor your system or network.";
+
+      objectDefs = mkOption {
+        description = "
+          A list of Nagios object configuration files that must define
+          the hosts, host groups, services and contacts for the
+          network that you want Nagios to monitor.
+        ";
+        type = types.listOf types.path;
+        example = literalExample "[ ./objects.cfg ]";
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = with pkgs; [ nagiosPluginsOfficial ssmtp mailutils ];
+        defaultText = "[pkgs.nagiosPluginsOfficial pkgs.ssmtp pkgs.mailutils]";
+        description = "
+          Packages to be added to the Nagios <envar>PATH</envar>.
+          Typically used to add plugins, but can be anything.
+        ";
+      };
+
+      mainConfigFile = mkOption {
+        type = types.nullOr types.package;
+        default = null;
+        description = "
+          If non-null, overrides the main configuration file of Nagios.
+        ";
+      };
+
+      extraConfig = mkOption {
+        type = types.attrsOf types.str;
+        example = {
+          debug_level = "-1";
+          debug_file = "/var/log/nagios/debug.log";
+        };
+        default = {};
+        description = "Configuration to add to /etc/nagios.cfg";
+      };
+
+      validateConfig = mkOption {
+        type = types.bool;
+        default = pkgs.stdenv.hostPlatform == pkgs.stdenv.buildPlatform;
+        description = "if true, the syntax of the nagios configuration file is checked at build time";
+      };
+
+      cgiConfigFile = mkOption {
+        type = types.package;
+        default = nagiosCGICfgFile;
+        defaultText = "nagiosCGICfgFile";
+        description = "
+          Derivation for the configuration file of Nagios CGI scripts
+          that can be used in web servers for running the Nagios web interface.
+        ";
+      };
+
+      enableWebInterface = mkOption {
+        type = types.bool;
+        default = false;
+        description = "
+          Whether to enable the Nagios web interface.  You should also
+          enable Apache (<option>services.httpd.enable</option>).
+        ";
+      };
+
+      virtualHost = mkOption {
+        type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
+        example = literalExample ''
+          { hostName = "example.org";
+            adminAddr = "webmaster@example.org";
+            enableSSL = true;
+            sslServerCert = "/var/lib/acme/example.org/full.pem";
+            sslServerKey = "/var/lib/acme/example.org/key.pem";
+          }
+        '';
+        description = ''
+          Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+          See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+        '';
+      };
+    };
+  };
+
+
+  config = mkIf cfg.enable {
+    users.users.nagios = {
+      description = "Nagios user ";
+      uid         = config.ids.uids.nagios;
+      home        = nagiosState;
+      group       = "nagios";
+    };
+
+    users.groups.nagios = { };
+
+    # This isn't needed, it's just so that the user can type "nagiostats
+    # -c /etc/nagios.cfg".
+    environment.etc."nagios.cfg".source = nagiosCfgFile;
+
+    environment.systemPackages = [ pkgs.nagios ];
+    systemd.services.nagios = {
+      description = "Nagios monitoring daemon";
+      path     = [ pkgs.nagios ] ++ cfg.plugins;
+      wantedBy = [ "multi-user.target" ];
+      after    = [ "network.target" ];
+
+      serviceConfig = {
+        User = "nagios";
+        Group = "nagios";
+        Restart = "always";
+        RestartSec = 2;
+        LogsDirectory = "nagios";
+        StateDirectory = "nagios";
+        ExecStart = "${pkgs.nagios}/bin/nagios /etc/nagios.cfg";
+        X-ReloadIfChanged = nagiosCfgFile;
+      };
+    };
+
+    services.httpd.virtualHosts = optionalAttrs cfg.enableWebInterface {
+      ${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { extraConfig = extraHttpdConfig; } ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/netdata.nix b/nixpkgs/nixos/modules/services/monitoring/netdata.nix
new file mode 100644
index 000000000000..a5233a46e341
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/netdata.nix
@@ -0,0 +1,219 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.netdata;
+
+  wrappedPlugins = pkgs.runCommand "wrapped-plugins" { preferLocalBuild = true; } ''
+    mkdir -p $out/libexec/netdata/plugins.d
+    ln -s /run/wrappers/bin/apps.plugin $out/libexec/netdata/plugins.d/apps.plugin
+    ln -s /run/wrappers/bin/freeipmi.plugin $out/libexec/netdata/plugins.d/freeipmi.plugin
+    ln -s /run/wrappers/bin/perf.plugin $out/libexec/netdata/plugins.d/perf.plugin
+    ln -s /run/wrappers/bin/slabinfo.plugin $out/libexec/netdata/plugins.d/slabinfo.plugin
+  '';
+
+  plugins = [
+    "${cfg.package}/libexec/netdata/plugins.d"
+    "${wrappedPlugins}/libexec/netdata/plugins.d"
+  ] ++ cfg.extraPluginPaths;
+
+  localConfig = {
+    global = {
+      "plugins directory" = concatStringsSep " " plugins;
+    };
+    web = {
+      "web files owner" = "root";
+      "web files group" = "root";
+    };
+  };
+  mkConfig = generators.toINI {} (recursiveUpdate localConfig cfg.config);
+  configFile = pkgs.writeText "netdata.conf" (if cfg.configText != null then cfg.configText else mkConfig);
+
+  defaultUser = "netdata";
+
+in {
+  options = {
+    services.netdata = {
+      enable = mkEnableOption "netdata";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.netdata;
+        defaultText = "pkgs.netdata";
+        description = "Netdata package to use.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "netdata";
+        description = "User account under which netdata runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "netdata";
+        description = "Group under which netdata runs.";
+      };
+
+      configText = mkOption {
+        type = types.nullOr types.lines;
+        description = "Verbatim netdata.conf, cannot be combined with config.";
+        default = null;
+        example = ''
+          [global]
+          debug log = syslog
+          access log = syslog
+          error log = syslog
+        '';
+      };
+
+      python = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to enable python-based plugins
+          '';
+        };
+        extraPackages = mkOption {
+          default = ps: [];
+          defaultText = "ps: []";
+          example = literalExample ''
+            ps: [
+              ps.psycopg2
+              ps.docker
+              ps.dnspython
+            ]
+          '';
+          description = ''
+            Extra python packages available at runtime
+            to enable additional python plugins.
+          '';
+        };
+      };
+
+      extraPluginPaths = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        example = literalExample ''
+          [ "/path/to/plugins.d" ]
+        '';
+        description = ''
+          Extra paths to add to the netdata global "plugins directory"
+          option.  Useful for when you want to include your own
+          collection scripts.
+          </para><para>
+          Details about writing a custom netdata plugin are available at:
+          <link xlink:href="https://docs.netdata.cloud/collectors/plugins.d/"/>
+          </para><para>
+          Cannot be combined with configText.
+        '';
+      };
+
+      config = mkOption {
+        type = types.attrsOf types.attrs;
+        default = {};
+        description = "netdata.conf configuration as nix attributes. cannot be combined with configText.";
+        example = literalExample ''
+          global = {
+            "debug log" = "syslog";
+            "access log" = "syslog";
+            "error log" = "syslog";
+          };
+        '';
+        };
+      };
+    };
+
+  config = mkIf cfg.enable {
+    assertions =
+      [ { assertion = cfg.config != {} -> cfg.configText == null ;
+          message = "Cannot specify both config and configText";
+        }
+      ];
+
+    systemd.tmpfiles.rules = [
+      "d /var/cache/netdata 0755 ${cfg.user} ${cfg.group} -"
+      "Z /var/cache/netdata - ${cfg.user} ${cfg.group} -"
+      "d /var/log/netdata 0755 ${cfg.user} ${cfg.group} -"
+      "Z /var/log/netdata - ${cfg.user} ${cfg.group} -"
+      "d /var/lib/netdata 0755 ${cfg.user} ${cfg.group} -"
+      "Z /var/lib/netdata - ${cfg.user} ${cfg.group} -"
+      "d /etc/netdata 0755 ${cfg.user} ${cfg.group} -"
+      "Z /etc/netdata - ${cfg.user} ${cfg.group} -"
+    ];
+    systemd.services.netdata = {
+      description = "Real time performance monitoring";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = (with pkgs; [ curl gawk which ]) ++ lib.optional cfg.python.enable
+        (pkgs.python3.withPackages cfg.python.extraPackages);
+      serviceConfig = {
+        Environment="PYTHONPATH=${cfg.package}/libexec/netdata/python.d/python_modules";
+        ExecStart = "${cfg.package}/bin/netdata -P /run/netdata/netdata.pid -D -c ${configFile}";
+        ExecReload = "${pkgs.utillinux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID";
+        TimeoutStopSec = 60;
+        Restart = "on-failure";
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+        # Runtime directory and mode
+        RuntimeDirectory = "netdata";
+        RuntimeDirectoryMode = "0755";
+        # Performance
+        LimitNOFILE = "30000";
+      };
+    };
+
+    systemd.enableCgroupAccounting = true;
+
+    security.wrappers."apps.plugin" = {
+      source = "${cfg.package}/libexec/netdata/plugins.d/apps.plugin.org";
+      capabilities = "cap_dac_read_search,cap_sys_ptrace+ep";
+      owner = cfg.user;
+      group = cfg.group;
+      permissions = "u+rx,g+rx,o-rwx";
+    };
+
+    security.wrappers."freeipmi.plugin" = {
+      source = "${cfg.package}/libexec/netdata/plugins.d/freeipmi.plugin.org";
+      capabilities = "cap_dac_override,cap_fowner+ep";
+      owner = cfg.user;
+      group = cfg.group;
+      permissions = "u+rx,g+rx,o-rwx";
+    };
+
+    security.wrappers."perf.plugin" = {
+      source = "${cfg.package}/libexec/netdata/plugins.d/perf.plugin.org";
+      capabilities = "cap_sys_admin+ep";
+      owner = cfg.user;
+      group = cfg.group;
+      permissions = "u+rx,g+rx,o-rx";
+    };
+
+    security.wrappers."slabinfo.plugin" = {
+      source = "${cfg.package}/libexec/netdata/plugins.d/slabinfo.plugin.org";
+      capabilities = "cap_dac_override+ep";
+      owner = cfg.user;
+      group = cfg.group;
+      permissions = "u+rx,g+rx,o-rx";
+    };
+
+    security.pam.loginLimits = [
+      { domain = "netdata"; type = "soft"; item = "nofile"; value = "10000"; }
+      { domain = "netdata"; type = "hard"; item = "nofile"; value = "30000"; }
+    ];
+
+    users.users = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} = {
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == defaultUser) {
+      ${defaultUser} = { };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix
new file mode 100644
index 000000000000..1b02ebf37045
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -0,0 +1,187 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.alertmanager;
+  mkConfigFile = pkgs.writeText "alertmanager.yml" (builtins.toJSON cfg.configuration);
+
+  checkedConfig = file: pkgs.runCommand "checked-config" { buildInputs = [ cfg.package ]; } ''
+    ln -s ${file} $out
+    amtool check-config $out
+  '';
+
+  alertmanagerYml = let
+    yml = if cfg.configText != null then
+        pkgs.writeText "alertmanager.yml" cfg.configText
+        else mkConfigFile;
+    in checkedConfig yml;
+
+  cmdlineArgs = cfg.extraFlags ++ [
+    "--config.file /tmp/alert-manager-substituted.yaml"
+    "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
+    "--log.level ${cfg.logLevel}"
+    "--storage.path /var/lib/alertmanager"
+    (toString (map (peer: "--cluster.peer ${peer}:9094") cfg.clusterPeers))
+    ] ++ (optional (cfg.webExternalUrl != null)
+      "--web.external-url ${cfg.webExternalUrl}"
+    ) ++ (optional (cfg.logFormat != null)
+      "--log.format ${cfg.logFormat}"
+  );
+in {
+  imports = [
+    (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "user" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a user setting.")
+    (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting.")
+    (mkRemovedOptionModule [ "services" "prometheus" "alertmanagerURL" ] ''
+      Due to incompatibility, the alertmanagerURL option has been removed,
+      please use 'services.prometheus2.alertmanagers' instead.
+    '')
+  ];
+
+  options = {
+    services.prometheus.alertmanager = {
+      enable = mkEnableOption "Prometheus Alertmanager";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.prometheus-alertmanager;
+        defaultText = "pkgs.alertmanager";
+        description = ''
+          Package that should be used for alertmanager.
+        '';
+      };
+
+      configuration = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = ''
+          Alertmanager configuration as nix attribute set.
+        '';
+      };
+
+      configText = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = ''
+          Alertmanager configuration as YAML text. If non-null, this option
+          defines the text that is written to alertmanager.yml. If null, the
+          contents of alertmanager.yml is generated from the structured config
+          options.
+        '';
+      };
+
+      logFormat = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          If set use a syslog logger or JSON logging.
+        '';
+      };
+
+      logLevel = mkOption {
+        type = types.enum ["debug" "info" "warn" "error" "fatal"];
+        default = "warn";
+        description = ''
+          Only log messages with the given severity or above.
+        '';
+      };
+
+      webExternalUrl = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy).
+          Used for generating relative and absolute links back to Alertmanager itself.
+          If the URL has a path portion, it will be used to prefix all HTTP endoints served by Alertmanager.
+          If omitted, relevant URL components will be derived automatically.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Address to listen on for the web interface and API. Empty string will listen on all interfaces.
+          "localhost" will listen on 127.0.0.1 (but not ::1).
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 9093;
+        description = ''
+          Port to listen on for the web interface and API.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open port in firewall for incoming connections.
+        '';
+      };
+
+      clusterPeers = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Initial peers for HA cluster.
+        '';
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Extra commandline options when launching the Alertmanager.
+        '';
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/root/alertmanager.env";
+        description = ''
+          File to load as environment file. Environment variables
+          from this file will be interpolated into the config file
+          using envsubst with this syntax:
+          <literal>$ENVIRONMENT ''${VARIABLE}</literal>
+        '';
+      };
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+      assertions = singleton {
+        assertion = cfg.configuration != null || cfg.configText != null;
+        message = "Can not enable alertmanager without a configuration. "
+         + "Set either the `configuration` or `configText` attribute.";
+      };
+    })
+    (mkIf cfg.enable {
+      networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.port;
+
+      systemd.services.alertmanager = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network-online.target" ];
+        preStart = ''
+           ${lib.getBin pkgs.envsubst}/bin/envsubst -o "/tmp/alert-manager-substituted.yaml" \
+                                                    -i "${alertmanagerYml}"
+        '';
+        serviceConfig = {
+          Restart  = "always";
+          StateDirectory = "alertmanager";
+          DynamicUser = true; # implies PrivateTmp
+          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
+          WorkingDirectory = "/tmp";
+          ExecStart = "${cfg.package}/bin/alertmanager" +
+            optionalString (length cmdlineArgs != 0) (" \\\n  " +
+              concatStringsSep " \\\n  " cmdlineArgs);
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        };
+      };
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix
new file mode 100644
index 000000000000..84a72afac2f7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/default.nix
@@ -0,0 +1,642 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus;
+
+  workingDir = "/var/lib/" + cfg.stateDir;
+
+  # a wrapper that verifies that the configuration is valid
+  promtoolCheck = what: name: file:
+    if cfg.checkConfig then
+      pkgs.runCommandNoCCLocal
+        "${name}-${replaceStrings [" "] [""] what}-checked"
+        { buildInputs = [ cfg.package ]; } ''
+      ln -s ${file} $out
+      promtool ${what} $out
+    '' else file;
+
+  # Pretty-print JSON to a file
+  writePrettyJSON = name: x:
+    pkgs.runCommandNoCCLocal name {} ''
+      echo '${builtins.toJSON x}' | ${pkgs.jq}/bin/jq . > $out
+    '';
+
+  generatedPrometheusYml = writePrettyJSON "prometheus.yml" promConfig;
+
+  # This becomes the main config file for Prometheus
+  promConfig = {
+    global = filterValidPrometheus cfg.globalConfig;
+    rule_files = map (promtoolCheck "check rules" "rules") (cfg.ruleFiles ++ [
+      (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg.rules))
+    ]);
+    scrape_configs = filterValidPrometheus cfg.scrapeConfigs;
+    alerting = {
+      inherit (cfg) alertmanagers;
+    };
+  };
+
+  prometheusYml = let
+    yml = if cfg.configText != null then
+      pkgs.writeText "prometheus.yml" cfg.configText
+      else generatedPrometheusYml;
+    in promtoolCheck "check config" "prometheus.yml" yml;
+
+  cmdlineArgs = cfg.extraFlags ++ [
+    "--storage.tsdb.path=${workingDir}/data/"
+    "--config.file=${prometheusYml}"
+    "--web.listen-address=${cfg.listenAddress}"
+    "--alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
+    "--alertmanager.timeout=${toString cfg.alertmanagerTimeout}s"
+  ] ++
+  optional (cfg.webExternalUrl != null) "--web.external-url=${cfg.webExternalUrl}";
+
+  filterValidPrometheus = filterAttrsListRecursive (n: v: !(n == "_module" || v == null));
+  filterAttrsListRecursive = pred: x:
+    if isAttrs x then
+      listToAttrs (
+        concatMap (name:
+          let v = x.${name}; in
+          if pred name v then [
+            (nameValuePair name (filterAttrsListRecursive pred v))
+          ] else []
+        ) (attrNames x)
+      )
+    else if isList x then
+      map (filterAttrsListRecursive pred) x
+    else x;
+
+  mkDefOpt = type : defaultStr : description : mkOpt type (description + ''
+
+    Defaults to <literal>${defaultStr}</literal> in prometheus
+    when set to <literal>null</literal>.
+  '');
+
+  mkOpt = type : description : mkOption {
+    type = types.nullOr type;
+    default = null;
+    inherit description;
+  };
+
+  promTypes.globalConfig = types.submodule {
+    options = {
+      scrape_interval = mkDefOpt types.str "1m" ''
+        How frequently to scrape targets by default.
+      '';
+
+      scrape_timeout = mkDefOpt types.str "10s" ''
+        How long until a scrape request times out.
+      '';
+
+      evaluation_interval = mkDefOpt types.str "1m" ''
+        How frequently to evaluate rules by default.
+      '';
+
+      external_labels = mkOpt (types.attrsOf types.str) ''
+        The labels to add to any time series or alerts when
+        communicating with external systems (federation, remote
+        storage, Alertmanager).
+      '';
+    };
+  };
+
+  promTypes.scrape_config = types.submodule {
+    options = {
+      job_name = mkOption {
+        type = types.str;
+        description = ''
+          The job name assigned to scraped metrics by default.
+        '';
+      };
+      scrape_interval = mkOpt types.str ''
+        How frequently to scrape targets from this job. Defaults to the
+        globally configured default.
+      '';
+
+      scrape_timeout = mkOpt types.str ''
+        Per-target timeout when scraping this job. Defaults to the
+        globally configured default.
+      '';
+
+      metrics_path = mkDefOpt types.str "/metrics" ''
+        The HTTP resource path on which to fetch metrics from targets.
+      '';
+
+      honor_labels = mkDefOpt types.bool "false" ''
+        Controls how Prometheus handles conflicts between labels
+        that are already present in scraped data and labels that
+        Prometheus would attach server-side ("job" and "instance"
+        labels, manually configured target labels, and labels
+        generated by service discovery implementations).
+
+        If honor_labels is set to "true", label conflicts are
+        resolved by keeping label values from the scraped data and
+        ignoring the conflicting server-side labels.
+
+        If honor_labels is set to "false", label conflicts are
+        resolved by renaming conflicting labels in the scraped data
+        to "exported_&lt;original-label&gt;" (for example
+        "exported_instance", "exported_job") and then attaching
+        server-side labels. This is useful for use cases such as
+        federation, where all labels specified in the target should
+        be preserved.
+      '';
+
+      honor_timestamps = mkDefOpt types.bool "true" ''
+        honor_timestamps controls whether Prometheus respects the timestamps present
+        in scraped data.
+
+        If honor_timestamps is set to <literal>true</literal>, the timestamps of the metrics exposed
+        by the target will be used.
+
+        If honor_timestamps is set to <literal>false</literal>, the timestamps of the metrics exposed
+        by the target will be ignored.
+      '';
+
+      scheme = mkDefOpt (types.enum ["http" "https"]) "http" ''
+        The URL scheme with which to fetch metrics from targets.
+      '';
+
+      params = mkOpt (types.attrsOf (types.listOf types.str)) ''
+        Optional HTTP URL parameters.
+      '';
+
+      basic_auth = mkOpt (types.submodule {
+        options = {
+          username = mkOption {
+            type = types.str;
+            description = ''
+              HTTP username
+            '';
+          };
+          password = mkOption {
+            type = types.str;
+            description = ''
+              HTTP password
+            '';
+          };
+        };
+      }) ''
+        Optional http login credentials for metrics scraping.
+      '';
+
+      bearer_token = mkOpt types.str ''
+        Sets the `Authorization` header on every scrape request with
+        the configured bearer token. It is mutually exclusive with
+        <option>bearer_token_file</option>.
+      '';
+
+      bearer_token_file = mkOpt types.str ''
+        Sets the `Authorization` header on every scrape request with
+        the bearer token read from the configured file. It is mutually
+        exclusive with <option>bearer_token</option>.
+      '';
+
+      tls_config = mkOpt promTypes.tls_config ''
+        Configures the scrape request's TLS settings.
+      '';
+
+      proxy_url = mkOpt types.str ''
+        Optional proxy URL.
+      '';
+
+      ec2_sd_configs = mkOpt (types.listOf promTypes.ec2_sd_config) ''
+        List of EC2 service discovery configurations.
+      '';
+
+      dns_sd_configs = mkOpt (types.listOf promTypes.dns_sd_config) ''
+        List of DNS service discovery configurations.
+      '';
+
+      consul_sd_configs = mkOpt (types.listOf promTypes.consul_sd_config) ''
+        List of Consul service discovery configurations.
+      '';
+
+      file_sd_configs = mkOpt (types.listOf promTypes.file_sd_config) ''
+        List of file service discovery configurations.
+      '';
+
+      static_configs = mkOpt (types.listOf promTypes.static_config) ''
+        List of labeled target groups for this job.
+      '';
+
+      relabel_configs = mkOpt (types.listOf promTypes.relabel_config) ''
+        List of relabel configurations.
+      '';
+
+      sample_limit = mkDefOpt types.int "0" ''
+        Per-scrape limit on number of scraped samples that will be accepted.
+        If more than this number of samples are present after metric relabelling
+        the entire scrape will be treated as failed. 0 means no limit.
+      '';
+    };
+  };
+
+  promTypes.static_config = types.submodule {
+    options = {
+      targets = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          The targets specified by the target group.
+        '';
+      };
+      labels = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        description = ''
+          Labels assigned to all metrics scraped from the targets.
+        '';
+      };
+    };
+  };
+
+  promTypes.ec2_sd_config = types.submodule {
+    options = {
+      region = mkOption {
+        type = types.str;
+        description = ''
+          The AWS Region.
+        '';
+      };
+      endpoint = mkOpt types.str ''
+        Custom endpoint to be used.
+      '';
+
+      access_key = mkOpt types.str ''
+        The AWS API key id. If blank, the environment variable
+        <literal>AWS_ACCESS_KEY_ID</literal> is used.
+      '';
+
+      secret_key = mkOpt types.str ''
+        The AWS API key secret. If blank, the environment variable
+         <literal>AWS_SECRET_ACCESS_KEY</literal> is used.
+      '';
+
+      profile = mkOpt  types.str ''
+        Named AWS profile used to connect to the API.
+      '';
+
+      role_arn = mkOpt types.str ''
+        AWS Role ARN, an alternative to using AWS API keys.
+      '';
+
+      refresh_interval = mkDefOpt types.str "60s" ''
+        Refresh interval to re-read the instance list.
+      '';
+
+      port = mkDefOpt types.int "80" ''
+        The port to scrape metrics from. If using the public IP
+        address, this must instead be specified in the relabeling
+        rule.
+      '';
+
+      filters = mkOpt (types.listOf promTypes.filter) ''
+        Filters can be used optionally to filter the instance list by other criteria.
+      '';
+    };
+  };
+
+  promTypes.filter = types.submodule {
+    options = {
+      name = mkOption {
+        type = types.str;
+        description = ''
+          See <link xlink:href="https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html">this list</link>
+          for the available filters.
+        '';
+      };
+
+      value = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Value of the filter.
+        '';
+      };
+    };
+  };
+
+  promTypes.dns_sd_config = types.submodule {
+    options = {
+      names = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          A list of DNS SRV record names to be queried.
+        '';
+      };
+
+      refresh_interval = mkDefOpt types.str "30s" ''
+        The time after which the provided names are refreshed.
+      '';
+    };
+  };
+
+  promTypes.consul_sd_config = types.submodule {
+    options = {
+      server = mkDefOpt types.str "localhost:8500" ''
+        Consul server to query.
+      '';
+
+      token = mkOpt types.str "Consul token";
+
+      datacenter = mkOpt types.str "Consul datacenter";
+
+      scheme = mkDefOpt types.str "http" "Consul scheme";
+
+      username = mkOpt types.str "Consul username";
+
+      password = mkOpt types.str "Consul password";
+
+      tls_config = mkOpt promTypes.tls_config ''
+        Configures the Consul request's TLS settings.
+      '';
+
+      services = mkOpt (types.listOf types.str) ''
+        A list of services for which targets are retrieved.
+      '';
+
+      tags = mkOpt (types.listOf types.str) ''
+        An optional list of tags used to filter nodes for a given
+        service. Services must contain all tags in the list.
+      '';
+
+      node_meta = mkOpt (types.attrsOf types.str) ''
+        Node metadata used to filter nodes for a given service.
+      '';
+
+      tag_separator = mkDefOpt types.str "," ''
+        The string by which Consul tags are joined into the tag label.
+      '';
+
+      allow_stale = mkOpt types.bool ''
+        Allow stale Consul results
+        (see <link xlink:href="https://www.consul.io/api/index.html#consistency-modes"/>).
+
+        Will reduce load on Consul.
+      '';
+
+      refresh_interval = mkDefOpt types.str "30s" ''
+        The time after which the provided names are refreshed.
+
+        On large setup it might be a good idea to increase this value
+        because the catalog will change all the time.
+      '';
+    };
+  };
+
+  promTypes.file_sd_config = types.submodule {
+    options = {
+      files = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          Patterns for files from which target groups are extracted. Refer
+          to the Prometheus documentation for permitted filename patterns
+          and formats.
+        '';
+      };
+
+      refresh_interval = mkDefOpt types.str "5m" ''
+        Refresh interval to re-read the files.
+      '';
+    };
+  };
+
+  promTypes.relabel_config = types.submodule {
+    options = {
+      source_labels = mkOpt (types.listOf types.str) ''
+        The source labels select values from existing labels. Their content
+        is concatenated using the configured separator and matched against
+        the configured regular expression.
+      '';
+
+      separator = mkDefOpt types.str ";" ''
+        Separator placed between concatenated source label values.
+      '';
+
+      target_label = mkOpt types.str ''
+        Label to which the resulting value is written in a replace action.
+        It is mandatory for replace actions.
+      '';
+
+      regex = mkDefOpt types.str "(.*)" ''
+        Regular expression against which the extracted value is matched.
+      '';
+
+      modulus = mkOpt types.int ''
+        Modulus to take of the hash of the source label values.
+      '';
+
+      replacement = mkDefOpt types.str "$1" ''
+        Replacement value against which a regex replace is performed if the
+        regular expression matches.
+      '';
+
+      action = mkDefOpt (types.enum ["replace" "keep" "drop"]) "replace" ''
+        Action to perform based on regex matching.
+      '';
+
+    };
+  };
+
+  promTypes.tls_config = types.submodule {
+    options = {
+      ca_file = mkOpt types.str ''
+        CA certificate to validate API server certificate with.
+      '';
+
+      cert_file = mkOpt types.str ''
+        Certificate file for client cert authentication to the server.
+      '';
+
+      key_file = mkOpt types.str ''
+        Key file for client cert authentication to the server.
+      '';
+
+      server_name = mkOpt types.str ''
+        ServerName extension to indicate the name of the server.
+        http://tools.ietf.org/html/rfc4366#section-3.1
+      '';
+
+      insecure_skip_verify = mkOpt types.bool ''
+        Disable validation of the server certificate.
+      '';
+    };
+  };
+
+in {
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "prometheus2" ] [ "services" "prometheus" ])
+  ];
+
+  options.services.prometheus = {
+
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable the Prometheus monitoring daemon.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.prometheus;
+      defaultText = "pkgs.prometheus";
+      description = ''
+        The prometheus package that should be used.
+      '';
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "0.0.0.0:9090";
+      description = ''
+        Address to listen on for the web interface, API, and telemetry.
+      '';
+    };
+
+    stateDir = mkOption {
+      type = types.str;
+      default = "prometheus2";
+      description = ''
+        Directory below <literal>/var/lib</literal> to store Prometheus metrics data.
+        This directory will be created automatically using systemd's StateDirectory mechanism.
+      '';
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra commandline options when launching Prometheus.
+      '';
+    };
+
+    configText = mkOption {
+      type = types.nullOr types.lines;
+      default = null;
+      description = ''
+        If non-null, this option defines the text that is written to
+        prometheus.yml. If null, the contents of prometheus.yml is generated
+        from the structured config options.
+      '';
+    };
+
+    globalConfig = mkOption {
+      type = promTypes.globalConfig;
+      default = {};
+      description = ''
+        Parameters that are valid in all  configuration contexts. They
+        also serve as defaults for other configuration sections
+      '';
+    };
+
+    rules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Alerting and/or Recording rules to evaluate at runtime.
+      '';
+    };
+
+    ruleFiles = mkOption {
+      type = types.listOf types.path;
+      default = [];
+      description = ''
+        Any additional rules files to include in this configuration.
+      '';
+    };
+
+    scrapeConfigs = mkOption {
+      type = types.listOf promTypes.scrape_config;
+      default = [];
+      description = ''
+        A list of scrape configurations.
+      '';
+    };
+
+    alertmanagers = mkOption {
+      type = types.listOf types.attrs;
+      example = literalExample ''
+        [ {
+          scheme = "https";
+          path_prefix = "/alertmanager";
+          static_configs = [ {
+            targets = [
+              "prometheus.domain.tld"
+            ];
+          } ];
+        } ]
+      '';
+      default = [];
+      description = ''
+        A list of alertmanagers to send alerts to.
+        See <link xlink:href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#alertmanager_config">the official documentation</link> for more information.
+      '';
+    };
+
+    alertmanagerNotificationQueueCapacity = mkOption {
+      type = types.int;
+      default = 10000;
+      description = ''
+        The capacity of the queue for pending alert manager notifications.
+      '';
+    };
+
+    alertmanagerTimeout = mkOption {
+      type = types.int;
+      default = 10;
+      description = ''
+        Alert manager HTTP API timeout (in seconds).
+      '';
+    };
+
+    webExternalUrl = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "https://example.com/";
+      description = ''
+        The URL under which Prometheus is externally reachable (for example,
+        if Prometheus is served via a reverse proxy).
+      '';
+    };
+
+    checkConfig = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Check configuration with <literal>promtool
+        check</literal>. The call to <literal>promtool</literal> is
+        subject to sandboxing by Nix. When credentials are stored in
+        external files (<literal>password_file</literal>,
+        <literal>bearer_token_file</literal>, etc), they will not be
+        visible to <literal>promtool</literal> and it will report
+        errors, despite a correct configuration.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups.prometheus.gid = config.ids.gids.prometheus;
+    users.users.prometheus = {
+      description = "Prometheus daemon user";
+      uid = config.ids.uids.prometheus;
+      group = "prometheus";
+    };
+    systemd.services.prometheus = {
+      wantedBy = [ "multi-user.target" ];
+      after    = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/prometheus" +
+          optionalString (length cmdlineArgs != 0) (" \\\n  " +
+            concatStringsSep " \\\n  " cmdlineArgs);
+        User = "prometheus";
+        Restart  = "always";
+        WorkingDirectory = workingDir;
+        StateDirectory = cfg.stateDir;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
new file mode 100644
index 000000000000..0318acae50f7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -0,0 +1,249 @@
+{ config, pkgs, lib, options, ... }:
+
+let
+  inherit (lib) concatStrings foldl foldl' genAttrs literalExample maintainers
+                mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
+                optional types;
+
+  cfg = config.services.prometheus.exporters;
+
+  # each attribute in `exporterOpts` is expected to have specified:
+  #   - port        (types.int):   port on which the exporter listens
+  #   - serviceOpts (types.attrs): config that is merged with the
+  #                                default definition of the exporter's
+  #                                systemd service
+  #   - extraOpts   (types.attrs): extra configuration options to
+  #                                configure the exporter with, which
+  #                                are appended to the default options
+  #
+  #  Note that `extraOpts` is optional, but a script for the exporter's
+  #  systemd service must be provided by specifying either
+  #  `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart`
+
+  exporterOpts = genAttrs [
+    "apcupsd"
+    "bind"
+    "blackbox"
+    "collectd"
+    "dnsmasq"
+    "dovecot"
+    "fritzbox"
+    "json"
+    "keylight"
+    "lnd"
+    "mail"
+    "mikrotik"
+    "minio"
+    "nextcloud"
+    "nginx"
+    "node"
+    "postfix"
+    "postgres"
+    "rspamd"
+    "snmp"
+    "surfboard"
+    "tor"
+    "unifi"
+    "varnish"
+    "wireguard"
+  ] (name:
+    import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options; }
+  );
+
+  mkExporterOpts = ({ name, port }: {
+    enable = mkEnableOption "the prometheus ${name} exporter";
+    port = mkOption {
+      type = types.int;
+      default = port;
+      description = ''
+        Port to listen on.
+      '';
+    };
+    listenAddress = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      description = ''
+        Address to listen on.
+      '';
+    };
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra commandline options to pass to the ${name} exporter.
+      '';
+    };
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Open port in firewall for incoming connections.
+      '';
+    };
+    firewallFilter = mkOption {
+      type = types.str;
+      default = "-p tcp -m tcp --dport ${toString port}";
+      example = literalExample ''
+        "-i eth0 -p tcp -m tcp --dport ${toString port}"
+      '';
+      description = ''
+        Specify a filter for iptables to use when
+        <option>services.prometheus.exporters.${name}.openFirewall</option>
+        is true. It is used as `ip46tables -I nixos-fw <option>firewallFilter</option> -j nixos-fw-accept`.
+      '';
+    };
+    user = mkOption {
+      type = types.str;
+      default = "${name}-exporter";
+      description = ''
+        User name under which the ${name} exporter shall be run.
+        Has no effect when <option>systemd.services.prometheus-${name}-exporter.serviceConfig.DynamicUser</option> is true.
+      '';
+    };
+    group = mkOption {
+      type = types.str;
+      default = "${name}-exporter";
+      description = ''
+        Group under which the ${name} exporter shall be run.
+        Has no effect when <option>systemd.services.prometheus-${name}-exporter.serviceConfig.DynamicUser</option> is true.
+      '';
+    };
+  });
+
+  mkSubModule = { name, port, extraOpts, imports }: {
+    ${name} = mkOption {
+      type = types.submodule {
+        inherit imports;
+        options = (mkExporterOpts {
+          inherit name port;
+        } // extraOpts);
+      };
+      internal = true;
+      default = {};
+    };
+  };
+
+  mkSubModules = (foldl' (a: b: a//b) {}
+    (mapAttrsToList (name: opts: mkSubModule {
+      inherit name;
+      inherit (opts) port;
+      extraOpts = opts.extraOpts or {};
+      imports = opts.imports or [];
+    }) exporterOpts)
+  );
+
+  mkExporterConf = { name, conf, serviceOpts }:
+    let
+      enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true;
+    in
+    mkIf conf.enable {
+      warnings = conf.warnings or [];
+      users.users."${name}-exporter" = (mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) {
+        description = "Prometheus ${name} exporter service user";
+        isSystemUser = true;
+        inherit (conf) group;
+      });
+      users.groups = (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) {
+        "${name}-exporter" = {};
+      });
+      networking.firewall.extraCommands = mkIf conf.openFirewall (concatStrings [
+        "ip46tables -A nixos-fw ${conf.firewallFilter} "
+        "-m comment --comment ${name}-exporter -j nixos-fw-accept"
+      ]);
+      systemd.services."prometheus-${name}-exporter" = mkMerge ([{
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        serviceConfig.Restart = mkDefault "always";
+        serviceConfig.PrivateTmp = mkDefault true;
+        serviceConfig.WorkingDirectory = mkDefault /tmp;
+        serviceConfig.DynamicUser = mkDefault enableDynamicUser;
+      } serviceOpts ] ++ optional (!enableDynamicUser) {
+        serviceConfig.User = conf.user;
+        serviceConfig.Group = conf.group;
+      });
+  };
+in
+{
+
+  imports = (lib.forEach [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
+                   "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
+                   "snmpExporter" "unifiExporter" "varnishExporter" ]
+       (opt: lib.mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] ''
+         The prometheus exporters are now configured using `services.prometheus.exporters'.
+         See the 18.03 release notes for more information.
+       '' ))
+
+    ++ (lib.forEach [ "enable" "substitutions" "preset" ]
+       (opt: lib.mkRemovedOptionModule [ "fonts" "fontconfig" "ultimate" "${opt}" ] ''
+         The fonts.fontconfig.ultimate module and configuration is obsolete.
+         The repository has since been archived and activity has ceased.
+         https://github.com/bohoomil/fontconfig-ultimate/issues/171.
+         No action should be needed for font configuration, as the fonts.fontconfig
+         module is already used by default.
+       '' ));
+
+  options.services.prometheus.exporters = mkOption {
+    type = types.submodule {
+      options = (mkSubModules);
+    };
+    description = "Prometheus exporter configuration";
+    default = {};
+    example = literalExample ''
+      {
+        node = {
+          enable = true;
+          enabledCollectors = [ "systemd" ];
+        };
+        varnish.enable = true;
+      }
+    '';
+  };
+
+  config = mkMerge ([{
+    assertions = [ {
+      assertion = cfg.snmp.enable -> (
+        (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null)
+      );
+      message = ''
+        Please ensure you have either `services.prometheus.exporters.snmp.configuration'
+          or `services.prometheus.exporters.snmp.configurationPath' set!
+      '';
+    } {
+      assertion = cfg.mikrotik.enable -> (
+        (cfg.mikrotik.configFile == null) != (cfg.mikrotik.configuration == null)
+      );
+      message = ''
+        Please specify either `services.prometheus.exporters.mikrotik.configuration'
+          or `services.prometheus.exporters.mikrotik.configFile'.
+      '';
+    } {
+      assertion = cfg.mail.enable -> (
+        (cfg.mail.configFile == null) != (cfg.mail.configuration == null)
+      );
+      message = ''
+        Please specify either 'services.prometheus.exporters.mail.configuration'
+          or 'services.prometheus.exporters.mail.configFile'.
+      '';
+    } ];
+  }] ++ [(mkIf config.services.minio.enable {
+    services.prometheus.exporters.minio.minioAddress  = mkDefault "http://localhost:9000";
+    services.prometheus.exporters.minio.minioAccessKey = mkDefault config.services.minio.accessKey;
+    services.prometheus.exporters.minio.minioAccessSecret = mkDefault config.services.minio.secretKey;
+  })] ++ [(mkIf config.services.rspamd.enable {
+    services.prometheus.exporters.rspamd.url = mkDefault "http://localhost:11334/stat";
+  })] ++ [(mkIf config.services.nginx.enable {
+    systemd.services.prometheus-nginx-exporter.after = [ "nginx.service" ];
+    systemd.services.prometheus-nginx-exporter.requires = [ "nginx.service" ];
+  })] ++ (mapAttrsToList (name: conf:
+    mkExporterConf {
+      inherit name;
+      inherit (conf) serviceOpts;
+      conf = cfg.${name};
+    }) exporterOpts)
+  );
+
+  meta = {
+    doc = ./exporters.xml;
+    maintainers = [ maintainers.willibutz ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml
new file mode 100644
index 000000000000..c2d4b05996a4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.xml
@@ -0,0 +1,227 @@
+<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-prometheus-exporters">
+ <title>Prometheus exporters</title>
+ <para>
+  Prometheus exporters provide metrics for the
+  <link xlink:href="https://prometheus.io">prometheus monitoring system</link>.
+ </para>
+ <section xml:id="module-services-prometheus-exporters-configuration">
+  <title>Configuration</title>
+
+  <para>
+   One of the most common exporters is the
+   <link xlink:href="https://github.com/prometheus/node_exporter">node
+   exporter</link>, it provides hardware and OS metrics from the host it's
+   running on. The exporter could be configured as follows:
+<programlisting>
+  services.prometheus.exporters.node = {
+    enable = true;
+    enabledCollectors = [
+      "logind"
+      "systemd"
+    ];
+    disabledCollectors = [
+      "textfile"
+    ];
+    openFirewall = true;
+    firewallFilter = "-i br0 -p tcp -m tcp --dport 9100";
+  };
+</programlisting>
+   It should now serve all metrics from the collectors that are explicitly
+   enabled and the ones that are
+   <link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled
+   by default</link>, via http under <literal>/metrics</literal>. In this
+   example the firewall should just allow incoming connections to the
+   exporter's port on the bridge interface <literal>br0</literal> (this would
+   have to be configured seperately of course). For more information about
+   configuration see <literal>man configuration.nix</literal> or search through
+   the
+   <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available
+   options</link>.
+  </para>
+ </section>
+ <section xml:id="module-services-prometheus-exporters-new-exporter">
+  <title>Adding a new exporter</title>
+
+  <para>
+   To add a new exporter, it has to be packaged first (see
+   <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for
+   examples), then a module can be added. The postfix exporter is used in this
+   example:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Some default options for all exporters are provided by
+     <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>:
+    </para>
+   </listitem>
+   <listitem override='none'>
+    <itemizedlist>
+     <listitem>
+      <para>
+       <literal>enable</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>port</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>listenAddress</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>extraFlags</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>openFirewall</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>firewallFilter</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>user</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>group</literal>
+      </para>
+     </listitem>
+    </itemizedlist>
+   </listitem>
+   <listitem>
+    <para>
+     As there is already a package available, the module can now be added. This
+     is accomplished by adding a new file to the
+     <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal>
+     directory, which will be called postfix.nix and contains all exporter
+     specific options and configuration:
+<programlisting>
+# nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  # for convenience we define cfg here
+  cfg = config.services.prometheus.exporters.postfix;
+in
+{
+  port = 9154; # The postfix exporter listens on this port by default
+
+  # `extraOpts` is an attribute set which contains additional options
+  # (and optional overrides for default options).
+  # Note that this attribute is optional.
+  extraOpts = {
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+    logfilePath = mkOption {
+      type = types.path;
+      default = /var/log/postfix_exporter_input.log;
+      example = /var/log/mail.log;
+      description = ''
+        Path where Postfix writes log entries.
+        This file will be truncated by this exporter!
+      '';
+    };
+    showqPath = mkOption {
+      type = types.path;
+      default = /var/spool/postfix/public/showq;
+      example = /var/lib/postfix/queue/public/showq;
+      description = ''
+        Path at which Postfix places its showq socket.
+      '';
+    };
+  };
+
+  # `serviceOpts` is an attribute set which contains configuration
+  # for the exporter's systemd service. One of
+  # `serviceOpts.script` and `serviceOpts.serviceConfig.ExecStart`
+  # has to be specified here. This will be merged with the default
+  # service confiuration.
+  # Note that by default 'DynamicUser' is 'true'.
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      ExecStart = ''
+        ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
+</programlisting>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     This should already be enough for the postfix exporter. Additionally one
+     could now add assertions and conditional default values. This can be done
+     in the 'meta-module' that combines all exporter definitions and generates
+     the submodules:
+     <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal>
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+ <section xml:id="module-services-prometheus-exporters-update-exporter-module">
+  <title>Updating an exporter module</title>
+   <para>
+     Should an exporter option change at some point, it is possible to add
+     information about the change to the exporter definition similar to
+     <literal>nixpkgs/nixos/modules/rename.nix</literal>:
+<programlisting>
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.nginx;
+in
+{
+  port = 9113;
+  extraOpts = {
+    # additional module options
+    # ...
+  };
+  serviceOpts = {
+    # service configuration
+    # ...
+  };
+  imports = [
+    # 'services.prometheus.exporters.nginx.telemetryEndpoint' -> 'services.prometheus.exporters.nginx.telemetryPath'
+    (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
+
+    # removed option 'services.prometheus.exporters.nginx.insecure'
+    (mkRemovedOptionModule [ "insecure" ] ''
+      This option was replaced by 'prometheus.exporters.nginx.sslVerify' which defaults to true.
+    '')
+    ({ options.warnings = options.warnings; })
+  ];
+}
+</programlisting>
+    </para>
+  </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix
new file mode 100644
index 000000000000..57c35a742c5f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/apcupsd.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.apcupsd;
+in
+{
+  port = 9162;
+  extraOpts = {
+    apcupsdAddress = mkOption {
+      type = types.str;
+      default = ":3551";
+      description = ''
+        Address of the apcupsd Network Information Server (NIS).
+      '';
+    };
+
+    apcupsdNetwork = mkOption {
+      type = types.enum ["tcp" "tcp4" "tcp6"];
+      default = "tcp";
+      description = ''
+        Network of the apcupsd Network Information Server (NIS): one of "tcp", "tcp4", or "tcp6".
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-apcupsd-exporter}/bin/apcupsd_exporter \
+          -telemetry.addr ${cfg.listenAddress}:${toString cfg.port} \
+          -apcupsd.addr ${cfg.apcupsdAddress} \
+          -apcupsd.network ${cfg.apcupsdNetwork} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bind.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
new file mode 100644
index 000000000000..972632b5a24a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.bind;
+in
+{
+  port = 9119;
+  extraOpts = {
+    bindURI = mkOption {
+      type = types.str;
+      default = "http://localhost:8053/";
+      description = ''
+        HTTP XML API address of an Bind server.
+      '';
+    };
+    bindTimeout = mkOption {
+      type = types.str;
+      default = "10s";
+      description = ''
+        Timeout for trying to get stats from Bind.
+      '';
+    };
+    bindVersion = mkOption {
+      type = types.enum [ "xml.v2" "xml.v3" "auto" ];
+      default = "auto";
+      description = ''
+        BIND statistics version. Can be detected automatically.
+      '';
+    };
+    bindGroups = mkOption {
+      type = types.listOf (types.enum [ "server" "view" "tasks" ]);
+      default = [ "server" "view" ];
+      description = ''
+        List of statistics to collect. Available: [server, view, tasks]
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-bind-exporter}/bin/bind_exporter \
+          -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          -bind.pid-file /var/run/named/named.pid \
+          -bind.timeout ${toString cfg.bindTimeout} \
+          -bind.stats-url ${cfg.bindURI} \
+          -bind.stats-version ${cfg.bindVersion} \
+          -bind.stats-groups ${concatStringsSep "," cfg.bindGroups} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
new file mode 100644
index 000000000000..fe8d905da3fe
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  logPrefix = "services.prometheus.exporter.blackbox";
+  cfg = config.services.prometheus.exporters.blackbox;
+
+  # This ensures that we can deal with string paths, path types and
+  # store-path strings with context.
+  coerceConfigFile = file:
+    if (builtins.isPath file) || (lib.isStorePath file) then
+      file
+    else
+      (lib.warn ''
+        ${logPrefix}: configuration file "${file}" is being copied to the nix-store.
+        If you would like to avoid that, please set enableConfigCheck to false.
+      '' /. + file);
+  checkConfigLocation = file:
+    if lib.hasPrefix "/tmp/" file then
+      throw
+      "${logPrefix}: configuration file must not reside within /tmp - it won't be visible to the systemd service."
+    else
+      true;
+  checkConfig = file:
+    pkgs.runCommand "checked-blackbox-exporter.conf" {
+      preferLocalBuild = true;
+      buildInputs = [ pkgs.buildPackages.prometheus-blackbox-exporter ];
+    } ''
+      ln -s ${coerceConfigFile file} $out
+      blackbox_exporter --config.check --config.file $out
+    '';
+in {
+  port = 9115;
+  extraOpts = {
+    configFile = mkOption {
+      type = types.path;
+      description = ''
+        Path to configuration file.
+      '';
+    };
+    enableConfigCheck = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to run a correctness check for the configuration file. This depends
+        on the configuration file residing in the nix-store. Paths passed as string will
+        be copied to the store.
+      '';
+    };
+  };
+
+  serviceOpts = let
+    adjustedConfigFile = if cfg.enableConfigCheck then
+      checkConfig cfg.configFile
+    else
+      checkConfigLocation cfg.configFile;
+  in {
+    serviceConfig = {
+      AmbientCapabilities = [ "CAP_NET_RAW" ]; # for ping probes
+      ExecStart = ''
+        ${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --config.file ${escapeShellArg adjustedConfigFile} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+      ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
new file mode 100644
index 000000000000..972104630275
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.collectd;
+in
+{
+  port = 9103;
+  extraOpts = {
+    collectdBinary = {
+      enable = mkEnableOption "collectd binary protocol receiver";
+
+      authFile = mkOption {
+        default = null;
+        type = types.nullOr types.path;
+        description = "File mapping user names to pre-shared keys (passwords).";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 25826;
+        description = ''Network address on which to accept collectd binary network packets.'';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = ''
+          Address to listen on for binary network packets.
+          '';
+      };
+
+      securityLevel = mkOption {
+        type = types.enum ["None" "Sign" "Encrypt"];
+        default = "None";
+        description = ''
+          Minimum required security level for accepted packets.
+        '';
+      };
+    };
+
+    logFormat = mkOption {
+      type = types.str;
+      default = "logger:stderr";
+      example = "logger:syslog?appname=bob&local=7 or logger:stdout?json=true";
+      description = ''
+        Set the log target and format.
+      '';
+    };
+
+    logLevel = mkOption {
+      type = types.enum ["debug" "info" "warn" "error" "fatal"];
+      default = "info";
+      description = ''
+        Only log messages with the given severity or above.
+      '';
+    };
+  };
+  serviceOpts = let
+    collectSettingsArgs = if (cfg.collectdBinary.enable) then ''
+      -collectd.listen-address ${cfg.collectdBinary.listenAddress}:${toString cfg.collectdBinary.port} \
+      -collectd.security-level ${cfg.collectdBinary.securityLevel} \
+    '' else "";
+  in {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-collectd-exporter}/bin/collectd_exporter \
+          -log.format ${escapeShellArg cfg.logFormat} \
+          -log.level ${cfg.logLevel} \
+          -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          ${collectSettingsArgs} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
new file mode 100644
index 000000000000..68afba21d64a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.dnsmasq;
+in
+{
+  port = 9153;
+  extraOpts = {
+    dnsmasqListenAddress = mkOption {
+      type = types.str;
+      default = "localhost:53";
+      description = ''
+        Address on which dnsmasq listens.
+      '';
+    };
+    leasesPath = mkOption {
+      type = types.path;
+      default = "/var/lib/misc/dnsmasq.leases";
+      example = "/var/lib/dnsmasq/dnsmasq.leases";
+      description = ''
+        Path to the <literal>dnsmasq.leases</literal> file.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-dnsmasq-exporter}/bin/dnsmasq_exporter \
+          --listen ${cfg.listenAddress}:${toString cfg.port} \
+          --dnsmasq ${cfg.dnsmasqListenAddress} \
+          --leases_path ${escapeShellArg cfg.leasesPath} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
new file mode 100644
index 000000000000..aba3533e4395
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.dovecot;
+in
+{
+  port = 9166;
+  extraOpts = {
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+    socketPath = mkOption {
+      type = types.path;
+      default = "/var/run/dovecot/stats";
+      example = "/var/run/dovecot2/old-stats";
+      description = ''
+        Path under which the stats socket is placed.
+        The user/group under which the exporter runs,
+        should be able to access the socket in order
+        to scrape the metrics successfully.
+
+        Please keep in mind that the stats module has changed in
+        <link xlink:href="https://wiki2.dovecot.org/Upgrading/2.3">Dovecot 2.3+</link> which
+        is not <link xlink:href="https://github.com/kumina/dovecot_exporter/issues/8">compatible with this exporter</link>.
+
+        The following extra config has to be passed to Dovecot to ensure that recent versions
+        work with this exporter:
+        <programlisting>
+        {
+          <xref linkend="opt-services.prometheus.exporters.dovecot.enable" /> = true;
+          <xref linkend="opt-services.prometheus.exporters.dovecot.socketPath" /> = "/var/run/dovecot2/old-stats";
+          <xref linkend="opt-services.dovecot2.extraConfig" /> = '''
+            mail_plugins = $mail_plugins old_stats
+            service old-stats {
+              unix_listener old-stats {
+                user = dovecot-exporter
+                group = dovecot-exporter
+              }
+            }
+          ''';
+        }
+        </programlisting>
+      '';
+    };
+    scopes = mkOption {
+      type = types.listOf types.str;
+      default = [ "user" ];
+      example = [ "user" "global" ];
+      description = ''
+        Stats scopes to query.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      ExecStart = ''
+        ${pkgs.prometheus-dovecot-exporter}/bin/dovecot_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          --dovecot.socket-path ${escapeShellArg cfg.socketPath} \
+          --dovecot.scopes ${concatStringsSep "," cfg.scopes} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
new file mode 100644
index 000000000000..9526597b8c96
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.fritzbox;
+in
+{
+  port = 9133;
+  extraOpts = {
+    gatewayAddress = mkOption {
+      type = types.str;
+      default = "fritz.box";
+      description = ''
+        The hostname or IP of the FRITZ!Box.
+      '';
+    };
+
+    gatewayPort = mkOption {
+      type = types.int;
+      default = 49000;
+      description = ''
+        The port of the FRITZ!Box UPnP service.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-fritzbox-exporter}/bin/exporter \
+          -listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          -gateway-address ${cfg.gatewayAddress} \
+          -gateway-port ${toString cfg.gatewayPort} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/json.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/json.nix
new file mode 100644
index 000000000000..bd0026b55f72
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/json.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.json;
+in
+{
+  port = 7979;
+  extraOpts = {
+    url = mkOption {
+      type = types.str;
+      description = ''
+        URL to scrape JSON from.
+      '';
+    };
+    configFile = mkOption {
+      type = types.path;
+      description = ''
+        Path to configuration file.
+      '';
+    };
+    listenAddress = {}; # not used
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \
+          --port ${toString cfg.port} \
+          ${cfg.url} ${escapeShellArg cfg.configFile} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/keylight.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/keylight.nix
new file mode 100644
index 000000000000..dfa56343b871
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/keylight.nix
@@ -0,0 +1,19 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.keylight;
+in
+{
+  port = 9288;
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-keylight-exporter}/bin/keylight_exporter \
+          -metrics.addr ${cfg.listenAddress}:${toString cfg.port} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix
new file mode 100644
index 000000000000..35f972020574
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/lnd.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.lnd;
+in
+{
+  port = 9092;
+  extraOpts = {
+    lndHost = mkOption {
+      type = types.str;
+      default = "localhost:10009";
+      description = ''
+        lnd instance gRPC address:port.
+      '';
+    };
+
+    lndTlsPath = mkOption {
+      type = types.path;
+      description = ''
+        Path to lnd TLS certificate.
+      '';
+    };
+
+    lndMacaroonDir = mkOption {
+      type = types.path;
+      description = ''
+        Path to lnd macaroons.
+      '';
+    };
+  };
+  serviceOpts.serviceConfig = {
+    ExecStart = ''
+      ${pkgs.prometheus-lnd-exporter}/bin/lndmon \
+        --prometheus.listenaddr=${cfg.listenAddress}:${toString cfg.port} \
+        --prometheus.logdir=/var/log/prometheus-lnd-exporter \
+        --lnd.host=${cfg.lndHost} \
+        --lnd.tlspath=${cfg.lndTlsPath} \
+        --lnd.macaroondir=${cfg.lndMacaroonDir} \
+        ${concatStringsSep " \\\n  " cfg.extraFlags}
+    '';
+    LogsDirectory = "prometheus-lnd-exporter";
+    ReadOnlyPaths = [ cfg.lndTlsPath cfg.lndMacaroonDir ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
new file mode 100644
index 000000000000..18c5c4dd1623
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
@@ -0,0 +1,158 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.mail;
+
+  configurationFile = pkgs.writeText "prometheus-mail-exporter.conf" (builtins.toJSON (
+    # removes the _module attribute, null values and converts attrNames to lowercase
+    mapAttrs' (name: value:
+      if name == "servers"
+      then nameValuePair (toLower name)
+        ((map (srv: (mapAttrs' (n: v: nameValuePair (toLower n) v)
+          (filterAttrs (n: v: !(n == "_module" || v == null)) srv)
+        ))) value)
+      else nameValuePair (toLower name) value
+    ) (filterAttrs (n: _: !(n == "_module")) cfg.configuration)
+  ));
+
+  serverOptions.options = {
+    name = mkOption {
+      type = types.str;
+      description = ''
+        Value for label 'configname' which will be added to all metrics.
+      '';
+    };
+    server = mkOption {
+      type = types.str;
+      description = ''
+        Hostname of the server that should be probed.
+      '';
+    };
+    port = mkOption {
+      type = types.int;
+      example = 587;
+      description = ''
+        Port to use for SMTP.
+      '';
+    };
+    from = mkOption {
+      type = types.str;
+      example = "exporteruser@domain.tld";
+      description = ''
+        Content of 'From' Header for probing mails.
+      '';
+    };
+    to = mkOption {
+      type = types.str;
+      example = "exporteruser@domain.tld";
+      description = ''
+        Content of 'To' Header for probing mails.
+      '';
+    };
+    detectionDir = mkOption {
+      type = types.path;
+      example = "/var/spool/mail/exporteruser/new";
+      description = ''
+        Directory in which new mails for the exporter user are placed.
+        Note that this needs to exist when the exporter starts.
+      '';
+    };
+    login = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "exporteruser@domain.tld";
+      description = ''
+        Username to use for SMTP authentication.
+      '';
+    };
+    passphrase = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Password to use for SMTP authentication.
+      '';
+    };
+  };
+
+  exporterOptions.options = {
+    monitoringInterval = mkOption {
+      type = types.str;
+      example = "10s";
+      description = ''
+        Time interval between two probe attempts.
+      '';
+    };
+    mailCheckTimeout = mkOption {
+      type = types.str;
+      description = ''
+        Timeout until mails are considered "didn't make it".
+      '';
+    };
+    disableFileDeletion = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Disables the exporter's function to delete probing mails.
+      '';
+    };
+    servers = mkOption {
+      type = types.listOf (types.submodule serverOptions);
+      default = [];
+      example = literalExample ''
+        [ {
+          name = "testserver";
+          server = "smtp.domain.tld";
+          port = 587;
+          from = "exporteruser@domain.tld";
+          to = "exporteruser@domain.tld";
+          detectionDir = "/path/to/Maildir/new";
+        } ]
+      '';
+      description = ''
+        List of servers that should be probed.
+      '';
+    };
+  };
+in
+{
+  port = 9225;
+  extraOpts = {
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Specify the mailexporter configuration file to use.
+      '';
+    };
+    configuration = mkOption {
+      type = types.nullOr (types.submodule exporterOptions);
+      default = null;
+      description = ''
+        Specify the mailexporter configuration file to use.
+      '';
+    };
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      ExecStart = ''
+        ${pkgs.prometheus-mail-exporter}/bin/mailexporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          --config.file ${
+            if cfg.configuration != null then configurationFile else (escapeShellArg cfg.configFile)
+          } \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix
new file mode 100644
index 000000000000..62c2cc568476
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/mikrotik.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.mikrotik;
+in
+{
+  port = 9436;
+  extraOpts = {
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Path to a mikrotik exporter configuration file. Mutually exclusive with
+        <option>configuration</option> option.
+      '';
+      example = literalExample "./mikrotik.yml";
+    };
+
+    configuration = mkOption {
+      type = types.nullOr types.attrs;
+      default = null;
+      description = ''
+        Mikrotik exporter configuration as nix attribute set. Mutually exclusive with
+        <option>configFile</option> option.
+
+        See <link xlink:href="https://github.com/nshttpd/mikrotik-exporter/blob/master/README.md"/>
+        for the description of the configuration file format.
+      '';
+      example = literalExample ''
+        {
+          devices = [
+            {
+              name = "my_router";
+              address = "10.10.0.1";
+              user = "prometheus";
+              password = "changeme";
+            }
+          ];
+          features = {
+            bgp = true;
+            dhcp = true;
+            routes = true;
+            optics = true;
+          };
+        }
+      '';
+    };
+  };
+  serviceOpts = let
+    configFile = if cfg.configFile != null
+                 then cfg.configFile
+                 else "${pkgs.writeText "mikrotik-exporter.yml" (builtins.toJSON cfg.configuration)}";
+    in {
+    serviceConfig = {
+      # -port is misleading name, it actually accepts address too
+      ExecStart = ''
+        ${pkgs.prometheus-mikrotik-exporter}/bin/mikrotik-exporter \
+          -config-file=${escapeShellArg configFile} \
+          -port=${cfg.listenAddress}:${toString cfg.port} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/minio.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
new file mode 100644
index 000000000000..d6dd62f871bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
@@ -0,0 +1,64 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.minio;
+in
+{
+  port = 9290;
+  extraOpts = {
+    minioAddress = mkOption {
+      type = types.str;
+      example = "https://10.0.0.1:9000";
+      description = ''
+        The URL of the minio server.
+        Use HTTPS if Minio accepts secure connections only.
+        By default this connects to the local minio server if enabled.
+      '';
+    };
+
+    minioAccessKey = mkOption {
+      type = types.str;
+      example = "yourMinioAccessKey";
+      description = ''
+        The value of the Minio access key.
+        It is required in order to connect to the server.
+        By default this uses the one from the local minio server if enabled
+        and <literal>config.services.minio.accessKey</literal>.
+      '';
+    };
+
+    minioAccessSecret = mkOption {
+      type = types.str;
+      description = ''
+        The value of the Minio access secret.
+        It is required in order to connect to the server.
+        By default this uses the one from the local minio server if enabled
+        and <literal>config.services.minio.secretKey</literal>.
+      '';
+    };
+
+    minioBucketStats = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Collect statistics about the buckets and files in buckets.
+        It requires more computation, use it carefully in case of large buckets..
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-minio-exporter}/bin/minio-exporter \
+          -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          -minio.server ${cfg.minioAddress} \
+          -minio.access-key ${escapeShellArg cfg.minioAccessKey} \
+          -minio.access-secret ${escapeShellArg cfg.minioAccessSecret} \
+          ${optionalString cfg.minioBucketStats "-minio.bucket-stats"} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix
new file mode 100644
index 000000000000..aee6bd5e66ce
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.nextcloud;
+in
+{
+  port = 9205;
+  extraOpts = {
+    url = mkOption {
+      type = types.str;
+      example = "https://domain.tld";
+      description = ''
+        URL to the Nextcloud serverinfo page.
+        Adding the path to the serverinfo API is optional, it defaults
+        to <literal>/ocs/v2.php/apps/serverinfo/api/v1/info</literal>.
+      '';
+    };
+    username = mkOption {
+      type = types.str;
+      default = "nextcloud-exporter";
+      description = ''
+        Username for connecting to Nextcloud.
+        Note that this account needs to have admin privileges in Nextcloud.
+      '';
+    };
+    passwordFile = mkOption {
+      type = types.path;
+      example = "/path/to/password-file";
+      description = ''
+        File containing the password for connecting to Nextcloud.
+        Make sure that this file is readable by the exporter user.
+      '';
+    };
+    timeout = mkOption {
+      type = types.str;
+      default = "5s";
+      description = ''
+        Timeout for getting server info document.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      ExecStart = ''
+        ${pkgs.prometheus-nextcloud-exporter}/bin/nextcloud-exporter \
+          -a ${cfg.listenAddress}:${toString cfg.port} \
+          -u ${cfg.username} \
+          -t ${cfg.timeout} \
+          -l ${cfg.url} \
+          -p ${escapeShellArg "@${cfg.passwordFile}"} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
new file mode 100644
index 000000000000..56cddfc55b71
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.nginx;
+in
+{
+  port = 9113;
+  extraOpts = {
+    scrapeUri = mkOption {
+      type = types.str;
+      default = "http://localhost/nginx_status";
+      description = ''
+        Address to access the nginx status page.
+        Can be enabled with services.nginx.statusPage = true.
+      '';
+    };
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+    sslVerify = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to perform certificate verification for https.
+      '';
+    };
+    constLabels = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [
+        "label1=value1"
+        "label2=value2"
+      ];
+      description = ''
+        A list of constant labels that will be used in every metric.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-nginx-exporter}/bin/nginx-prometheus-exporter \
+          --nginx.scrape-uri '${cfg.scrapeUri}' \
+          --nginx.ssl-verify ${toString cfg.sslVerify} \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          --prometheus.const-labels ${concatStringsSep "," cfg.constLabels} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+  imports = [
+    (mkRenamedOptionModule [ "telemetryEndpoint" ] [ "telemetryPath" ])
+    (mkRemovedOptionModule [ "insecure" ] ''
+      This option was replaced by 'prometheus.exporters.nginx.sslVerify'.
+    '')
+    ({ options.warnings = options.warnings; options.assertions = options.assertions; })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
new file mode 100644
index 000000000000..adc2abe0b91c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/node.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.node;
+in
+{
+  port = 9100;
+  extraOpts = {
+    enabledCollectors = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = ''[ "systemd" ]'';
+      description = ''
+        Collectors to enable. The collectors listed here are enabled in addition to the default ones.
+      '';
+    };
+    disabledCollectors = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = ''[ "timex" ]'';
+      description = ''
+        Collectors to disable which are enabled by default.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      RuntimeDirectory = "prometheus-node-exporter";
+      ExecStart = ''
+        ${pkgs.prometheus-node-exporter}/bin/node_exporter \
+          ${concatMapStringsSep " " (x: "--collector." + x) cfg.enabledCollectors} \
+          ${concatMapStringsSep " " (x: "--no-collector." + x) cfg.disabledCollectors} \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
new file mode 100644
index 000000000000..3b6ef1631f89
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.postfix;
+in
+{
+  port = 9154;
+  extraOpts = {
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+    logfilePath = mkOption {
+      type = types.path;
+      default = "/var/log/postfix_exporter_input.log";
+      example = "/var/log/mail.log";
+      description = ''
+        Path where Postfix writes log entries.
+        This file will be truncated by this exporter!
+      '';
+    };
+    showqPath = mkOption {
+      type = types.path;
+      default = "/var/spool/postfix/public/showq";
+      example = "/var/lib/postfix/queue/public/showq";
+      description = ''
+        Path where Postfix places it's showq socket.
+      '';
+    };
+    systemd = {
+      enable = mkEnableOption ''
+        reading metrics from the systemd-journal instead of from a logfile
+      '';
+      unit = mkOption {
+        type = types.str;
+        default = "postfix.service";
+        description = ''
+          Name of the postfix systemd unit.
+        '';
+      };
+      slice = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Name of the postfix systemd slice.
+          This overrides the <option>systemd.unit</option>.
+        '';
+      };
+      journalPath = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Path to the systemd journal.
+        '';
+      };
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = false;
+      ExecStart = ''
+        ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          --postfix.showq_path ${escapeShellArg cfg.showqPath} \
+          ${concatStringsSep " \\\n  " (cfg.extraFlags
+          ++ optional cfg.systemd.enable "--systemd.enable"
+          ++ optional cfg.systemd.enable (if cfg.systemd.slice != null
+                                          then "--systemd.slice ${cfg.systemd.slice}"
+                                          else "--systemd.unit ${cfg.systemd.unit}")
+          ++ optional (cfg.systemd.enable && (cfg.systemd.journalPath != null))
+                       "--systemd.journal_path ${escapeShellArg cfg.systemd.journalPath}"
+          ++ optional (!cfg.systemd.enable) "--postfix.logfile_path ${escapeShellArg cfg.logfilePath}")}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
new file mode 100644
index 000000000000..1ece73a1159a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.postgres;
+in
+{
+  port = 9187;
+  extraOpts = {
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+    dataSourceName = mkOption {
+      type = types.str;
+      default = "user=postgres database=postgres host=/run/postgresql sslmode=disable";
+      example = "postgresql://username:password@localhost:5432/postgres?sslmode=disable";
+      description = ''
+        Accepts PostgreSQL URI form and key=value form arguments.
+      '';
+    };
+    runAsLocalSuperUser = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run the exporter as the local 'postgres' super user.
+      '';
+    };
+  };
+  serviceOpts = {
+    environment.DATA_SOURCE_NAME = cfg.dataSourceName;
+    serviceConfig = {
+      DynamicUser = false;
+      User = mkIf cfg.runAsLocalSuperUser (mkForce "postgres");
+      ExecStart = ''
+        ${pkgs.prometheus-postgres-exporter}/bin/postgres_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
new file mode 100644
index 000000000000..1f02ae207249
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.rspamd;
+
+  prettyJSON = conf:
+    pkgs.runCommand "rspamd-exporter-config.yml" { } ''
+      echo '${builtins.toJSON conf}' | ${pkgs.buildPackages.jq}/bin/jq '.' > $out
+    '';
+
+  generateConfig = extraLabels: (map (path: {
+    name = "rspamd_${replaceStrings [ "." " " ] [ "_" "_" ] path}";
+    path = "$.${path}";
+    labels = extraLabels;
+  }) [
+    "actions.'add header'"
+    "actions.'no action'"
+    "actions.'rewrite subject'"
+    "actions.'soft reject'"
+    "actions.greylist"
+    "actions.reject"
+    "bytes_allocated"
+    "chunks_allocated"
+    "chunks_freed"
+    "chunks_oversized"
+    "connections"
+    "control_connections"
+    "ham_count"
+    "learned"
+    "pools_allocated"
+    "pools_freed"
+    "read_only"
+    "scanned"
+    "shared_chunks_allocated"
+    "spam_count"
+    "total_learns"
+  ]) ++ [{
+    name = "rspamd_statfiles";
+    type = "object";
+    path = "$.statfiles[*]";
+    labels = recursiveUpdate {
+      symbol = "$.symbol";
+      type = "$.type";
+    } extraLabels;
+    values = {
+      revision = "$.revision";
+      size = "$.size";
+      total = "$.total";
+      used = "$.used";
+      languages = "$.languages";
+      users = "$.users";
+    };
+  }];
+in
+{
+  port = 7980;
+  extraOpts = {
+    listenAddress = {}; # not used
+
+    url = mkOption {
+      type = types.str;
+      description = ''
+        URL to the rspamd metrics endpoint.
+        Defaults to http://localhost:11334/stat when
+        <option>services.rspamd.enable</option> is true.
+      '';
+    };
+
+    extraLabels = mkOption {
+      type = types.attrsOf types.str;
+      default = {
+        host = config.networking.hostName;
+      };
+      defaultText = "{ host = config.networking.hostName; }";
+      example = literalExample ''
+        {
+          host = config.networking.hostName;
+          custom_label = "some_value";
+        }
+      '';
+      description = "Set of labels added to each metric.";
+    };
+  };
+  serviceOpts.serviceConfig.ExecStart = ''
+    ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \
+      --port ${toString cfg.port} \
+      ${cfg.url} ${prettyJSON (generateConfig cfg.extraLabels)} \
+      ${concatStringsSep " \\\n  " cfg.extraFlags}
+  '';
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
new file mode 100644
index 000000000000..01276366e97b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.snmp;
+in
+{
+  port = 9116;
+  extraOpts = {
+    configurationPath = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Path to a snmp exporter configuration file. Mutually exclusive with 'configuration' option.
+      '';
+      example = "./snmp.yml";
+    };
+
+    configuration = mkOption {
+      type = types.nullOr types.attrs;
+      default = null;
+      description = ''
+        Snmp exporter configuration as nix attribute set. Mutually exclusive with 'configurationPath' option.
+      '';
+      example = ''
+        {
+          "default" = {
+            "version" = 2;
+            "auth" = {
+              "community" = "public";
+            };
+          };
+        };
+      '';
+    };
+
+    logFormat = mkOption {
+      type = types.enum ["logfmt" "json"];
+      default = "logfmt";
+      description = ''
+        Output format of log messages.
+      '';
+    };
+
+    logLevel = mkOption {
+      type = types.enum ["debug" "info" "warn" "error"];
+      default = "info";
+      description = ''
+        Only log messages with the given severity or above.
+      '';
+    };
+  };
+  serviceOpts = let
+    configFile = if cfg.configurationPath != null
+                 then cfg.configurationPath
+                 else "${pkgs.writeText "snmp-exporter-conf.yml" (builtins.toJSON cfg.configuration)}";
+    in {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-snmp-exporter}/bin/snmp_exporter \
+          --config.file=${escapeShellArg configFile} \
+          --log.format=${escapeShellArg cfg.logFormat} \
+          --log.level=${cfg.logLevel} \
+          --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
new file mode 100644
index 000000000000..81c5c70ed93f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.surfboard;
+in
+{
+  port = 9239;
+  extraOpts = {
+    modemAddress = mkOption {
+      type = types.str;
+      default = "192.168.100.1";
+      description = ''
+        The hostname or IP of the cable modem.
+      '';
+    };
+  };
+  serviceOpts = {
+    description = "Prometheus exporter for surfboard cable modem";
+    unitConfig.Documentation = "https://github.com/ipstatic/surfboard_exporter";
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-surfboard-exporter}/bin/surfboard_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --modem-address ${cfg.modemAddress} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
new file mode 100644
index 000000000000..36c473677efa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.tor;
+in
+{
+  port = 9130;
+  extraOpts = {
+    torControlAddress = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = ''
+        Tor control IP address or hostname.
+      '';
+    };
+
+    torControlPort = mkOption {
+      type = types.int;
+      default = 9051;
+      description = ''
+        Tor control port.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-tor-exporter}/bin/prometheus-tor-exporter \
+          -b ${cfg.listenAddress} \
+          -p ${toString cfg.port} \
+          -a ${cfg.torControlAddress} \
+          -c ${toString cfg.torControlPort} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+
+    # CPython requires a process to either have $HOME defined or run as a UID
+    # defined in /etc/passwd. The latter is false with DynamicUser, so define a
+    # dummy $HOME. https://bugs.python.org/issue10496
+    environment = { HOME = "/var/empty"; };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
new file mode 100644
index 000000000000..8d0e8764001c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.unifi;
+in
+{
+  port = 9130;
+  extraOpts = {
+    unifiAddress = mkOption {
+      type = types.str;
+      example = "https://10.0.0.1:8443";
+      description = ''
+        URL of the UniFi Controller API.
+      '';
+    };
+
+    unifiInsecure = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If enabled skip the verification of the TLS certificate of the UniFi Controller API.
+        Use with caution.
+      '';
+    };
+
+    unifiUsername = mkOption {
+      type = types.str;
+      example = "ReadOnlyUser";
+      description = ''
+        username for authentication against UniFi Controller API.
+      '';
+    };
+
+    unifiPassword = mkOption {
+      type = types.str;
+      description = ''
+        Password for authentication against UniFi Controller API.
+      '';
+    };
+
+    unifiTimeout = mkOption {
+      type = types.str;
+      default = "5s";
+      example = "2m";
+      description = ''
+        Timeout including unit for UniFi Controller API requests.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      ExecStart = ''
+        ${pkgs.prometheus-unifi-exporter}/bin/unifi_exporter \
+          -telemetry.addr ${cfg.listenAddress}:${toString cfg.port} \
+          -unifi.addr ${cfg.unifiAddress} \
+          -unifi.username ${escapeShellArg cfg.unifiUsername} \
+          -unifi.password ${escapeShellArg cfg.unifiPassword} \
+          -unifi.timeout ${cfg.unifiTimeout} \
+          ${optionalString cfg.unifiInsecure "-unifi.insecure" } \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
new file mode 100644
index 000000000000..5b5a6e18fcd6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
@@ -0,0 +1,88 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.varnish;
+in
+{
+  port = 9131;
+  extraOpts = {
+    noExit = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Do not exit server on Varnish scrape errors.
+      '';
+    };
+    withGoMetrics = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Export go runtime and http handler metrics.
+      '';
+    };
+    verbose = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable verbose logging.
+      '';
+    };
+    raw = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable raw stdout logging without timestamps.
+      '';
+    };
+    varnishStatPath = mkOption {
+      type = types.str;
+      default = "varnishstat";
+      description = ''
+        Path to varnishstat.
+      '';
+    };
+    instance = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        varnishstat -n value.
+      '';
+    };
+    healthPath = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Path under which to expose healthcheck. Disabled unless configured.
+      '';
+    };
+    telemetryPath = mkOption {
+      type = types.str;
+      default = "/metrics";
+      description = ''
+        Path under which to expose metrics.
+      '';
+    };
+  };
+  serviceOpts = {
+    path = [ pkgs.varnish ];
+    serviceConfig = {
+      RestartSec = mkDefault 1;
+      DynamicUser = false;
+      ExecStart = ''
+        ${pkgs.prometheus-varnish-exporter}/bin/prometheus_varnish_exporter \
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --web.telemetry-path ${cfg.telemetryPath} \
+          --varnishstat-path ${escapeShellArg cfg.varnishStatPath} \
+          ${concatStringsSep " \\\n  " (cfg.extraFlags
+            ++ optional (cfg.healthPath != null) "--web.health-path ${cfg.healthPath}"
+            ++ optional (cfg.instance != null) "-n ${escapeShellArg cfg.instance}"
+            ++ optional cfg.noExit "--no-exit"
+            ++ optional cfg.withGoMetrics "--with-go-metrics"
+            ++ optional cfg.verbose "--verbose"
+            ++ optional cfg.raw "--raw")}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
new file mode 100644
index 000000000000..04421fc2d25a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, options }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.wireguard;
+in {
+  port = 9586;
+  imports = [
+    (mkRenamedOptionModule [ "addr" ] [ "listenAddress" ])
+    ({ options.warnings = options.warnings; options.assertions = options.assertions; })
+  ];
+  extraOpts = {
+    verbose = mkEnableOption "Verbose logging mode for prometheus-wireguard-exporter";
+
+    wireguardConfig = mkOption {
+      type = with types; nullOr (either path str);
+      default = null;
+
+      description = ''
+        Path to the Wireguard Config to
+        <link xlink:href="https://github.com/MindFlavor/prometheus_wireguard_exporter/tree/2.0.0#usage">add the peer's name to the stats of a peer</link>.
+
+        Please note that <literal>networking.wg-quick</literal> is required for this feature
+        as <literal>networking.wireguard</literal> uses
+        <citerefentry><refentrytitle>wg</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        to set the peers up.
+      '';
+    };
+
+    singleSubnetPerField = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        By default, all allowed IPs and subnets are comma-separated in the
+        <literal>allowed_ips</literal> field. With this option enabled,
+        a single IP and subnet will be listed in fields like <literal>allowed_ip_0</literal>,
+        <literal>allowed_ip_1</literal> and so on.
+      '';
+    };
+
+    withRemoteIp = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether or not the remote IP of a WireGuard peer should be exposed via prometheus.
+      '';
+    };
+  };
+  serviceOpts = {
+    path = [ pkgs.wireguard-tools ];
+
+    serviceConfig = {
+      AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+      ExecStart = ''
+        ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \
+          -p ${toString cfg.port} \
+          -l ${cfg.listenAddress} \
+          ${optionalString cfg.verbose "-v"} \
+          ${optionalString cfg.singleSubnetPerField "-s"} \
+          ${optionalString cfg.withRemoteIp "-r"} \
+          ${optionalString (cfg.wireguardConfig != null) "-n ${escapeShellArg cfg.wireguardConfig}"}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix
new file mode 100644
index 000000000000..f8fcc3eb97ef
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/pushgateway.nix
@@ -0,0 +1,166 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.pushgateway;
+
+  cmdlineArgs =
+       opt "web.listen-address" cfg.web.listen-address
+    ++ opt "web.telemetry-path" cfg.web.telemetry-path
+    ++ opt "web.external-url" cfg.web.external-url
+    ++ opt "web.route-prefix" cfg.web.route-prefix
+    ++ optional cfg.persistMetrics ''--persistence.file="/var/lib/${cfg.stateDir}/metrics"''
+    ++ opt "persistence.interval" cfg.persistence.interval
+    ++ opt "log.level" cfg.log.level
+    ++ opt "log.format" cfg.log.format
+    ++ cfg.extraFlags;
+
+  opt = k : v : optional (v != null) ''--${k}="${v}"'';
+
+in {
+  options = {
+    services.prometheus.pushgateway = {
+      enable = mkEnableOption "Prometheus Pushgateway";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.prometheus-pushgateway;
+        defaultText = "pkgs.prometheus-pushgateway";
+        description = ''
+          Package that should be used for the prometheus pushgateway.
+        '';
+      };
+
+      web.listen-address = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Address to listen on for the web interface, API and telemetry.
+
+          <literal>null</literal> will default to <literal>:9091</literal>.
+        '';
+      };
+
+      web.telemetry-path = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Path under which to expose metrics.
+
+          <literal>null</literal> will default to <literal>/metrics</literal>.
+        '';
+      };
+
+      web.external-url = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The URL under which Pushgateway is externally reachable.
+        '';
+      };
+
+      web.route-prefix = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Prefix for the internal routes of web endpoints.
+
+          Defaults to the path of
+          <option>services.prometheus.pushgateway.web.external-url</option>.
+        '';
+      };
+
+      persistence.interval = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "10m";
+        description = ''
+          The minimum interval at which to write out the persistence file.
+
+          <literal>null</literal> will default to <literal>5m</literal>.
+        '';
+      };
+
+      log.level = mkOption {
+        type = types.nullOr (types.enum ["debug" "info" "warn" "error" "fatal"]);
+        default = null;
+        description = ''
+          Only log messages with the given severity or above.
+
+          <literal>null</literal> will default to <literal>info</literal>.
+        '';
+      };
+
+      log.format = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "logger:syslog?appname=bob&local=7";
+        description = ''
+          Set the log target and format.
+
+          <literal>null</literal> will default to <literal>logger:stderr</literal>.
+        '';
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Extra commandline options when launching the Pushgateway.
+        '';
+      };
+
+      persistMetrics = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to persist metrics to a file.
+
+          When enabled metrics will be saved to a file called
+          <literal>metrics</literal> in the directory
+          <literal>/var/lib/pushgateway</literal>. The directory below
+          <literal>/var/lib</literal> can be set using
+          <option>services.prometheus.pushgateway.stateDir</option>.
+        '';
+      };
+
+      stateDir = mkOption {
+        type = types.str;
+        default = "pushgateway";
+        description = ''
+          Directory below <literal>/var/lib</literal> to store metrics.
+
+          This directory will be created automatically using systemd's
+          StateDirectory mechanism when
+          <option>services.prometheus.pushgateway.persistMetrics</option>
+          is enabled.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !hasPrefix "/" cfg.stateDir;
+        message =
+          "The option services.prometheus.pushgateway.stateDir" +
+          " shouldn't be an absolute directory." +
+          " It should be a directory relative to /var/lib.";
+      }
+    ];
+    systemd.services.pushgateway = {
+      wantedBy = [ "multi-user.target" ];
+      after    = [ "network.target" ];
+      serviceConfig = {
+        Restart  = "always";
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/pushgateway" +
+          optionalString (length cmdlineArgs != 0) (" \\\n  " +
+            concatStringsSep " \\\n  " cmdlineArgs);
+        StateDirectory = if cfg.persistMetrics then cfg.stateDir else null;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix b/nixpkgs/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix
new file mode 100644
index 000000000000..44b15cb2034c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/prometheus/xmpp-alerts.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.xmpp-alerts;
+
+  configFile = pkgs.writeText "prometheus-xmpp-alerts.yml" (builtins.toJSON cfg.configuration);
+
+in
+
+{
+  options.services.prometheus.xmpp-alerts = {
+
+    enable = mkEnableOption "XMPP Web hook service for Alertmanager";
+
+    configuration = mkOption {
+      type = types.attrs;
+      description = "Configuration as attribute set which will be converted to YAML";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.prometheus-xmpp-alerts = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.prometheus-xmpp-alerts}/bin/prometheus-xmpp-alerts --config ${configFile}";
+        Restart = "on-failure";
+        DynamicUser = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHome = true;
+        ProtectSystem = "strict";
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        NoNewPrivileges = true;
+        SystemCallArchitectures = "native";
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        SystemCallFilter = [ "@system-service" ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/riemann-dash.nix b/nixpkgs/nixos/modules/services/monitoring/riemann-dash.nix
new file mode 100644
index 000000000000..16eb83008509
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/riemann-dash.nix
@@ -0,0 +1,81 @@
+{ config, pkgs, lib, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+  cfg = config.services.riemann-dash;
+
+  conf = writeText "config.rb" ''
+    riemann_base = "${cfg.dataDir}"
+    config.store[:ws_config] = "#{riemann_base}/config/config.json"
+    ${cfg.config}
+  '';
+
+  launcher = writeScriptBin "riemann-dash" ''
+    #!/bin/sh
+    exec ${pkgs.riemann-dash}/bin/riemann-dash ${conf}
+  '';
+
+in {
+
+  options = {
+
+    services.riemann-dash = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the riemann-dash dashboard daemon.
+        '';
+      };
+      config = mkOption {
+        type = types.lines;
+        description = ''
+          Contents added to the end of the riemann-dash configuration file.
+        '';
+      };
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/riemann-dash";
+        description = ''
+          Location of the riemann-base dir. The dashboard configuration file is
+          is stored to this directory. The directory is created automatically on
+          service start, and owner is set to the riemanndash user.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.groups.riemanndash.gid = config.ids.gids.riemanndash;
+
+    users.users.riemanndash = {
+      description = "riemann-dash daemon user";
+      uid = config.ids.uids.riemanndash;
+      group = "riemanndash";
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - riemanndash riemanndash - -"
+    ];
+
+    systemd.services.riemann-dash = {
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "riemann.service" ];
+      after = [ "riemann.service" ];
+      preStart = ''
+        mkdir -p '${cfg.dataDir}/config'
+      '';
+      serviceConfig = {
+        User = "riemanndash";
+        ExecStart = "${launcher}/bin/riemann-dash";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix b/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix
new file mode 100644
index 000000000000..86a11694e7b4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/riemann-tools.nix
@@ -0,0 +1,70 @@
+{ config, pkgs, lib, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+  cfg = config.services.riemann-tools;
+
+  riemannHost = "${cfg.riemannHost}";
+
+  healthLauncher = writeScriptBin "riemann-health" ''
+    #!/bin/sh
+    exec ${pkgs.riemann-tools}/bin/riemann-health ${builtins.concatStringsSep " " cfg.extraArgs} --host ${riemannHost}
+  '';
+
+
+in {
+
+  options = {
+
+    services.riemann-tools = {
+      enableHealth = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the riemann-health daemon.
+        '';
+      };
+      riemannHost = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = ''
+          Address of the host riemann node. Defaults to localhost.
+        '';
+      };
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          A list of commandline-switches forwarded to a riemann-tool.
+          See for example `riemann-health --help` for available options.
+        '';
+        example = ["-p 5555" "--timeout=30" "--attribute=myattribute=42"];
+      };
+    };
+  };
+
+  config = mkIf cfg.enableHealth {
+
+    users.groups.riemanntools.gid = config.ids.gids.riemanntools;
+
+    users.users.riemanntools = {
+      description = "riemann-tools daemon user";
+      uid = config.ids.uids.riemanntools;
+      group = "riemanntools";
+    };
+
+    systemd.services.riemann-health = {
+      wantedBy = [ "multi-user.target" ];
+      path = [ procps ];
+      serviceConfig = {
+        User = "riemanntools";
+        ExecStart = "${healthLauncher}/bin/riemann-health";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/riemann.nix b/nixpkgs/nixos/modules/services/monitoring/riemann.nix
new file mode 100644
index 000000000000..13d2b1cc0602
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/riemann.nix
@@ -0,0 +1,105 @@
+{ config, pkgs, lib, ... }:
+
+with pkgs;
+with lib;
+
+let
+
+  cfg = config.services.riemann;
+
+  classpath = concatStringsSep ":" (
+    cfg.extraClasspathEntries ++ [ "${riemann}/share/java/riemann.jar" ]
+  );
+
+  riemannConfig = concatStringsSep "\n" (
+    [cfg.config] ++ (map (f: ''(load-file "${f}")'') cfg.configFiles)
+  );
+
+  launcher = writeScriptBin "riemann" ''
+    #!/bin/sh
+    exec ${jdk}/bin/java ${concatStringsSep " " cfg.extraJavaOpts} \
+      -cp ${classpath} \
+      riemann.bin ${cfg.configFile}
+  '';
+
+in {
+
+  options = {
+
+    services.riemann = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the Riemann network monitoring daemon.
+        '';
+      };
+      config = mkOption {
+        type = types.lines;
+        description = ''
+          Contents of the Riemann configuration file. For more complicated
+          config you should use configFile.
+        '';
+      };
+      configFiles = mkOption {
+        type = with types; listOf path;
+        default = [];
+        description = ''
+          Extra files containing Riemann configuration. These files will be
+          loaded at runtime by Riemann (with Clojure's
+          <literal>load-file</literal> function) at the end of the
+          configuration if you use the config option, this is ignored if you
+          use configFile.
+        '';
+      };
+      configFile = mkOption {
+        type = types.str;
+        description = ''
+          A Riemann config file. Any files in the same directory as this file
+          will be added to the classpath by Riemann.
+        '';
+      };
+      extraClasspathEntries = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = ''
+          Extra entries added to the Java classpath when running Riemann.
+        '';
+      };
+      extraJavaOpts = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = ''
+          Extra Java options used when launching Riemann.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users.groups.riemann.gid = config.ids.gids.riemann;
+
+    users.users.riemann = {
+      description = "riemann daemon user";
+      uid = config.ids.uids.riemann;
+      group = "riemann";
+    };
+
+    services.riemann.configFile = mkDefault (
+      writeText "riemann-config.clj" riemannConfig
+    );
+
+    systemd.services.riemann = {
+      wantedBy = [ "multi-user.target" ];
+      path = [ inetutils ];
+      serviceConfig = {
+        User = "riemann";
+        ExecStart = "${launcher}/bin/riemann";
+      };
+      serviceConfig.LimitNOFILE = 65536;
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/scollector.nix b/nixpkgs/nixos/modules/services/monitoring/scollector.nix
new file mode 100644
index 000000000000..6f13ce889cba
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/scollector.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.scollector;
+
+  collectors = pkgs.runCommand "collectors" { preferLocalBuild = true; }
+    ''
+    mkdir -p $out
+    ${lib.concatStringsSep
+        "\n"
+        (lib.mapAttrsToList
+          (frequency: binaries:
+            "mkdir -p $out/${frequency}\n" +
+            (lib.concatStringsSep
+              "\n"
+              (map (path: "ln -s ${path} $out/${frequency}/$(basename ${path})")
+                   binaries)))
+          cfg.collectors)}
+    '';
+
+  conf = pkgs.writeText "scollector.toml" ''
+    Host = "${cfg.bosunHost}"
+    ColDir = "${collectors}"
+    ${cfg.extraConfig}
+  '';
+
+in {
+
+  options = {
+
+    services.scollector = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run scollector.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.scollector;
+        defaultText = "pkgs.scollector";
+        example = literalExample "pkgs.scollector";
+        description = ''
+          scollector binary to use.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "scollector";
+        description = ''
+          User account under which scollector runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "scollector";
+        description = ''
+          Group account under which scollector runs.
+        '';
+      };
+
+      bosunHost = mkOption {
+        type = types.str;
+        default = "localhost:8070";
+        description = ''
+          Host and port of the bosun server that will store the collected
+          data.
+        '';
+      };
+
+      collectors = mkOption {
+        type = with types; attrsOf (listOf path);
+        default = {};
+        example = literalExample "{ \"0\" = [ \"\${postgresStats}/bin/collect-stats\" ]; }";
+        description = ''
+          An attribute set mapping the frequency of collection to a list of
+          binaries that should be executed at that frequency. You can use "0"
+          to run a binary forever.
+        '';
+      };
+
+      extraOpts = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = [ "-d" ];
+        description = ''
+          Extra scollector command line options
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra scollector configuration added to the end of scollector.toml
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf config.services.scollector.enable {
+
+    systemd.services.scollector = {
+      description = "scollector metrics collector (part of Bosun)";
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ pkgs.coreutils pkgs.iproute ];
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/scollector -conf=${conf} ${lib.concatStringsSep " " cfg.extraOpts}";
+      };
+    };
+
+    users.users.scollector = {
+      description = "scollector user";
+      group = "scollector";
+      uid = config.ids.uids.scollector;
+    };
+
+    users.groups.scollector.gid = config.ids.gids.scollector;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/smartd.nix b/nixpkgs/nixos/modules/services/monitoring/smartd.nix
new file mode 100644
index 000000000000..c345ec48a018
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/smartd.nix
@@ -0,0 +1,242 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  host = config.networking.hostName or "unknown"
+       + optionalString (config.networking.domain != null) ".${config.networking.domain}";
+
+  cfg = config.services.smartd;
+
+  nm = cfg.notifications.mail;
+  nw = cfg.notifications.wall;
+  nx = cfg.notifications.x11;
+
+  smartdNotify = pkgs.writeScript "smartd-notify.sh" ''
+    #! ${pkgs.runtimeShell}
+    ${optionalString nm.enable ''
+      {
+      ${pkgs.coreutils}/bin/cat << EOF
+      From: smartd on ${host} <root>
+      To: undisclosed-recipients:;
+      Subject: SMART error on $SMARTD_DEVICESTRING: $SMARTD_FAILTYPE
+
+      $SMARTD_FULLMESSAGE
+      EOF
+
+      ${pkgs.smartmontools}/sbin/smartctl -a -d "$SMARTD_DEVICETYPE" "$SMARTD_DEVICE"
+      } | ${nm.mailer} -i "${nm.recipient}"
+    ''}
+    ${optionalString nw.enable ''
+      {
+      ${pkgs.coreutils}/bin/cat << EOF
+      Problem detected with disk: $SMARTD_DEVICESTRING
+      Warning message from smartd is:
+
+      $SMARTD_MESSAGE
+      EOF
+      } | ${pkgs.utillinux}/bin/wall 2>/dev/null
+    ''}
+    ${optionalString nx.enable ''
+      export DISPLAY=${nx.display}
+      {
+      ${pkgs.coreutils}/bin/cat << EOF
+      Problem detected with disk: $SMARTD_DEVICESTRING
+      Warning message from smartd is:
+
+      $SMARTD_FULLMESSAGE
+      EOF
+      } | ${pkgs.xorg.xmessage}/bin/xmessage -file - 2>/dev/null &
+    ''}
+  '';
+
+  notifyOpts = optionalString (nm.enable || nw.enable || nx.enable)
+    ("-m <nomailer> -M exec ${smartdNotify} " + optionalString cfg.notifications.test "-M test ");
+
+  smartdConf = pkgs.writeText "smartd.conf" ''
+    # Autogenerated smartd startup config file
+    DEFAULT ${notifyOpts}${cfg.defaults.monitored}
+
+    ${concatMapStringsSep "\n" (d: "${d.device} ${d.options}") cfg.devices}
+
+    ${optionalString cfg.autodetect
+       "DEVICESCAN ${notifyOpts}${cfg.defaults.autodetected}"}
+  '';
+
+  smartdDeviceOpts = { ... }: {
+
+    options = {
+
+      device = mkOption {
+        example = "/dev/sda";
+        type = types.str;
+        description = "Location of the device.";
+      };
+
+      options = mkOption {
+        default = "";
+        example = "-d sat";
+        type = types.separatedString " ";
+        description = "Options that determine how smartd monitors the device.";
+      };
+
+    };
+
+  };
+
+in
+
+{
+  ###### interface
+
+  options = {
+
+    services.smartd = {
+
+      enable = mkEnableOption "smartd daemon from <literal>smartmontools</literal> package";
+
+      autodetect = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Whenever smartd should monitor all devices connected to the
+          machine at the time it's being started (the default).
+
+          Set to false to monitor the devices listed in
+          <option>services.smartd.devices</option> only.
+        '';
+      };
+
+      extraOptions = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        example = ["-A /var/log/smartd/" "--interval=3600"];
+        description = ''
+          Extra command-line options passed to the <literal>smartd</literal>
+          daemon on startup.
+
+          (See <literal>man 8 smartd</literal>.)
+        '';
+      };
+
+      notifications = {
+
+        mail = {
+          enable = mkOption {
+            default = config.services.mail.sendmailSetuidWrapper != null;
+            type = types.bool;
+            description = "Whenever to send e-mail notifications.";
+          };
+
+          recipient = mkOption {
+            default = "root";
+            type = types.str;
+            description = "Recipient of the notification messages.";
+          };
+
+          mailer = mkOption {
+            default = "/run/wrappers/bin/sendmail";
+            type = types.path;
+            description = ''
+              Sendmail-compatible binary to be used to send the messages.
+
+              You should probably enable
+              <option>services.postfix</option> or some other MTA for
+              this to work.
+            '';
+          };
+        };
+
+        wall = {
+          enable = mkOption {
+            default = true;
+            type = types.bool;
+            description = "Whenever to send wall notifications to all users.";
+          };
+        };
+
+        x11 = {
+          enable = mkOption {
+            default = config.services.xserver.enable;
+            type = types.bool;
+            description = "Whenever to send X11 xmessage notifications.";
+          };
+
+          display = mkOption {
+            default = ":${toString config.services.xserver.display}";
+            type = types.str;
+            description = "DISPLAY to send X11 notifications to.";
+          };
+        };
+
+        test = mkOption {
+          default = false;
+          type = types.bool;
+          description = "Whenever to send a test notification on startup.";
+        };
+
+      };
+
+      defaults = {
+        monitored = mkOption {
+          default = "-a";
+          type = types.separatedString " ";
+          example = "-a -o on -s (S/../.././02|L/../../7/04)";
+          description = ''
+            Common default options for explicitly monitored (listed in
+            <option>services.smartd.devices</option>) devices.
+
+            The default value turns on monitoring of all the things (see
+            <literal>man 5 smartd.conf</literal>).
+
+            The example also turns on SMART Automatic Offline Testing on
+            startup, and schedules short self-tests daily, and long
+            self-tests weekly.
+          '';
+        };
+
+        autodetected = mkOption {
+          default = cfg.defaults.monitored;
+          type = types.separatedString " ";
+          description = ''
+            Like <option>services.smartd.defaults.monitored</option>, but for the
+            autodetected devices.
+          '';
+        };
+      };
+
+      devices = mkOption {
+        default = [];
+        example = [ { device = "/dev/sda"; } { device = "/dev/sdb"; options = "-d sat"; } ];
+        type = with types; listOf (submodule smartdDeviceOpts);
+        description = "List of devices to monitor.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [ {
+      assertion = cfg.autodetect || cfg.devices != [];
+      message = "smartd can't run with both disabled autodetect and an empty list of devices to monitor.";
+    } ];
+
+    systemd.services.smartd = {
+      description = "S.M.A.R.T. Daemon";
+
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ pkgs.nettools ]; # for hostname and dnsdomanname calls in smartd
+
+      serviceConfig.ExecStart = "${pkgs.smartmontools}/sbin/smartd ${lib.concatStringsSep " " cfg.extraOptions} --no-fork --configfile=${smartdConf}";
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/statsd.nix b/nixpkgs/nixos/modules/services/monitoring/statsd.nix
new file mode 100644
index 000000000000..30b2916a9928
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/statsd.nix
@@ -0,0 +1,149 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.statsd;
+
+  isBuiltinBackend = name:
+    builtins.elem name [ "graphite" "console" "repeater" ];
+
+  backendsToPackages = let
+    mkMap = list: name:
+      if isBuiltinBackend name then list
+      else list ++ [ pkgs.nodePackages.${name} ];
+  in foldl mkMap [];
+
+  configFile = pkgs.writeText "statsd.conf" ''
+    {
+      address: "${cfg.listenAddress}",
+      port: "${toString cfg.port}",
+      mgmt_address: "${cfg.mgmt_address}",
+      mgmt_port: "${toString cfg.mgmt_port}",
+      backends: [${
+        concatMapStringsSep "," (name:
+          if (isBuiltinBackend name)
+          then ''"./backends/${name}"''
+          else ''"${name}"''
+        ) cfg.backends}],
+      ${optionalString (cfg.graphiteHost!=null) ''graphiteHost: "${cfg.graphiteHost}",''}
+      ${optionalString (cfg.graphitePort!=null) ''graphitePort: "${toString cfg.graphitePort}",''}
+      console: {
+        prettyprint: false
+      },
+      log: {
+        backend: "stdout"
+      },
+      automaticConfigReload: false${optionalString (cfg.extraConfig != null) ","}
+      ${cfg.extraConfig}
+    }
+  '';
+
+  deps = pkgs.buildEnv {
+    name = "statsd-runtime-deps";
+    pathsToLink = [ "/lib" ];
+    ignoreCollisions = true;
+
+    paths = backendsToPackages cfg.backends;
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options.services.statsd = {
+
+    enable = mkEnableOption "statsd";
+
+    listenAddress = mkOption {
+      description = "Address that statsd listens on over UDP";
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    port = mkOption {
+      description = "Port that stats listens for messages on over UDP";
+      default = 8125;
+      type = types.int;
+    };
+
+    mgmt_address = mkOption {
+      description = "Address to run management TCP interface on";
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    mgmt_port = mkOption {
+      description = "Port to run the management TCP interface on";
+      default = 8126;
+      type = types.int;
+    };
+
+    backends = mkOption {
+      description = "List of backends statsd will use for data persistence";
+      default = [];
+      example = [
+        "graphite"
+        "console"
+        "repeater"
+        "statsd-librato-backend"
+        "stackdriver-statsd-backend"
+        "statsd-influxdb-backend"
+      ];
+      type = types.listOf types.str;
+    };
+
+    graphiteHost = mkOption {
+      description = "Hostname or IP of Graphite server";
+      default = null;
+      type = types.nullOr types.str;
+    };
+
+    graphitePort = mkOption {
+      description = "Port of Graphite server (i.e. carbon-cache).";
+      default = null;
+      type = types.nullOr types.int;
+    };
+
+    extraConfig = mkOption {
+      description = "Extra configuration options for statsd";
+      default = "";
+      type = types.nullOr types.str;
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = map (backend: {
+      assertion = !isBuiltinBackend backend -> hasAttrByPath [ backend ] pkgs.nodePackages;
+      message = "Only builtin backends (graphite, console, repeater) or backends enumerated in `pkgs.nodePackages` are allowed!";
+    }) cfg.backends;
+
+    users.users.statsd = {
+      uid = config.ids.uids.statsd;
+      description = "Statsd daemon user";
+    };
+
+    systemd.services.statsd = {
+      description = "Statsd Server";
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        NODE_PATH = "${deps}/lib/node_modules";
+      };
+      serviceConfig = {
+        ExecStart = "${pkgs.statsd}/bin/statsd ${configFile}";
+        User = "statsd";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.statsd ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/sysstat.nix b/nixpkgs/nixos/modules/services/monitoring/sysstat.nix
new file mode 100644
index 000000000000..ca2cff827232
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/sysstat.nix
@@ -0,0 +1,76 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.sysstat;
+in {
+  options = {
+    services.sysstat = {
+      enable = mkEnableOption "sar system activity collection";
+
+      collect-frequency = mkOption {
+        type = types.str;
+        default = "*:00/10";
+        description = ''
+          OnCalendar specification for sysstat-collect
+        '';
+      };
+
+      collect-args = mkOption {
+        type = types.str;
+        default = "1 1";
+        description = ''
+          Arguments to pass sa1 when collecting statistics
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.sysstat = {
+      description = "Resets System Activity Logs";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User = "root";
+        RemainAfterExit = true;
+        Type = "oneshot";
+        ExecStart = "${pkgs.sysstat}/lib/sa/sa1 --boot";
+        LogsDirectory = "sa";
+      };
+    };
+
+    systemd.services.sysstat-collect = {
+      description = "system activity accounting tool";
+      unitConfig.Documentation = "man:sa1(8)";
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = "root";
+        ExecStart = "${pkgs.sysstat}/lib/sa/sa1 ${cfg.collect-args}";
+      };
+    };
+
+    systemd.timers.sysstat-collect = {
+      description = "Run system activity accounting tool on a regular basis";
+      wantedBy = [ "timers.target" ];
+      timerConfig.OnCalendar = cfg.collect-frequency;
+    };
+
+    systemd.services.sysstat-summary = {
+      description = "Generate a daily summary of process accounting";
+      unitConfig.Documentation = "man:sa2(8)";
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = "root";
+        ExecStart = "${pkgs.sysstat}/lib/sa/sa2 -A";
+      };
+    };
+
+    systemd.timers.sysstat-summary = {
+      description = "Generate summary of yesterday's process accounting";
+      wantedBy = [ "timers.target" ];
+      timerConfig.OnCalendar = "00:07:00";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix b/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix
new file mode 100644
index 000000000000..dd98ecab828d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/teamviewer.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.teamviewer;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.teamviewer.enable = mkEnableOption "TeamViewer daemon";
+      
+  };
+
+  ###### implementation
+
+  config = mkIf (cfg.enable) {
+
+    environment.systemPackages = [ pkgs.teamviewer ];
+
+    systemd.services.teamviewerd = {
+      description = "TeamViewer remote control daemon";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "NetworkManager-wait-online.service" "network.target" ];
+      preStart = "mkdir -pv /var/lib/teamviewer /var/log/teamviewer";
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.teamviewer}/bin/teamviewerd -d";
+        PIDFile = "/run/teamviewerd.pid";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "on-abort";
+        StartLimitInterval = "60";
+        StartLimitBurst = "10";
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/telegraf.nix b/nixpkgs/nixos/modules/services/monitoring/telegraf.nix
new file mode 100644
index 000000000000..5d131557e8be
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/telegraf.nix
@@ -0,0 +1,71 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.telegraf;
+
+  configFile = pkgs.runCommand "config.toml" {
+    buildInputs = [ pkgs.remarshal ];
+    preferLocalBuild = true;
+  } ''
+    remarshal -if json -of toml \
+      < ${pkgs.writeText "config.json" (builtins.toJSON cfg.extraConfig)} \
+      > $out
+  '';
+in {
+  ###### interface
+  options = {
+    services.telegraf = {
+      enable = mkEnableOption "telegraf server";
+
+      package = mkOption {
+        default = pkgs.telegraf;
+        defaultText = "pkgs.telegraf";
+        description = "Which telegraf derivation to use";
+        type = types.package;
+      };
+
+      extraConfig = mkOption {
+        default = {};
+        description = "Extra configuration options for telegraf";
+        type = types.attrs;
+        example = {
+          outputs = {
+            influxdb = {
+              urls = ["http://localhost:8086"];
+              database = "telegraf";
+            };
+          };
+          inputs = {
+            statsd = {
+              service_address = ":8125";
+              delete_timings = true;
+            };
+          };
+        };
+      };
+    };
+  };
+
+
+  ###### implementation
+  config = mkIf config.services.telegraf.enable {
+    systemd.services.telegraf = {
+      description = "Telegraf Agent";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      serviceConfig = {
+        ExecStart=''${cfg.package}/bin/telegraf -config "${configFile}"'';
+        ExecReload="${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        User = "telegraf";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users.telegraf = {
+      uid = config.ids.uids.telegraf;
+      description = "telegraf daemon user";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/thanos.nix b/nixpkgs/nixos/modules/services/monitoring/thanos.nix
new file mode 100644
index 000000000000..52dab28cf72f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/thanos.nix
@@ -0,0 +1,835 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.thanos;
+
+  nullOpt = type: description: mkOption {
+    type = types.nullOr type;
+    default = null;
+    inherit description;
+  };
+
+  optionToArgs = opt: v  : optional (v != null)  ''--${opt}="${toString v}"'';
+  flagToArgs   = opt: v  : optional v            ''--${opt}'';
+  listToArgs   = opt: vs : map               (v: ''--${opt}="${v}"'') vs;
+  attrsToArgs  = opt: kvs: mapAttrsToList (k: v: ''--${opt}=${k}=\"${v}\"'') kvs;
+
+  mkParamDef = type: default: description: mkParam type (description + ''
+
+    Defaults to <literal>${toString default}</literal> in Thanos
+    when set to <literal>null</literal>.
+  '');
+
+  mkParam = type: description: {
+    toArgs = optionToArgs;
+    option = nullOpt type description;
+  };
+
+  mkFlagParam = description: {
+    toArgs = flagToArgs;
+    option = mkOption {
+      type = types.bool;
+      default = false;
+      inherit description;
+    };
+  };
+
+  mkListParam = opt: description: {
+    toArgs = _opt: listToArgs opt;
+    option = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      inherit description;
+    };
+  };
+
+  mkAttrsParam = opt: description: {
+    toArgs = _opt: attrsToArgs opt;
+    option = mkOption {
+      type = types.attrsOf types.str;
+      default = {};
+      inherit description;
+    };
+  };
+
+  mkStateDirParam = opt: default: description: {
+    toArgs = _opt: stateDir: optionToArgs opt "/var/lib/${stateDir}";
+    option = mkOption {
+      type = types.str;
+      inherit default;
+      inherit description;
+    };
+  };
+
+  toYAML = name: attrs: pkgs.runCommandNoCC name {
+    preferLocalBuild = true;
+    json = builtins.toFile "${name}.json" (builtins.toJSON attrs);
+    nativeBuildInputs = [ pkgs.remarshal ];
+  } ''json2yaml -i $json -o $out'';
+
+  thanos = cmd: "${cfg.package}/bin/thanos ${cmd}" +
+    (let args = cfg.${cmd}.arguments;
+     in optionalString (length args != 0) (" \\\n  " +
+         concatStringsSep " \\\n  " args));
+
+  argumentsOf = cmd: concatLists (collect isList
+    (flip mapParamsRecursive params.${cmd} (path: param:
+      let opt = concatStringsSep "." path;
+          v = getAttrFromPath path cfg.${cmd};
+      in param.toArgs opt v)));
+
+  mkArgumentsOption = cmd: mkOption {
+    type = types.listOf types.str;
+    default = argumentsOf cmd;
+    description = ''
+      Arguments to the <literal>thanos ${cmd}</literal> command.
+
+      Defaults to a list of arguments formed by converting the structured
+      options of <option>services.thanos.${cmd}</option> to a list of arguments.
+
+      Overriding this option will cause none of the structured options to have
+      any effect. So only set this if you know what you're doing!
+    '';
+  };
+
+  mapParamsRecursive =
+    let noParam = attr: !(attr ? toArgs && attr ? option);
+    in mapAttrsRecursiveCond noParam;
+
+  paramsToOptions = mapParamsRecursive (_path: param: param.option);
+
+  params = {
+
+    log = {
+
+      log.level = mkParamDef (types.enum ["debug" "info" "warn" "error" "fatal"]) "info" ''
+        Log filtering level.
+      '';
+
+      log.format = mkParam types.str ''
+        Log format to use.
+      '';
+    };
+
+    tracing = cfg: {
+      tracing.config-file = {
+        toArgs = _opt: path: optionToArgs "tracing.config-file" path;
+        option = mkOption {
+          type = with types; nullOr str;
+          default = if cfg.tracing.config == null then null
+                    else toString (toYAML "tracing.yaml" cfg.tracing.config);
+          defaultText = ''
+            if config.services.thanos.<cmd>.tracing.config == null then null
+            else toString (toYAML "tracing.yaml" config.services.thanos.<cmd>.tracing.config);
+          '';
+          description = ''
+            Path to YAML file that contains tracing configuration.
+
+            See format details: <link xlink:href="https://thanos.io/tracing.md/#configuration"/>
+          '';
+        };
+      };
+
+      tracing.config =
+        {
+          toArgs = _opt: _attrs: [];
+          option = nullOpt types.attrs ''
+            Tracing configuration.
+
+            When not <literal>null</literal> the attribute set gets converted to
+            a YAML file and stored in the Nix store. The option
+            <option>tracing.config-file</option> will default to its path.
+
+            If <option>tracing.config-file</option> is set this option has no effect.
+
+            See format details: <link xlink:href="https://thanos.io/tracing.md/#configuration"/>
+          '';
+        };
+    };
+
+    common = cfg: params.log // params.tracing cfg // {
+
+      http-address = mkParamDef types.str "0.0.0.0:10902" ''
+        Listen <literal>host:port</literal> for HTTP endpoints.
+      '';
+
+      grpc-address = mkParamDef types.str "0.0.0.0:10901" ''
+        Listen <literal>ip:port</literal> address for gRPC endpoints (StoreAPI).
+
+        Make sure this address is routable from other components.
+      '';
+
+      grpc-server-tls-cert = mkParam types.str ''
+        TLS Certificate for gRPC server, leave blank to disable TLS
+      '';
+
+      grpc-server-tls-key = mkParam types.str ''
+        TLS Key for the gRPC server, leave blank to disable TLS
+      '';
+
+      grpc-server-tls-client-ca = mkParam types.str ''
+        TLS CA to verify clients against.
+
+        If no client CA is specified, there is no client verification on server side.
+        (tls.NoClientCert)
+      '';
+    };
+
+    objstore = cfg: {
+
+      objstore.config-file = {
+        toArgs = _opt: path: optionToArgs "objstore.config-file" path;
+        option = mkOption {
+          type = with types; nullOr str;
+          default = if cfg.objstore.config == null then null
+                    else toString (toYAML "objstore.yaml" cfg.objstore.config);
+          defaultText = ''
+            if config.services.thanos.<cmd>.objstore.config == null then null
+            else toString (toYAML "objstore.yaml" config.services.thanos.<cmd>.objstore.config);
+          '';
+          description = ''
+            Path to YAML file that contains object store configuration.
+
+            See format details: <link xlink:href="https://thanos.io/storage.md/#configuration"/>
+          '';
+        };
+      };
+
+      objstore.config =
+        {
+          toArgs = _opt: _attrs: [];
+          option = nullOpt types.attrs ''
+            Object store configuration.
+
+            When not <literal>null</literal> the attribute set gets converted to
+            a YAML file and stored in the Nix store. The option
+            <option>objstore.config-file</option> will default to its path.
+
+            If <option>objstore.config-file</option> is set this option has no effect.
+
+            See format details: <link xlink:href="https://thanos.io/storage.md/#configuration"/>
+          '';
+        };
+    };
+
+    sidecar = params.common cfg.sidecar // params.objstore cfg.sidecar // {
+
+      prometheus.url = mkParamDef types.str "http://localhost:9090" ''
+        URL at which to reach Prometheus's API.
+
+        For better performance use local network.
+      '';
+
+      tsdb.path = {
+        toArgs = optionToArgs;
+        option = mkOption {
+          type = types.str;
+          default = "/var/lib/${config.services.prometheus.stateDir}/data";
+          defaultText = "/var/lib/\${config.services.prometheus.stateDir}/data";
+          description = ''
+            Data directory of TSDB.
+          '';
+        };
+      };
+
+      reloader.config-file = mkParam types.str ''
+        Config file watched by the reloader.
+      '';
+
+      reloader.config-envsubst-file = mkParam types.str ''
+        Output file for environment variable substituted config file.
+      '';
+
+      reloader.rule-dirs = mkListParam "reloader.rule-dir" ''
+        Rule directories for the reloader to refresh.
+      '';
+
+    };
+
+    store = params.common cfg.store // params.objstore cfg.store // {
+
+      stateDir = mkStateDirParam "data-dir" "thanos-store" ''
+        Data directory relative to <literal>/var/lib</literal>
+        in which to cache remote blocks.
+      '';
+
+      index-cache-size = mkParamDef types.str "250MB" ''
+        Maximum size of items held in the index cache.
+      '';
+
+      chunk-pool-size = mkParamDef types.str "2GB" ''
+        Maximum size of concurrently allocatable bytes for chunks.
+      '';
+
+      store.grpc.series-sample-limit = mkParamDef types.int 0 ''
+        Maximum amount of samples returned via a single Series call.
+
+        <literal>0</literal> means no limit.
+
+        NOTE: for efficiency we take 120 as the number of samples in chunk (it
+        cannot be bigger than that), so the actual number of samples might be
+        lower, even though the maximum could be hit.
+      '';
+
+      store.grpc.series-max-concurrency = mkParamDef types.int 20 ''
+        Maximum number of concurrent Series calls.
+      '';
+
+      sync-block-duration = mkParamDef types.str "3m" ''
+        Repeat interval for syncing the blocks between local and remote view.
+      '';
+
+      block-sync-concurrency = mkParamDef types.int 20 ''
+        Number of goroutines to use when syncing blocks from object storage.
+      '';
+
+      min-time = mkParamDef types.str "0000-01-01T00:00:00Z" ''
+        Start of time range limit to serve.
+
+        Thanos Store serves only metrics, which happened later than this
+        value. Option can be a constant time in RFC3339 format or time duration
+        relative to current time, such as -1d or 2h45m. Valid duration units are
+        ms, s, m, h, d, w, y.
+      '';
+
+      max-time = mkParamDef types.str "9999-12-31T23:59:59Z" ''
+        End of time range limit to serve.
+
+        Thanos Store serves only blocks, which happened eariler than this
+        value. Option can be a constant time in RFC3339 format or time duration
+        relative to current time, such as -1d or 2h45m. Valid duration units are
+        ms, s, m, h, d, w, y.
+      '';
+    };
+
+    query = params.common cfg.query // {
+
+      grpc-client-tls-secure = mkFlagParam ''
+        Use TLS when talking to the gRPC server
+      '';
+
+      grpc-client-tls-cert = mkParam types.str ''
+        TLS Certificates to use to identify this client to the server
+      '';
+
+      grpc-client-tls-key = mkParam types.str ''
+        TLS Key for the client's certificate
+      '';
+
+      grpc-client-tls-ca = mkParam types.str ''
+        TLS CA Certificates to use to verify gRPC servers
+      '';
+
+      grpc-client-server-name = mkParam types.str ''
+        Server name to verify the hostname on the returned gRPC certificates.
+        See <link xlink:href="https://tools.ietf.org/html/rfc4366#section-3.1"/>
+      '';
+
+      web.route-prefix = mkParam types.str ''
+        Prefix for API and UI endpoints.
+
+        This allows thanos UI to be served on a sub-path. This option is
+        analogous to <option>web.route-prefix</option> of Promethus.
+      '';
+
+      web.external-prefix = mkParam types.str ''
+        Static prefix for all HTML links and redirect URLs in the UI query web
+        interface.
+
+        Actual endpoints are still served on / or the
+        <option>web.route-prefix</option>. This allows thanos UI to be served
+        behind a reverse proxy that strips a URL sub-path.
+      '';
+
+      web.prefix-header = mkParam types.str ''
+        Name of HTTP request header used for dynamic prefixing of UI links and
+        redirects.
+
+        This option is ignored if the option
+        <literal>web.external-prefix</literal> is set.
+
+        Security risk: enable this option only if a reverse proxy in front of
+        thanos is resetting the header.
+
+        The setting <literal>web.prefix-header="X-Forwarded-Prefix"</literal>
+        can be useful, for example, if Thanos UI is served via Traefik reverse
+        proxy with <literal>PathPrefixStrip</literal> option enabled, which
+        sends the stripped prefix value in <literal>X-Forwarded-Prefix</literal>
+        header. This allows thanos UI to be served on a sub-path.
+      '';
+
+      query.timeout = mkParamDef types.str "2m" ''
+        Maximum time to process query by query node.
+      '';
+
+      query.max-concurrent = mkParamDef types.int 20 ''
+        Maximum number of queries processed concurrently by query node.
+      '';
+
+      query.replica-label = mkParam types.str ''
+        Label to treat as a replica indicator along which data is
+        deduplicated.
+
+        Still you will be able to query without deduplication using
+        <literal>dedup=false</literal> parameter.
+      '';
+
+      selector-labels = mkAttrsParam "selector-label" ''
+        Query selector labels that will be exposed in info endpoint.
+      '';
+
+      store.addresses = mkListParam "store" ''
+        Addresses of statically configured store API servers.
+
+        The scheme may be prefixed with <literal>dns+</literal> or
+        <literal>dnssrv+</literal> to detect store API servers through
+        respective DNS lookups.
+      '';
+
+      store.sd-files = mkListParam "store.sd-files" ''
+        Path to files that contain addresses of store API servers. The path
+        can be a glob pattern.
+      '';
+
+      store.sd-interval = mkParamDef types.str "5m" ''
+        Refresh interval to re-read file SD files. It is used as a resync fallback.
+      '';
+
+      store.sd-dns-interval = mkParamDef types.str "30s" ''
+        Interval between DNS resolutions.
+      '';
+
+      store.unhealthy-timeout = mkParamDef types.str "5m" ''
+        Timeout before an unhealthy store is cleaned from the store UI page.
+      '';
+
+      query.auto-downsampling = mkFlagParam ''
+        Enable automatic adjustment (step / 5) to what source of data should
+        be used in store gateways if no
+        <literal>max_source_resolution</literal> param is specified.
+      '';
+
+      query.partial-response = mkFlagParam ''
+        Enable partial response for queries if no
+        <literal>partial_response</literal> param is specified.
+      '';
+
+      query.default-evaluation-interval = mkParamDef types.str "1m" ''
+        Set default evaluation interval for sub queries.
+      '';
+
+      store.response-timeout = mkParamDef types.str "0ms" ''
+        If a Store doesn't send any data in this specified duration then a
+        Store will be ignored and partial data will be returned if it's
+        enabled. <literal>0</literal> disables timeout.
+      '';
+    };
+
+    rule = params.common cfg.rule // params.objstore cfg.rule // {
+
+      labels = mkAttrsParam "label" ''
+        Labels to be applied to all generated metrics.
+
+        Similar to external labels for Prometheus,
+        used to identify ruler and its blocks as unique source.
+      '';
+
+      stateDir = mkStateDirParam "data-dir" "thanos-rule" ''
+        Data directory relative to <literal>/var/lib</literal>.
+      '';
+
+      rule-files = mkListParam "rule-file" ''
+        Rule files that should be used by rule manager. Can be in glob format.
+      '';
+
+      eval-interval = mkParamDef types.str "30s" ''
+        The default evaluation interval to use.
+      '';
+
+      tsdb.block-duration = mkParamDef types.str "2h" ''
+        Block duration for TSDB block.
+      '';
+
+      tsdb.retention = mkParamDef types.str "48h" ''
+        Block retention time on local disk.
+      '';
+
+      alertmanagers.urls = mkListParam "alertmanagers.url" ''
+        Alertmanager replica URLs to push firing alerts.
+
+        Ruler claims success if push to at least one alertmanager from
+        discovered succeeds. The scheme may be prefixed with
+        <literal>dns+</literal> or <literal>dnssrv+</literal> to detect
+        Alertmanager IPs through respective DNS lookups. The port defaults to
+        <literal>9093</literal> or the SRV record's value. The URL path is
+        used as a prefix for the regular Alertmanager API path.
+      '';
+
+      alertmanagers.send-timeout = mkParamDef types.str "10s" ''
+        Timeout for sending alerts to alertmanager.
+      '';
+
+      alert.query-url = mkParam types.str ''
+        The external Thanos Query URL that would be set in all alerts 'Source' field.
+      '';
+
+      alert.label-drop = mkListParam "alert.label-drop" ''
+        Labels by name to drop before sending to alertmanager.
+
+        This allows alert to be deduplicated on replica label.
+
+        Similar Prometheus alert relabelling
+      '';
+
+      web.route-prefix = mkParam types.str ''
+        Prefix for API and UI endpoints.
+
+        This allows thanos UI to be served on a sub-path.
+
+        This option is analogous to <literal>--web.route-prefix</literal> of Promethus.
+      '';
+
+      web.external-prefix = mkParam types.str ''
+        Static prefix for all HTML links and redirect URLs in the UI query web
+        interface.
+
+        Actual endpoints are still served on / or the
+        <option>web.route-prefix</option>. This allows thanos UI to be served
+        behind a reverse proxy that strips a URL sub-path.
+      '';
+
+      web.prefix-header = mkParam types.str ''
+        Name of HTTP request header used for dynamic prefixing of UI links and
+        redirects.
+
+        This option is ignored if the option
+        <option>web.external-prefix</option> is set.
+
+        Security risk: enable this option only if a reverse proxy in front of
+        thanos is resetting the header.
+
+        The header <literal>X-Forwarded-Prefix</literal> can be useful, for
+        example, if Thanos UI is served via Traefik reverse proxy with
+        <literal>PathPrefixStrip</literal> option enabled, which sends the
+        stripped prefix value in <literal>X-Forwarded-Prefix</literal>
+        header. This allows thanos UI to be served on a sub-path.
+      '';
+
+      query.addresses = mkListParam "query" ''
+        Addresses of statically configured query API servers.
+
+        The scheme may be prefixed with <literal>dns+</literal> or
+        <literal>dnssrv+</literal> to detect query API servers through
+        respective DNS lookups.
+      '';
+
+      query.sd-files = mkListParam "query.sd-files" ''
+        Path to file that contain addresses of query peers.
+        The path can be a glob pattern.
+      '';
+
+      query.sd-interval = mkParamDef types.str "5m" ''
+        Refresh interval to re-read file SD files. (used as a fallback)
+      '';
+
+      query.sd-dns-interval = mkParamDef types.str "30s" ''
+        Interval between DNS resolutions.
+      '';
+    };
+
+    compact = params.log // params.tracing cfg.compact // params.objstore cfg.compact // {
+
+      http-address = mkParamDef types.str "0.0.0.0:10902" ''
+        Listen <literal>host:port</literal> for HTTP endpoints.
+      '';
+
+      stateDir = mkStateDirParam "data-dir" "thanos-compact" ''
+        Data directory relative to <literal>/var/lib</literal>
+        in which to cache blocks and process compactions.
+      '';
+
+      consistency-delay = mkParamDef types.str "30m" ''
+        Minimum age of fresh (non-compacted) blocks before they are being
+        processed. Malformed blocks older than the maximum of consistency-delay
+        and 30m0s will be removed.
+      '';
+
+      retention.resolution-raw = mkParamDef types.str "0d" ''
+        How long to retain raw samples in bucket.
+
+        <literal>0d</literal> - disables this retention
+      '';
+
+      retention.resolution-5m = mkParamDef types.str "0d" ''
+        How long to retain samples of resolution 1 (5 minutes) in bucket.
+
+        <literal>0d</literal> - disables this retention
+      '';
+
+      retention.resolution-1h = mkParamDef types.str "0d" ''
+        How long to retain samples of resolution 2 (1 hour) in bucket.
+
+        <literal>0d</literal> - disables this retention
+      '';
+
+      startAt = {
+        toArgs = _opt: startAt: flagToArgs "wait" (startAt == null);
+        option = nullOpt types.str ''
+          When this option is set to a <literal>systemd.time</literal>
+          specification the Thanos compactor will run at the specified period.
+
+          When this option is <literal>null</literal> the Thanos compactor service
+          will run continuously. So it will not exit after all compactions have
+          been processed but wait for new work.
+        '';
+      };
+
+      downsampling.disable = mkFlagParam ''
+        Disables downsampling.
+
+        This is not recommended as querying long time ranges without
+        non-downsampled data is not efficient and useful e.g it is not possible
+        to render all samples for a human eye anyway
+      '';
+
+      block-sync-concurrency = mkParamDef types.int 20 ''
+        Number of goroutines to use when syncing block metadata from object storage.
+      '';
+
+      compact.concurrency = mkParamDef types.int 1 ''
+        Number of goroutines to use when compacting groups.
+      '';
+    };
+
+    downsample = params.log // params.tracing cfg.downsample // params.objstore cfg.downsample // {
+
+      stateDir = mkStateDirParam "data-dir" "thanos-downsample" ''
+        Data directory relative to <literal>/var/lib</literal>
+        in which to cache blocks and process downsamplings.
+      '';
+
+    };
+
+    receive = params.common cfg.receive // params.objstore cfg.receive // {
+
+      remote-write.address = mkParamDef types.str "0.0.0.0:19291" ''
+        Address to listen on for remote write requests.
+      '';
+
+      stateDir = mkStateDirParam "tsdb.path" "thanos-receive" ''
+        Data directory relative to <literal>/var/lib</literal> of TSDB.
+      '';
+
+      labels = mkAttrsParam "labels" ''
+        External labels to announce.
+
+        This flag will be removed in the future when handling multiple tsdb
+        instances is added.
+      '';
+
+      tsdb.retention = mkParamDef types.str "15d" ''
+        How long to retain raw samples on local storage.
+
+        <literal>0d</literal> - disables this retention
+      '';
+    };
+
+  };
+
+  assertRelativeStateDir = cmd: {
+    assertions = [
+      {
+        assertion = !hasPrefix "/" cfg.${cmd}.stateDir;
+        message =
+          "The option services.thanos.${cmd}.stateDir should not be an absolute directory." +
+          " It should be a directory relative to /var/lib.";
+      }
+    ];
+  };
+
+in {
+
+  options.services.thanos = {
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.thanos;
+      defaultText = "pkgs.thanos";
+      description = ''
+        The thanos package that should be used.
+      '';
+    };
+
+    sidecar = paramsToOptions params.sidecar // {
+      enable = mkEnableOption
+        "the Thanos sidecar for Prometheus server";
+      arguments = mkArgumentsOption "sidecar";
+    };
+
+    store = paramsToOptions params.store // {
+      enable = mkEnableOption
+        "the Thanos store node giving access to blocks in a bucket provider.";
+      arguments = mkArgumentsOption "store";
+    };
+
+    query = paramsToOptions params.query // {
+      enable = mkEnableOption
+        ("the Thanos query node exposing PromQL enabled Query API " +
+         "with data retrieved from multiple store nodes");
+      arguments = mkArgumentsOption "query";
+    };
+
+    rule = paramsToOptions params.rule // {
+      enable = mkEnableOption
+        ("the Thanos ruler service which evaluates Prometheus rules against" +
+        " given Query nodes, exposing Store API and storing old blocks in bucket");
+      arguments = mkArgumentsOption "rule";
+    };
+
+    compact = paramsToOptions params.compact // {
+      enable = mkEnableOption
+        "the Thanos compactor which continuously compacts blocks in an object store bucket";
+      arguments = mkArgumentsOption "compact";
+    };
+
+    downsample = paramsToOptions params.downsample // {
+      enable = mkEnableOption
+        "the Thanos downsampler which continuously downsamples blocks in an object store bucket";
+      arguments = mkArgumentsOption "downsample";
+    };
+
+    receive = paramsToOptions params.receive // {
+      enable = mkEnableOption
+        ("the Thanos receiver which accept Prometheus remote write API requests " +
+         "and write to local tsdb (EXPERIMENTAL, this may change drastically without notice)");
+      arguments = mkArgumentsOption "receive";
+    };
+  };
+
+  config = mkMerge [
+
+    (mkIf cfg.sidecar.enable {
+      assertions = [
+        {
+          assertion = config.services.prometheus.enable;
+          message =
+            "Please enable services.prometheus when enabling services.thanos.sidecar.";
+        }
+        {
+          assertion = !(config.services.prometheus.globalConfig.external_labels == null ||
+                        config.services.prometheus.globalConfig.external_labels == {});
+          message =
+            "services.thanos.sidecar requires uniquely identifying external labels " +
+            "to be configured in the Prometheus server. " +
+            "Please set services.prometheus.globalConfig.external_labels.";
+        }
+      ];
+      systemd.services.thanos-sidecar = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network.target" "prometheus.service" ];
+        serviceConfig = {
+          User = "prometheus";
+          Restart = "always";
+          ExecStart = thanos "sidecar";
+        };
+      };
+    })
+
+    (mkIf cfg.store.enable (mkMerge [
+      (assertRelativeStateDir "store")
+      {
+        systemd.services.thanos-store = {
+          wantedBy = [ "multi-user.target" ];
+          after    = [ "network.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            StateDirectory = cfg.store.stateDir;
+            Restart = "always";
+            ExecStart = thanos "store";
+          };
+        };
+      }
+    ]))
+
+    (mkIf cfg.query.enable {
+      systemd.services.thanos-query = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network.target" ];
+        serviceConfig = {
+          DynamicUser = true;
+          Restart = "always";
+          ExecStart = thanos "query";
+        };
+      };
+    })
+
+    (mkIf cfg.rule.enable (mkMerge [
+      (assertRelativeStateDir "rule")
+      {
+        systemd.services.thanos-rule = {
+          wantedBy = [ "multi-user.target" ];
+          after    = [ "network.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            StateDirectory = cfg.rule.stateDir;
+            Restart = "always";
+            ExecStart = thanos "rule";
+          };
+        };
+      }
+    ]))
+
+    (mkIf cfg.compact.enable (mkMerge [
+      (assertRelativeStateDir "compact")
+      {
+        systemd.services.thanos-compact =
+          let wait = cfg.compact.startAt == null; in {
+            wantedBy = [ "multi-user.target" ];
+            after    = [ "network.target" ];
+            serviceConfig = {
+              Type    = if wait then "simple" else "oneshot";
+              Restart = if wait then "always" else "no";
+              DynamicUser = true;
+              StateDirectory = cfg.compact.stateDir;
+              ExecStart = thanos "compact";
+            };
+          } // optionalAttrs (!wait) { inherit (cfg.compact) startAt; };
+      }
+    ]))
+
+    (mkIf cfg.downsample.enable (mkMerge [
+      (assertRelativeStateDir "downsample")
+      {
+        systemd.services.thanos-downsample = {
+          wantedBy = [ "multi-user.target" ];
+          after    = [ "network.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            StateDirectory = cfg.downsample.stateDir;
+            Restart = "always";
+            ExecStart = thanos "downsample";
+          };
+        };
+      }
+    ]))
+
+    (mkIf cfg.receive.enable (mkMerge [
+      (assertRelativeStateDir "receive")
+      {
+        systemd.services.thanos-receive = {
+          wantedBy = [ "multi-user.target" ];
+          after    = [ "network.target" ];
+          serviceConfig = {
+            DynamicUser = true;
+            StateDirectory = cfg.receive.stateDir;
+            Restart = "always";
+            ExecStart = thanos "receive";
+          };
+        };
+      }
+    ]))
+
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/tuptime.nix b/nixpkgs/nixos/modules/services/monitoring/tuptime.nix
new file mode 100644
index 000000000000..731260a5c20a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/tuptime.nix
@@ -0,0 +1,84 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.tuptime;
+
+in {
+
+  options.services.tuptime = {
+
+    enable = mkEnableOption "the total uptime service";
+
+    timer = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether to regularly log uptime to detect bad shutdowns.";
+      };
+
+      period = mkOption {
+        type = types.str;
+        default = "*:0/5";
+        description = "systemd calendar event";
+      };
+    };
+  };
+
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.tuptime ];
+
+    users.users.tuptime.description = "tuptime database owner";
+
+    systemd = {
+      services = {
+
+        tuptime = {
+          description = "the total uptime service";
+          documentation = [ "man:tuptime(1)" ];
+          after = [ "time-sync.target" ];
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            StateDirectory = "tuptime";
+            Type = "oneshot";
+            User = "tuptime";
+            RemainAfterExit = true;
+            ExecStart = "${pkgs.tuptime}/bin/tuptime -x";
+            ExecStop = "${pkgs.tuptime}/bin/tuptime -xg";
+          };
+        };
+
+        tuptime-oneshot = mkIf cfg.timer.enable {
+          description = "the tuptime scheduled execution unit";
+          serviceConfig = {
+            StateDirectory = "tuptime";
+            Type = "oneshot";
+            User = "tuptime";
+            ExecStart = "${pkgs.tuptime}/bin/tuptime -x";
+          };
+        };
+      };
+
+      timers.tuptime = mkIf cfg.timer.enable {
+        description = "the tuptime scheduled execution timer";
+        # this timer should be started if the service is started
+        # even if the timer was previously stopped
+        wantedBy = [ "tuptime.service" "timers.target" ];
+        # this timer should be stopped if the service is stopped
+        partOf = [ "tuptime.service" ];
+        timerConfig = {
+          OnBootSec = "1min";
+          OnCalendar = cfg.timer.period;
+          Unit = "tuptime-oneshot.service";
+        };
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.evils ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/ups.nix b/nixpkgs/nixos/modules/services/monitoring/ups.nix
new file mode 100644
index 000000000000..a45e806d4ad8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/ups.nix
@@ -0,0 +1,263 @@
+{ config, lib, pkgs, ... }:
+
+# TODO: This is not secure, have a look at the file docs/security.txt inside
+# the project sources.
+with lib;
+
+let
+  cfg = config.power.ups;
+in
+
+let
+  upsOptions = {name, config, ...}:
+  {
+    options = {
+      # This can be infered from the UPS model by looking at
+      # /nix/store/nut/share/driver.list
+      driver = mkOption {
+        type = types.str;
+        description = ''
+          Specify the program to run to talk to this UPS.  apcsmart,
+          bestups, and sec are some examples.
+        '';
+      };
+
+      port = mkOption {
+        type = types.str;
+        description = ''
+          The serial port to which your UPS is connected.  /dev/ttyS0 is
+          usually the first port on Linux boxes, for example.
+        '';
+      };
+
+      shutdownOrder = mkOption {
+        default = 0;
+        type = types.int;
+        description = ''
+          When you have multiple UPSes on your system, you usually need to
+          turn them off in a certain order.  upsdrvctl shuts down all the
+          0s, then the 1s, 2s, and so on.  To exclude a UPS from the
+          shutdown sequence, set this to -1.
+        '';
+      };
+
+      maxStartDelay = mkOption {
+        default = null;
+        type = types.uniq (types.nullOr types.int);
+        description = ''
+          This can be set as a global variable above your first UPS
+          definition and it can also be set in a UPS section.  This value
+          controls how long upsdrvctl will wait for the driver to finish
+          starting.  This keeps your system from getting stuck due to a
+          broken driver or UPS.
+        '';
+      };
+
+      description = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Description of the UPS.
+        '';
+      };
+
+      directives = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        description = ''
+          List of configuration directives for this UPS.
+        '';
+      };
+
+      summary = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Lines which would be added inside ups.conf for handling this UPS.
+        '';
+      };
+
+    };
+
+    config = {
+      directives = mkOrder 10 ([
+        "driver = ${config.driver}"
+        "port = ${config.port}"
+        ''desc = "${config.description}"''
+        "sdorder = ${toString config.shutdownOrder}"
+      ] ++ (optional (config.maxStartDelay != null)
+            "maxstartdelay = ${toString config.maxStartDelay}")
+      );
+
+      summary =
+        concatStringsSep "\n      "
+          (["[${name}]"] ++ config.directives);
+    };
+  };
+
+in
+
+
+{
+  options = {
+    # powerManagement.powerDownCommands
+
+    power.ups = {
+      enable = mkOption {
+        default = false;
+        type = with types; bool;
+        description = ''
+          Enables support for Power Devices, such as Uninterruptible Power
+          Supplies, Power Distribution Units and Solar Controllers.
+        '';
+      };
+
+      # This option is not used yet.
+      mode = mkOption {
+        default = "standalone";
+        type = types.str;
+        description = ''
+          The MODE determines which part of the NUT is to be started, and
+          which configuration files must be modified.
+
+          The values of MODE can be:
+
+          - none: NUT is not configured, or use the Integrated Power
+            Management, or use some external system to startup NUT
+            components. So nothing is to be started.
+
+          - standalone: This mode address a local only configuration, with 1
+            UPS protecting the local system. This implies to start the 3 NUT
+            layers (driver, upsd and upsmon) and the matching configuration
+            files. This mode can also address UPS redundancy.
+
+          - netserver: same as for the standalone configuration, but also
+            need some more ACLs and possibly a specific LISTEN directive in
+            upsd.conf.  Since this MODE is opened to the network, a special
+            care should be applied to security concerns.
+
+          - netclient: this mode only requires upsmon.
+        '';
+      };
+
+      schedulerRules = mkOption {
+        example = "/etc/nixos/upssched.conf";
+        type = types.str;
+        description = ''
+          File which contains the rules to handle UPS events.
+        '';
+      };
+
+
+      maxStartDelay = mkOption {
+        default = 45;
+        type = types.int;
+        description = ''
+          This can be set as a global variable above your first UPS
+          definition and it can also be set in a UPS section.  This value
+          controls how long upsdrvctl will wait for the driver to finish
+          starting.  This keeps your system from getting stuck due to a
+          broken driver or UPS.
+        '';
+      };
+
+      ups = mkOption {
+        default = {};
+        # see nut/etc/ups.conf.sample
+        description = ''
+          This is where you configure all the UPSes that this system will be
+          monitoring directly.  These are usually attached to serial ports,
+          but USB devices are also supported.
+        '';
+        type = with types; attrsOf (submodule upsOptions);
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.nut ];
+
+    systemd.services.upsmon = {
+      description = "Uninterruptible Power Supplies (Monitor)";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Type = "forking";
+      script = "${pkgs.nut}/sbin/upsmon";
+      environment.NUT_CONFPATH = "/etc/nut/";
+      environment.NUT_STATEPATH = "/var/lib/nut/";
+    };
+
+    systemd.services.upsd = {
+      description = "Uninterruptible Power Supplies (Daemon)";
+      after = [ "network.target" "upsmon.service" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Type = "forking";
+      # TODO: replace 'root' by another username.
+      script = "${pkgs.nut}/sbin/upsd -u root";
+      environment.NUT_CONFPATH = "/etc/nut/";
+      environment.NUT_STATEPATH = "/var/lib/nut/";
+    };
+
+    systemd.services.upsdrv = {
+      description = "Uninterruptible Power Supplies (Register all UPS)";
+      after = [ "upsd.service" ];
+      wantedBy = [ "multi-user.target" ];
+      # TODO: replace 'root' by another username.
+      script = ''${pkgs.nut}/bin/upsdrvctl -u root start'';
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+      environment.NUT_CONFPATH = "/etc/nut/";
+      environment.NUT_STATEPATH = "/var/lib/nut/";
+    };
+
+    environment.etc = {
+      "nut/nut.conf".source = pkgs.writeText "nut.conf"
+        ''
+          MODE = ${cfg.mode}
+        '';
+      "nut/ups.conf".source = pkgs.writeText "ups.conf"
+        ''
+          maxstartdelay = ${toString cfg.maxStartDelay}
+
+          ${flip concatStringsSep (forEach (attrValues cfg.ups) (ups: ups.summary)) "
+
+          "}
+        '';
+      "nut/upssched.conf".source = cfg.schedulerRules;
+      # These file are containing private informations and thus should not
+      # be stored inside the Nix store.
+      /*
+      "nut/upsd.conf".source = "";
+      "nut/upsd.users".source = "";
+      "nut/upsmon.conf".source = "";
+      */
+    };
+
+    power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample";
+
+    system.activationScripts.upsSetup = stringAfter [ "users" "groups" ]
+      ''
+        # Used to store pid files of drivers.
+        mkdir -p /var/state/ups
+      '';
+
+
+/*
+    users.users.nut =
+      { uid = 84;
+        home = "/var/lib/nut";
+        createHome = true;
+        group = "nut";
+        description = "UPnP A/V Media Server user";
+      };
+
+    users.groups."nut" =
+      { gid = 84; };
+*/
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/uptime.nix b/nixpkgs/nixos/modules/services/monitoring/uptime.nix
new file mode 100644
index 000000000000..245badc3e44f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/uptime.nix
@@ -0,0 +1,96 @@
+{ config, pkgs, lib, ... }:
+let
+  inherit (lib) mkOption mkEnableOption mkIf mkMerge types optional;
+
+  cfg = config.services.uptime;
+
+  configDir = pkgs.runCommand "config" { preferLocalBuild = true; }
+  (if cfg.configFile != null then ''
+    mkdir $out
+    ext=`echo ${cfg.configFile} | grep -o \\..*`
+    ln -sv ${cfg.configFile} $out/default$ext
+    ln -sv /var/lib/uptime/runtime.json $out/runtime.json
+  '' else ''
+    mkdir $out
+    cat ${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/config/default.yaml > $out/default.yaml
+    cat >> $out/default.yaml <<EOF
+
+    autoStartMonitor: false
+
+    mongodb:
+      connectionString: 'mongodb://localhost/uptime'
+    EOF
+    ln -sv /var/lib/uptime/runtime.json $out/runtime.json
+  '');
+in {
+  options.services.uptime = {
+    configFile = mkOption {
+      description = ''
+        The uptime configuration file
+
+        If mongodb: server != localhost, please set usesRemoteMongo = true
+
+        If you only want to run the monitor, please set enableWebService = false
+        and enableSeparateMonitoringService = true
+
+        If autoStartMonitor: false (recommended) and you want to run both
+        services, please set enableSeparateMonitoringService = true
+      '';
+
+      type = types.nullOr types.path;
+
+      default = null;
+    };
+
+    usesRemoteMongo = mkOption {
+      description = "Whether the configuration file specifies a remote mongo instance";
+
+      default = false;
+
+      type = types.bool;
+    };
+
+    enableWebService = mkEnableOption "the uptime monitoring program web service";
+
+    enableSeparateMonitoringService = mkEnableOption "the uptime monitoring service" // { default = cfg.enableWebService; };
+
+    nodeEnv = mkOption {
+      description = "The node environment to run in (development, production, etc.)";
+
+      type = types.str;
+
+      default = "production";
+    };
+  };
+
+  config = mkMerge [ (mkIf cfg.enableWebService {
+    systemd.services.uptime = {
+      description = "uptime web service";
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        NODE_CONFIG_DIR = configDir;
+        NODE_ENV = cfg.nodeEnv;
+        NODE_PATH = "${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/node_modules";
+      };
+      preStart = "mkdir -p /var/lib/uptime";
+      serviceConfig.ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/app.js";
+    };
+
+    services.mongodb.enable = mkIf (!cfg.usesRemoteMongo) true;
+  }) (mkIf cfg.enableSeparateMonitoringService {
+    systemd.services.uptime-monitor = {
+      description = "uptime monitoring service";
+      wantedBy = [ "multi-user.target" ];
+      requires = optional cfg.enableWebService "uptime.service";
+      after = optional cfg.enableWebService "uptime.service";
+      environment = {
+        NODE_CONFIG_DIR = configDir;
+        NODE_ENV = cfg.nodeEnv;
+        NODE_PATH = "${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/node_modules";
+      };
+      # Ugh, need to wait for web service to be up
+      preStart = if cfg.enableWebService then "sleep 1s" else "mkdir -p /var/lib/uptime";
+      serviceConfig.ExecStart = "${pkgs.nodejs}/bin/node ${pkgs.nodePackages.node-uptime}/lib/node_modules/node-uptime/monitor.js";
+    };
+  }) ];
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/vnstat.nix b/nixpkgs/nixos/modules/services/monitoring/vnstat.nix
new file mode 100644
index 000000000000..e9bedb704a43
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/vnstat.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.vnstat;
+in {
+  options.services.vnstat = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable update of network usage statistics via vnstatd.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.vnstatd = {
+      isSystemUser = true;
+      description = "vnstat daemon user";
+      home = "/var/lib/vnstat";
+      createHome = true;
+    };
+
+    systemd.services.vnstat = {
+      description = "vnStat network traffic monitor";
+      path = [ pkgs.coreutils ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      documentation = [
+        "man:vnstatd(1)"
+        "man:vnstat(1)"
+        "man:vnstat.conf(5)"
+      ];
+      preStart = "chmod 755 /var/lib/vnstat";
+      serviceConfig = {
+        ExecStart = "${pkgs.vnstat}/bin/vnstatd -n";
+        ExecReload = "${pkgs.procps}/bin/kill -HUP $MAINPID";
+
+        # Hardening (from upstream example service)
+        ProtectSystem = "strict";
+        StateDirectory = "vnstat";
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectKernelModules = true;
+        PrivateTmp = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictNamespaces = true;
+
+        User = "vnstatd";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix
new file mode 100644
index 000000000000..b3383ed628b2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -0,0 +1,159 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.zabbixAgent;
+
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+  inherit (lib) attrValues concatMapStringsSep literalExample optionalString types;
+
+  user = "zabbix-agent";
+  group = "zabbix-agent";
+
+  moduleEnv = pkgs.symlinkJoin {
+    name = "zabbix-agent-module-env";
+    paths = attrValues cfg.modules;
+  };
+
+  configFile = pkgs.writeText "zabbix_agent.conf" ''
+    LogType = console
+    Server = ${cfg.server}
+    ListenIP = ${cfg.listen.ip}
+    ListenPort = ${toString cfg.listen.port}
+    ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
+    ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
+    ${cfg.extraConfig}
+  '';
+
+in
+
+{
+  # interface
+
+  options = {
+
+    services.zabbixAgent = {
+      enable = mkEnableOption "the Zabbix Agent";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.zabbix.agent;
+        defaultText = "pkgs.zabbix.agent";
+        description = "The Zabbix package to use.";
+      };
+
+      extraPackages = mkOption {
+        type = types.listOf types.package;
+        default = with pkgs; [ nettools ];
+        defaultText = "[ nettools ]";
+        example = "[ nettools mysql ]";
+        description = ''
+          Packages to be added to the Zabbix <envar>PATH</envar>.
+          Typically used to add executables for scripts, but can be anything.
+        '';
+      };
+
+      modules = mkOption {
+        type = types.attrsOf types.package;
+        description = "A set of modules to load.";
+        default = {};
+        example = literalExample ''
+          {
+            "dummy.so" = pkgs.stdenv.mkDerivation {
+              name = "zabbix-dummy-module-''${cfg.package.version}";
+              src = cfg.package.src;
+              buildInputs = [ cfg.package ];
+              sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
+              installPhase = '''
+                mkdir -p $out/lib
+                cp dummy.so $out/lib/
+              ''';
+            };
+          }
+        '';
+      };
+
+      server = mkOption {
+        type = types.str;
+        description = ''
+          The IP address or hostname of the Zabbix server to connect to.
+        '';
+      };
+
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            List of comma delimited IP addresses that the agent should listen on.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 10050;
+          description = ''
+            Agent will listen on this port for connections from the server.
+          '';
+        };
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Zabbix Agent.
+        '';
+      };
+
+      # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Configuration that is injected verbatim into the configuration file. Refer to
+          <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_agentd"/>
+          for details on supported values.
+        '';
+      };
+
+    };
+
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
+    };
+
+    users.users.${user} = {
+      description = "Zabbix Agent daemon user";
+      inherit group;
+      isSystemUser = true;
+    };
+
+    users.groups.${group} = { };
+
+    systemd.services.zabbix-agent = {
+      description = "Zabbix Agent";
+
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/zabbix_agentd zabbix_agentd -f --config ${configFile}";
+        Restart = "always";
+        RestartSec = 2;
+
+        User = user;
+        Group = group;
+        PrivateTmp = true;
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
new file mode 100644
index 000000000000..9d214469c3b3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -0,0 +1,299 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.zabbixProxy;
+  pgsql = config.services.postgresql;
+  mysql = config.services.mysql;
+
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+  inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
+
+  user = "zabbix";
+  group = "zabbix";
+  runtimeDir = "/run/zabbix";
+  stateDir = "/var/lib/zabbix";
+  passwordFile = "${runtimeDir}/zabbix-dbpassword.conf";
+
+  moduleEnv = pkgs.symlinkJoin {
+    name = "zabbix-proxy-module-env";
+    paths = attrValues cfg.modules;
+  };
+
+  configFile = pkgs.writeText "zabbix_proxy.conf" ''
+    LogType = console
+    ListenIP = ${cfg.listen.ip}
+    ListenPort = ${toString cfg.listen.port}
+    Server = ${cfg.server}
+    # TODO: set to cfg.database.socket if database type is pgsql?
+    DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host}
+    ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"}
+    DBName = ${cfg.database.name}
+    DBUser = ${cfg.database.user}
+    ${optionalString (cfg.database.passwordFile != null) "Include ${passwordFile}"}
+    ${optionalString (mysqlLocal && cfg.database.socket != null) "DBSocket = ${cfg.database.socket}"}
+    SocketDir = ${runtimeDir}
+    FpingLocation = /run/wrappers/bin/fping
+    ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
+    ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
+    ${cfg.extraConfig}
+  '';
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+in
+
+{
+  # interface
+
+  options = {
+
+    services.zabbixProxy = {
+      enable = mkEnableOption "the Zabbix Proxy";
+
+      server = mkOption {
+        type = types.str;
+        description = ''
+          The IP address or hostname of the Zabbix server to connect to.
+          '';
+        };
+
+      package = mkOption {
+        type = types.package;
+        default =
+          if cfg.database.type == "mysql" then pkgs.zabbix.proxy-mysql
+          else if cfg.database.type == "pgsql" then pkgs.zabbix.proxy-pgsql
+          else pkgs.zabbix.proxy-sqlite;
+        defaultText = "pkgs.zabbix.proxy-pgsql";
+        description = "The Zabbix package to use.";
+      };
+
+      extraPackages = mkOption {
+        type = types.listOf types.package;
+        default = with pkgs; [ nettools nmap traceroute ];
+        defaultText = "[ nettools nmap traceroute ]";
+        description = ''
+          Packages to be added to the Zabbix <envar>PATH</envar>.
+          Typically used to add executables for scripts, but can be anything.
+        '';
+      };
+
+      modules = mkOption {
+        type = types.attrsOf types.package;
+        description = "A set of modules to load.";
+        default = {};
+        example = literalExample ''
+          {
+            "dummy.so" = pkgs.stdenv.mkDerivation {
+              name = "zabbix-dummy-module-''${cfg.package.version}";
+              src = cfg.package.src;
+              buildInputs = [ cfg.package ];
+              sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
+              installPhase = '''
+                mkdir -p $out/lib
+                cp dummy.so $out/lib/
+              ''';
+            };
+          }
+        '';
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql" "pgsql" "sqlite" ];
+          example = "mysql";
+          default = "pgsql";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = if cfg.database.type == "sqlite" then "${stateDir}/zabbix.db" else "zabbix";
+          defaultText = "zabbix";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/zabbix-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/postgresql";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to create a local database automatically.";
+        };
+      };
+
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            List of comma delimited IP addresses that the trapper should listen on.
+            Trapper will listen on all network interfaces if this parameter is missing.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 10051;
+          description = ''
+            Listen port for trapper.
+          '';
+        };
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Zabbix Proxy.
+        '';
+      };
+
+      # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Configuration that is injected verbatim into the configuration file. Refer to
+          <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_proxy"/>
+          for details on supported values.
+        '';
+      };
+
+    };
+
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = !config.services.zabbixServer.enable;
+        message = "Please choose one of services.zabbixServer or services.zabbixProxy.";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.zabbixProxy.database.user must be set to ${user} if services.zabbixProxy.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.zabbixProxy.database.createLocally is set to true";
+      }
+    ];
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
+    };
+
+    services.mysql = optionalAttrs mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.postgresql = optionalAttrs pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    users.users.${user} = {
+      description = "Zabbix daemon user";
+      uid = config.ids.uids.zabbix;
+      inherit group;
+    };
+
+    users.groups.${group} = {
+      gid = config.ids.gids.zabbix;
+    };
+
+    security.wrappers = {
+      fping.source = "${pkgs.fping}/bin/fping";
+    };
+
+    systemd.services.zabbix-proxy = {
+      description = "Zabbix Proxy";
+
+      wantedBy = [ "multi-user.target" ];
+      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+      path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+      preStart = optionalString pgsqlLocal ''
+        if ! test -e "${stateDir}/db-created"; then
+          cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString mysqlLocal ''
+        if ! test -e "${stateDir}/db-created"; then
+          cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString (cfg.database.type == "sqlite") ''
+        if ! test -e "${cfg.database.name}"; then
+          ${pkgs.sqlite}/bin/sqlite3 "${cfg.database.name}" < ${cfg.package}/share/zabbix/database/sqlite3/schema.sql
+        fi
+      '' + optionalString (cfg.database.passwordFile != null) ''
+        # create a copy of the supplied password file in a format zabbix can consume
+        touch ${passwordFile}
+        chmod 0600 ${passwordFile}
+        echo -n "DBPassword = " > ${passwordFile}
+        cat ${cfg.database.passwordFile} >> ${passwordFile}
+      '';
+
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/zabbix_proxy zabbix_proxy -f --config ${configFile}";
+        Restart = "always";
+        RestartSec = 2;
+
+        User = user;
+        Group = group;
+        RuntimeDirectory = "zabbix";
+        StateDirectory = "zabbix";
+        PrivateTmp = true;
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix b/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
new file mode 100644
index 000000000000..b4e4378ce1e7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/monitoring/zabbix-server.nix
@@ -0,0 +1,299 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.zabbixServer;
+  pgsql = config.services.postgresql;
+  mysql = config.services.mysql;
+
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+  inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
+
+  user = "zabbix";
+  group = "zabbix";
+  runtimeDir = "/run/zabbix";
+  stateDir = "/var/lib/zabbix";
+  passwordFile = "${runtimeDir}/zabbix-dbpassword.conf";
+
+  moduleEnv = pkgs.symlinkJoin {
+    name = "zabbix-server-module-env";
+    paths = attrValues cfg.modules;
+  };
+
+  configFile = pkgs.writeText "zabbix_server.conf" ''
+    LogType = console
+    ListenIP = ${cfg.listen.ip}
+    ListenPort = ${toString cfg.listen.port}
+    # TODO: set to cfg.database.socket if database type is pgsql?
+    DBHost = ${optionalString (cfg.database.createLocally != true) cfg.database.host}
+    ${optionalString (cfg.database.createLocally != true) "DBPort = ${cfg.database.port}"}
+    DBName = ${cfg.database.name}
+    DBUser = ${cfg.database.user}
+    ${optionalString (cfg.database.passwordFile != null) "Include ${passwordFile}"}
+    ${optionalString (mysqlLocal && cfg.database.socket != null) "DBSocket = ${cfg.database.socket}"}
+    PidFile = ${runtimeDir}/zabbix_server.pid
+    SocketDir = ${runtimeDir}
+    FpingLocation = /run/wrappers/bin/fping
+    ${optionalString (cfg.modules != {}) "LoadModulePath = ${moduleEnv}/lib"}
+    ${concatMapStringsSep "\n" (name: "LoadModule = ${name}") (builtins.attrNames cfg.modules)}
+    ${cfg.extraConfig}
+  '';
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+in
+
+{
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ])
+    (lib.mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.")
+  ];
+
+  # interface
+
+  options = {
+
+    services.zabbixServer = {
+      enable = mkEnableOption "the Zabbix Server";
+
+      package = mkOption {
+        type = types.package;
+        default = if cfg.database.type == "mysql" then pkgs.zabbix.server-mysql else pkgs.zabbix.server-pgsql;
+        defaultText = "pkgs.zabbix.server-pgsql";
+        description = "The Zabbix package to use.";
+      };
+
+      extraPackages = mkOption {
+        type = types.listOf types.package;
+        default = with pkgs; [ nettools nmap traceroute ];
+        defaultText = "[ nettools nmap traceroute ]";
+        description = ''
+          Packages to be added to the Zabbix <envar>PATH</envar>.
+          Typically used to add executables for scripts, but can be anything.
+        '';
+      };
+
+      modules = mkOption {
+        type = types.attrsOf types.package;
+        description = "A set of modules to load.";
+        default = {};
+        example = literalExample ''
+          {
+            "dummy.so" = pkgs.stdenv.mkDerivation {
+              name = "zabbix-dummy-module-''${cfg.package.version}";
+              src = cfg.package.src;
+              buildInputs = [ cfg.package ];
+              sourceRoot = "zabbix-''${cfg.package.version}/src/modules/dummy";
+              installPhase = '''
+                mkdir -p $out/lib
+                cp dummy.so $out/lib/
+              ''';
+            };
+          }
+        '';
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql" "pgsql" ];
+          example = "mysql";
+          default = "pgsql";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = if cfg.database.type == "mysql" then mysql.port else pgsql.port;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/zabbix-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/postgresql";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to create a local database automatically.";
+        };
+      };
+
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            List of comma delimited IP addresses that the trapper should listen on.
+            Trapper will listen on all network interfaces if this parameter is missing.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 10051;
+          description = ''
+            Listen port for trapper.
+          '';
+        };
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Zabbix Server.
+        '';
+      };
+
+      # TODO: for bonus points migrate this to https://github.com/NixOS/rfcs/pull/42
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Configuration that is injected verbatim into the configuration file. Refer to
+          <link xlink:href="https://www.zabbix.com/documentation/current/manual/appendix/config/zabbix_server"/>
+          for details on supported values.
+        '';
+      };
+
+    };
+
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.zabbixServer.database.user must be set to ${user} if services.zabbixServer.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.zabbixServer.database.createLocally is set to true";
+      }
+    ];
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
+    };
+
+    services.mysql = optionalAttrs mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.postgresql = optionalAttrs pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    users.users.${user} = {
+      description = "Zabbix daemon user";
+      uid = config.ids.uids.zabbix;
+      inherit group;
+    };
+
+    users.groups.${group} = {
+      gid = config.ids.gids.zabbix;
+    };
+
+    security.wrappers = {
+      fping.source = "${pkgs.fping}/bin/fping";
+    };
+
+    systemd.services.zabbix-server = {
+      description = "Zabbix Server";
+
+      wantedBy = [ "multi-user.target" ];
+      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+      path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+      preStart = ''
+        # pre 19.09 compatibility
+        if test -e "${runtimeDir}/db-created"; then
+          mv "${runtimeDir}/db-created" "${stateDir}/"
+        fi
+      '' + optionalString pgsqlLocal ''
+        if ! test -e "${stateDir}/db-created"; then
+          cat ${cfg.package}/share/zabbix/database/postgresql/schema.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/postgresql/images.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/postgresql/data.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString mysqlLocal ''
+        if ! test -e "${stateDir}/db-created"; then
+          cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/mysql/images.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/mysql/data.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString (cfg.database.passwordFile != null) ''
+        # create a copy of the supplied password file in a format zabbix can consume
+        touch ${passwordFile}
+        chmod 0600 ${passwordFile}
+        echo -n "DBPassword = " > ${passwordFile}
+        cat ${cfg.database.passwordFile} >> ${passwordFile}
+      '';
+
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/zabbix_server zabbix_server -f --config ${configFile}";
+        Restart = "always";
+        RestartSec = 2;
+
+        User = user;
+        Group = group;
+        RuntimeDirectory = "zabbix";
+        StateDirectory = "zabbix";
+        PrivateTmp = true;
+      };
+    };
+
+    systemd.services.httpd.after =
+      optional (config.services.zabbixWeb.enable && mysqlLocal) "mysql.service" ++
+      optional (config.services.zabbixWeb.enable && pgsqlLocal) "postgresql.service";
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/cachefilesd.nix b/nixpkgs/nixos/modules/services/network-filesystems/cachefilesd.nix
new file mode 100644
index 000000000000..619813408405
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/cachefilesd.nix
@@ -0,0 +1,59 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.cachefilesd;
+
+  cfgFile = pkgs.writeText "cachefilesd.conf" ''
+    dir ${cfg.cacheDir}
+    ${cfg.extraConfig}
+  '';
+
+in
+
+{
+  options = {
+    services.cachefilesd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable cachefilesd network filesystems caching daemon.";
+      };
+
+      cacheDir = mkOption {
+        type = types.str;
+        default = "/var/cache/fscache";
+        description = "Directory to contain filesystem cache.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = "brun 10%";
+        description = "Additional configuration file entries. See cachefilesd.conf(5) for more information.";
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.cachefilesd = {
+      description = "Local network file caching management daemon";
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.kmod pkgs.cachefilesd ];
+      script = ''
+        modprobe -qab cachefiles
+        mkdir -p ${cfg.cacheDir}
+        chmod 700 ${cfg.cacheDir}
+        exec cachefilesd -n -f ${cfgFile}
+      '';
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix b/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix
new file mode 100644
index 000000000000..d17959a6a305
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/ceph.nix
@@ -0,0 +1,418 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg  = config.services.ceph;
+
+  # function that translates "camelCaseOptions" to "camel case options", credits to tilpner in #nixos@freenode
+  expandCamelCase = replaceStrings upperChars (map (s: " ${s}") lowerChars);
+  expandCamelCaseAttrs = mapAttrs' (name: value: nameValuePair (expandCamelCase name) value);
+
+  makeServices = (daemonType: daemonIds:
+    mkMerge (map (daemonId:
+      { "ceph-${daemonType}-${daemonId}" = makeService daemonType daemonId cfg.global.clusterName pkgs.ceph; })
+      daemonIds));
+
+  makeService = (daemonType: daemonId: clusterName: ceph:
+    let
+      stateDirectory = "ceph/${if daemonType == "rgw" then "radosgw" else daemonType}/${clusterName}-${daemonId}"; in {
+    enable = true;
+    description = "Ceph ${builtins.replaceStrings lowerChars upperChars daemonType} daemon ${daemonId}";
+    after = [ "network-online.target" "time-sync.target" ] ++ optional (daemonType == "osd") "ceph-mon.target";
+    wants = [ "network-online.target" "time-sync.target" ];
+    partOf = [ "ceph-${daemonType}.target" ];
+    wantedBy = [ "ceph-${daemonType}.target" ];
+
+    path = [ pkgs.getopt ];
+
+    # Don't start services that are not yet initialized
+    unitConfig.ConditionPathExists = "/var/lib/${stateDirectory}/keyring";
+
+    serviceConfig = {
+      LimitNOFILE = 1048576;
+      LimitNPROC = 1048576;
+      Environment = "CLUSTER=${clusterName}";
+      ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      PrivateDevices = "yes";
+      PrivateTmp = "true";
+      ProtectHome = "true";
+      ProtectSystem = "full";
+      Restart = "on-failure";
+      StartLimitBurst = "5";
+      StartLimitInterval = "30min";
+      StateDirectory = stateDirectory;
+      User = "ceph";
+      Group = if daemonType == "osd" then "disk" else "ceph";
+      ExecStart = ''${ceph.out}/bin/${if daemonType == "rgw" then "radosgw" else "ceph-${daemonType}"} \
+                    -f --cluster ${clusterName} --id ${daemonId}'';
+    } // optionalAttrs (daemonType == "osd") {
+      ExecStartPre = ''${ceph.lib}/libexec/ceph/ceph-osd-prestart.sh --id ${daemonId} --cluster ${clusterName}'';
+      StartLimitBurst = "30";
+      RestartSec = "20s";
+      PrivateDevices = "no"; # osd needs disk access
+    } // optionalAttrs ( daemonType == "mon") {
+      RestartSec = "10";
+    } // optionalAttrs (lib.elem daemonType ["mgr" "mds"]) {
+      StartLimitBurst = "3";
+    };
+  });
+
+  makeTarget = (daemonType:
+    {
+      "ceph-${daemonType}" = {
+        description = "Ceph target allowing to start/stop all ceph-${daemonType} services at once";
+        partOf = [ "ceph.target" ];
+        wantedBy = [ "ceph.target" ];
+        before = [ "ceph.target" ];
+        unitConfig.StopWhenUnneeded = true;
+      };
+    }
+  );
+in
+{
+  options.services.ceph = {
+    # Ceph has a monolithic configuration file but different sections for
+    # each daemon, a separate client section and a global section
+    enable = mkEnableOption "Ceph global configuration";
+
+    global = {
+      fsid = mkOption {
+        type = types.str;
+        example = ''
+          433a2193-4f8a-47a0-95d2-209d7ca2cca5
+        '';
+        description = ''
+          Filesystem ID, a generated uuid, its must be generated and set before
+          attempting to start a cluster
+        '';
+      };
+
+      clusterName = mkOption {
+        type = types.str;
+        default = "ceph";
+        description = ''
+          Name of cluster
+        '';
+      };
+
+      mgrModulePath = mkOption {
+        type = types.path;
+        default = "${pkgs.ceph.lib}/lib/ceph/mgr";
+        description = ''
+          Path at which to find ceph-mgr modules.
+        '';
+      };
+
+      monInitialMembers = mkOption {
+        type = with types; nullOr commas;
+        default = null;
+        example = ''
+          node0, node1, node2
+        '';
+        description = ''
+          List of hosts that will be used as monitors at startup.
+        '';
+      };
+
+      monHost = mkOption {
+        type = with types; nullOr commas;
+        default = null;
+        example = ''
+          10.10.0.1, 10.10.0.2, 10.10.0.3
+        '';
+        description = ''
+          List of hostname shortnames/IP addresses of the initial monitors.
+        '';
+      };
+
+      maxOpenFiles = mkOption {
+        type = types.int;
+        default = 131072;
+        description = ''
+          Max open files for each OSD daemon.
+        '';
+      };
+
+      authClusterRequired = mkOption {
+        type = types.enum [ "cephx" "none" ];
+        default = "cephx";
+        description = ''
+          Enables requiring daemons to authenticate with eachother in the cluster.
+        '';
+      };
+
+      authServiceRequired = mkOption {
+        type = types.enum [ "cephx" "none" ];
+        default = "cephx";
+        description = ''
+          Enables requiring clients to authenticate with the cluster to access services in the cluster (e.g. radosgw, mds or osd).
+        '';
+      };
+
+      authClientRequired = mkOption {
+        type = types.enum [ "cephx" "none" ];
+        default = "cephx";
+        description = ''
+          Enables requiring the cluster to authenticate itself to the client.
+        '';
+      };
+
+      publicNetwork = mkOption {
+        type = with types; nullOr commas;
+        default = null;
+        example = ''
+          10.20.0.0/24, 192.168.1.0/24
+        '';
+        description = ''
+          A comma-separated list of subnets that will be used as public networks in the cluster.
+        '';
+      };
+
+      clusterNetwork = mkOption {
+        type = with types; nullOr commas;
+        default = null;
+        example = ''
+          10.10.0.0/24, 192.168.0.0/24
+        '';
+        description = ''
+          A comma-separated list of subnets that will be used as cluster networks in the cluster.
+        '';
+      };
+
+      rgwMimeTypesFile = mkOption {
+        type = with types; nullOr path;
+        default = "${pkgs.mime-types}/etc/mime.types";
+        description = ''
+          Path to mime types used by radosgw.
+        '';
+      };
+    };
+
+    extraConfig = mkOption {
+      type = with types; attrsOf str;
+      default = {};
+      example = ''
+        {
+          "ms bind ipv6" = "true";
+        };
+      '';
+      description = ''
+        Extra configuration to add to the global section. Use for setting values that are common for all daemons in the cluster.
+      '';
+    };
+
+    mgr = {
+      enable = mkEnableOption "Ceph MGR daemon";
+      daemons = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = ''
+          [ "name1" "name2" ];
+        '';
+        description = ''
+          A list of names for manager daemons that should have a service created. The names correspond
+          to the id part in ceph i.e. [ "name1" ] would result in mgr.name1
+        '';
+      };
+      extraConfig = mkOption {
+        type = with types; attrsOf str;
+        default = {};
+        description = ''
+          Extra configuration to add to the global section for manager daemons.
+        '';
+      };
+    };
+
+    mon = {
+      enable = mkEnableOption "Ceph MON daemon";
+      daemons = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = ''
+          [ "name1" "name2" ];
+        '';
+        description = ''
+          A list of monitor daemons that should have a service created. The names correspond
+          to the id part in ceph i.e. [ "name1" ] would result in mon.name1
+        '';
+      };
+      extraConfig = mkOption {
+        type = with types; attrsOf str;
+        default = {};
+        description = ''
+          Extra configuration to add to the monitor section.
+        '';
+      };
+    };
+
+    osd = {
+      enable = mkEnableOption "Ceph OSD daemon";
+      daemons = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = ''
+          [ "name1" "name2" ];
+        '';
+        description = ''
+          A list of OSD daemons that should have a service created. The names correspond
+          to the id part in ceph i.e. [ "name1" ] would result in osd.name1
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = with types; attrsOf str;
+        default = {
+          "osd journal size" = "10000";
+          "osd pool default size" = "3";
+          "osd pool default min size" = "2";
+          "osd pool default pg num" = "200";
+          "osd pool default pgp num" = "200";
+          "osd crush chooseleaf type" = "1";
+        };
+        description = ''
+          Extra configuration to add to the OSD section.
+        '';
+      };
+    };
+
+    mds = {
+      enable = mkEnableOption "Ceph MDS daemon";
+      daemons = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = ''
+          [ "name1" "name2" ];
+        '';
+        description = ''
+          A list of metadata service daemons that should have a service created. The names correspond
+          to the id part in ceph i.e. [ "name1" ] would result in mds.name1
+        '';
+      };
+      extraConfig = mkOption {
+        type = with types; attrsOf str;
+        default = {};
+        description = ''
+          Extra configuration to add to the MDS section.
+        '';
+      };
+    };
+
+    rgw = {
+      enable = mkEnableOption "Ceph RadosGW daemon";
+      daemons = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = ''
+          [ "name1" "name2" ];
+        '';
+        description = ''
+          A list of rados gateway daemons that should have a service created. The names correspond
+          to the id part in ceph i.e. [ "name1" ] would result in client.name1, radosgw daemons
+          aren't daemons to cluster in the sense that OSD, MGR or MON daemons are. They are simply
+          daemons, from ceph, that uses the cluster as a backend.
+        '';
+      };
+    };
+
+    client = {
+      enable = mkEnableOption "Ceph client configuration";
+      extraConfig = mkOption {
+        type = with types; attrsOf str;
+        default = {};
+        example = ''
+          {
+            # This would create a section for a radosgw daemon named node0 and related
+            # configuration for it
+            "client.radosgw.node0" = { "some config option" = "true"; };
+          };
+        '';
+        description = ''
+          Extra configuration to add to the client section. Configuration for rados gateways
+          would be added here, with their own sections, see example.
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.ceph.enable {
+    assertions = [
+      { assertion = cfg.global.fsid != "";
+        message = "fsid has to be set to a valid uuid for the cluster to function";
+      }
+      { assertion = cfg.mon.enable == true -> cfg.mon.daemons != [];
+        message = "have to set id of atleast one MON if you're going to enable Monitor";
+      }
+      { assertion = cfg.mds.enable == true -> cfg.mds.daemons != [];
+        message = "have to set id of atleast one MDS if you're going to enable Metadata Service";
+      }
+      { assertion = cfg.osd.enable == true -> cfg.osd.daemons != [];
+        message = "have to set id of atleast one OSD if you're going to enable OSD";
+      }
+      { assertion = cfg.mgr.enable == true -> cfg.mgr.daemons != [];
+        message = "have to set id of atleast one MGR if you're going to enable MGR";
+      }
+    ];
+
+    warnings = optional (cfg.global.monInitialMembers == null)
+      ''Not setting up a list of members in monInitialMembers requires that you set the host variable for each mon daemon or else the cluster won't function'';
+
+    environment.etc."ceph/ceph.conf".text = let
+      # Merge the extraConfig set for mgr daemons, as mgr don't have their own section
+      globalSection = expandCamelCaseAttrs (cfg.global // cfg.extraConfig // optionalAttrs cfg.mgr.enable cfg.mgr.extraConfig);
+      # Remove all name-value pairs with null values from the attribute set to avoid making empty sections in the ceph.conf
+      globalSection' = filterAttrs (name: value: value != null) globalSection;
+      totalConfig = {
+          global = globalSection';
+        } // optionalAttrs (cfg.mon.enable && cfg.mon.extraConfig != {}) { mon = cfg.mon.extraConfig; }
+          // optionalAttrs (cfg.mds.enable && cfg.mds.extraConfig != {}) { mds = cfg.mds.extraConfig; }
+          // optionalAttrs (cfg.osd.enable && cfg.osd.extraConfig != {}) { osd = cfg.osd.extraConfig; }
+          // optionalAttrs (cfg.client.enable && cfg.client.extraConfig != {})  cfg.client.extraConfig;
+      in
+        generators.toINI {} totalConfig;
+
+    users.users.ceph = {
+      uid = config.ids.uids.ceph;
+      description = "Ceph daemon user";
+      group = "ceph";
+      extraGroups = [ "disk" ];
+    };
+
+    users.groups.ceph = {
+      gid = config.ids.gids.ceph;
+    };
+
+    systemd.services = let
+      services = []
+        ++ optional cfg.mon.enable (makeServices "mon" cfg.mon.daemons)
+        ++ optional cfg.mds.enable (makeServices "mds" cfg.mds.daemons)
+        ++ optional cfg.osd.enable (makeServices "osd" cfg.osd.daemons)
+        ++ optional cfg.rgw.enable (makeServices "rgw" cfg.rgw.daemons)
+        ++ optional cfg.mgr.enable (makeServices "mgr" cfg.mgr.daemons);
+      in
+        mkMerge services;
+
+    systemd.targets = let
+      targets = [
+        { ceph = {
+          description = "Ceph target allowing to start/stop all ceph service instances at once";
+          wantedBy = [ "multi-user.target" ];
+          unitConfig.StopWhenUnneeded = true;
+        }; } ]
+        ++ optional cfg.mon.enable (makeTarget "mon")
+        ++ optional cfg.mds.enable (makeTarget "mds")
+        ++ optional cfg.osd.enable (makeTarget "osd")
+        ++ optional cfg.rgw.enable (makeTarget "rgw")
+        ++ optional cfg.mgr.enable (makeTarget "mgr");
+      in
+        mkMerge targets;
+
+    systemd.tmpfiles.rules = [
+      "d /etc/ceph - ceph ceph - -"
+      "d /run/ceph 0770 ceph ceph -"
+      "d /var/lib/ceph - ceph ceph - -"]
+    ++ optionals cfg.mgr.enable [ "d /var/lib/ceph/mgr - ceph ceph - -"]
+    ++ optionals cfg.mon.enable [ "d /var/lib/ceph/mon - ceph ceph - -"]
+    ++ optionals cfg.osd.enable [ "d /var/lib/ceph/osd - ceph ceph - -"];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/davfs2.nix b/nixpkgs/nixos/modules/services/network-filesystems/davfs2.nix
new file mode 100644
index 000000000000..4b6f85e4a2c9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/davfs2.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.davfs2;
+  cfgFile = pkgs.writeText "davfs2.conf" ''
+    dav_user ${cfg.davUser}
+    dav_group ${cfg.davGroup}
+    ${cfg.extraConfig}
+  '';
+in
+{
+  options.services.davfs2 = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable davfs2.
+      '';
+    };
+
+    davUser = mkOption {
+      type = types.str;
+      default = "davfs2";
+      description = ''
+        When invoked by root the mount.davfs daemon will run as this user.
+        Value must be given as name, not as numerical id.
+      '';
+    };
+
+    davGroup = mkOption {
+      type = types.str;
+      default = "davfs2";
+      description = ''
+        The group of the running mount.davfs daemon. Ordinary users must be
+        member of this group in order to mount a davfs2 file system. Value must
+        be given as name, not as numerical id.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        kernel_fs coda
+        proxy foo.bar:8080
+        use_locks 0
+      '';
+      description = ''
+        Extra lines appended to the configuration of davfs2.
+      ''  ;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.davfs2 ];
+    environment.etc."davfs2/davfs2.conf".source = cfgFile;
+
+    users.groups = optionalAttrs (cfg.davGroup == "davfs2") {
+      davfs2.gid = config.ids.gids.davfs2;
+    };
+
+    users.users = optionalAttrs (cfg.davUser == "davfs2") {
+      davfs2 = {
+        createHome = false;
+        group = cfg.davGroup;
+        uid = config.ids.uids.davfs2;
+        description = "davfs2 user";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/diod.nix b/nixpkgs/nixos/modules/services/network-filesystems/diod.nix
new file mode 100644
index 000000000000..063bae6ddb1d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/diod.nix
@@ -0,0 +1,159 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.diod;
+
+  diodBool = b: if b then "1" else "0";
+
+  diodConfig = pkgs.writeText "diod.conf" ''
+    allsquash = ${diodBool cfg.allsquash}
+    auth_required = ${diodBool cfg.authRequired}
+    exportall = ${diodBool cfg.exportall}
+    exportopts = "${concatStringsSep "," cfg.exportopts}"
+    exports = { ${concatStringsSep ", " (map (s: ''"${s}"'' ) cfg.exports)} }
+    listen = { ${concatStringsSep ", " (map (s: ''"${s}"'' ) cfg.listen)} }
+    logdest = "${cfg.logdest}"
+    nwthreads = ${toString cfg.nwthreads}
+    squashuser = "${cfg.squashuser}"
+    statfs_passthru = ${diodBool cfg.statfsPassthru}
+    userdb = ${diodBool cfg.userdb}
+    ${cfg.extraConfig}
+  '';
+in
+{
+  options = {
+    services.diod = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the diod 9P file server.";
+      };
+
+      listen = mkOption {
+        type = types.listOf types.str;
+        default = [ "0.0.0.0:564" ];
+        description = ''
+          [ "IP:PORT" [,"IP:PORT",...] ]
+          List the interfaces and ports that diod should listen on.
+        '';
+      };
+
+      exports = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          List the file systems that clients will be allowed to mount. All paths should
+          be fully qualified. The exports table can include two types of element:
+          a string element (as above),
+          or an alternate table element form { path="/path", opts="ro" }.
+          In the alternate form, the (optional) opts attribute is a comma-separated list
+          of export options. The two table element forms can be mixed in the exports
+          table. Note that although diod will not traverse file system boundaries for a
+          given mount due to inode uniqueness constraints, subdirectories of a file
+          system can be separately exported.
+        '';
+      };
+
+      exportall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Export all file systems listed in /proc/mounts. If new file systems are mounted
+          after diod has started, they will become immediately mountable. If there is a
+          duplicate entry for a file system in the exports list, any options listed in
+          the exports entry will apply.
+        '';
+      };
+
+      exportopts = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Establish a default set of export options. These are overridden, not appended
+          to, by opts attributes in an "exports" entry.
+        '';
+      };
+
+      nwthreads = mkOption {
+        type = types.int;
+        default = 16;
+        description = ''
+          Sets the (fixed) number of worker threads created to handle 9P
+          requests for a unique aname.
+        '';
+      };
+
+      authRequired = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Allow clients to connect without authentication, i.e. without a valid MUNGE credential.
+        '';
+      };
+
+      userdb = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          This option disables password/group lookups. It allows any uid to attach and
+          assumes gid=uid, and supplementary groups contain only the primary gid.
+        '';
+      };
+
+      allsquash = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Remap all users to "nobody". The attaching user need not be present in the
+          password file.
+        '';
+      };
+
+      squashuser = mkOption {
+        type = types.str;
+        default = "nobody";
+        description = ''
+          Change the squash user. The squash user must be present in the password file.
+        '';
+      };
+
+      logdest = mkOption {
+        type = types.str;
+        default = "syslog:daemon:err";
+        description = ''
+          Set the destination for logging.
+          The value has the form of "syslog:facility:level" or "filename".
+        '';
+      };
+
+
+      statfsPassthru = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          This option configures statfs to return the host file system's type
+          rather than V9FS_MAGIC.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra configuration options for diod.conf.";
+      };
+    };
+  };
+
+  config = mkIf config.services.diod.enable {
+    environment.systemPackages = [ pkgs.diod ];
+
+    systemd.services.diod = {
+      description = "diod 9P file server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.diod}/sbin/diod -f -c ${diodConfig}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix b/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix
new file mode 100644
index 000000000000..916e7eaaaa94
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/drbd.nix
@@ -0,0 +1,65 @@
+# Support for DRBD, the Distributed Replicated Block Device.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.drbd; in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.drbd.enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable support for DRBD, the Distributed Replicated
+        Block Device.
+      '';
+    };
+
+    services.drbd.config = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Contents of the <filename>drbd.conf</filename> configuration file.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.drbd ];
+
+    services.udev.packages = [ pkgs.drbd ];
+
+    boot.kernelModules = [ "drbd" ];
+
+    boot.extraModprobeConfig =
+      ''
+        options drbd usermode_helper=/run/current-system/sw/bin/drbdadm
+      '';
+
+    environment.etc.drbd.conf =
+      { source = pkgs.writeText "drbd.conf" cfg.config; };
+
+    systemd.services.drbd = {
+      after = [ "systemd-udev.settle.service" "network.target" ];
+      wants = [ "systemd-udev.settle.service" ];
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        ${pkgs.drbd}/sbin/drbdadm up all
+      '';
+      serviceConfig.ExecStop = ''
+        ${pkgs.drbd}/sbin/drbdadm down all
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
new file mode 100644
index 000000000000..d70092999f67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -0,0 +1,211 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  inherit (pkgs) glusterfs rsync;
+
+  tlsCmd = if (cfg.tlsSettings != null) then
+  ''
+    mkdir -p /var/lib/glusterd
+    touch /var/lib/glusterd/secure-access
+  ''
+  else
+  ''
+    rm -f /var/lib/glusterd/secure-access
+  '';
+
+  restartTriggers = if (cfg.tlsSettings != null) then [
+    config.environment.etc."ssl/glusterfs.pem".source
+    config.environment.etc."ssl/glusterfs.key".source
+    config.environment.etc."ssl/glusterfs.ca".source
+  ] else [];
+
+  cfg = config.services.glusterfs;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.glusterfs = {
+
+      enable = mkEnableOption "GlusterFS Daemon";
+
+      logLevel = mkOption {
+        type = types.enum ["DEBUG" "INFO" "WARNING" "ERROR" "CRITICAL" "TRACE" "NONE"];
+        description = "Log level used by the GlusterFS daemon";
+        default = "INFO";
+      };
+
+      useRpcbind = mkOption {
+        type = types.bool;
+        description = ''
+          Enable use of rpcbind. This is required for Gluster's NFS functionality.
+
+          You may want to turn it off to reduce the attack surface for DDoS reflection attacks.
+
+          See https://davelozier.com/glusterfs-and-rpcbind-portmap-ddos-reflection-attacks/
+          and https://bugzilla.redhat.com/show_bug.cgi?id=1426842 for details.
+        '';
+        default = true;
+      };
+
+      enableGlustereventsd = mkOption {
+        type = types.bool;
+        description = "Whether to enable the GlusterFS Events Daemon";
+        default = true;
+      };
+
+      killMode = mkOption {
+        type = types.enum ["control-group" "process" "mixed" "none"];
+        description = ''
+          The systemd KillMode to use for glusterd.
+
+          glusterd spawns other daemons like gsyncd.
+          If you want these to stop when glusterd is stopped (e.g. to ensure
+          that NixOS config changes are reflected even for these sub-daemons),
+          set this to 'control-group'.
+          If however you want running volume processes (glusterfsd) and thus
+          gluster mounts not be interrupted when glusterd is restarted
+          (for example, when you want to restart them manually at a later time),
+          set this to 'process'.
+        '';
+        default = "control-group";
+      };
+
+      stopKillTimeout = mkOption {
+        type = types.str;
+        description = ''
+          The systemd TimeoutStopSec to use.
+
+          After this time after having been asked to shut down, glusterd
+          (and depending on the killMode setting also its child processes)
+          are killed by systemd.
+
+          The default is set low because GlusterFS (as of 3.10) is known to
+          not tell its children (like gsyncd) to terminate at all.
+        '';
+        default = "5s";
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        description = "Extra flags passed to the GlusterFS daemon";
+        default = [];
+      };
+
+      tlsSettings = mkOption {
+        description = ''
+          Make the server communicate via TLS.
+          This means it will only connect to other gluster
+          servers having certificates signed by the same CA.
+
+          Enabling this will create a file <filename>/var/lib/glusterd/secure-access</filename>.
+          Disabling will delete this file again.
+
+          See also: https://gluster.readthedocs.io/en/latest/Administrator%20Guide/SSL/
+        '';
+        default = null;
+        type = types.nullOr (types.submodule {
+          options = {
+            tlsKeyPath = mkOption {
+              default = null;
+              type = types.str;
+              description = "Path to the private key used for TLS.";
+            };
+
+            tlsPem = mkOption {
+              default = null;
+              type = types.path;
+              description = "Path to the certificate used for TLS.";
+            };
+
+            caCert = mkOption {
+              default = null;
+              type = types.path;
+              description = "Path certificate authority used to sign the cluster certificates.";
+            };
+          };
+        });
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.glusterfs ];
+
+    services.rpcbind.enable = cfg.useRpcbind;
+
+    environment.etc = mkIf (cfg.tlsSettings != null) {
+      "ssl/glusterfs.pem".source = cfg.tlsSettings.tlsPem;
+      "ssl/glusterfs.key".source = cfg.tlsSettings.tlsKeyPath;
+      "ssl/glusterfs.ca".source = cfg.tlsSettings.caCert;
+    };
+
+    systemd.services.glusterd = {
+      inherit restartTriggers;
+
+      description = "GlusterFS, a clustered file-system server";
+
+      wantedBy = [ "multi-user.target" ];
+
+      requires = lib.optional cfg.useRpcbind "rpcbind.service";
+      after = [ "network.target" ] ++ lib.optional cfg.useRpcbind "rpcbind.service";
+
+      preStart = ''
+        install -m 0755 -d /var/log/glusterfs
+      ''
+      # The copying of hooks is due to upstream bug https://bugzilla.redhat.com/show_bug.cgi?id=1452761
+      + ''
+        mkdir -p /var/lib/glusterd/hooks/
+        ${rsync}/bin/rsync -a ${glusterfs}/var/lib/glusterd/hooks/ /var/lib/glusterd/hooks/
+
+        ${tlsCmd}
+      ''
+      # `glusterfind` needs dirs that upstream installs at `make install` phase
+      # https://github.com/gluster/glusterfs/blob/v3.10.2/tools/glusterfind/Makefile.am#L16-L17
+      + ''
+        mkdir -p /var/lib/glusterd/glusterfind/.keys
+        mkdir -p /var/lib/glusterd/hooks/1/delete/post/
+      '';
+
+      serviceConfig = {
+        LimitNOFILE=65536;
+        ExecStart="${glusterfs}/sbin/glusterd --no-daemon --log-level=${cfg.logLevel} ${toString cfg.extraFlags}";
+        KillMode=cfg.killMode;
+        TimeoutStopSec=cfg.stopKillTimeout;
+      };
+    };
+
+    systemd.services.glustereventsd = mkIf cfg.enableGlustereventsd {
+      inherit restartTriggers;
+
+      description = "Gluster Events Notifier";
+
+      wantedBy = [ "multi-user.target" ];
+
+      after = [ "syslog.target" "network.target" ];
+
+      preStart = ''
+        install -m 0755 -d /var/log/glusterfs
+      '';
+
+      # glustereventsd uses the `gluster` executable
+      path = [ glusterfs ];
+
+      serviceConfig = {
+        Type="simple";
+        PIDFile="/run/glustereventsd.pid";
+        ExecStart="${glusterfs}/sbin/glustereventsd --pid-file /run/glustereventsd.pid";
+        ExecReload="/bin/kill -SIGUSR2 $MAINPID";
+        KillMode="control-group";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix
new file mode 100644
index 000000000000..a3bd40135d19
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/ipfs.nix
@@ -0,0 +1,264 @@
+{ config, lib, pkgs, options, ... }:
+with lib;
+let
+  cfg = config.services.ipfs;
+  opt = options.services.ipfs;
+
+  ipfsFlags = toString ([
+    (optionalString  cfg.autoMount                   "--mount")
+    (optionalString  cfg.enableGC                    "--enable-gc")
+    (optionalString (cfg.serviceFdlimit != null)     "--manage-fdlimit=false")
+    (optionalString (cfg.defaultMode == "offline")   "--offline")
+    (optionalString (cfg.defaultMode == "norouting") "--routing=none")
+  ] ++ cfg.extraFlags);
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.ipfs = {
+
+      enable = mkEnableOption "Interplanetary File System (WARNING: may cause severe network degredation)";
+
+      user = mkOption {
+        type = types.str;
+        default = "ipfs";
+        description = "User under which the IPFS daemon runs";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "ipfs";
+        description = "Group under which the IPFS daemon runs";
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = if versionAtLeast config.system.stateVersion "17.09"
+                  then "/var/lib/ipfs"
+                  else "/var/lib/ipfs/.ipfs";
+        description = "The data dir for IPFS";
+      };
+
+      defaultMode = mkOption {
+        type = types.enum [ "online" "offline" "norouting" ];
+        default = "online";
+        description = "systemd service that is enabled by default";
+      };
+
+      autoMount = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether IPFS should try to mount /ipfs and /ipns at startup.";
+      };
+
+      ipfsMountDir = mkOption {
+        type = types.str;
+        default = "/ipfs";
+        description = "Where to mount the IPFS namespace to";
+      };
+
+      ipnsMountDir = mkOption {
+        type = types.str;
+        default = "/ipns";
+        description = "Where to mount the IPNS namespace to";
+      };
+
+      gatewayAddress = mkOption {
+        type = types.str;
+        default = "/ip4/127.0.0.1/tcp/8080";
+        description = "Where the IPFS Gateway can be reached";
+      };
+
+      apiAddress = mkOption {
+        type = types.str;
+        default = "/ip4/127.0.0.1/tcp/5001";
+        description = "Where IPFS exposes its API to";
+      };
+
+      swarmAddress = mkOption {
+        type = types.listOf types.str;
+        default = [ "/ip4/0.0.0.0/tcp/4001" "/ip6/::/tcp/4001" ];
+        description = "Where IPFS listens for incoming p2p connections";
+      };
+
+      enableGC = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable automatic garbage collection";
+      };
+
+      emptyRepo = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If set to true, the repo won't be initialized with help files";
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        description = ''
+          Attrset of daemon configuration to set using <command>ipfs config</command>, every time the daemon starts.
+          These are applied last, so may override configuration set by other options in this module.
+          Keep in mind that this configuration is stateful; i.e., unsetting anything in here does not reset the value to the default!
+        '';
+        default = {};
+        example = {
+          Datastore.StorageMax = "100GB";
+          Discovery.MDNS.Enabled = false;
+          Bootstrap = [
+            "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu"
+            "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm"
+          ];
+          Swarm.AddrFilters = null;
+        };
+
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        description = "Extra flags passed to the IPFS daemon";
+        default = [];
+      };
+
+      localDiscovery = mkOption {
+        type = types.bool;
+        description = ''Whether to enable local discovery for the ipfs daemon.
+          This will allow ipfs to scan ports on your local network. Some hosting services will ban you if you do this.
+        '';
+        default = true;
+      };
+
+      serviceFdlimit = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = "The fdlimit for the IPFS systemd unit or <literal>null</literal> to have the daemon attempt to manage it";
+        example = 64*1024;
+      };
+
+      startWhenNeeded = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to use socket activation to start IPFS when needed.";
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.ipfs ];
+    environment.variables.IPFS_PATH = cfg.dataDir;
+
+    programs.fuse = mkIf cfg.autoMount {
+      userAllowOther = true;
+    };
+
+    users.users = mkIf (cfg.user == "ipfs") {
+      ipfs = {
+        group = cfg.group;
+        home = cfg.dataDir;
+        createHome = false;
+        uid = config.ids.uids.ipfs;
+        description = "IPFS daemon user";
+        packages = [
+          pkgs.ipfs-migrator
+        ];
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "ipfs") {
+      ipfs.gid = config.ids.gids.ipfs;
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${cfg.user} ${cfg.group} - -"
+    ] ++ optionals cfg.autoMount [
+      "d '${cfg.ipfsMountDir}' - ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.ipnsMountDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.packages = [ pkgs.ipfs ];
+
+    systemd.services.ipfs-init = {
+      description = "IPFS Initializer";
+
+      environment.IPFS_PATH = cfg.dataDir;
+
+      path = [ pkgs.ipfs ];
+
+      script = ''
+        if [[ ! -f ${cfg.dataDir}/config ]]; then
+          ipfs init ${optionalString cfg.emptyRepo "-e"} \
+            ${optionalString (! cfg.localDiscovery) "--profile=server"}
+        else
+          ${if cfg.localDiscovery
+            then "ipfs config profile apply local-discovery"
+            else "ipfs config profile apply server"
+          }
+        fi
+      '';
+
+      wantedBy = [ "default.target" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+
+    systemd.services.ipfs = {
+      path = [ "/run/wrappers" pkgs.ipfs ];
+      environment.IPFS_PATH = cfg.dataDir;
+
+      wants = [ "ipfs-init.service" ];
+      after = [ "ipfs-init.service" ];
+
+      preStart = optionalString cfg.autoMount ''
+        ipfs --local config Mounts.FuseAllowOther --json true
+        ipfs --local config Mounts.IPFS ${cfg.ipfsMountDir}
+        ipfs --local config Mounts.IPNS ${cfg.ipnsMountDir}
+      '' + concatStringsSep "\n" (collect
+            isString
+            (mapAttrsRecursive
+              (path: value:
+              # Using heredoc below so that the value is never improperly quoted
+              ''
+                read value <<EOF
+                ${builtins.toJSON value}
+                EOF
+                ipfs --local config --json "${concatStringsSep "." path}" "$value"
+              '')
+              ({ Addresses.API = cfg.apiAddress;
+                 Addresses.Gateway = cfg.gatewayAddress;
+                 Addresses.Swarm = cfg.swarmAddress;
+              } //
+              cfg.extraConfig))
+          );
+      serviceConfig = {
+        ExecStart = ["" "${pkgs.ipfs}/bin/ipfs daemon ${ipfsFlags}"];
+        User = cfg.user;
+        Group = cfg.group;
+      } // optionalAttrs (cfg.serviceFdlimit != null) { LimitNOFILE = cfg.serviceFdlimit; };
+    } // optionalAttrs (!cfg.startWhenNeeded) {
+      wantedBy = [ "default.target" ];
+    };
+
+    systemd.sockets.ipfs-gateway = {
+      wantedBy = [ "sockets.target" ];
+      socketConfig.ListenStream = [ "" ]
+        ++ lib.optional (cfg.gatewayAddress == opt.gatewayAddress.default) [ "127.0.0.1:8080" "[::1]:8080" ];
+    };
+
+    systemd.sockets.ipfs-api = {
+      wantedBy = [ "sockets.target" ];
+      socketConfig.ListenStream = [ "" "%t/ipfs.sock" ]
+        ++ lib.optional (cfg.apiAddress == opt.apiAddress.default) [ "127.0.0.1:5001" "[::1]:5001" ];
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/kbfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/kbfs.nix
new file mode 100644
index 000000000000..a43ac656f667
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/kbfs.nix
@@ -0,0 +1,118 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  inherit (config.security) wrapperDir;
+  cfg = config.services.kbfs;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.kbfs = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to mount the Keybase filesystem.";
+      };
+
+      enableRedirector = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Keybase root redirector service, allowing
+          any user to access KBFS files via <literal>/keybase</literal>,
+          which will show different contents depending on the requester.
+        '';
+      };
+
+      mountPoint = mkOption {
+        type = types.str;
+        default = "%h/keybase";
+        example = "/keybase";
+        description = "Mountpoint for the Keybase filesystem.";
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [
+          "-label kbfs"
+          "-mount-type normal"
+        ];
+        description = ''
+          Additional flags to pass to the Keybase filesystem on launch.
+        '';
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+      # Upstream: https://github.com/keybase/client/blob/master/packaging/linux/systemd/kbfs.service
+      systemd.user.services.kbfs = {
+        description = "Keybase File System";
+
+        # Note that the "Requires" directive will cause a unit to be restarted whenever its dependency is restarted.
+        # Do not issue a hard dependency on keybase, because kbfs can reconnect to a restarted service.
+        # Do not issue a hard dependency on keybase-redirector, because it's ok if it fails (e.g., if it is disabled).
+        wants = [ "keybase.service" ] ++ optional cfg.enableRedirector "keybase-redirector.service";
+        path = [ "/run/wrappers" ];
+        unitConfig.ConditionUser = "!@system";
+
+        serviceConfig = {
+          Type = "notify";
+          # Keybase notifies from a forked process
+          EnvironmentFile = [
+            "-%E/keybase/keybase.autogen.env"
+            "-%E/keybase/keybase.env"
+          ];
+          ExecStartPre = [
+            "${pkgs.coreutils}/bin/mkdir -p \"${cfg.mountPoint}\""
+            "-${wrapperDir}/fusermount -uz \"${cfg.mountPoint}\""
+          ];
+          ExecStart = "${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} \"${cfg.mountPoint}\"";
+          ExecStop = "${wrapperDir}/fusermount -uz \"${cfg.mountPoint}\"";
+          Restart = "on-failure";
+          PrivateTmp = true;
+        };
+        wantedBy = [ "default.target" ];
+      };
+
+      services.keybase.enable = true;
+
+      environment.systemPackages = [ pkgs.kbfs ];
+    }
+
+    (mkIf cfg.enableRedirector {
+      security.wrappers."keybase-redirector".source = "${pkgs.kbfs}/bin/redirector";
+
+      systemd.tmpfiles.rules = [ "d /keybase 0755 root root 0" ];
+
+      # Upstream: https://github.com/keybase/client/blob/master/packaging/linux/systemd/keybase-redirector.service
+      systemd.user.services.keybase-redirector = {
+        description = "Keybase Root Redirector for KBFS";
+        wants = [ "keybase.service" ];
+        unitConfig.ConditionUser = "!@system";
+
+        serviceConfig = {
+          EnvironmentFile = [
+            "-%E/keybase/keybase.autogen.env"
+            "-%E/keybase/keybase.env"
+          ];
+          # Note: The /keybase mount point is not currently configurable upstream.
+          ExecStart = "${wrapperDir}/keybase-redirector /keybase";
+          Restart = "on-failure";
+          PrivateTmp = true;
+        };
+
+        wantedBy = [ "default.target" ];
+      };
+    })
+  ]);
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix b/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix
new file mode 100644
index 000000000000..7674c8f7fa8d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/netatalk.nix
@@ -0,0 +1,149 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.netatalk;
+
+  extmapFile = pkgs.writeText "extmap.conf" cfg.extmap;
+
+  afpToString = x: if builtins.typeOf x == "bool"
+                   then boolToString x
+                   else toString x;
+
+  volumeConfig = name:
+    let vol = getAttr name cfg.volumes; in
+    "[${name}]\n " + (toString (
+       map
+         (key: "${key} = ${afpToString (getAttr key vol)}\n")
+         (attrNames vol)
+    ));
+
+  afpConf = ''[Global]
+    extmap file = ${extmapFile}
+    afp port = ${toString cfg.port}
+
+    ${cfg.extraConfig}
+
+    ${if cfg.homes.enable then ''[Homes]
+    ${optionalString (cfg.homes.path != "") "path = ${cfg.homes.path}"}
+    basedir regex = ${cfg.homes.basedirRegex}
+    ${cfg.homes.extraConfig}
+    '' else ""}
+
+     ${toString (map volumeConfig (attrNames cfg.volumes))}
+  '';
+
+  afpConfFile = pkgs.writeText "afp.conf" afpConf;
+
+in
+
+{
+  options = {
+    services.netatalk = {
+
+      enable = mkEnableOption "the Netatalk AFP fileserver";
+
+      port = mkOption {
+        default = 548;
+        description = "TCP port to be used for AFP.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = "uam list = uams_guest.so";
+        description = ''
+          Lines of configuration to add to the <literal>[Global]</literal> section.
+          See <literal>man apf.conf</literal> for more information.
+        '';
+      };
+
+      homes = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Enable sharing of the UNIX server user home directories.";
+        };
+
+        path = mkOption {
+          default = "";
+          example = "afp-data";
+          description = "Share not the whole user home but this subdirectory path.";
+        };
+
+        basedirRegex = mkOption {
+          example = "/home";
+          description = "Regex which matches the parent directory of the user homes.";
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Lines of configuration to add to the <literal>[Homes]</literal> section.
+            See <literal>man apf.conf</literal> for more information.
+          '';
+         };
+      };
+
+      volumes = mkOption {
+        default = { };
+        type = types.attrsOf (types.attrsOf types.unspecified);
+        description =
+          ''
+            Set of AFP volumes to export.
+            See <literal>man apf.conf</literal> for more information.
+          '';
+        example = literalExample ''
+          { srv =
+             { path = "/srv";
+               "read only" = true;
+               "hosts allow" = "10.1.0.0/16 10.2.1.100 2001:0db8:1234::/48";
+             };
+          }
+        '';
+      };
+
+      extmap = mkOption {
+        type = types.lines;
+	default = "";
+	description = ''
+	  File name extension mappings.
+	  See <literal>man extmap.conf</literal> for more information.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.netatalk = {
+      description = "Netatalk AFP fileserver for Macintosh clients";
+      unitConfig.Documentation = "man:afp.conf(5) man:netatalk(8) man:afpd(8) man:cnid_metad(8) man:cnid_dbd(8)";
+      after = [ "network.target" "avahi-daemon.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ pkgs.netatalk ];
+
+      serviceConfig = {
+        Type = "forking";
+        GuessMainPID = "no";
+        PIDFile = "/run/lock/netatalk";
+	ExecStartPre = "${pkgs.coreutils}/bin/mkdir -m 0755 -p /var/lib/netatalk/CNID";
+        ExecStart  = "${pkgs.netatalk}/sbin/netatalk -F ${afpConfFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP  $MAINPID";
+	ExecStop   = "${pkgs.coreutils}/bin/kill -TERM $MAINPID";
+        Restart = "always";
+        RestartSec = 1;
+      };
+
+    };
+
+    security.pam.services.netatalk.unixAuth = true;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/nfsd.nix b/nixpkgs/nixos/modules/services/network-filesystems/nfsd.nix
new file mode 100644
index 000000000000..1b62bfa82035
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/nfsd.nix
@@ -0,0 +1,175 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.nfs.server;
+
+  exports = pkgs.writeText "exports" cfg.exports;
+
+in
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "nfs" "lockdPort" ] [ "services" "nfs" "server" "lockdPort" ])
+    (mkRenamedOptionModule [ "services" "nfs" "statdPort" ] [ "services" "nfs" "server" "statdPort" ])
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.nfs = {
+
+      server = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to enable the kernel's NFS server.
+          '';
+        };
+
+        extraNfsdConfig = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            Extra configuration options for the [nfsd] section of /etc/nfs.conf.
+          '';
+        };
+
+        exports = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Contents of the /etc/exports file.  See
+            <citerefentry><refentrytitle>exports</refentrytitle>
+            <manvolnum>5</manvolnum></citerefentry> for the format.
+          '';
+        };
+
+        hostName = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Hostname or address on which NFS requests will be accepted.
+            Default is all.  See the <option>-H</option> option in
+            <citerefentry><refentrytitle>nfsd</refentrytitle>
+            <manvolnum>8</manvolnum></citerefentry>.
+          '';
+        };
+
+        nproc = mkOption {
+          type = types.int;
+          default = 8;
+          description = ''
+            Number of NFS server threads.  Defaults to the recommended value of 8.
+          '';
+        };
+
+        createMountPoints = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Whether to create the mount points in the exports file at startup time.";
+        };
+
+        mountdPort = mkOption {
+          type = types.nullOr types.int;
+          default = null;
+          example = 4002;
+          description = ''
+            Use fixed port for rpc.mountd, useful if server is behind firewall.
+          '';
+        };
+
+        lockdPort = mkOption {
+          type = types.nullOr types.int;
+          default = null;
+          example = 4001;
+          description = ''
+            Use a fixed port for the NFS lock manager kernel module
+            (<literal>lockd/nlockmgr</literal>).  This is useful if the
+            NFS server is behind a firewall.
+          '';
+        };
+
+        statdPort = mkOption {
+          type = types.nullOr types.int;
+          default = null;
+          example = 4000;
+          description = ''
+            Use a fixed port for <command>rpc.statd</command>. This is
+            useful if the NFS server is behind a firewall.
+          '';
+        };
+
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.nfs.extraConfig = ''
+      [nfsd]
+      threads=${toString cfg.nproc}
+      ${optionalString (cfg.hostName != null) "host=${cfg.hostName}"}
+      ${cfg.extraNfsdConfig}
+
+      [mountd]
+      ${optionalString (cfg.mountdPort != null) "port=${toString cfg.mountdPort}"}
+
+      [statd]
+      ${optionalString (cfg.statdPort != null) "port=${toString cfg.statdPort}"}
+
+      [lockd]
+      ${optionalString (cfg.lockdPort != null) ''
+        port=${toString cfg.lockdPort}
+        udp-port=${toString cfg.lockdPort}
+      ''}
+    '';
+
+    services.rpcbind.enable = true;
+
+    boot.supportedFilesystems = [ "nfs" ]; # needed for statd and idmapd
+
+    environment.etc.exports.source = exports;
+
+    systemd.services.nfs-server =
+      { enable = true;
+        wantedBy = [ "multi-user.target" ];
+
+        preStart =
+          ''
+            mkdir -p /var/lib/nfs/v4recovery
+          '';
+      };
+
+    systemd.services.nfs-mountd =
+      { enable = true;
+        restartTriggers = [ exports ];
+
+        preStart =
+          ''
+            mkdir -p /var/lib/nfs
+
+            ${optionalString cfg.createMountPoints
+              ''
+                # create export directories:
+                # skip comments, take first col which may either be a quoted
+                # "foo bar" or just foo (-> man export)
+                sed '/^#.*/d;s/^"\([^"]*\)".*/\1/;t;s/[ ].*//' ${exports} \
+                | xargs -d '\n' mkdir -p
+              ''
+            }
+          '';
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/openafs/client.nix b/nixpkgs/nixos/modules/services/network-filesystems/openafs/client.nix
new file mode 100644
index 000000000000..677111814a01
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/openafs/client.nix
@@ -0,0 +1,253 @@
+{ config, lib, pkgs, ... }:
+
+# openafsMod, openafsBin, mkCellServDB
+with import ./lib.nix { inherit config lib pkgs; };
+
+let
+  inherit (lib) getBin mkOption mkIf optionalString singleton types;
+
+  cfg = config.services.openafsClient;
+
+  cellServDB = pkgs.fetchurl {
+    url = "http://dl.central.org/dl/cellservdb/CellServDB.2018-05-14";
+    sha256 = "1wmjn6mmyy2r8p10nlbdzs4nrqxy8a9pjyrdciy5nmppg4053rk2";
+  };
+
+  clientServDB = pkgs.writeText "client-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.cellServDB);
+
+  afsConfig = pkgs.runCommand "afsconfig" { preferLocalBuild = true; } ''
+    mkdir -p $out
+    echo ${cfg.cellName} > $out/ThisCell
+    cat ${cellServDB} ${clientServDB} > $out/CellServDB
+    echo "${cfg.mountPoint}:${cfg.cache.directory}:${toString cfg.cache.blocks}" > $out/cacheinfo
+  '';
+
+in
+{
+  ###### interface
+
+  options = {
+
+    services.openafsClient = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to enable the OpenAFS client.";
+      };
+
+      afsdb = mkOption {
+        default = true;
+        type = types.bool;
+        description = "Resolve cells via AFSDB DNS records.";
+      };
+
+      cellName = mkOption {
+        default = "";
+        type = types.str;
+        description = "Cell name.";
+        example = "grand.central.org";
+      };
+
+      cellServDB = mkOption {
+        default = [];
+        type = with types; listOf (submodule { options = cellServDBConfig; });
+        description = ''
+          This cell's database server records, added to the global
+          CellServDB. See CellServDB(5) man page for syntax. Ignored when
+          <literal>afsdb</literal> is set to <literal>true</literal>.
+        '';
+        example = ''
+          [ { ip = "1.2.3.4"; dnsname = "first.afsdb.server.dns.fqdn.org"; }
+            { ip = "2.3.4.5"; dnsname = "second.afsdb.server.dns.fqdn.org"; }
+          ]
+        '';
+      };
+
+      cache = {
+        blocks = mkOption {
+          default = 100000;
+          type = types.int;
+          description = "Cache size in 1KB blocks.";
+        };
+
+        chunksize = mkOption {
+          default = 0;
+          type = types.ints.between 0 30;
+          description = ''
+            Size of each cache chunk given in powers of
+            2. <literal>0</literal> resets the chunk size to its default
+            values (13 (8 KB) for memcache, 18-20 (256 KB to 1 MB) for
+            diskcache). Maximum value is 30. Important performance
+            parameter. Set to higher values when dealing with large files.
+          '';
+        };
+
+        directory = mkOption {
+          default = "/var/cache/openafs";
+          type = types.str;
+          description = "Cache directory.";
+        };
+
+        diskless = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            Use in-memory cache for diskless machines. Has no real
+            performance benefit anymore.
+          '';
+        };
+      };
+
+      crypt = mkOption {
+        default = true;
+        type = types.bool;
+        description = "Whether to enable (weak) protocol encryption.";
+      };
+
+      daemons = mkOption {
+        default = 2;
+        type = types.int;
+        description = ''
+          Number of daemons to serve user requests. Numbers higher than 6
+          usually do no increase performance. Default is sufficient for up
+          to five concurrent users.
+        '';
+      };
+
+      fakestat = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Return fake data on stat() calls. If <literal>true</literal>,
+          always do so. If <literal>false</literal>, only do so for
+          cross-cell mounts (as these are potentially expensive).
+        '';
+      };
+
+      inumcalc = mkOption {
+        default = "compat";
+        type = types.strMatching "compat|md5";
+        description = ''
+          Inode calculation method. <literal>compat</literal> is
+          computationally less expensive, but <literal>md5</literal> greatly
+          reduces the likelihood of inode collisions in larger scenarios
+          involving multiple cells mounted into one AFS space.
+        '';
+      };
+
+      mountPoint = mkOption {
+        default = "/afs";
+        type = types.str;
+        description = ''
+          Mountpoint of the AFS file tree, conventionally
+          <literal>/afs</literal>. When set to a different value, only
+          cross-cells that use the same value can be accessed.
+        '';
+      };
+
+      packages = {
+        module = mkOption {
+          default = config.boot.kernelPackages.openafs;
+          defaultText = "config.boot.kernelPackages.openafs";
+          type = types.package;
+          description = "OpenAFS kernel module package. MUST match the userland package!";
+        };
+        programs = mkOption {
+          default = getBin pkgs.openafs;
+          defaultText = "getBin pkgs.openafs";
+          type = types.package;
+          description = "OpenAFS programs package. MUST match the kernel module package!";
+        };
+      };
+
+      sparse = mkOption {
+        default = true;
+        type = types.bool;
+        description = "Minimal cell list in /afs.";
+      };
+
+      startDisconnected = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Start up in disconnected mode.  You need to execute
+          <literal>fs disco online</literal> (as root) to switch to
+          connected mode. Useful for roaming devices.
+        '';
+      };
+
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.afsdb || cfg.cellServDB != [];
+        message = "You should specify all cell-local database servers in config.services.openafsClient.cellServDB or set config.services.openafsClient.afsdb.";
+      }
+      { assertion = cfg.cellName != "";
+        message = "You must specify the local cell name in config.services.openafsClient.cellName.";
+      }
+    ];
+
+    environment.systemPackages = [ openafsBin ];
+
+    environment.etc = {
+      clientCellServDB = {
+        source = pkgs.runCommand "CellServDB" { preferLocalBuild = true; } ''
+          cat ${cellServDB} ${clientServDB} > $out
+        '';
+        target = "openafs/CellServDB";
+        mode = "0644";
+      };
+      clientCell = {
+        text = ''
+          ${cfg.cellName}
+        '';
+        target = "openafs/ThisCell";
+        mode = "0644";
+      };
+    };
+
+    systemd.services.afsd = {
+      description = "AFS client";
+      wantedBy = [ "multi-user.target" ];
+      after = singleton (if cfg.startDisconnected then  "network.target" else "network-online.target");
+      serviceConfig = { RemainAfterExit = true; };
+      restartIfChanged = false;
+
+      preStart = ''
+        mkdir -p -m 0755 ${cfg.mountPoint}
+        mkdir -m 0700 -p ${cfg.cache.directory}
+        ${pkgs.kmod}/bin/insmod ${openafsMod}/lib/modules/*/extra/openafs/libafs.ko.xz
+        ${openafsBin}/sbin/afsd \
+          -mountdir ${cfg.mountPoint} \
+          -confdir ${afsConfig} \
+          ${optionalString (!cfg.cache.diskless) "-cachedir ${cfg.cache.directory}"} \
+          -blocks ${toString cfg.cache.blocks} \
+          -chunksize ${toString cfg.cache.chunksize} \
+          ${optionalString cfg.cache.diskless "-memcache"} \
+          -inumcalc ${cfg.inumcalc} \
+          ${if cfg.fakestat then "-fakestat-all" else "-fakestat"} \
+          ${if cfg.sparse then "-dynroot-sparse" else "-dynroot"} \
+          ${optionalString cfg.afsdb "-afsdb"}
+        ${openafsBin}/bin/fs setcrypt ${if cfg.crypt then "on" else "off"}
+        ${optionalString cfg.startDisconnected "${openafsBin}/bin/fs discon offline"}
+      '';
+
+      # Doing this in preStop, because after these commands AFS is basically
+      # stopped, so systemd has nothing to do, just noticing it.  If done in
+      # postStop, then we get a hang + kernel oops, because AFS can't be
+      # stopped simply by sending signals to processes.
+      preStop = ''
+        ${pkgs.utillinux}/bin/umount ${cfg.mountPoint}
+        ${openafsBin}/sbin/afsd -shutdown
+        ${pkgs.kmod}/sbin/rmmod libafs
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix b/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix
new file mode 100644
index 000000000000..e068ee761c2a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/openafs/lib.nix
@@ -0,0 +1,33 @@
+{ config, lib, ...}:
+
+let
+  inherit (lib) concatStringsSep mkOption types;
+
+in {
+
+  mkCellServDB = cellName: db: ''
+    >${cellName}
+  '' + (concatStringsSep "\n" (map (dbm: if (dbm.ip != "" && dbm.dnsname != "") then dbm.ip + " #" + dbm.dnsname else "")
+                                   db))
+     + "\n";
+
+  # CellServDB configuration type
+  cellServDBConfig = {
+    ip = mkOption {
+      type = types.str;
+      default = "";
+      example = "1.2.3.4";
+      description = "IP Address of a database server";
+    };
+    dnsname = mkOption {
+      type = types.str;
+      default = "";
+      example = "afs.example.org";
+      description = "DNS full-qualified domain name of a database server";
+    };
+  };
+
+  openafsMod = config.services.openafsClient.packages.module;
+  openafsBin = config.services.openafsClient.packages.programs;
+  openafsSrv = config.services.openafsServer.package;
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix b/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
new file mode 100644
index 000000000000..095024d2c8af
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -0,0 +1,269 @@
+{ config, lib, pkgs, ... }:
+
+# openafsBin, openafsSrv, mkCellServDB
+with import ./lib.nix { inherit config lib pkgs; };
+
+let
+  inherit (lib) concatStringsSep mkIf mkOption optionalString types;
+
+  bosConfig = pkgs.writeText "BosConfig" (''
+    restrictmode 1
+    restarttime 16 0 0 0 0
+    checkbintime 3 0 5 0 0
+  '' + (optionalString cfg.roles.database.enable ''
+    bnode simple vlserver 1
+    parm ${openafsSrv}/libexec/openafs/vlserver ${optionalString cfg.dottedPrincipals "-allow-dotted-principals"} ${cfg.roles.database.vlserverArgs}
+    end
+    bnode simple ptserver 1
+    parm ${openafsSrv}/libexec/openafs/ptserver ${optionalString cfg.dottedPrincipals "-allow-dotted-principals"} ${cfg.roles.database.ptserverArgs}
+    end
+  '') + (optionalString cfg.roles.fileserver.enable ''
+    bnode dafs dafs 1
+    parm ${openafsSrv}/libexec/openafs/dafileserver ${optionalString cfg.dottedPrincipals "-allow-dotted-principals"} -udpsize ${udpSizeStr} ${cfg.roles.fileserver.fileserverArgs}
+    parm ${openafsSrv}/libexec/openafs/davolserver ${optionalString cfg.dottedPrincipals "-allow-dotted-principals"} -udpsize ${udpSizeStr} ${cfg.roles.fileserver.volserverArgs}
+    parm ${openafsSrv}/libexec/openafs/salvageserver ${cfg.roles.fileserver.salvageserverArgs}
+    parm ${openafsSrv}/libexec/openafs/dasalvager ${cfg.roles.fileserver.salvagerArgs}
+    end
+  '') + (optionalString (cfg.roles.database.enable && cfg.roles.backup.enable) ''
+    bnode simple buserver 1
+    parm ${openafsSrv}/libexec/openafs/buserver ${cfg.roles.backup.buserverArgs} ${optionalString (cfg.roles.backup.cellServDB != []) "-cellservdb /etc/openafs/backup/"}
+    end
+  ''));
+
+  netInfo = if (cfg.advertisedAddresses != []) then
+    pkgs.writeText "NetInfo" ((concatStringsSep "\nf " cfg.advertisedAddresses) + "\n")
+  else null;
+
+  buCellServDB = pkgs.writeText "backup-cellServDB-${cfg.cellName}" (mkCellServDB cfg.cellName cfg.roles.backup.cellServDB);
+
+  cfg = config.services.openafsServer;
+
+  udpSizeStr = toString cfg.udpPacketSize;
+
+in {
+
+  options = {
+
+    services.openafsServer = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to enable the OpenAFS server. An OpenAFS server needs a
+          complex setup. So, be aware that enabling this service and setting
+          some options does not give you a turn-key-ready solution. You need
+          at least a running Kerberos 5 setup, as OpenAFS relies on it for
+          authentication. See the Guide "QuickStartUnix" coming with
+          <literal>pkgs.openafs.doc</literal> for complete setup
+          instructions.
+        '';
+      };
+
+      advertisedAddresses = mkOption {
+        default = [];
+        description = "List of IP addresses this server is advertised under. See NetInfo(5)";
+      };
+
+      cellName = mkOption {
+        default = "";
+        type = types.str;
+        description = "Cell name, this server will serve.";
+        example = "grand.central.org";
+      };
+
+      cellServDB = mkOption {
+        default = [];
+        type = with types; listOf (submodule [ { options = cellServDBConfig;} ]);
+        description = "Definition of all cell-local database server machines.";
+      };
+
+      package = mkOption {
+        default = pkgs.openafs.server or pkgs.openafs;
+        defaultText = "pkgs.openafs.server or pkgs.openafs";
+        type = types.package;
+        description = "OpenAFS package for the server binaries";
+      };
+
+      roles = {
+        fileserver = {
+          enable = mkOption {
+            default = true;
+            type = types.bool;
+            description = "Fileserver role, serves files and volumes from its local storage.";
+          };
+
+          fileserverArgs = mkOption {
+            default = "-vattachpar 128 -vhashsize 11 -L -rxpck 400 -cb 1000000";
+            type = types.str;
+            description = "Arguments to the dafileserver process. See its man page.";
+          };
+
+          volserverArgs = mkOption {
+            default = "";
+            type = types.str;
+            description = "Arguments to the davolserver process. See its man page.";
+            example = "-sync never";
+          };
+
+          salvageserverArgs = mkOption {
+            default = "";
+            type = types.str;
+            description = "Arguments to the salvageserver process. See its man page.";
+            example = "-showlog";
+          };
+
+          salvagerArgs = mkOption {
+            default = "";
+            type = types.str;
+            description = "Arguments to the dasalvager process. See its man page.";
+            example = "-showlog -showmounts";
+          };
+        };
+
+        database = {
+          enable = mkOption {
+            default = true;
+            type = types.bool;
+            description = ''
+              Database server role, maintains the Volume Location Database,
+              Protection Database (and Backup Database, see
+              <literal>backup</literal> role). There can be multiple
+              servers in the database role for replication, which then need
+              reliable network connection to each other.
+
+              Servers in this role appear in AFSDB DNS records or the
+              CellServDB.
+            '';
+          };
+
+          vlserverArgs = mkOption {
+            default = "";
+            type = types.str;
+            description = "Arguments to the vlserver process. See its man page.";
+            example = "-rxbind";
+          };
+
+          ptserverArgs = mkOption {
+            default = "";
+            type = types.str;
+            description = "Arguments to the ptserver process. See its man page.";
+            example = "-restricted -default_access S---- S-M---";
+          };
+        };
+
+        backup = {
+          enable = mkOption {
+            default = false;
+            type = types.bool;
+            description = ''
+              Backup server role. Use in conjunction with the
+              <literal>database</literal> role to maintain the Backup
+              Database. Normally only used in conjunction with tape storage
+              or IBM's Tivoli Storage Manager.
+            '';
+          };
+
+          buserverArgs = mkOption {
+            default = "";
+            type = types.str;
+            description = "Arguments to the buserver process. See its man page.";
+            example = "-p 8";
+          };
+
+          cellServDB = mkOption {
+            default = [];
+            type = with types; listOf (submodule [ { options = cellServDBConfig;} ]);
+            description = ''
+              Definition of all cell-local backup database server machines.
+              Use this when your cell uses less backup database servers than
+              other database server machines.
+            '';
+          };
+        };
+      };
+
+      dottedPrincipals= mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If enabled, allow principal names containing (.) dots. Enabling
+          this has security implications!
+        '';
+      };
+
+      udpPacketSize = mkOption {
+        default = 1310720;
+        type = types.int;
+        description = ''
+          UDP packet size to use in Bytes. Higher values can speed up
+          communications. The default of 1 MB is a sufficient in most
+          cases. Make sure to increase the kernel's UDP buffer size
+          accordingly via <literal>net.core(w|r|opt)mem_max</literal>
+          sysctl.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.cellServDB != [];
+        message = "You must specify all cell-local database servers in config.services.openafsServer.cellServDB.";
+      }
+      { assertion = cfg.cellName != "";
+        message = "You must specify the local cell name in config.services.openafsServer.cellName.";
+      }
+    ];
+
+    environment.systemPackages = [ openafsBin ];
+
+    environment.etc = {
+      bosConfig = {
+        source = bosConfig;
+        target = "openafs/BosConfig";
+        mode = "0644";
+      };
+      cellServDB = {
+        text = mkCellServDB cfg.cellName cfg.cellServDB;
+        target = "openafs/server/CellServDB";
+        mode = "0644";
+      };
+      thisCell = {
+        text = cfg.cellName;
+        target = "openafs/server/ThisCell";
+        mode = "0644";
+      };
+      buCellServDB = {
+        enable = (cfg.roles.backup.cellServDB != []);
+        text = mkCellServDB cfg.cellName cfg.roles.backup.cellServDB;
+        target = "openafs/backup/CellServDB";
+      };
+    };
+
+    systemd.services = {
+      openafs-server = {
+        description = "OpenAFS server";
+        after = [ "syslog.target" "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        restartIfChanged = false;
+        unitConfig.ConditionPathExists = [
+          "|/etc/openafs/server/rxkad.keytab"
+          "|/etc/openafs/server/KeyFileExt"
+        ];
+        preStart = ''
+          mkdir -m 0755 -p /var/openafs
+          ${optionalString (netInfo != null) "cp ${netInfo} /var/openafs/netInfo"}
+          ${optionalString (cfg.roles.backup.cellServDB != []) "cp ${buCellServDB}"}
+        '';
+        serviceConfig = {
+          ExecStart = "${openafsBin}/bin/bosserver -nofork";
+          ExecStop = "${openafsBin}/bin/bos shutdown localhost -wait -localauth";
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/orangefs/client.nix b/nixpkgs/nixos/modules/services/network-filesystems/orangefs/client.nix
new file mode 100644
index 000000000000..b69d9e713c3d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/orangefs/client.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ...} :
+
+with lib;
+
+let
+  cfg = config.services.orangefs.client;
+
+in {
+  ###### interface
+
+  options = {
+    services.orangefs.client = {
+      enable = mkEnableOption "OrangeFS client daemon";
+
+      extraOptions = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = "Extra command line options for pvfs2-client.";
+      };
+
+      fileSystems = mkOption {
+        description = ''
+          The orangefs file systems to be mounted.
+          This option is prefered over using <option>fileSystems</option> directly since
+          the pvfs client service needs to be running for it to be mounted.
+        '';
+
+        example = [{
+          mountPoint = "/orangefs";
+          target = "tcp://server:3334/orangefs";
+        }];
+
+        type = with types; listOf (submodule ({ ... } : {
+          options = {
+
+            mountPoint = mkOption {
+              type = types.str;
+              default = "/orangefs";
+              description = "Mount point.";
+            };
+
+            options = mkOption {
+              type = with types; listOf str;
+              default = [];
+              description = "Mount options";
+            };
+
+            target = mkOption {
+              type = types.str;
+              default = null;
+              example = "tcp://server:3334/orangefs";
+              description = "Target URL";
+            };
+          };
+        }));
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.orangefs ];
+
+    boot.supportedFilesystems = [ "pvfs2" ];
+    boot.kernelModules = [ "orangefs" ];
+
+    systemd.services.orangefs-client = {
+      requires = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+
+         ExecStart = ''
+           ${pkgs.orangefs}/bin/pvfs2-client-core \
+              --logtype=syslog ${concatStringsSep " " cfg.extraOptions}
+        '';
+
+        TimeoutStopSec = "120";
+      };
+    };
+
+    systemd.mounts = map (fs: {
+      requires = [ "orangefs-client.service" ];
+      after = [ "orangefs-client.service" ];
+      bindsTo = [ "orangefs-client.service" ];
+      wantedBy = [ "remote-fs.target" ];
+      type = "pvfs2";
+      options = concatStringsSep "," fs.options;
+      what = fs.target;
+      where = fs.mountPoint;
+    }) cfg.fileSystems;
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/orangefs/server.nix b/nixpkgs/nixos/modules/services/network-filesystems/orangefs/server.nix
new file mode 100644
index 000000000000..74ebdc134024
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/orangefs/server.nix
@@ -0,0 +1,225 @@
+{ config, lib, pkgs, ...} :
+
+with lib;
+
+let
+  cfg = config.services.orangefs.server;
+
+  aliases = mapAttrsToList (alias: url: alias) cfg.servers;
+
+  # Maximum handle number is 2^63
+  maxHandle = 9223372036854775806;
+
+  # One range of handles for each meta/data instance
+  handleStep = maxHandle / (length aliases) / 2;
+
+  fileSystems = mapAttrsToList (name: fs: ''
+    <FileSystem>
+      Name ${name}
+      ID ${toString fs.id}
+      RootHandle ${toString fs.rootHandle}
+
+      ${fs.extraConfig}
+
+      <MetaHandleRanges>
+      ${concatStringsSep "\n" (
+          imap0 (i: alias:
+            let
+              begin = i * handleStep + 3;
+              end = begin + handleStep - 1;
+            in "Range ${alias} ${toString begin}-${toString end}") aliases
+       )}
+      </MetaHandleRanges>
+
+      <DataHandleRanges>
+      ${concatStringsSep "\n" (
+          imap0 (i: alias:
+            let
+              begin = i * handleStep + 3 + (length aliases) * handleStep;
+              end = begin + handleStep - 1;
+            in "Range ${alias} ${toString begin}-${toString end}") aliases
+       )}
+      </DataHandleRanges>
+
+      <StorageHints>
+      TroveSyncMeta ${if fs.troveSyncMeta then "yes" else "no"}
+      TroveSyncData ${if fs.troveSyncData then "yes" else "no"}
+      ${fs.extraStorageHints}
+      </StorageHints>
+
+    </FileSystem>
+  '') cfg.fileSystems;
+
+  configFile = ''
+    <Defaults>
+    LogType ${cfg.logType}
+    DataStorageSpace ${cfg.dataStorageSpace}
+    MetaDataStorageSpace ${cfg.metadataStorageSpace}
+
+    BMIModules ${concatStringsSep "," cfg.BMIModules}
+    ${cfg.extraDefaults}
+    </Defaults>
+
+    ${cfg.extraConfig}
+
+    <Aliases>
+    ${concatStringsSep "\n" (mapAttrsToList (alias: url: "Alias ${alias} ${url}") cfg.servers)}
+    </Aliases>
+
+    ${concatStringsSep "\n" fileSystems}
+  '';
+
+in {
+  ###### interface
+
+  options = {
+    services.orangefs.server = {
+      enable = mkEnableOption "OrangeFS server";
+
+      logType = mkOption {
+        type = with types; enum [ "file" "syslog" ];
+        default = "syslog";
+        description = "Destination for log messages.";
+      };
+
+      dataStorageSpace = mkOption {
+        type = types.str;
+        default = null;
+        example = "/data/storage";
+        description = "Directory for data storage.";
+      };
+
+      metadataStorageSpace = mkOption {
+        type = types.str;
+        default = null;
+        example = "/data/meta";
+        description = "Directory for meta data storage.";
+      };
+
+      BMIModules = mkOption {
+        type = with types; listOf str;
+        default = [ "bmi_tcp" ];
+        example = [ "bmi_tcp" "bmi_ib"];
+        description = "List of BMI modules to load.";
+      };
+
+      extraDefaults = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra config for <literal>&lt;Defaults&gt;</literal> section.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra config for the global section.";
+      };
+
+      servers = mkOption {
+        type = with types; attrsOf types.str;
+        default = {};
+        example = ''
+          {
+            node1="tcp://node1:3334";
+            node2="tcp://node2:3334";
+          }
+        '';
+        description = "URLs for storage server including port. The attribute names define the server alias.";
+      };
+
+      fileSystems = mkOption {
+        description = ''
+          These options will create the <literal>&lt;FileSystem&gt;</literal> sections of config file.
+        '';
+        default = { orangefs = {}; };
+        defaultText = literalExample "{ orangefs = {}; }";
+        example = literalExample ''
+          {
+            fs1 = {
+              id = 101;
+            };
+
+            fs2 = {
+              id = 102;
+            };
+          }
+        '';
+        type = with types; attrsOf (submodule ({ ... } : {
+          options = {
+            id = mkOption {
+              type = types.int;
+              default = 1;
+              description = "File system ID (must be unique within configuration).";
+            };
+
+            rootHandle = mkOption {
+              type = types.int;
+              default = 3;
+              description = "File system root ID.";
+            };
+
+            extraConfig = mkOption {
+              type = types.lines;
+              default = "";
+              description = "Extra config for <literal>&lt;FileSystem&gt;</literal> section.";
+            };
+
+            troveSyncMeta = mkOption {
+              type = types.bool;
+              default = true;
+              description = "Sync meta data.";
+            };
+
+            troveSyncData = mkOption {
+              type = types.bool;
+              default = false;
+              description = "Sync data.";
+            };
+
+            extraStorageHints = mkOption {
+              type = types.lines;
+              default = "";
+              description = "Extra config for <literal>&lt;StorageHints&gt;</literal> section.";
+            };
+          };
+        }));
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.orangefs ];
+
+    # orangefs daemon will run as user
+    users.users.orangefs.isSystemUser = true;
+    users.groups.orangefs = {};
+
+    # To format the file system the config file is needed.
+    environment.etc."orangefs/server.conf" = {
+      text = configFile;
+      user = "orangefs";
+      group = "orangefs";
+    };
+
+    systemd.services.orangefs-server = {
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+
+      serviceConfig = {
+        # Run as "simple" in forground mode.
+        # This is more reliable
+        ExecStart = ''
+          ${pkgs.orangefs}/bin/pvfs2-server -d \
+            /etc/orangefs/server.conf
+        '';
+        TimeoutStopSec = "120";
+        User = "orangefs";
+        Group = "orangefs";
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix b/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix
new file mode 100644
index 000000000000..fa29e18a9395
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/rsyncd.nix
@@ -0,0 +1,122 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.rsyncd;
+
+  motdFile = builtins.toFile "rsyncd-motd" cfg.motd;
+
+  foreach = attrs: f:
+    concatStringsSep "\n" (mapAttrsToList f attrs);
+
+  cfgFile = ''
+    ${optionalString (cfg.motd != "") "motd file = ${motdFile}"}
+    ${optionalString (cfg.address != "") "address = ${cfg.address}"}
+    ${optionalString (cfg.port != 873) "port = ${toString cfg.port}"}
+    ${cfg.extraConfig}
+    ${foreach cfg.modules (name: module: ''
+      [${name}]
+      ${foreach module (k: v:
+        "${k} = ${v}"
+      )}
+    '')}
+  '';
+in
+
+{
+  options = {
+    services.rsyncd = {
+
+      enable = mkEnableOption "the rsync daemon";
+
+      motd = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Message of the day to display to clients on each connect.
+          This usually contains site information and any legal notices.
+        '';
+      };
+
+      port = mkOption {
+        default = 873;
+        type = types.int;
+        description = "TCP port the daemon will listen on.";
+      };
+
+      address = mkOption {
+        default = "";
+        example = "192.168.1.2";
+        description = ''
+          IP address the daemon will listen on; rsyncd will listen on
+          all addresses if this is not specified.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+            Lines of configuration to add to rsyncd globally.
+            See <command>man rsyncd.conf</command> for options.
+          '';
+      };
+
+      modules = mkOption {
+        default = {};
+        description = ''
+            A set describing exported directories.
+            See <command>man rsyncd.conf</command> for options.
+          '';
+        type = types.attrsOf (types.attrsOf types.str);
+        example = literalExample ''
+          { srv =
+             { path = "/srv";
+               "read only" = "yes";
+               comment = "Public rsync share.";
+             };
+          }
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "root";
+        description = ''
+          The user to run the daemon as.
+          By default the daemon runs as root.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "root";
+        description = ''
+          The group to run the daemon as.
+          By default the daemon runs as root.
+        '';
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.etc."rsyncd.conf".text = cfgFile;
+
+    systemd.services.rsyncd = {
+      description = "Rsync daemon";
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ config.environment.etc."rsyncd.conf".source ];
+      serviceConfig = {
+        ExecStart = "${pkgs.rsync}/bin/rsync --daemon --no-detach";
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/samba.nix b/nixpkgs/nixos/modules/services/network-filesystems/samba.nix
new file mode 100644
index 000000000000..08c912e0fcd4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/samba.nix
@@ -0,0 +1,255 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  smbToString = x: if builtins.typeOf x == "bool"
+                   then boolToString x
+                   else toString x;
+
+  cfg = config.services.samba;
+
+  samba = cfg.package;
+
+  shareConfig = name:
+    let share = getAttr name cfg.shares; in
+    "[${name}]\n " + (smbToString (
+       map
+         (key: "${key} = ${smbToString (getAttr key share)}\n")
+         (attrNames share)
+    ));
+
+  configFile = pkgs.writeText "smb.conf"
+    (if cfg.configText != null then cfg.configText else
+    ''
+      [global]
+      security = ${cfg.securityType}
+      passwd program = /run/wrappers/bin/passwd %u
+      pam password change = ${smbToString cfg.syncPasswordsByPam}
+      invalid users = ${smbToString cfg.invalidUsers}
+
+      ${cfg.extraConfig}
+
+      ${smbToString (map shareConfig (attrNames cfg.shares))}
+    '');
+
+  # This may include nss_ldap, needed for samba if it has to use ldap.
+  nssModulesPath = config.system.nssModules.path;
+
+  daemonService = appName: args:
+    { description = "Samba Service Daemon ${appName}";
+
+      after = [ (mkIf (cfg.enableNmbd && "${appName}" == "smbd") "samba-nmbd.service") ];
+      requiredBy = [ "samba.target" ];
+      partOf = [ "samba.target" ];
+
+      environment = {
+        LD_LIBRARY_PATH = nssModulesPath;
+        LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
+      };
+
+      serviceConfig = {
+        ExecStart = "${samba}/sbin/${appName} --foreground --no-process-group ${args}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LimitNOFILE = 16384;
+        PIDFile = "/run/${appName}.pid";
+        Type = "notify";
+        NotifyAccess = "all"; #may not do anything...
+      };
+      unitConfig.RequiresMountsFor = "/var/lib/samba";
+
+      restartTriggers = [ configFile ];
+    };
+
+in
+
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "samba" "defaultShare" ] "")
+  ];
+
+  ###### interface
+
+  options = {
+
+    # !!! clean up the descriptions.
+
+    services.samba = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Samba, which provides file and print
+          services to Windows clients through the SMB/CIFS protocol.
+
+          <note>
+            <para>If you use the firewall consider adding the following:</para>
+          <programlisting>
+            networking.firewall.allowedTCPPorts = [ 139 445 ];
+            networking.firewall.allowedUDPPorts = [ 137 138 ];
+          </programlisting>
+          </note>
+        '';
+      };
+
+      enableNmbd = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable Samba's nmbd, which replies to NetBIOS over IP name
+          service requests. It also participates in the browsing protocols
+          which make up the Windows "Network Neighborhood" view.
+        '';
+      };
+
+      enableWinbindd = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable Samba's winbindd, which provides a number of services
+          to the Name Service Switch capability found in most modern C libraries,
+          to arbitrary applications via PAM and ntlm_auth and to Samba itself.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.samba;
+        defaultText = "pkgs.samba";
+        example = literalExample "pkgs.samba4Full";
+        description = ''
+          Defines which package should be used for the samba server.
+        '';
+      };
+
+      syncPasswordsByPam = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enabling this will add a line directly after pam_unix.so.
+          Whenever a password is changed the samba password will be updated as well.
+          However, you still have to add the samba password once, using smbpasswd -a user.
+          If you don't want to maintain an extra password database, you still can send plain text
+          passwords which is not secure.
+        '';
+      };
+
+      invalidUsers = mkOption {
+        type = types.listOf types.str;
+        default = [ "root" ];
+        description = ''
+          List of users who are denied to login via Samba.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional global section and extra section lines go in here.
+        '';
+        example = ''
+          guest account = nobody
+          map to guest = bad user
+        '';
+      };
+
+      configText = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = ''
+          Verbatim contents of smb.conf. If null (default), use the
+          autogenerated file from NixOS instead.
+        '';
+      };
+
+      securityType = mkOption {
+        type = types.str;
+        default = "user";
+        example = "share";
+        description = "Samba security type";
+      };
+
+      nsswins = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to enable the WINS NSS (Name Service Switch) plug-in.
+          Enabling it allows applications to resolve WINS/NetBIOS names (a.k.a.
+          Windows machine names) by transparently querying the winbindd daemon.
+        '';
+      };
+
+      shares = mkOption {
+        default = {};
+        description = ''
+          A set describing shared resources.
+          See <command>man smb.conf</command> for options.
+        '';
+        type = types.attrsOf (types.attrsOf types.unspecified);
+        example = literalExample ''
+          { public =
+            { path = "/srv/public";
+              "read only" = true;
+              browseable = "yes";
+              "guest ok" = "yes";
+              comment = "Public samba share.";
+            };
+          }
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkMerge
+    [ { assertions =
+          [ { assertion = cfg.nsswins -> cfg.enableWinbindd;
+              message   = "If samba.nsswins is enabled, then samba.enableWinbindd must also be enabled";
+            }
+          ];
+        # Always provide a smb.conf to shut up programs like smbclient and smbspool.
+        environment.etc."samba/smb.conf".source = mkOptionDefault (
+          if cfg.enable then configFile
+          else pkgs.writeText "smb-dummy.conf" "# Samba is disabled."
+        );
+      }
+
+      (mkIf cfg.enable {
+
+        system.nssModules = optional cfg.nsswins samba;
+        system.nssDatabases.hosts = optional cfg.nsswins "wins";
+
+        systemd = {
+          targets.samba = {
+            description = "Samba Server";
+            after = [ "network.target" ];
+            wantedBy = [ "multi-user.target" ];
+          };
+          # Refer to https://github.com/samba-team/samba/tree/master/packaging/systemd
+          # for correct use with systemd
+          services = {
+            samba-smbd = daemonService "smbd" "";
+            samba-nmbd = mkIf cfg.enableNmbd (daemonService "nmbd" "");
+            samba-winbindd = mkIf cfg.enableWinbindd (daemonService "winbindd" "");
+          };
+          tmpfiles.rules = [
+            "d /var/lock/samba - - - - -"
+            "d /var/log/samba - - - - -"
+            "d /var/cache/samba - - - - -"
+            "d /var/lib/samba/private - - - - -"
+          ];
+        };
+
+        security.pam.services.samba = {};
+
+      })
+    ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix b/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix
new file mode 100644
index 000000000000..7d75eb286106
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/tahoe.nix
@@ -0,0 +1,368 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.tahoe;
+in
+  {
+    options.services.tahoe = {
+      introducers = mkOption {
+        default = {};
+        type = with types; attrsOf (submodule {
+          options = {
+            nickname = mkOption {
+              type = types.str;
+              description = ''
+                The nickname of this Tahoe introducer.
+              '';
+            };
+            tub.port = mkOption {
+              default = 3458;
+              type = types.int;
+              description = ''
+                The port on which the introducer will listen.
+              '';
+            };
+            tub.location = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                The external location that the introducer should listen on.
+
+                If specified, the port should be included.
+              '';
+            };
+            package = mkOption {
+              default = pkgs.tahoelafs;
+              defaultText = "pkgs.tahoelafs";
+              type = types.package;
+              example = literalExample "pkgs.tahoelafs";
+              description = ''
+                The package to use for the Tahoe LAFS daemon.
+              '';
+            };
+          };
+        });
+        description = ''
+          The Tahoe introducers.
+        '';
+      };
+      nodes = mkOption {
+        default = {};
+        type = with types; attrsOf (submodule {
+          options = {
+            nickname = mkOption {
+              type = types.str;
+              description = ''
+                The nickname of this Tahoe node.
+              '';
+            };
+            tub.port = mkOption {
+              default = 3457;
+              type = types.int;
+              description = ''
+                The port on which the tub will listen.
+
+                This is the correct setting to tweak if you want Tahoe's storage
+                system to listen on a different port.
+              '';
+            };
+            tub.location = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                The external location that the node should listen on.
+
+                This is the setting to tweak if there are multiple interfaces
+                and you want to alter which interface Tahoe is advertising.
+
+                If specified, the port should be included.
+              '';
+            };
+            web.port = mkOption {
+              default = 3456;
+              type = types.int;
+              description = ''
+                The port on which the Web server will listen.
+
+                This is the correct setting to tweak if you want Tahoe's WUI to
+                listen on a different port.
+              '';
+            };
+            client.introducer = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                The furl for a Tahoe introducer node.
+
+                Like all furls, keep this safe and don't share it.
+              '';
+            };
+            client.helper = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                The furl for a Tahoe helper node.
+
+                Like all furls, keep this safe and don't share it.
+              '';
+            };
+            client.shares.needed = mkOption {
+              default = 3;
+              type = types.int;
+              description = ''
+                The number of shares required to reconstitute a file.
+              '';
+            };
+            client.shares.happy = mkOption {
+              default = 7;
+              type = types.int;
+              description = ''
+                The number of distinct storage nodes required to store
+                a file.
+              '';
+            };
+            client.shares.total = mkOption {
+              default = 10;
+              type = types.int;
+              description = ''
+                The number of shares required to store a file.
+              '';
+            };
+            storage.enable = mkEnableOption "storage service";
+            storage.reservedSpace = mkOption {
+              default = "1G";
+              type = types.str;
+              description = ''
+                The amount of filesystem space to not use for storage.
+              '';
+            };
+            helper.enable = mkEnableOption "helper service";
+            sftpd.enable = mkEnableOption "SFTP service";
+            sftpd.port = mkOption {
+              default = null;
+              type = types.nullOr types.int;
+              description = ''
+                The port on which the SFTP server will listen.
+
+                This is the correct setting to tweak if you want Tahoe's SFTP
+                daemon to listen on a different port.
+              '';
+            };
+            sftpd.hostPublicKeyFile = mkOption {
+              default = null;
+              type = types.nullOr types.path;
+              description = ''
+                Path to the SSH host public key.
+              '';
+            };
+            sftpd.hostPrivateKeyFile = mkOption {
+              default = null;
+              type = types.nullOr types.path;
+              description = ''
+                Path to the SSH host private key.
+              '';
+            };
+            sftpd.accounts.file = mkOption {
+              default = null;
+              type = types.nullOr types.path;
+              description = ''
+                Path to the accounts file.
+              '';
+            };
+            sftpd.accounts.url = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                URL of the accounts server.
+              '';
+            };
+            package = mkOption {
+              default = pkgs.tahoelafs;
+              defaultText = "pkgs.tahoelafs";
+              type = types.package;
+              example = literalExample "pkgs.tahoelafs";
+              description = ''
+                The package to use for the Tahoe LAFS daemon.
+              '';
+            };
+          };
+        });
+        description = ''
+          The Tahoe nodes.
+        '';
+      };
+    };
+    config = mkMerge [
+      (mkIf (cfg.introducers != {}) {
+        environment = {
+          etc = flip mapAttrs' cfg.introducers (node: settings:
+            nameValuePair "tahoe-lafs/introducer-${node}.cfg" {
+              mode = "0444";
+              text = ''
+                # This configuration is generated by Nix. Edit at your own
+                # peril; here be dragons.
+
+                [node]
+                nickname = ${settings.nickname}
+                tub.port = ${toString settings.tub.port}
+                ${optionalString (settings.tub.location != null)
+                  "tub.location = ${settings.tub.location}"}
+              '';
+            });
+          # Actually require Tahoe, so that we will have it installed.
+          systemPackages = flip mapAttrsToList cfg.introducers (node: settings:
+            settings.package
+          );
+        };
+        # Open up the firewall.
+        # networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.introducers
+        #   (node: settings: settings.tub.port);
+        systemd.services = flip mapAttrs' cfg.introducers (node: settings:
+          let
+            pidfile = "/run/tahoe.introducer-${node}.pid";
+            # This is a directory, but it has no trailing slash. Tahoe commands
+            # get antsy when there's a trailing slash.
+            nodedir = "/var/db/tahoe-lafs/introducer-${node}";
+          in nameValuePair "tahoe.introducer-${node}" {
+            description = "Tahoe LAFS node ${node}";
+            wantedBy = [ "multi-user.target" ];
+            path = [ settings.package ];
+            restartTriggers = [
+              config.environment.etc."tahoe-lafs/introducer-${node}.cfg".source ];
+            serviceConfig = {
+              Type = "simple";
+              PIDFile = pidfile;
+              # Believe it or not, Tahoe is very brittle about the order of
+              # arguments to $(tahoe run). The node directory must come first,
+              # and arguments which alter Twisted's behavior come afterwards.
+              ExecStart = ''
+                ${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
+              '';
+            };
+            preStart = ''
+              if [ ! -d ${lib.escapeShellArg nodedir} ]; then
+                mkdir -p /var/db/tahoe-lafs
+                # See https://github.com/NixOS/nixpkgs/issues/25273
+                tahoe create-introducer \
+                  --hostname="${config.networking.hostName}" \
+                  ${lib.escapeShellArg nodedir}
+              fi
+
+              # Tahoe has created a predefined tahoe.cfg which we must now
+              # scribble over.
+              # XXX I thought that a symlink would work here, but it doesn't, so
+              # we must do this on every prestart. Fixes welcome.
+              # rm ${nodedir}/tahoe.cfg
+              # ln -s /etc/tahoe-lafs/introducer-${node}.cfg ${nodedir}/tahoe.cfg
+              cp /etc/tahoe-lafs/introducer-"${node}".cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
+            '';
+          });
+        users.users = flip mapAttrs' cfg.introducers (node: _:
+          nameValuePair "tahoe.introducer-${node}" {
+            description = "Tahoe node user for introducer ${node}";
+            isSystemUser = true;
+          });
+      })
+      (mkIf (cfg.nodes != {}) {
+        environment = {
+          etc = flip mapAttrs' cfg.nodes (node: settings:
+            nameValuePair "tahoe-lafs/${node}.cfg" {
+              mode = "0444";
+              text = ''
+                # This configuration is generated by Nix. Edit at your own
+                # peril; here be dragons.
+
+                [node]
+                nickname = ${settings.nickname}
+                tub.port = ${toString settings.tub.port}
+                ${optionalString (settings.tub.location != null)
+                  "tub.location = ${settings.tub.location}"}
+                # This is a Twisted endpoint. Twisted Web doesn't work on
+                # non-TCP. ~ C.
+                web.port = tcp:${toString settings.web.port}
+
+                [client]
+                ${optionalString (settings.client.introducer != null)
+                  "introducer.furl = ${settings.client.introducer}"}
+                ${optionalString (settings.client.helper != null)
+                  "helper.furl = ${settings.client.helper}"}
+
+                shares.needed = ${toString settings.client.shares.needed}
+                shares.happy = ${toString settings.client.shares.happy}
+                shares.total = ${toString settings.client.shares.total}
+
+                [storage]
+                enabled = ${boolToString settings.storage.enable}
+                reserved_space = ${settings.storage.reservedSpace}
+
+                [helper]
+                enabled = ${boolToString settings.helper.enable}
+
+                [sftpd]
+                enabled = ${boolToString settings.sftpd.enable}
+                ${optionalString (settings.sftpd.port != null)
+                  "port = ${toString settings.sftpd.port}"}
+                ${optionalString (settings.sftpd.hostPublicKeyFile != null)
+                  "host_pubkey_file = ${settings.sftpd.hostPublicKeyFile}"}
+                ${optionalString (settings.sftpd.hostPrivateKeyFile != null)
+                  "host_privkey_file = ${settings.sftpd.hostPrivateKeyFile}"}
+                ${optionalString (settings.sftpd.accounts.file != null)
+                  "accounts.file = ${settings.sftpd.accounts.file}"}
+                ${optionalString (settings.sftpd.accounts.url != null)
+                  "accounts.url = ${settings.sftpd.accounts.url}"}
+              '';
+            });
+          # Actually require Tahoe, so that we will have it installed.
+          systemPackages = flip mapAttrsToList cfg.nodes (node: settings:
+            settings.package
+          );
+        };
+        # Open up the firewall.
+        # networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.nodes
+        #   (node: settings: settings.tub.port);
+        systemd.services = flip mapAttrs' cfg.nodes (node: settings:
+          let
+            pidfile = "/run/tahoe.${node}.pid";
+            # This is a directory, but it has no trailing slash. Tahoe commands
+            # get antsy when there's a trailing slash.
+            nodedir = "/var/db/tahoe-lafs/${node}";
+          in nameValuePair "tahoe.${node}" {
+            description = "Tahoe LAFS node ${node}";
+            wantedBy = [ "multi-user.target" ];
+            path = [ settings.package ];
+            restartTriggers = [
+              config.environment.etc."tahoe-lafs/${node}.cfg".source ];
+            serviceConfig = {
+              Type = "simple";
+              PIDFile = pidfile;
+              # Believe it or not, Tahoe is very brittle about the order of
+              # arguments to $(tahoe run). The node directory must come first,
+              # and arguments which alter Twisted's behavior come afterwards.
+              ExecStart = ''
+                ${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
+              '';
+            };
+            preStart = ''
+              if [ ! -d ${lib.escapeShellArg nodedir} ]; then
+                mkdir -p /var/db/tahoe-lafs
+                tahoe create-node --hostname=localhost ${lib.escapeShellArg nodedir}
+              fi
+
+              # Tahoe has created a predefined tahoe.cfg which we must now
+              # scribble over.
+              # XXX I thought that a symlink would work here, but it doesn't, so
+              # we must do this on every prestart. Fixes welcome.
+              # rm ${nodedir}/tahoe.cfg
+              # ln -s /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${nodedir}/tahoe.cfg
+              cp /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
+            '';
+          });
+        users.users = flip mapAttrs' cfg.nodes (node: _:
+          nameValuePair "tahoe.${node}" {
+            description = "Tahoe node user for node ${node}";
+            isSystemUser = true;
+          });
+      })
+    ];
+  }
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/u9fs.nix b/nixpkgs/nixos/modules/services/network-filesystems/u9fs.nix
new file mode 100644
index 000000000000..77961b78cadb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/u9fs.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.u9fs;
+in
+{
+
+  options = {
+
+    services.u9fs = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to run the u9fs 9P server for Unix.";
+      };
+
+      listenStreams = mkOption {
+        type = types.listOf types.str;
+        default = [ "564" ];
+        example = [ "192.168.16.1:564" ];
+        description = ''
+          Sockets to listen for clients on.
+          See <command>man 5 systemd.socket</command> for socket syntax.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "nobody";
+        description =
+          "User to run u9fs under.";
+      };
+
+      extraArgs = mkOption {
+        type = types.str;
+        default = "";
+        example = "-a none";
+        description =
+          ''
+            Extra arguments to pass on invocation,
+            see <command>man 4 u9fs</command>
+          '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd = {
+      sockets.u9fs = {
+        description = "U9fs Listening Socket";
+        wantedBy = [ "sockets.target" ];
+        after = [ "network.target" ];
+        inherit (cfg) listenStreams;
+        socketConfig.Accept = "yes";
+      };
+      services."u9fs@" = {
+        description = "9P Protocol Server";
+        reloadIfChanged = true;
+        requires = [ "u9fs.socket" ];
+        serviceConfig =
+          { ExecStart = "-${pkgs.u9fs}/bin/u9fs ${cfg.extraArgs}";
+            StandardInput = "socket";
+            StandardError = "journal";
+            User = cfg.user;
+            AmbientCapabilities = "cap_setuid cap_setgid";
+          };
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix b/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix
new file mode 100644
index 000000000000..b8f8c1d71174
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/xtreemfs.nix
@@ -0,0 +1,480 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xtreemfs;
+
+  xtreemfs = pkgs.xtreemfs;
+
+  home = cfg.homeDir;
+
+  startupScript = class: configPath: pkgs.writeScript "xtreemfs-osd.sh" ''
+    #! ${pkgs.runtimeShell}
+    JAVA_HOME="${pkgs.jdk}"
+    JAVADIR="${xtreemfs}/share/java"
+    JAVA_CALL="$JAVA_HOME/bin/java -ea -cp $JAVADIR/XtreemFS.jar:$JAVADIR/BabuDB.jar:$JAVADIR/Flease.jar:$JAVADIR/protobuf-java-2.5.0.jar:$JAVADIR/Foundation.jar:$JAVADIR/jdmkrt.jar:$JAVADIR/jdmktk.jar:$JAVADIR/commons-codec-1.3.jar"
+    $JAVA_CALL ${class} ${configPath}
+  '';
+
+  dirReplicationConfig = pkgs.writeText "xtreemfs-dir-replication-plugin.properties" ''
+    babudb.repl.backupDir = ${home}/server-repl-dir
+    plugin.jar = ${xtreemfs}/share/java/BabuDB_replication_plugin.jar
+    babudb.repl.dependency.0 = ${xtreemfs}/share/java/Flease.jar
+
+    ${cfg.dir.replication.extraConfig}
+  '';
+
+  dirConfig = pkgs.writeText "xtreemfs-dir-config.properties" ''
+    uuid = ${cfg.dir.uuid}
+    listen.port = ${toString cfg.dir.port}
+    ${optionalString (cfg.dir.address != "") "listen.address = ${cfg.dir.address}"}
+    http_port = ${toString cfg.dir.httpPort}
+    babudb.baseDir = ${home}/dir/database
+    babudb.logDir = ${home}/dir/db-log
+    babudb.sync = ${if cfg.dir.replication.enable then "FDATASYNC" else cfg.dir.syncMode}
+
+    ${optionalString cfg.dir.replication.enable "babudb.plugin.0 = ${dirReplicationConfig}"}
+
+    ${cfg.dir.extraConfig}
+  '';
+
+  mrcReplicationConfig = pkgs.writeText "xtreemfs-mrc-replication-plugin.properties" ''
+    babudb.repl.backupDir = ${home}/server-repl-mrc
+    plugin.jar = ${xtreemfs}/share/java/BabuDB_replication_plugin.jar
+    babudb.repl.dependency.0 = ${xtreemfs}/share/java/Flease.jar
+
+    ${cfg.mrc.replication.extraConfig}
+  '';
+
+  mrcConfig = pkgs.writeText "xtreemfs-mrc-config.properties" ''
+    uuid = ${cfg.mrc.uuid}
+    listen.port = ${toString cfg.mrc.port}
+    ${optionalString (cfg.mrc.address != "") "listen.address = ${cfg.mrc.address}"}
+    http_port = ${toString cfg.mrc.httpPort}
+    babudb.baseDir = ${home}/mrc/database
+    babudb.logDir = ${home}/mrc/db-log
+    babudb.sync = ${if cfg.mrc.replication.enable then "FDATASYNC" else cfg.mrc.syncMode}
+
+    ${optionalString cfg.mrc.replication.enable "babudb.plugin.0 = ${mrcReplicationConfig}"}
+
+    ${cfg.mrc.extraConfig}
+  '';
+
+  osdConfig = pkgs.writeText "xtreemfs-osd-config.properties" ''
+    uuid = ${cfg.osd.uuid}
+    listen.port = ${toString cfg.osd.port}
+    ${optionalString (cfg.osd.address != "") "listen.address = ${cfg.osd.address}"}
+    http_port = ${toString cfg.osd.httpPort}
+    object_dir = ${home}/osd/
+
+    ${cfg.osd.extraConfig}
+  '';
+
+  optionalDir = optionals cfg.dir.enable ["xtreemfs-dir.service"];
+
+  systemdOptionalDependencies = {
+    after = [ "network.target" ] ++ optionalDir;
+    wantedBy = [ "multi-user.target" ] ++ optionalDir;
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.xtreemfs = {
+
+      enable = mkEnableOption "XtreemFS";
+
+      homeDir = mkOption {
+        default = "/var/lib/xtreemfs";
+        description = ''
+          XtreemFS home dir for the xtreemfs user.
+        '';
+      };
+
+      dir = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to enable XtreemFS DIR service.
+          '';
+        };
+
+        uuid = mkOption {
+          example = "eacb6bab-f444-4ebf-a06a-3f72d7465e40";
+          description = ''
+            Must be set to a unique identifier, preferably a UUID according to
+            RFC 4122. UUIDs can be generated with `uuidgen` command, found in
+            the `utillinux` package.
+          '';
+        };
+        port = mkOption {
+          default = 32638;
+          description = ''
+            The port to listen on for incoming connections (TCP).
+          '';
+        };
+        address = mkOption {
+          example = "127.0.0.1";
+          default = "";
+          description = ''
+            If specified, it defines the interface to listen on. If not
+            specified, the service will listen on all interfaces (any).
+          '';
+        };
+        httpPort = mkOption {
+          default = 30638;
+          description = ''
+            Specifies the listen port for the HTTP service that returns the
+            status page.
+          '';
+        };
+        syncMode = mkOption {
+          default = "FSYNC";
+          example = "FDATASYNC";
+          description = ''
+            The sync mode influences how operations are committed to the disk
+            log before the operation is acknowledged to the caller.
+
+            -ASYNC mode the writes to the disk log are buffered in memory by the operating system. This is the fastest mode but will lead to data loss in case of a crash, kernel panic or power failure.
+            -SYNC_WRITE_METADATA opens the file with O_SYNC, the system will not buffer any writes. The operation will be acknowledged when data has been safely written to disk. This mode is slow but offers maximum data safety. However, BabuDB cannot influence the disk drive caches, this depends on the OS and hard disk model.
+            -SYNC_WRITE similar to SYNC_WRITE_METADATA but opens file with O_DSYNC which means that only the data is commit to disk. This can lead to some data loss depending on the implementation of the underlying file system. Linux does not implement this mode.
+            -FDATASYNC is similar to SYNC_WRITE but opens the file in asynchronous mode and calls fdatasync() after writing the data to disk.
+            -FSYNC is similar to SYNC_WRITE_METADATA but opens the file in asynchronous mode and calls fsync() after writing the data to disk.
+
+            For best throughput use ASYNC, for maximum data safety use FSYNC.
+
+            (If xtreemfs.dir.replication.enable is true then FDATASYNC is forced)
+          '';
+        };
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          example = ''
+            # specify whether SSL is required
+            ssl.enabled = true
+            ssl.service_creds.pw = passphrase
+            ssl.service_creds.container = pkcs12
+            ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/dir.p12
+            ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+            ssl.trusted_certs.pw = jks_passphrase
+            ssl.trusted_certs.container = jks
+          '';
+          description = ''
+            Configuration of XtreemFS DIR service.
+            WARNING: configuration is saved as plaintext inside nix store.
+            For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+          '';
+        };
+        replication = {
+          enable = mkEnableOption "XtreemFS DIR replication plugin";
+          extraConfig = mkOption {
+            type = types.lines;
+            example = ''
+              # participants of the replication including this replica
+              babudb.repl.participant.0 = 192.168.0.10
+              babudb.repl.participant.0.port = 35676
+              babudb.repl.participant.1 = 192.168.0.11
+              babudb.repl.participant.1.port = 35676
+              babudb.repl.participant.2 = 192.168.0.12
+              babudb.repl.participant.2.port = 35676
+
+              # number of servers that at least have to be up to date
+              # To have a fault-tolerant system, this value has to be set to the
+              # majority of nodes i.e., if you have three replicas, set this to 2
+              # Please note that a setup with two nodes provides no fault-tolerance.
+              babudb.repl.sync.n = 2
+
+              # specify whether SSL is required
+              babudb.ssl.enabled = true
+
+              babudb.ssl.protocol = tlsv12
+
+              # server credentials for SSL handshakes
+              babudb.ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/osd.p12
+              babudb.ssl.service_creds.pw = passphrase
+              babudb.ssl.service_creds.container = pkcs12
+
+              # trusted certificates for SSL handshakes
+              babudb.ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+              babudb.ssl.trusted_certs.pw = jks_passphrase
+              babudb.ssl.trusted_certs.container = jks
+
+              babudb.ssl.authenticationWithoutEncryption = false
+            '';
+            description = ''
+              Configuration of XtreemFS DIR replication plugin.
+              WARNING: configuration is saved as plaintext inside nix store.
+              For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+            '';
+          };
+        };
+      };
+
+      mrc = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to enable XtreemFS MRC service.
+          '';
+        };
+
+        uuid = mkOption {
+          example = "eacb6bab-f444-4ebf-a06a-3f72d7465e41";
+          description = ''
+            Must be set to a unique identifier, preferably a UUID according to
+            RFC 4122. UUIDs can be generated with `uuidgen` command, found in
+            the `utillinux` package.
+          '';
+        };
+        port = mkOption {
+          default = 32636;
+          description = ''
+            The port to listen on for incoming connections (TCP).
+          '';
+        };
+        address = mkOption {
+          example = "127.0.0.1";
+          default = "";
+          description = ''
+            If specified, it defines the interface to listen on. If not
+            specified, the service will listen on all interfaces (any).
+          '';
+        };
+        httpPort = mkOption {
+          default = 30636;
+          description = ''
+            Specifies the listen port for the HTTP service that returns the
+            status page.
+          '';
+        };
+        syncMode = mkOption {
+          default = "FSYNC";
+          example = "FDATASYNC";
+          description = ''
+            The sync mode influences how operations are committed to the disk
+            log before the operation is acknowledged to the caller.
+
+            -ASYNC mode the writes to the disk log are buffered in memory by the operating system. This is the fastest mode but will lead to data loss in case of a crash, kernel panic or power failure.
+            -SYNC_WRITE_METADATA opens the file with O_SYNC, the system will not buffer any writes. The operation will be acknowledged when data has been safely written to disk. This mode is slow but offers maximum data safety. However, BabuDB cannot influence the disk drive caches, this depends on the OS and hard disk model.
+            -SYNC_WRITE similar to SYNC_WRITE_METADATA but opens file with O_DSYNC which means that only the data is commit to disk. This can lead to some data loss depending on the implementation of the underlying file system. Linux does not implement this mode.
+            -FDATASYNC is similar to SYNC_WRITE but opens the file in asynchronous mode and calls fdatasync() after writing the data to disk.
+            -FSYNC is similar to SYNC_WRITE_METADATA but opens the file in asynchronous mode and calls fsync() after writing the data to disk.
+
+            For best throughput use ASYNC, for maximum data safety use FSYNC.
+
+            (If xtreemfs.mrc.replication.enable is true then FDATASYNC is forced)
+          '';
+        };
+        extraConfig = mkOption {
+          type = types.lines;
+          example = ''
+            osd_check_interval = 300
+            no_atime = true
+            local_clock_renewal = 0
+            remote_time_sync = 30000
+            authentication_provider = org.xtreemfs.common.auth.NullAuthProvider
+
+            # shared secret between the MRC and all OSDs
+            capability_secret = iNG8UuQJrJ6XVDTe
+
+            dir_service.host = 192.168.0.10
+            dir_service.port = 32638
+
+            # if replication is enabled
+            dir_service.1.host = 192.168.0.11
+            dir_service.1.port = 32638
+            dir_service.2.host = 192.168.0.12
+            dir_service.2.port = 32638
+
+            # specify whether SSL is required
+            ssl.enabled = true
+            ssl.protocol = tlsv12
+            ssl.service_creds.pw = passphrase
+            ssl.service_creds.container = pkcs12
+            ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/mrc.p12
+            ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+            ssl.trusted_certs.pw = jks_passphrase
+            ssl.trusted_certs.container = jks
+          '';
+          description = ''
+            Configuration of XtreemFS MRC service.
+            WARNING: configuration is saved as plaintext inside nix store.
+            For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+          '';
+        };
+        replication = {
+          enable = mkEnableOption "XtreemFS MRC replication plugin";
+          extraConfig = mkOption {
+            type = types.lines;
+            example = ''
+              # participants of the replication including this replica
+              babudb.repl.participant.0 = 192.168.0.10
+              babudb.repl.participant.0.port = 35678
+              babudb.repl.participant.1 = 192.168.0.11
+              babudb.repl.participant.1.port = 35678
+              babudb.repl.participant.2 = 192.168.0.12
+              babudb.repl.participant.2.port = 35678
+
+              # number of servers that at least have to be up to date
+              # To have a fault-tolerant system, this value has to be set to the
+              # majority of nodes i.e., if you have three replicas, set this to 2
+              # Please note that a setup with two nodes provides no fault-tolerance.
+              babudb.repl.sync.n = 2
+
+              # specify whether SSL is required
+              babudb.ssl.enabled = true
+
+              babudb.ssl.protocol = tlsv12
+
+              # server credentials for SSL handshakes
+              babudb.ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/osd.p12
+              babudb.ssl.service_creds.pw = passphrase
+              babudb.ssl.service_creds.container = pkcs12
+
+              # trusted certificates for SSL handshakes
+              babudb.ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+              babudb.ssl.trusted_certs.pw = jks_passphrase
+              babudb.ssl.trusted_certs.container = jks
+
+              babudb.ssl.authenticationWithoutEncryption = false
+            '';
+            description = ''
+              Configuration of XtreemFS MRC replication plugin.
+              WARNING: configuration is saved as plaintext inside nix store.
+              For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+            '';
+          };
+        };
+      };
+
+      osd = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to enable XtreemFS OSD service.
+          '';
+        };
+
+        uuid = mkOption {
+          example = "eacb6bab-f444-4ebf-a06a-3f72d7465e42";
+          description = ''
+            Must be set to a unique identifier, preferably a UUID according to
+            RFC 4122. UUIDs can be generated with `uuidgen` command, found in
+            the `utillinux` package.
+          '';
+        };
+        port = mkOption {
+          default = 32640;
+          description = ''
+            The port to listen on for incoming connections (TCP and UDP).
+          '';
+        };
+        address = mkOption {
+          example = "127.0.0.1";
+          default = "";
+          description = ''
+            If specified, it defines the interface to listen on. If not
+            specified, the service will listen on all interfaces (any).
+          '';
+        };
+        httpPort = mkOption {
+          default = 30640;
+          description = ''
+            Specifies the listen port for the HTTP service that returns the
+            status page.
+          '';
+        };
+        extraConfig = mkOption {
+          type = types.lines;
+          example = ''
+            local_clock_renewal = 0
+            remote_time_sync = 30000
+            report_free_space = true
+            capability_secret = iNG8UuQJrJ6XVDTe
+
+            dir_service.host = 192.168.0.10
+            dir_service.port = 32638
+
+            # if replication is used
+            dir_service.1.host = 192.168.0.11
+            dir_service.1.port = 32638
+            dir_service.2.host = 192.168.0.12
+            dir_service.2.port = 32638
+
+            # specify whether SSL is required
+            ssl.enabled = true
+            ssl.service_creds.pw = passphrase
+            ssl.service_creds.container = pkcs12
+            ssl.service_creds = /etc/xos/xtreemfs/truststore/certs/osd.p12
+            ssl.trusted_certs = /etc/xos/xtreemfs/truststore/certs/trusted.jks
+            ssl.trusted_certs.pw = jks_passphrase
+            ssl.trusted_certs.container = jks
+          '';
+          description = ''
+            Configuration of XtreemFS OSD service.
+            WARNING: configuration is saved as plaintext inside nix store.
+            For more options: http://www.xtreemfs.org/xtfs-guide-1.5.1/index.html
+          '';
+        };
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = lib.mkIf cfg.enable {
+
+    environment.systemPackages = [ xtreemfs ];
+
+    users.users.xtreemfs =
+      { uid = config.ids.uids.xtreemfs;
+        description = "XtreemFS user";
+        createHome = true;
+        home = home;
+      };
+
+    users.groups.xtreemfs =
+      { gid = config.ids.gids.xtreemfs;
+      };
+
+    systemd.services.xtreemfs-dir = mkIf cfg.dir.enable {
+      description = "XtreemFS-DIR Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = "xtreemfs";
+        ExecStart = "${startupScript "org.xtreemfs.dir.DIR" dirConfig}";
+      };
+    };
+
+    systemd.services.xtreemfs-mrc = mkIf cfg.mrc.enable ({
+      description = "XtreemFS-MRC Server";
+      serviceConfig = {
+        User = "xtreemfs";
+        ExecStart = "${startupScript "org.xtreemfs.mrc.MRC" mrcConfig}";
+      };
+    } // systemdOptionalDependencies);
+
+    systemd.services.xtreemfs-osd = mkIf cfg.osd.enable ({
+      description = "XtreemFS-OSD Server";
+      serviceConfig = {
+        User = "xtreemfs";
+        ExecStart = "${startupScript "org.xtreemfs.osd.OSD" osdConfig}";
+      };
+    } // systemdOptionalDependencies);
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix b/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix
new file mode 100644
index 000000000000..cc73f13bf77a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/network-filesystems/yandex-disk.nix
@@ -0,0 +1,114 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.yandex-disk;
+
+  dir = "/var/lib/yandex-disk";
+
+  u = if cfg.user != null then cfg.user else "yandexdisk";
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.yandex-disk = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "
+          Whether to enable Yandex-disk client. See https://disk.yandex.ru/
+        ";
+      };
+
+      username = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Your yandex.com login name.
+        '';
+      };
+
+      password = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Your yandex.com password. Warning: it will be world-readable in /nix/store.
+        '';
+      };
+
+      user = mkOption {
+        default = null;
+        description = ''
+          The user the yandex-disk daemon should run as.
+        '';
+      };
+
+      directory = mkOption {
+        default = "/home/Yandex.Disk";
+        description = "The directory to use for Yandex.Disk storage";
+      };
+
+      excludes = mkOption {
+        default = "";
+        type = types.commas;
+        example = "data,backup";
+        description = ''
+          Comma-separated list of directories which are excluded from synchronization.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = mkIf (cfg.user == null) [ {
+      name = u;
+      uid = config.ids.uids.yandexdisk;
+      group = "nogroup";
+      home = dir;
+    } ];
+
+    systemd.services.yandex-disk = {
+      description = "Yandex-disk server";
+
+      after = [ "network.target" ];
+
+      wantedBy = [ "multi-user.target" ];
+
+      # FIXME: have to specify ${directory} here as well
+      unitConfig.RequiresMountsFor = dir;
+
+      script = ''
+        mkdir -p -m 700 ${dir}
+        chown ${u} ${dir}
+
+        if ! test -d "${cfg.directory}" ; then
+          (mkdir -p -m 755 ${cfg.directory} && chown ${u} ${cfg.directory}) ||
+            exit 1
+        fi
+
+        ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${u} \
+          -c '${pkgs.yandex-disk}/bin/yandex-disk token -p ${cfg.password} ${cfg.username} ${dir}/token'
+
+        ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${u} \
+          -c '${pkgs.yandex-disk}/bin/yandex-disk start --no-daemon -a ${dir}/token -d ${cfg.directory} --exclude-dirs=${cfg.excludes}'
+      '';
+
+    };
+  };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/3proxy.nix b/nixpkgs/nixos/modules/services/networking/3proxy.nix
new file mode 100644
index 000000000000..ae8a4958ca96
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/3proxy.nix
@@ -0,0 +1,426 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  pkg = pkgs._3proxy;
+  cfg = config.services._3proxy;
+  optionalList = list: if list == [ ] then "*" else concatMapStringsSep "," toString list;
+in {
+  options.services._3proxy = {
+    enable = mkEnableOption "3proxy";
+    confFile = mkOption {
+      type = types.path;
+      example = "/var/lib/3proxy/3proxy.conf";
+      description = ''
+        Ignore all other 3proxy options and load configuration from this file.
+      '';
+    };
+    usersFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/var/lib/3proxy/3proxy.passwd";
+      description = ''
+        Load users and passwords from this file.
+
+        Example users file with plain-text passwords:
+
+        <literal>
+          test1:CL:password1
+          test2:CL:password2
+        </literal>
+
+        Example users file with md5-crypted passwords:
+
+        <literal>
+          test1:CR:$1$tFkisVd2$1GA8JXkRmTXdLDytM/i3a1
+          test2:CR:$1$rkpibm5J$Aq1.9VtYAn0JrqZ8M.1ME.
+        </literal>
+
+        You can generate md5-crypted passwords via https://unix4lyfe.org/crypt/
+        Note that htpasswd tool generates incompatible md5-crypted passwords.
+        Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/How-To-(incomplete)#USERS">documentation</link> for more information.
+      '';
+    };
+    services = mkOption {
+      type = types.listOf (types.submodule {
+        options = {
+          type = mkOption {
+            type = types.enum [
+              "proxy"
+              "socks"
+              "pop3p"
+              "ftppr"
+              "admin"
+              "dnspr"
+              "tcppm"
+              "udppm"
+            ];
+            example = "proxy";
+            description = ''
+              Service type. The following values are valid:
+
+              <itemizedlist>
+                <listitem><para>
+                  <literal>"proxy"</literal>: HTTP/HTTPS proxy (default port 3128).
+                </para></listitem>
+                <listitem><para>
+                  <literal>"socks"</literal>: SOCKS 4/4.5/5 proxy (default port 1080).
+                </para></listitem>
+                <listitem><para>
+                  <literal>"pop3p"</literal>: POP3 proxy (default port 110).
+                </para></listitem>
+                <listitem><para>
+                  <literal>"ftppr"</literal>: FTP proxy (default port 21).
+                </para></listitem>
+                <listitem><para>
+                  <literal>"admin"</literal>: Web interface (default port 80).
+                </para></listitem>
+                <listitem><para>
+                  <literal>"dnspr"</literal>: Caching DNS proxy (default port 53).
+                </para></listitem>
+                <listitem><para>
+                  <literal>"tcppm"</literal>: TCP portmapper.
+                </para></listitem>
+                <listitem><para>
+                  <literal>"udppm"</literal>: UDP portmapper.
+                </para></listitem>
+              </itemizedlist>
+            '';
+          };
+          bindAddress = mkOption {
+            type = types.str;
+            default = "[::]";
+            example = "127.0.0.1";
+            description = ''
+              Address used for service.
+            '';
+          };
+          bindPort = mkOption {
+            type = types.nullOr types.int;
+            default = null;
+            example = 3128;
+            description = ''
+              Override default port used for service.
+            '';
+          };
+          maxConnections = mkOption {
+            type = types.int;
+            default = 100;
+            example = 1000;
+            description = ''
+              Maximum number of simulationeous connections to this service.
+            '';
+          };
+          auth = mkOption {
+            type = types.listOf (types.enum [ "none" "iponly" "strong" ]);
+            example = [ "iponly" "strong" ];
+            description = ''
+              Authentication type. The following values are valid:
+
+              <itemizedlist>
+                <listitem><para>
+                  <literal>"none"</literal>: disables both authentication and authorization. You can not use ACLs.
+                </para></listitem>
+                <listitem><para>
+                  <literal>"iponly"</literal>: specifies no authentication. ACLs authorization is used.
+                </para></listitem>
+                <listitem><para>
+                  <literal>"strong"</literal>: authentication by username/password. If user is not registered his access is denied regardless of ACLs.
+                </para></listitem>
+              </itemizedlist>
+
+              Double authentication is possible, e.g.
+
+              <literal>
+                {
+                  auth = [ "iponly" "strong" ];
+                  acl = [
+                    {
+                      rule = "allow";
+                      targets = [ "192.168.0.0/16" ];
+                    }
+                    {
+                      rule = "allow"
+                      users = [ "user1" "user2" ];
+                    }
+                  ];
+                }
+              </literal>
+              In this example strong username authentication is not required to access 192.168.0.0/16.
+            '';
+          };
+          acl = mkOption {
+            type = types.listOf (types.submodule {
+              options = {
+                rule = mkOption {
+                  type = types.enum [ "allow" "deny" ];
+                  example = "allow";
+                  description = ''
+                    ACL rule. The following values are valid:
+
+                    <itemizedlist>
+                      <listitem><para>
+                        <literal>"allow"</literal>: connections allowed.
+                      </para></listitem>
+                      <listitem><para>
+                        <literal>"deny"</literal>: connections not allowed.
+                      </para></listitem>
+                    </itemizedlist>
+                  '';
+                };
+                users = mkOption {
+                  type = types.listOf types.str;
+                  default = [ ];
+                  example = [ "user1" "user2" "user3" ];
+                  description = ''
+                    List of users, use empty list for any.
+                  '';
+                };
+                sources = mkOption {
+                  type = types.listOf types.str;
+                  default = [ ];
+                  example = [ "127.0.0.1" "192.168.1.0/24" ];
+                  description = ''
+                    List of source IP range, use empty list for any.
+                  '';
+                };
+                targets = mkOption {
+                  type = types.listOf types.str;
+                  default = [ ];
+                  example = [ "127.0.0.1" "192.168.1.0/24" ];
+                  description = ''
+                    List of target IP ranges, use empty list for any.
+                    May also contain host names instead of addresses.
+                    It's possible to use wildmask in the begginning and in the the end of hostname, e.g. *badsite.com or *badcontent*.
+                    Hostname is only checked if hostname presents in request.
+                  '';
+                };
+                targetPorts = mkOption {
+                  type = types.listOf types.int;
+                  default = [ ];
+                  example = [ 80 443 ];
+                  description = ''
+                    List of target ports, use empty list for any.
+                  '';
+                };
+              };
+            });
+            default = [ ];
+            example = literalExample ''
+              [
+                {
+                  rule = "allow";
+                  users = [ "user1" ];
+                }
+                {
+                  rule = "allow";
+                  sources = [ "192.168.1.0/24" ];
+                }
+                {
+                  rule = "deny";
+                }
+              ]
+            '';
+            description = ''
+              Use this option to limit user access to resources.
+            '';
+          };
+          extraArguments = mkOption {
+            type = types.nullOr types.str;
+            default = null;
+            example = "-46";
+            description = ''
+              Extra arguments for service.
+              Consult "Options" section in <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available arguments.
+            '';
+          };
+          extraConfig = mkOption {
+            type = types.nullOr types.lines;
+            default = null;
+            description = ''
+              Extra configuration for service. Use this to configure things like bandwidth limiter or ACL-based redirection.
+              Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options.
+            '';
+          };
+        };
+      });
+      default = [ ];
+      example = literalExample ''
+        [
+          {
+            type = "proxy";
+            bindAddress = "192.168.1.24";
+            bindPort = 3128;
+            auth = [ "none" ];
+          }
+          {
+            type = "proxy";
+            bindAddress = "10.10.1.20";
+            bindPort = 3128;
+            auth = [ "iponly" ];
+          }
+          {
+            type = "socks";
+            bindAddress = "172.17.0.1";
+            bindPort = 1080;
+            auth = [ "strong" ];
+          }
+        ]
+      '';
+      description = ''
+        Use this option to define 3proxy services.
+      '';
+    };
+    denyPrivate = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to deny access to private IP ranges including loopback.
+      '';
+    };
+    privateRanges = mkOption {
+      type = types.listOf types.str;
+      default = [
+        "0.0.0.0/8"
+        "127.0.0.0/8"
+        "10.0.0.0/8"
+        "100.64.0.0/10"
+        "172.16.0.0/12"
+        "192.168.0.0/16"
+        "::"
+        "::1"
+        "fc00::/7"
+      ];
+      example = [
+        "0.0.0.0/8"
+        "127.0.0.0/8"
+        "10.0.0.0/8"
+        "100.64.0.0/10"
+        "172.16.0.0/12"
+        "192.168.0.0/16"
+        "::"
+        "::1"
+        "fc00::/7"
+      ];
+      description = ''
+        What IP ranges to deny access when denyPrivate is set tu true.
+      '';
+    };
+    resolution = mkOption {
+      type = types.submodule {
+        options = {
+          nserver = mkOption {
+            type = types.listOf types.str;
+            default = [ ];
+            example = [ "127.0.0.53" "192.168.1.3:5353/tcp" ];
+            description = ''
+              List of nameservers to use.
+
+              Up to 5 nservers may be specified. If no nserver is configured,
+              default system name resolution functions are used.
+            '';
+          };
+          nscache = mkOption {
+            type = types.int;
+            default = 65535;
+            example = 65535;
+            description = "Set name cache size for IPv4.";
+          };
+          nscache6 = mkOption {
+            type = types.int;
+            default = 65535;
+            example = 65535;
+            description = "Set name cache size for IPv6.";
+          };
+          nsrecord = mkOption {
+            type = types.attrsOf types.str;
+            default = { };
+            example = literalExample ''
+              {
+                "files.local" = "192.168.1.12";
+                "site.local" = "192.168.1.43";
+              }
+            '';
+            description = "Adds static nsrecords.";
+          };
+        };
+      };
+      default = { };
+      description = ''
+        Use this option to configure name resolution and DNS caching.
+      '';
+    };
+    extraConfig = mkOption {
+      type = types.nullOr types.lines;
+      default = null;
+      description = ''
+        Extra configuration, appended to the 3proxy configuration file.
+        Consult <link xlink:href="https://github.com/z3APA3A/3proxy/wiki/3proxy.cfg">documentation</link> for available options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services._3proxy.confFile = mkDefault (pkgs.writeText "3proxy.conf" ''
+      # log to stdout
+      log
+
+      ${concatMapStringsSep "\n" (x: "nserver " + x) cfg.resolution.nserver}
+
+      nscache ${toString cfg.resolution.nscache}
+      nscache6 ${toString cfg.resolution.nscache6}
+
+      ${concatMapStringsSep "\n" (x: "nsrecord " + x)
+      (mapAttrsToList (name: value: "${name} ${value}")
+        cfg.resolution.nsrecord)}
+
+      ${optionalString (cfg.usersFile != null)
+        ''users $"${cfg.usersFile}"''
+      }
+
+      ${concatMapStringsSep "\n" (service: ''
+        auth ${concatStringsSep " " service.auth}
+
+        ${optionalString (cfg.denyPrivate)
+        "deny * * ${optionalList cfg.privateRanges}"}
+
+        ${concatMapStringsSep "\n" (acl:
+          "${acl.rule} ${
+            concatMapStringsSep " " optionalList [
+              acl.users
+              acl.sources
+              acl.targets
+              acl.targetPorts
+            ]
+          }") service.acl}
+
+        maxconn ${toString service.maxConnections}
+
+        ${optionalString (service.extraConfig != null) service.extraConfig}
+
+        ${service.type} -i${toString service.bindAddress} ${
+          optionalString (service.bindPort != null)
+          "-p${toString service.bindPort}"
+        } ${
+          optionalString (service.extraArguments != null) service.extraArguments
+        }
+
+        flush
+      '') cfg.services}
+      ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
+    '');
+    systemd.services."3proxy" = {
+      description = "Tiny free proxy server";
+      documentation = [ "https://github.com/z3APA3A/3proxy/wiki" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = "3proxy";
+        ExecStart = "${pkg}/bin/3proxy ${cfg.confFile}";
+        Restart = "on-failure";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ misuzu ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/amuled.nix b/nixpkgs/nixos/modules/services/networking/amuled.nix
new file mode 100644
index 000000000000..1128ee2c3e61
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/amuled.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.amule;
+  user = if cfg.user != null then cfg.user else "amule";
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.amule = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run the AMule daemon. You need to manually run "amuled --ec-config" to configure the service for the first time.
+        '';
+      };
+
+      dataDir = mkOption {
+        default = ''/home/${user}/'';
+        description = ''
+          The directory holding configuration, incoming and temporary files.
+        '';
+      };
+
+      user = mkOption {
+        default = null;
+        description = ''
+          The user the AMule daemon should run as.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = mkIf (cfg.user == null) [
+      { name = "amule";
+        description = "AMule daemon";
+        group = "amule";
+        uid = config.ids.uids.amule;
+      } ];
+
+    users.groups = mkIf (cfg.user == null) [
+      { name = "amule";
+        gid = config.ids.gids.amule;
+      } ];
+
+    systemd.services.amuled = {
+      description = "AMule daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        mkdir -p ${cfg.dataDir}
+        chown ${user} ${cfg.dataDir}
+      '';
+
+      script = ''
+        ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${user} \
+            -c 'HOME="${cfg.dataDir}" ${pkgs.amuleDaemon}/bin/amuled'
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/aria2.nix b/nixpkgs/nixos/modules/services/networking/aria2.nix
new file mode 100644
index 000000000000..156fef144791
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/aria2.nix
@@ -0,0 +1,131 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.aria2;
+
+  homeDir = "/var/lib/aria2";
+
+  settingsDir = "${homeDir}";
+  sessionFile = "${homeDir}/aria2.session";
+  downloadDir = "${homeDir}/Downloads";
+
+  rangesToStringList = map (x: builtins.toString x.from +"-"+ builtins.toString x.to);
+
+  settingsFile = pkgs.writeText "aria2.conf"
+  ''
+    dir=${cfg.downloadDir}
+    listen-port=${concatStringsSep "," (rangesToStringList cfg.listenPortRange)}
+    rpc-listen-port=${toString cfg.rpcListenPort}
+    rpc-secret=${cfg.rpcSecret}
+  '';
+
+in
+{
+  options = {
+    services.aria2 = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not to enable the headless Aria2 daemon service.
+
+          Aria2 daemon can be controlled via the RPC interface using
+          one of many WebUI (http://localhost:6800/ by default).
+
+          Targets are downloaded to ${downloadDir} by default and are
+          accessible to users in the "aria2" group.
+        '';
+      };
+      openPorts = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open listen and RPC ports found in listenPortRange and rpcListenPort
+          options in the firewall.
+        '';
+      };
+      downloadDir = mkOption {
+        type = types.path;
+        default = downloadDir;
+        description = ''
+          Directory to store downloaded files.
+        '';
+      };
+      listenPortRange = mkOption {
+        type = types.listOf types.attrs;
+        default = [ { from = 6881; to = 6999; } ];
+        description = ''
+          Set UDP listening port range used by DHT(IPv4, IPv6) and UDP tracker.
+        '';
+      };
+      rpcListenPort = mkOption {
+        type = types.int;
+        default = 6800;
+        description = "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535";
+      };
+      rpcSecret = mkOption {
+        type = types.str;
+        default = "aria2rpc";
+        description = ''
+          Set RPC secret authorization token.
+          Read https://aria2.github.io/manual/en/html/aria2c.html#rpc-auth to know how this option value is used.
+        '';
+      };
+      extraArguments = mkOption {
+        type = types.separatedString " ";
+        example = "--rpc-listen-all --remote-time=true";
+        default = "";
+        description = ''
+          Additional arguments to be passed to Aria2.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    # Need to open ports for proper functioning
+    networking.firewall = mkIf cfg.openPorts {
+      allowedUDPPortRanges = config.services.aria2.listenPortRange;
+      allowedTCPPorts = [ config.services.aria2.rpcListenPort ];
+    };
+
+    users.users.aria2 = {
+      group = "aria2";
+      uid = config.ids.uids.aria2;
+      description = "aria2 user";
+      home = homeDir;
+      createHome = false;
+    };
+
+    users.groups.aria2.gid = config.ids.gids.aria2;
+
+    systemd.tmpfiles.rules = [
+      "d '${homeDir}' 0770 aria2 aria2 - -"
+      "d '${config.services.aria2.downloadDir}' 0770 aria2 aria2 - -"
+    ];
+
+    systemd.services.aria2 = {
+      description = "aria2 Service";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        if [[ ! -e "${sessionFile}" ]]
+        then
+          touch "${sessionFile}"
+        fi
+        cp -f "${settingsFile}" "${settingsDir}/aria2.conf"
+      '';
+
+      serviceConfig = {
+        Restart = "on-abort";
+        ExecStart = "${pkgs.aria2}/bin/aria2c --enable-rpc --conf-path=${settingsDir}/aria2.conf ${config.services.aria2.extraArguments} --save-session=${sessionFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        User = "aria2";
+        Group = "aria2";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/asterisk.nix b/nixpkgs/nixos/modules/services/networking/asterisk.nix
new file mode 100644
index 000000000000..03a2544b9a7e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/asterisk.nix
@@ -0,0 +1,264 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.asterisk;
+
+  asteriskUser = "asterisk";
+  asteriskGroup = "asterisk";
+
+  varlibdir = "/var/lib/asterisk";
+  spooldir = "/var/spool/asterisk";
+  logdir = "/var/log/asterisk";
+
+  # Add filecontents from files of useTheseDefaultConfFiles to confFiles, do not override
+  defaultConfFiles = subtractLists (attrNames cfg.confFiles) cfg.useTheseDefaultConfFiles;
+  allConfFiles =
+    cfg.confFiles //
+    builtins.listToAttrs (map (x: { name = x;
+                                    value = builtins.readFile (cfg.package + "/etc/asterisk/" + x); })
+                              defaultConfFiles);
+
+  asteriskEtc = pkgs.stdenv.mkDerivation
+  ((mapAttrs' (name: value: nameValuePair
+        # Fudge the names to make bash happy
+        ((replaceChars ["."] ["_"] name) + "_")
+        (value)
+      ) allConfFiles) //
+  {
+    confFilesString = concatStringsSep " " (
+      attrNames allConfFiles
+    );
+
+    name = "asterisk-etc";
+
+    # Default asterisk.conf file
+    # (Notice that astetcdir will be set to the path of this derivation)
+    asteriskConf = ''
+      [directories]
+      astetcdir => /etc/asterisk
+      astmoddir => ${cfg.package}/lib/asterisk/modules
+      astvarlibdir => /var/lib/asterisk
+      astdbdir => /var/lib/asterisk
+      astkeydir => /var/lib/asterisk
+      astdatadir => /var/lib/asterisk
+      astagidir => /var/lib/asterisk/agi-bin
+      astspooldir => /var/spool/asterisk
+      astrundir => /run/asterisk
+      astlogdir => /var/log/asterisk
+      astsbindir => ${cfg.package}/sbin
+    '';
+    extraConf = cfg.extraConfig;
+
+    # Loading all modules by default is considered sensible by the authors of
+    # "Asterisk: The Definitive Guide". Secure sites will likely want to
+    # specify their own "modules.conf" in the confFiles option.
+    modulesConf = ''
+      [modules]
+      autoload=yes
+    '';
+
+    # Use syslog for logging so logs can be viewed with journalctl
+    loggerConf = ''
+      [general]
+
+      [logfiles]
+      syslog.local0 => notice,warning,error
+    '';
+
+    buildCommand = ''
+      mkdir -p "$out"
+
+      # Create asterisk.conf, pointing astetcdir to the path of this derivation
+      echo "$asteriskConf" | sed "s|@out@|$out|g" > "$out"/asterisk.conf
+      echo "$extraConf" >> "$out"/asterisk.conf
+
+      echo "$modulesConf" > "$out"/modules.conf
+
+      echo "$loggerConf" > "$out"/logger.conf
+
+      # Config files specified in confFiles option override all other files
+      for i in $confFilesString; do
+        conf=$(echo "$i"_ | sed 's/\./_/g')
+        echo "''${!conf}" > "$out"/"$i"
+      done
+    '';
+  });
+in
+
+{
+  options = {
+    services.asterisk = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Asterisk PBX server.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''
+          [options]
+          verbose=3
+          debug=3
+        '';
+        description = ''
+          Extra configuration options appended to the default
+          <literal>asterisk.conf</literal> file.
+        '';
+      };
+
+      confFiles = mkOption {
+        default = {};
+        type = types.attrsOf types.str;
+        example = literalExample
+          ''
+            {
+              "extensions.conf" = '''
+                [tests]
+                ; Dial 100 for "hello, world"
+                exten => 100,1,Answer()
+                same  =>     n,Wait(1)
+                same  =>     n,Playback(hello-world)
+                same  =>     n,Hangup()
+
+                [softphones]
+                include => tests
+
+                [unauthorized]
+              ''';
+              "sip.conf" = '''
+                [general]
+                allowguest=no              ; Require authentication
+                context=unauthorized       ; Send unauthorized users to /dev/null
+                srvlookup=no               ; Don't do DNS lookup
+                udpbindaddr=0.0.0.0        ; Listen on all interfaces
+                nat=force_rport,comedia    ; Assume device is behind NAT
+
+                [softphone](!)
+                type=friend                ; Match on username first, IP second
+                context=softphones         ; Send to softphones context in
+                                           ; extensions.conf file
+                host=dynamic               ; Device will register with asterisk
+                disallow=all               ; Manually specify codecs to allow
+                allow=g722
+                allow=ulaw
+                allow=alaw
+
+                [myphone](softphone)
+                secret=GhoshevFew          ; Change this password!
+              ''';
+              "logger.conf" = '''
+                [general]
+
+                [logfiles]
+                ; Add debug output to log
+                syslog.local0 => notice,warning,error,debug
+              ''';
+            }
+        '';
+        description = ''
+          Sets the content of config files (typically ending with
+          <literal>.conf</literal>) in the Asterisk configuration directory.
+
+          Note that if you want to change <literal>asterisk.conf</literal>, it
+          is preferable to use the <option>services.asterisk.extraConfig</option>
+          option over this option. If <literal>"asterisk.conf"</literal> is
+          specified with the <option>confFiles</option> option (not recommended),
+          you must be prepared to set your own <literal>astetcdir</literal>
+          path.
+
+          See
+          <link xlink:href="http://www.asterisk.org/community/documentation"/>
+          for more examples of what is possible here.
+        '';
+      };
+
+      useTheseDefaultConfFiles = mkOption {
+        default = [ "ari.conf" "acl.conf" "agents.conf" "amd.conf" "calendar.conf" "cdr.conf" "cdr_syslog.conf" "cdr_custom.conf" "cel.conf" "cel_custom.conf" "cli_aliases.conf" "confbridge.conf" "dundi.conf" "features.conf" "hep.conf" "iax.conf" "pjsip.conf" "pjsip_wizard.conf" "phone.conf" "phoneprov.conf" "queues.conf" "res_config_sqlite3.conf" "res_parking.conf" "statsd.conf" "udptl.conf" "unistim.conf" ];
+        type = types.listOf types.str;
+        example = [ "sip.conf" "dundi.conf" ];
+        description = ''Sets these config files to the default content. The default value for
+          this option contains all necesscary files to avoid errors at startup.
+          This does not override settings via <option>services.asterisk.confFiles</option>.
+        '';
+      };
+
+      extraArguments = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        example =
+          [ "-vvvddd" "-e" "1024" ];
+        description = ''
+          Additional command line arguments to pass to Asterisk.
+        '';
+      };
+      package = mkOption {
+        type = types.package;
+        default = pkgs.asterisk;
+        defaultText = "pkgs.asterisk";
+        description = "The Asterisk package to use.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    environment.etc.asterisk.source = asteriskEtc;
+
+    users.users.asterisk =
+      { name = asteriskUser;
+        group = asteriskGroup;
+        uid = config.ids.uids.asterisk;
+        description = "Asterisk daemon user";
+        home = varlibdir;
+      };
+
+    users.groups.asterisk =
+      { name = asteriskGroup;
+        gid = config.ids.gids.asterisk;
+      };
+
+    systemd.services.asterisk = {
+      description = ''
+        Asterisk PBX server
+      '';
+
+      wantedBy = [ "multi-user.target" ];
+
+      # Do not restart, to avoid disruption of running calls. Restart unit by yourself!
+      restartIfChanged = false;
+
+      preStart = ''
+        # Copy skeleton directory tree to /var
+        for d in '${varlibdir}' '${spooldir}' '${logdir}'; do
+          # TODO: Make exceptions for /var directories that likely should be updated
+          if [ ! -e "$d" ]; then
+            mkdir -p "$d"
+            cp --recursive ${cfg.package}/"$d"/* "$d"/
+            chown --recursive ${asteriskUser}:${asteriskGroup} "$d"
+            find "$d" -type d | xargs chmod 0755
+          fi
+        done
+      '';
+
+      serviceConfig = {
+        ExecStart =
+          let
+            # FIXME: This doesn't account for arguments with spaces
+            argString = concatStringsSep " " cfg.extraArguments;
+          in
+          "${cfg.package}/bin/asterisk -U ${asteriskUser} -C /etc/asterisk/asterisk.conf ${argString} -F";
+        ExecReload = ''${cfg.package}/bin/asterisk -x "core reload"
+          '';
+        Type = "forking";
+        PIDFile = "/run/asterisk/asterisk.pid";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/atftpd.nix b/nixpkgs/nixos/modules/services/networking/atftpd.nix
new file mode 100644
index 000000000000..e7fd48c99a85
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/atftpd.nix
@@ -0,0 +1,65 @@
+# NixOS module for atftpd TFTP server
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.atftpd;
+
+in
+
+{
+
+  options = {
+
+    services.atftpd = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to enable the atftpd TFTP server. By default, the server
+          binds to address 0.0.0.0.
+        '';
+      };
+
+      extraOptions = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        example = literalExample ''
+          [ "--bind-address 192.168.9.1"
+            "--verbose=7"
+          ]
+        '';
+        description = ''
+          Extra command line arguments to pass to atftp.
+        '';
+      };
+
+      root = mkOption {
+        default = "/srv/tftp";
+        type = types.path;
+        description = ''
+          Document root directory for the atftpd.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.atftpd = {
+      description = "TFTP Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      # runs as nobody
+      serviceConfig.ExecStart = "${pkgs.atftp}/sbin/atftpd --daemon --no-fork ${lib.concatStringsSep " " cfg.extraOptions} ${cfg.root}";
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/autossh.nix b/nixpkgs/nixos/modules/services/networking/autossh.nix
new file mode 100644
index 000000000000..a8d9a027e9fa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/autossh.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.autossh;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.autossh = {
+
+      sessions = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = types.str;
+              example = "socks-peer";
+              description = "Name of the local AutoSSH session";
+            };
+            user = mkOption {
+              type = types.str;
+              example = "bill";
+              description = "Name of the user the AutoSSH session should run as";
+            };
+            monitoringPort = mkOption {
+              type = types.int;
+              default = 0;
+              example = 20000;
+              description = ''
+                Port to be used by AutoSSH for peer monitoring. Note, that
+                AutoSSH also uses mport+1. Value of 0 disables the keep-alive
+                style monitoring
+              '';
+            };
+            extraArguments = mkOption {
+              type = types.separatedString " ";
+              example = "-N -D4343 bill@socks.example.net";
+              description = ''
+                Arguments to be passed to AutoSSH and retransmitted to SSH
+                process. Some meaningful options include -N (don't run remote
+                command), -D (open SOCKS proxy on local port), -R (forward
+                remote port), -L (forward local port), -v (Enable debug). Check
+                ssh manual for the complete list.
+              '';
+            };
+          };
+        });
+
+        default = [];
+        description = ''
+          List of AutoSSH sessions to start as systemd services. Each service is
+          named 'autossh-{session.name}'.
+        '';
+
+        example = [
+          {
+            name="socks-peer";
+            user="bill";
+            monitoringPort = 20000;
+            extraArguments="-N -D4343 billremote@socks.host.net";
+          }
+        ];
+
+      };
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf (cfg.sessions != []) {
+
+    systemd.services =
+
+      lib.fold ( s : acc : acc //
+        {
+          "autossh-${s.name}" =
+            let
+              mport = if s ? monitoringPort then s.monitoringPort else 0;
+            in
+            {
+              description = "AutoSSH session (" + s.name + ")";
+
+              after = [ "network.target" ];
+              wantedBy = [ "multi-user.target" ];
+
+              # To be able to start the service with no network connection
+              environment.AUTOSSH_GATETIME="0";
+
+              # How often AutoSSH checks the network, in seconds
+              environment.AUTOSSH_POLL="30";
+
+              serviceConfig = {
+                  User = "${s.user}";
+                  # AutoSSH may exit with 0 code if the SSH session was
+                  # gracefully terminated by either local or remote side.
+                  Restart = "on-success";
+                  ExecStart = "${pkgs.autossh}/bin/autossh -M ${toString mport} ${s.extraArguments}";
+              };
+            };
+        }) {} cfg.sessions;
+
+    environment.systemPackages = [ pkgs.autossh ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix b/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix
new file mode 100644
index 000000000000..c876b252e8cd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/avahi-daemon.nix
@@ -0,0 +1,285 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.avahi;
+
+  yesNo = yes : if yes then "yes" else "no";
+
+  avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" ''
+    [server]
+    ${# Users can set `networking.hostName' to the empty string, when getting
+      # a host name from DHCP.  In that case, let Avahi take whatever the
+      # current host name is; setting `host-name' to the empty string in
+      # `avahi-daemon.conf' would be invalid.
+      optionalString (hostName != "") "host-name=${hostName}"}
+    browse-domains=${concatStringsSep ", " browseDomains}
+    use-ipv4=${yesNo ipv4}
+    use-ipv6=${yesNo ipv6}
+    ${optionalString (interfaces!=null) "allow-interfaces=${concatStringsSep "," interfaces}"}
+    ${optionalString (domainName!=null) "domain-name=${domainName}"}
+    allow-point-to-point=${yesNo allowPointToPoint}
+    ${optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"}
+
+    [wide-area]
+    enable-wide-area=${yesNo wideArea}
+
+    [publish]
+    disable-publishing=${yesNo (!publish.enable)}
+    disable-user-service-publishing=${yesNo (!publish.userServices)}
+    publish-addresses=${yesNo (publish.userServices || publish.addresses)}
+    publish-hinfo=${yesNo publish.hinfo}
+    publish-workstation=${yesNo publish.workstation}
+    publish-domain=${yesNo publish.domain}
+
+    [reflector]
+    enable-reflector=${yesNo reflector}
+    ${extraConfig}
+  '';
+in
+{
+  options.services.avahi = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to run the Avahi daemon, which allows Avahi clients
+        to use Avahi's service discovery facilities and also allows
+        the local machine to advertise its presence and services
+        (through the mDNS responder implemented by `avahi-daemon').
+      '';
+    };
+
+    hostName = mkOption {
+      type = types.str;
+      default = config.networking.hostName;
+      defaultText = literalExample "config.networking.hostName";
+      description = ''
+        Host name advertised on the LAN. If not set, avahi will use the value
+        of <option>config.networking.hostName</option>.
+      '';
+    };
+
+    domainName = mkOption {
+      type = types.str;
+      default = "local";
+      description = ''
+        Domain name for all advertisements.
+      '';
+    };
+
+    browseDomains = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "0pointer.de" "zeroconf.org" ];
+      description = ''
+        List of non-local DNS domains to be browsed.
+      '';
+    };
+
+    ipv4 = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to use IPv4.";
+    };
+
+    ipv6 = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Whether to use IPv6.";
+    };
+
+    interfaces = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = ''
+        List of network interfaces that should be used by the <command>avahi-daemon</command>.
+        Other interfaces will be ignored. If <literal>null</literal>, all local interfaces
+        except loopback and point-to-point will be used.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to open the firewall for UDP port 5353.
+      '';
+    };
+
+    allowPointToPoint = mkOption {
+      type = types.bool;
+      default = false;
+      description= ''
+        Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large
+        latencies with such links and opens a potential security hole by allowing mDNS access from Internet
+        connections.
+      '';
+    };
+
+    wideArea = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to enable wide-area service discovery.";
+    };
+
+    reflector = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Reflect incoming mDNS requests to all allowed network interfaces.";
+    };
+
+    extraServiceFiles = mkOption {
+      type = with types; attrsOf (either str path);
+      default = {};
+      example = literalExample ''
+        {
+          ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
+          smb = '''
+            <?xml version="1.0" standalone='no'?><!--*-nxml-*-->
+            <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
+            <service-group>
+              <name replace-wildcards="yes">%h</name>
+              <service>
+                <type>_smb._tcp</type>
+                <port>445</port>
+              </service>
+            </service-group>
+          ''';
+        }
+      '';
+      description = ''
+        Specify custom service definitions which are placed in the avahi service directory.
+        See the <citerefentry><refentrytitle>avahi.service</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> manpage for detailed information.
+      '';
+    };
+
+    publish = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to allow publishing in general.";
+      };
+
+      userServices = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to publish user services. Will set <literal>addresses=true</literal>.";
+      };
+
+      addresses = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to register mDNS address records for all local IP addresses.";
+      };
+
+      hinfo = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to register a mDNS HINFO record which contains information about the
+          local operating system and CPU.
+        '';
+      };
+
+      workstation = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to register a service of type "_workstation._tcp" on the local LAN.
+        '';
+      };
+
+      domain = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to announce the locally used domain name for browsing by other hosts.";
+      };
+    };
+
+    nssmdns = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable the mDNS NSS (Name Service Switch) plug-in.
+        Enabling it allows applications to resolve names in the `.local'
+        domain by transparently querying the Avahi daemon.
+      '';
+    };
+
+    cacheEntriesMax = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      description = ''
+        Number of resource records to be cached per interface. Use 0 to
+        disable caching. Avahi daemon defaults to 4096 if not set.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Extra config to append to avahi-daemon.conf.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.avahi = {
+      description = "avahi-daemon privilege separation user";
+      home = "/var/empty";
+      group = "avahi";
+      isSystemUser = true;
+    };
+
+    users.groups.avahi = {};
+
+    system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
+    system.nssDatabases.hosts = optionals cfg.nssmdns (mkMerge [
+      [ "mdns_minimal [NOTFOUND=return]" ]
+      (mkOrder 1501 [ "mdns" ]) # 1501 to ensure it's after dns
+    ]);
+
+    environment.systemPackages = [ pkgs.avahi ];
+
+    environment.etc = (mapAttrs' (n: v: nameValuePair
+      "avahi/services/${n}.service"
+      { ${if types.path.check v then "source" else "text"} = v; }
+    ) cfg.extraServiceFiles);
+
+    systemd.sockets.avahi-daemon = {
+      description = "Avahi mDNS/DNS-SD Stack Activation Socket";
+      listenStreams = [ "/run/avahi-daemon/socket" ];
+      wantedBy = [ "sockets.target" ];
+    };
+
+    systemd.tmpfiles.rules = [ "d /run/avahi-daemon - avahi avahi -" ];
+
+    systemd.services.avahi-daemon = {
+      description = "Avahi mDNS/DNS-SD Stack";
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "avahi-daemon.socket" ];
+
+      # Make NSS modules visible so that `avahi_nss_support ()' can
+      # return a sensible value.
+      environment.LD_LIBRARY_PATH = config.system.nssModules.path;
+
+      path = [ pkgs.coreutils pkgs.avahi ];
+
+      serviceConfig = {
+        NotifyAccess = "main";
+        BusName = "org.freedesktop.Avahi";
+        Type = "dbus";
+        ExecStart = "${pkgs.avahi}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}";
+      };
+    };
+
+    services.dbus.enable = true;
+    services.dbus.packages = [ pkgs.avahi ];
+
+    networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ 5353 ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/babeld.nix b/nixpkgs/nixos/modules/services/networking/babeld.nix
new file mode 100644
index 000000000000..e62c74d0069d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/babeld.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.babeld;
+
+  conditionalBoolToString = value: if (isBool value) then (boolToString value) else (toString value);
+
+  paramsString = params:
+    concatMapStringsSep " " (name: "${name} ${conditionalBoolToString (getAttr name params)}")
+                   (attrNames params);
+
+  interfaceConfig = name:
+    let
+      interface = getAttr name cfg.interfaces;
+    in
+    "interface ${name} ${paramsString interface}\n";
+
+  configFile = with cfg; pkgs.writeText "babeld.conf" (
+    (optionalString (cfg.interfaceDefaults != null) ''
+      default ${paramsString cfg.interfaceDefaults}
+    '')
+    + (concatMapStrings interfaceConfig (attrNames cfg.interfaces))
+    + extraConfig);
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.babeld = {
+
+      enable = mkEnableOption "the babeld network routing daemon";
+
+      interfaceDefaults = mkOption {
+        default = null;
+        description = ''
+          A set describing default parameters for babeld interfaces.
+          See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for options.
+        '';
+        type = types.nullOr (types.attrsOf types.unspecified);
+        example =
+          {
+            type = "tunnel";
+            split-horizon = true;
+          };
+      };
+
+      interfaces = mkOption {
+        default = {};
+        description = ''
+          A set describing babeld interfaces.
+          See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for options.
+        '';
+        type = types.attrsOf (types.attrsOf types.unspecified);
+        example =
+          { enp0s2 =
+            { type = "wired";
+              hello-interval = 5;
+              split-horizon = "auto";
+            };
+          };
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        description = ''
+          Options that will be copied to babeld.conf.
+          See <citerefentry><refentrytitle>babeld</refentrytitle><manvolnum>8</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.babeld.enable {
+
+    systemd.services.babeld = {
+      description = "Babel routing daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${pkgs.babeld}/bin/babeld -c ${configFile}";
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/bind.nix b/nixpkgs/nixos/modules/services/networking/bind.nix
new file mode 100644
index 000000000000..faad88635759
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/bind.nix
@@ -0,0 +1,205 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.bind;
+
+  bindUser = "named";
+
+  confFile = pkgs.writeText "named.conf"
+    ''
+      include "/etc/bind/rndc.key";
+      controls {
+        inet 127.0.0.1 allow {localhost;} keys {"rndc-key";};
+      };
+
+      acl cachenetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} };
+      acl badnetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} };
+
+      options {
+        listen-on { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOn} };
+        listen-on-v6 { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6} };
+        allow-query { cachenetworks; };
+        blackhole { badnetworks; };
+        forward first;
+        forwarders { ${concatMapStrings (entry: " ${entry}; ") cfg.forwarders} };
+        directory "/run/named";
+        pid-file "/run/named/named.pid";
+        ${cfg.extraOptions}
+      };
+
+      ${cfg.extraConfig}
+
+      ${ concatMapStrings
+          ({ name, file, master ? true, slaves ? [], masters ? [], extraConfig ? "" }:
+            ''
+              zone "${name}" {
+                type ${if master then "master" else "slave"};
+                file "${file}";
+                ${ if master then
+                   ''
+                     allow-transfer {
+                       ${concatMapStrings (ip: "${ip};\n") slaves}
+                     };
+                   ''
+                   else
+                   ''
+                     masters {
+                       ${concatMapStrings (ip: "${ip};\n") masters}
+                     };
+                   ''
+                }
+                allow-query { any; };
+                ${extraConfig}
+              };
+            '')
+          cfg.zones }
+    '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.bind = {
+
+      enable = mkEnableOption "BIND domain name server";
+
+      cacheNetworks = mkOption {
+        default = ["127.0.0.0/24"];
+        description = "
+          What networks are allowed to use us as a resolver.  Note
+          that this is for recursive queries -- all networks are
+          allowed to query zones configured with the `zones` option.
+          It is recommended that you limit cacheNetworks to avoid your
+          server being used for DNS amplification attacks.
+        ";
+      };
+
+      blockedNetworks = mkOption {
+        default = [];
+        description = "
+          What networks are just blocked.
+        ";
+      };
+
+      ipv4Only = mkOption {
+        default = false;
+        description = "
+          Only use ipv4, even if the host supports ipv6.
+        ";
+      };
+
+      forwarders = mkOption {
+        default = config.networking.nameservers;
+        description = "
+          List of servers we should forward requests to.
+        ";
+      };
+
+      listenOn = mkOption {
+        default = ["any"];
+        type = types.listOf types.str;
+        description = "
+          Interfaces to listen on.
+        ";
+      };
+
+      listenOnIpv6 = mkOption {
+        default = ["any"];
+        type = types.listOf types.str;
+        description = "
+          Ipv6 interfaces to listen on.
+        ";
+      };
+
+      zones = mkOption {
+        default = [];
+        description = "
+          List of zones we claim authority over.
+            master=false means slave server; slaves means addresses
+           who may request zone transfer.
+        ";
+        example = [{
+          name = "example.com";
+          master = false;
+          file = "/var/dns/example.com";
+          masters = ["192.168.0.1"];
+          slaves = [];
+          extraConfig = "";
+        }];
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Extra lines to be added verbatim to the generated named configuration file.
+        ";
+      };
+
+      extraOptions = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra lines to be added verbatim to the options section of the
+          generated named configuration file.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        default = confFile;
+        defaultText = "confFile";
+        description = "
+          Overridable config file to use for named. By default, that
+          generated by nixos.
+        ";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    networking.resolvconf.useLocalResolver = mkDefault true;
+
+    users.users.${bindUser} =
+      { uid = config.ids.uids.bind;
+        description = "BIND daemon user";
+      };
+
+    systemd.services.bind = {
+      description = "BIND Domain Name Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        mkdir -m 0755 -p /etc/bind
+        if ! [ -f "/etc/bind/rndc.key" ]; then
+          ${pkgs.bind.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null
+        fi
+
+        ${pkgs.coreutils}/bin/mkdir -p /run/named
+        chown ${bindUser} /run/named
+      '';
+
+      serviceConfig = {
+        ExecStart  = "${pkgs.bind.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f";
+        ExecReload = "${pkgs.bind.out}/sbin/rndc -k '/etc/bind/rndc.key' reload";
+        ExecStop   = "${pkgs.bind.out}/sbin/rndc -k '/etc/bind/rndc.key' stop";
+      };
+
+      unitConfig.Documentation = "man:named(8)";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/bird.nix b/nixpkgs/nixos/modules/services/networking/bird.nix
new file mode 100644
index 000000000000..4ae35875c0f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/bird.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mkEnableOption mkIf mkOption types;
+
+  generic = variant:
+    let
+      cfg = config.services.${variant};
+      pkg = pkgs.${variant};
+      birdBin = if variant == "bird6" then "bird6" else "bird";
+      birdc = if variant == "bird6" then "birdc6" else "birdc";
+      descr =
+        { bird = "1.9.x with IPv4 suport";
+          bird6 = "1.9.x with IPv6 suport";
+          bird2 = "2.x";
+        }.${variant};
+    in {
+      ###### interface
+      options = {
+        services.${variant} = {
+          enable = mkEnableOption "BIRD Internet Routing Daemon (${descr})";
+          config = mkOption {
+            type = types.lines;
+            description = ''
+              BIRD Internet Routing Daemon configuration file.
+              <link xlink:href='http://bird.network.cz/'/>
+            '';
+          };
+        };
+      };
+
+      ###### implementation
+      config = mkIf cfg.enable {
+        environment.systemPackages = [ pkg ];
+
+        environment.etc."bird/${variant}.conf".source = pkgs.writeTextFile {
+          name = "${variant}.conf";
+          text = cfg.config;
+          checkPhase = ''
+            ${pkg}/bin/${birdBin} -d -p -c $out
+          '';
+        };
+
+        systemd.services.${variant} = {
+          description = "BIRD Internet Routing Daemon (${descr})";
+          wantedBy = [ "multi-user.target" ];
+          reloadIfChanged = true;
+          restartTriggers = [ config.environment.etc."bird/${variant}.conf".source ];
+          serviceConfig = {
+            Type = "forking";
+            Restart = "on-failure";
+            ExecStart = "${pkg}/bin/${birdBin} -c /etc/bird/${variant}.conf -u ${variant} -g ${variant}";
+            ExecReload = "${pkg}/bin/${birdc} configure";
+            ExecStop = "${pkg}/bin/${birdc} down";
+            CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_SETUID" "CAP_SETGID"
+                                      # see bird/sysdep/linux/syspriv.h
+                                      "CAP_NET_BIND_SERVICE" "CAP_NET_BROADCAST" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
+            ProtectSystem = "full";
+            ProtectHome = "yes";
+            SystemCallFilter="~@cpu-emulation @debug @keyring @module @mount @obsolete @raw-io";
+            MemoryDenyWriteExecute = "yes";
+          };
+        };
+        users = {
+          users.${variant} = {
+            description = "BIRD Internet Routing Daemon user";
+            group = variant;
+          };
+          groups.${variant} = {};
+        };
+      };
+    };
+
+in
+
+{
+  imports = map generic [ "bird" "bird6" "bird2" ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/bitcoind.nix b/nixpkgs/nixos/modules/services/networking/bitcoind.nix
new file mode 100644
index 000000000000..4e00a8865474
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/bitcoind.nix
@@ -0,0 +1,193 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.bitcoind;
+  pidFile = "${cfg.dataDir}/bitcoind.pid";
+  configFile = pkgs.writeText "bitcoin.conf" ''
+    ${optionalString cfg.testnet "testnet=1"}
+    ${optionalString (cfg.dbCache != null) "dbcache=${toString cfg.dbCache}"}
+    ${optionalString (cfg.prune != null) "prune=${toString cfg.prune}"}
+
+    # Connection options
+    ${optionalString (cfg.port != null) "port=${toString cfg.port}"}
+
+    # RPC server options
+    ${optionalString (cfg.rpc.port != null) "rpcport=${toString cfg.rpc.port}"}
+    ${concatMapStringsSep  "\n"
+      (rpcUser: "rpcauth=${rpcUser.name}:${rpcUser.passwordHMAC}")
+      (attrValues cfg.rpc.users)
+    }
+
+    # Extra config options (from bitcoind nixos service)
+    ${cfg.extraConfig}
+  '';
+  cmdlineOptions = escapeShellArgs [
+    "-conf=${cfg.configFile}"
+    "-datadir=${cfg.dataDir}"
+    "-pid=${pidFile}"
+  ];
+
+  rpcUserOpts = { name, ... }: {
+    options = {
+      name = mkOption {
+        type = types.str;
+        example = "alice";
+        description = ''
+          Username for JSON-RPC connections.
+        '';
+      };
+      passwordHMAC = mkOption {
+        type = with types; uniq (strMatching "[0-9a-f]+\\$[0-9a-f]{64}");
+        example = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
+        description = ''
+          Password HMAC-SHA-256 for JSON-RPC connections. Must be a string of the
+          format &lt;SALT-HEX&gt;$&lt;HMAC-HEX&gt;.
+        '';
+      };
+    };
+    config = {
+      name = mkDefault name;
+    };
+  };
+in {
+  options = {
+
+    services.bitcoind = {
+      enable = mkEnableOption "Bitcoin daemon";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.bitcoind;
+        defaultText = "pkgs.bitcoind";
+        description = "The package providing bitcoin binaries.";
+      };
+      configFile = mkOption {
+        type = types.path;
+        default = configFile;
+        example = "/etc/bitcoind.conf";
+        description = "The configuration file path to supply bitcoind.";
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          par=16
+          rpcthreads=16
+          logips=1
+        '';
+        description = "Additional configurations to be appended to <filename>bitcoin.conf</filename>.";
+      };
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/bitcoind";
+        description = "The data directory for bitcoind.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "bitcoin";
+        description = "The user as which to run bitcoind.";
+      };
+      group = mkOption {
+        type = types.str;
+        default = cfg.user;
+        description = "The group as which to run bitcoind.";
+      };
+
+      rpc = {
+        port = mkOption {
+          type = types.nullOr types.port;
+          default = null;
+          description = "Override the default port on which to listen for JSON-RPC connections.";
+        };
+        users = mkOption {
+          default = {};
+          example = literalExample ''
+            {
+              alice.passwordHMAC = "f7efda5c189b999524f151318c0c86$d5b51b3beffbc02b724e5d095828e0bc8b2456e9ac8757ae3211a5d9b16a22ae";
+              bob.passwordHMAC = "b2dd077cb54591a2f3139e69a897ac$4e71f08d48b4347cf8eff3815c0e25ae2e9a4340474079f55705f40574f4ec99";
+            }
+          '';
+          type = with types; loaOf (submodule rpcUserOpts);
+          description = ''
+            RPC user information for JSON-RPC connnections.
+          '';
+        };
+      };
+
+      testnet = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to use the test chain.";
+      };
+      port = mkOption {
+        type = types.nullOr types.port;
+        default = null;
+        description = "Override the default port on which to listen for connections.";
+      };
+      dbCache = mkOption {
+        type = types.nullOr (types.ints.between 4 16384);
+        default = null;
+        example = 4000;
+        description = "Override the default database cache size in megabytes.";
+      };
+      prune = mkOption {
+        type = types.nullOr (types.coercedTo
+          (types.enum [ "disable" "manual" ])
+          (x: if x == "disable" then 0 else 1)
+          types.ints.unsigned
+        );
+        default = null;
+        example = 10000;
+        description = ''
+          Reduce storage requirements by enabling pruning (deleting) of old
+          blocks. This allows the pruneblockchain RPC to be called to delete
+          specific blocks, and enables automatic pruning of old blocks if a
+          target size in MiB is provided. This mode is incompatible with -txindex
+          and -rescan. Warning: Reverting this setting requires re-downloading
+          the entire blockchain. ("disable" = disable pruning blocks, "manual"
+          = allow manual pruning via RPC, >=550 = automatically prune block files
+          to stay under the specified target size in MiB)
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
+      "L '${cfg.dataDir}/bitcoin.conf' - - - - '${cfg.configFile}'"
+    ];
+    systemd.services.bitcoind = {
+      description = "Bitcoin daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/bitcoind ${cmdlineOptions}";
+        Restart = "on-failure";
+
+        # Hardening measures
+        PrivateTmp = "true";
+        ProtectSystem = "full";
+        NoNewPrivileges = "true";
+        PrivateDevices = "true";
+        MemoryDenyWriteExecute = "true";
+      };
+    };
+    users.users.${cfg.user} = {
+      name = cfg.user;
+      group = cfg.group;
+      description = "Bitcoin daemon user";
+      home = cfg.dataDir;
+      isSystemUser = true;
+    };
+    users.groups.${cfg.group} = {
+      name = cfg.group;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/bitlbee.nix b/nixpkgs/nixos/modules/services/networking/bitlbee.nix
new file mode 100644
index 000000000000..9ebf382fce42
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/bitlbee.nix
@@ -0,0 +1,193 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.bitlbee;
+  bitlbeeUid = config.ids.uids.bitlbee;
+
+  bitlbeePkg = pkgs.bitlbee.override {
+    enableLibPurple = cfg.libpurple_plugins != [];
+    enablePam = cfg.authBackend == "pam";
+  };
+
+  bitlbeeConfig = pkgs.writeText "bitlbee.conf"
+    ''
+    [settings]
+    RunMode = Daemon
+    User = bitlbee
+    ConfigDir = ${cfg.configDir}
+    DaemonInterface = ${cfg.interface}
+    DaemonPort = ${toString cfg.portNumber}
+    AuthMode = ${cfg.authMode}
+    AuthBackend = ${cfg.authBackend}
+    Plugindir = ${pkgs.bitlbee-plugins cfg.plugins}/lib/bitlbee
+    ${lib.optionalString (cfg.hostName != "") "HostName = ${cfg.hostName}"}
+    ${lib.optionalString (cfg.protocols != "") "Protocols = ${cfg.protocols}"}
+    ${cfg.extraSettings}
+
+    [defaults]
+    ${cfg.extraDefaults}
+    '';
+
+  purple_plugin_path =
+    lib.concatMapStringsSep ":"
+      (plugin: "${plugin}/lib/pidgin/:${plugin}/lib/purple-2/")
+      cfg.libpurple_plugins
+    ;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.bitlbee = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run the BitlBee IRC to other chat network gateway.
+          Running it allows you to access the MSN, Jabber, Yahoo! and ICQ chat
+          networks via an IRC client.
+        '';
+      };
+
+      interface = mkOption {
+        default = "127.0.0.1";
+        description = ''
+          The interface the BitlBee deamon will be listening to.  If `127.0.0.1',
+          only clients on the local host can connect to it; if `0.0.0.0', clients
+          can access it from any network interface.
+        '';
+      };
+
+      portNumber = mkOption {
+        default = 6667;
+        description = ''
+          Number of the port BitlBee will be listening to.
+        '';
+      };
+
+      authBackend = mkOption {
+        default = "storage";
+        type = types.enum [ "storage" "pam" ];
+        description = ''
+          How users are authenticated
+            storage -- save passwords internally
+            pam -- Linux PAM authentication
+        '';
+      };
+
+      authMode = mkOption {
+        default = "Open";
+        type = types.enum [ "Open" "Closed" "Registered" ];
+        description = ''
+          The following authentication modes are available:
+            Open -- Accept connections from anyone, use NickServ for user authentication.
+            Closed -- Require authorization (using the PASS command during login) before allowing the user to connect at all.
+            Registered -- Only allow registered users to use this server; this disables the register- and the account command until the user identifies himself.
+        '';
+      };
+
+      hostName = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Normally, BitlBee gets a hostname using getsockname(). If you have a nicer
+          alias for your BitlBee daemon, you can set it here and BitlBee will identify
+          itself with that name instead.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = literalExample "[ pkgs.bitlbee-facebook ]";
+        description = ''
+          The list of bitlbee plugins to install.
+        '';
+      };
+
+      libpurple_plugins = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = literalExample "[ pkgs.purple-matrix ]";
+        description = ''
+          The list of libpurple plugins to install.
+        '';
+      };
+
+      configDir = mkOption {
+        default = "/var/lib/bitlbee";
+        type = types.path;
+        description = ''
+          Specify an alternative directory to store all the per-user configuration
+          files.
+        '';
+      };
+
+      protocols = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          This option allows to remove the support of protocol, even if compiled
+          in. If nothing is given, there are no restrictions.
+        '';
+      };
+
+      extraSettings = mkOption {
+        default = "";
+        description = ''
+          Will be inserted in the Settings section of the config file.
+        '';
+      };
+
+      extraDefaults = mkOption {
+        default = "";
+        description = ''
+          Will be inserted in the Default section of the config file.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config =  mkMerge [
+    (mkIf config.services.bitlbee.enable {
+      users.users.bitlbee = {
+        uid = bitlbeeUid;
+        description = "BitlBee user";
+        home = "/var/lib/bitlbee";
+        createHome = true;
+      };
+
+      users.groups.bitlbee = {
+        gid = config.ids.gids.bitlbee;
+      };
+
+      systemd.services.bitlbee = {
+        environment.PURPLE_PLUGIN_PATH = purple_plugin_path;
+        description = "BitlBee IRC to other chat networks gateway";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig.User = "bitlbee";
+        serviceConfig.ExecStart = "${bitlbeePkg}/sbin/bitlbee -F -n -c ${bitlbeeConfig}";
+      };
+
+      environment.systemPackages = [ bitlbeePkg ];
+
+    })
+    (mkIf (config.services.bitlbee.authBackend == "pam") {
+      security.pam.services.bitlbee = {};
+    })
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/charybdis.nix b/nixpkgs/nixos/modules/services/networking/charybdis.nix
new file mode 100644
index 000000000000..43829d36e417
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/charybdis.nix
@@ -0,0 +1,107 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mkEnableOption mkIf mkOption singleton types;
+  inherit (pkgs) coreutils charybdis;
+  cfg = config.services.charybdis;
+
+  configFile = pkgs.writeText "charybdis.conf" ''
+    ${cfg.config}
+  '';
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.charybdis = {
+
+      enable = mkEnableOption "Charybdis IRC daemon";
+
+      config = mkOption {
+        type = types.str;
+        description = ''
+          Charybdis IRC daemon configuration file.
+        '';
+      };
+
+      statedir = mkOption {
+        type = types.path;
+        default = "/var/lib/charybdis";
+        description = ''
+          Location of the state directory of charybdis.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "ircd";
+        description = ''
+          Charybdis IRC daemon user.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "ircd";
+        description = ''
+          Charybdis IRC daemon group.
+        '';
+      };
+
+      motd = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = ''
+          Charybdis MOTD text.
+
+          Charybdis will read its MOTD from /etc/charybdis/ircd.motd .
+          If set, the value of this option will be written to this path.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable (lib.mkMerge [
+    {
+      users.users.${cfg.user} = {
+        description = "Charybdis IRC daemon user";
+        uid = config.ids.uids.ircd;
+        group = cfg.group;
+      };
+
+      users.groups.${cfg.group} = {
+        gid = config.ids.gids.ircd;
+      };
+
+      systemd.tmpfiles.rules = [
+        "d ${cfg.statedir} - ${cfg.user} ${cfg.group} - -"
+      ];
+
+      systemd.services.charybdis = {
+        description = "Charybdis IRC daemon";
+        wantedBy = [ "multi-user.target" ];
+        environment = {
+          BANDB_DBPATH = "${cfg.statedir}/ban.db";
+        };
+        serviceConfig = {
+          ExecStart   = "${charybdis}/bin/charybdis -foreground -logfile /dev/stdout -configfile ${configFile}";
+          Group = cfg.group;
+          User = cfg.user;
+        };
+      };
+
+    }
+
+    (mkIf (cfg.motd != null) {
+      environment.etc."charybdis/ircd.motd".text = cfg.motd;
+    })
+  ]);
+}
diff --git a/nixpkgs/nixos/modules/services/networking/cjdns.nix b/nixpkgs/nixos/modules/services/networking/cjdns.nix
new file mode 100644
index 000000000000..5f8ac96b2292
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/cjdns.nix
@@ -0,0 +1,294 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  pkg = pkgs.cjdns;
+
+  cfg = config.services.cjdns;
+
+  connectToSubmodule =
+  { ... }:
+  { options =
+    { password = mkOption {
+      type = types.str;
+      description = "Authorized password to the opposite end of the tunnel.";
+      };
+      publicKey = mkOption {
+        type = types.str;
+        description = "Public key at the opposite end of the tunnel.";
+      };
+      hostname = mkOption {
+        default = "";
+        example = "foobar.hype";
+        type = types.str;
+        description = "Optional hostname to add to /etc/hosts; prevents reverse lookup failures.";
+      };
+    };
+  };
+
+  # Additional /etc/hosts entries for peers with an associated hostname
+  cjdnsExtraHosts = pkgs.runCommandNoCC "cjdns-hosts" {} ''
+    exec >$out
+    ${concatStringsSep "\n" (mapAttrsToList (k: v:
+        optionalString (v.hostname != "")
+          "echo $(${pkgs.cjdns}/bin/publictoip6 ${v.publicKey}) ${v.hostname}")
+        (cfg.ETHInterface.connectTo // cfg.UDPInterface.connectTo))}
+  '';
+
+  parseModules = x:
+    x // { connectTo = mapAttrs (name: value: { inherit (value) password publicKey; }) x.connectTo; };
+
+  cjdrouteConf = builtins.toJSON ( recursiveUpdate {
+    admin = {
+      bind = cfg.admin.bind;
+      password = "@CJDNS_ADMIN_PASSWORD@";
+    };
+    authorizedPasswords = map (p: { password = p; }) cfg.authorizedPasswords;
+    interfaces = {
+      ETHInterface = if (cfg.ETHInterface.bind != "") then [ (parseModules cfg.ETHInterface) ] else [ ];
+      UDPInterface = if (cfg.UDPInterface.bind != "") then [ (parseModules cfg.UDPInterface) ] else [ ];
+    };
+
+    privateKey = "@CJDNS_PRIVATE_KEY@";
+
+    resetAfterInactivitySeconds = 100;
+
+    router = {
+      interface = { type = "TUNInterface"; };
+      ipTunnel = {
+        allowedConnections = [];
+        outgoingConnections = [];
+      };
+    };
+
+    security = [ { exemptAngel = 1; setuser = "nobody"; } ];
+
+  } cfg.extraConfig);
+
+in
+
+{
+  options = {
+
+    services.cjdns = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the cjdns network encryption
+          and routing engine. A file at /etc/cjdns.keys will
+          be created if it does not exist to contain a random
+          secret key that your IPv6 address will be derived from.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        example = { router.interface.tunDevice = "tun10"; };
+        description = ''
+          Extra configuration, given as attrs, that will be merged recursively
+          with the rest of the JSON generated by this module, at the root node.
+        '';
+      };
+
+      confFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/etc/cjdroute.conf";
+        description = ''
+          Ignore all other cjdns options and load configuration from this file.
+        '';
+      };
+
+      authorizedPasswords = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [
+          "snyrfgkqsc98qh1y4s5hbu0j57xw5s0"
+          "z9md3t4p45mfrjzdjurxn4wuj0d8swv"
+          "49275fut6tmzu354pq70sr5b95qq0vj"
+        ];
+        description = ''
+          Any remote cjdns nodes that offer these passwords on
+          connection will be allowed to route through this node.
+        '';
+      };
+
+      admin = {
+        bind = mkOption {
+          type = types.str;
+          default = "127.0.0.1:11234";
+          description = ''
+            Bind the administration port to this address and port.
+          '';
+        };
+      };
+
+      UDPInterface = {
+        bind = mkOption {
+          type = types.str;
+          default = "";
+          example = "192.168.1.32:43211";
+          description = ''
+            Address and port to bind UDP tunnels to.
+          '';
+         };
+        connectTo = mkOption {
+          type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
+          default = { };
+          example = literalExample ''
+            {
+              "192.168.1.1:27313" = {
+                hostname = "homer.hype";
+                password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
+                publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
+              };
+            }
+          '';
+          description = ''
+            Credentials for making UDP tunnels.
+          '';
+        };
+      };
+
+      ETHInterface = {
+        bind = mkOption {
+          type = types.str;
+          default = "";
+          example = "eth0";
+          description =
+            ''
+              Bind to this device for native ethernet operation.
+              <literal>all</literal> is a pseudo-name which will try to connect to all devices.
+            '';
+        };
+
+        beacon = mkOption {
+          type = types.int;
+          default = 2;
+          description = ''
+            Auto-connect to other cjdns nodes on the same network.
+            Options:
+              0: Disabled.
+              1: Accept beacons, this will cause cjdns to accept incoming
+                 beacon messages and try connecting to the sender.
+              2: Accept and send beacons, this will cause cjdns to broadcast
+                 messages on the local network which contain a randomly
+                 generated per-session password, other nodes which have this
+                 set to 1 or 2 will hear the beacon messages and connect
+                 automatically.
+          '';
+        };
+
+        connectTo = mkOption {
+          type = types.attrsOf ( types.submodule ( connectToSubmodule ) );
+          default = { };
+          example = literalExample ''
+            {
+              "01:02:03:04:05:06" = {
+                hostname = "homer.hype";
+                password = "5kG15EfpdcKNX3f2GSQ0H1HC7yIfxoCoImnO5FHM";
+                publicKey = "371zpkgs8ss387tmr81q04mp0hg1skb51hw34vk1cq644mjqhup0.k";
+              };
+            }
+          '';
+          description = ''
+            Credentials for connecting look similar to UDP credientials
+            except they begin with the mac address.
+          '';
+        };
+      };
+
+      addExtraHosts = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to add cjdns peers with an associated hostname to
+          <filename>/etc/hosts</filename>.  Beware that enabling this
+          incurs heavy eval-time costs.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    boot.kernelModules = [ "tun" ];
+
+    # networking.firewall.allowedUDPPorts = ...
+
+    systemd.services.cjdns = {
+      description = "cjdns: routing engine designed for security, scalability, speed and ease of use";
+      wantedBy = [ "multi-user.target" "sleep.target"];
+      after = [ "network-online.target" ];
+      bindsTo = [ "network-online.target" ];
+
+      preStart = if cfg.confFile != null then "" else ''
+        [ -e /etc/cjdns.keys ] && source /etc/cjdns.keys
+
+        if [ -z "$CJDNS_PRIVATE_KEY" ]; then
+            shopt -s lastpipe
+            ${pkg}/bin/makekeys | { read private ipv6 public; }
+
+            umask 0077
+            echo "CJDNS_PRIVATE_KEY=$private" >> /etc/cjdns.keys
+            echo -e "CJDNS_IPV6=$ipv6\nCJDNS_PUBLIC_KEY=$public" > /etc/cjdns.public
+
+            chmod 600 /etc/cjdns.keys
+            chmod 444 /etc/cjdns.public
+        fi
+
+        if [ -z "$CJDNS_ADMIN_PASSWORD" ]; then
+            echo "CJDNS_ADMIN_PASSWORD=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 96)" \
+                >> /etc/cjdns.keys
+        fi
+      '';
+
+      script = (
+        if cfg.confFile != null then "${pkg}/bin/cjdroute < ${cfg.confFile}" else
+          ''
+            source /etc/cjdns.keys
+            (cat <<'EOF'
+            ${cjdrouteConf}
+            EOF
+            ) | sed \
+                -e "s/@CJDNS_ADMIN_PASSWORD@/$CJDNS_ADMIN_PASSWORD/g" \
+                -e "s/@CJDNS_PRIVATE_KEY@/$CJDNS_PRIVATE_KEY/g" \
+                | ${pkg}/bin/cjdroute
+         ''
+      );
+
+      serviceConfig = {
+        Type = "forking";
+        Restart = "always";
+        StartLimitInterval = 0;
+        RestartSec = 1;
+        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW CAP_SETUID";
+        ProtectSystem = true;
+        # Doesn't work on i686, causing service to fail
+        MemoryDenyWriteExecute = !pkgs.stdenv.isi686;
+        ProtectHome = true;
+        PrivateTmp = true;
+      };
+    };
+
+    networking.hostFiles = mkIf cfg.addExtraHosts [ cjdnsExtraHosts ];
+
+    assertions = [
+      { assertion = ( cfg.ETHInterface.bind != "" || cfg.UDPInterface.bind != "" || cfg.confFile != null );
+        message = "Neither cjdns.ETHInterface.bind nor cjdns.UDPInterface.bind defined.";
+      }
+      { assertion = config.networking.enableIPv6;
+        message = "networking.enableIPv6 must be enabled for CJDNS to work";
+      }
+    ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/cntlm.nix b/nixpkgs/nixos/modules/services/networking/cntlm.nix
new file mode 100644
index 000000000000..5b5068e43d7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/cntlm.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.cntlm;
+
+  configFile = if cfg.configText != "" then
+    pkgs.writeText "cntlm.conf" ''
+      ${cfg.configText}
+    ''
+    else
+    pkgs.writeText "lighttpd.conf" ''
+      # Cntlm Authentication Proxy Configuration
+      Username ${cfg.username}
+      Domain ${cfg.domain}
+      Password ${cfg.password}
+      ${optionalString (cfg.netbios_hostname != "") "Workstation ${cfg.netbios_hostname}"}
+      ${concatMapStrings (entry: "Proxy ${entry}\n") cfg.proxy}
+      ${optionalString (cfg.noproxy != []) "NoProxy ${concatStringsSep ", " cfg.noproxy}"}
+
+      ${concatMapStrings (port: ''
+        Listen ${toString port}
+      '') cfg.port}
+
+      ${cfg.extraConfig}
+    '';
+
+in
+
+{
+
+  options.services.cntlm = {
+
+    enable = mkEnableOption "cntlm, which starts a local proxy";
+
+    username = mkOption {
+      description = ''
+        Proxy account name, without the possibility to include domain name ('at' sign is interpreted literally).
+      '';
+    };
+
+    domain = mkOption {
+      description = ''Proxy account domain/workgroup name.'';
+    };
+
+    password = mkOption {
+      default = "/etc/cntlm.password";
+      type = types.str;
+      description = ''Proxy account password. Note: use chmod 0600 on /etc/cntlm.password for security.'';
+    };
+
+    netbios_hostname = mkOption {
+      type = types.str;
+      default = "";
+      description = ''
+        The hostname of your machine.
+      '';
+    };
+
+    proxy = mkOption {
+      description = ''
+        A list of NTLM/NTLMv2 authenticating HTTP proxies.
+
+        Parent proxy, which requires authentication. The same as proxy on the command-line, can be used more than  once  to  specify  unlimited
+        number  of  proxies.  Should  one proxy fail, cntlm automatically moves on to the next one. The connect request fails only if the whole
+        list of proxies is scanned and (for each request) and found to be invalid. Command-line takes precedence over the configuration file.
+      '';
+      example = [ "proxy.example.com:81" ];
+    };
+
+    noproxy = mkOption {
+      description = ''
+        A list of domains where the proxy is skipped.
+      '';
+      default = [];
+      example = [ "*.example.com" "example.com" ];
+    };
+
+    port = mkOption {
+      default = [3128];
+      description = "Specifies on which ports the cntlm daemon listens.";
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Additional config appended to the end of the generated <filename>cntlm.conf</filename>.";
+    };
+
+    configText = mkOption {
+       type = types.lines;
+       default = "";
+       description = "Verbatim contents of <filename>cntlm.conf</filename>.";
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.cntlm = {
+      description = "CNTLM is an NTLM / NTLM Session Response / NTLMv2 authenticating HTTP proxy";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = "cntlm";
+        ExecStart = ''
+          ${pkgs.cntlm}/bin/cntlm -U cntlm -c ${configFile} -v -f
+        '';
+      };
+    };
+
+    users.users.cntlm = {
+      name = "cntlm";
+      description = "cntlm system-wide daemon";
+      isSystemUser = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/connman.nix b/nixpkgs/nixos/modules/services/networking/connman.nix
new file mode 100644
index 000000000000..6ccc2dffb267
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/connman.nix
@@ -0,0 +1,161 @@
+{ config, lib, pkgs, ... }:
+
+with pkgs;
+with lib;
+
+let
+  cfg = config.services.connman;
+  configFile = pkgs.writeText "connman.conf" ''
+    [General]
+    NetworkInterfaceBlacklist=${concatStringsSep "," cfg.networkInterfaceBlacklist}
+
+    ${cfg.extraConfig}
+  '';
+  enableIwd = cfg.wifi.backend == "iwd";
+in {
+
+  imports = [
+    (mkRenamedOptionModule [ "networking" "connman" ] [ "services" "connman" ])
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.connman = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use ConnMan for managing your network connections.
+        '';
+      };
+
+      enableVPN = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable ConnMan VPN service.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = ''
+        '';
+        description = ''
+          Configuration lines appended to the generated connman configuration file.
+        '';
+      };
+
+      networkInterfaceBlacklist = mkOption {
+        type = with types; listOf str;
+        default = [ "vmnet" "vboxnet" "virbr" "ifb" "ve" ];
+        description = ''
+          Default blacklisted interfaces, this includes NixOS containers interfaces (ve).
+        '';
+      };
+
+      wifi = {
+        backend = mkOption {
+          type = types.enum [ "wpa_supplicant" "iwd" ];
+          default = "wpa_supplicant";
+          description = ''
+            Specify the Wi-Fi backend used.
+            Currently supported are <option>wpa_supplicant</option> or <option>iwd</option>.
+          '';
+        };
+      };
+
+      extraFlags = mkOption {
+        type = with types; listOf str;
+        default = [ ];
+        example = [ "--nodnsproxy" ];
+        description = ''
+          Extra flags to pass to connmand
+        '';
+      };
+
+      package = mkOption {
+        type = types.path;
+        description = "The connman package / build flavor";
+        default = connman;
+        example = literalExample "pkgs.connmanFull";
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [{
+      assertion = !config.networking.useDHCP;
+      message = "You can not use services.connman with networking.useDHCP";
+    }{
+      # TODO: connman seemingly can be used along network manager and
+      # connmanFull supports this - so this should be worked out somehow
+      assertion = !config.networking.networkmanager.enable;
+      message = "You can not use services.connman with networking.networkmanager";
+    }];
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.connman = {
+      description = "Connection service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "syslog.target" ] ++ optional enableIwd "iwd.service";
+      requires = optional enableIwd "iwd.service";
+      serviceConfig = {
+        Type = "dbus";
+        BusName = "net.connman";
+        Restart = "on-failure";
+        ExecStart = toString ([
+          "${cfg.package}/sbin/connmand"
+          "--config=${configFile}"
+          "--nodaemon"
+        ] ++ optional enableIwd "--wifi=iwd_agent"
+          ++ cfg.extraFlags);
+        StandardOutput = "null";
+      };
+    };
+
+    systemd.services.connman-vpn = mkIf cfg.enableVPN {
+      description = "ConnMan VPN service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "syslog.target" ];
+      before = [ "connman" ];
+      serviceConfig = {
+        Type = "dbus";
+        BusName = "net.connman.vpn";
+        ExecStart = "${cfg.package}/sbin/connman-vpnd -n";
+        StandardOutput = "null";
+      };
+    };
+
+    systemd.services.net-connman-vpn = mkIf cfg.enableVPN {
+      description = "D-BUS Service";
+      serviceConfig = {
+        Name = "net.connman.vpn";
+        before = [ "connman" ];
+        ExecStart = "${cfg.package}/sbin/connman-vpnd -n";
+        User = "root";
+        SystemdService = "connman-vpn.service";
+      };
+    };
+
+    networking = {
+      useDHCP = false;
+      wireless = {
+        enable = mkIf (!enableIwd) true;
+        iwd = mkIf enableIwd {
+          enable = true;
+        };
+      };
+      networkmanager.enable = false;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/consul.nix b/nixpkgs/nixos/modules/services/networking/consul.nix
new file mode 100644
index 000000000000..f7d2afead06c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/consul.nix
@@ -0,0 +1,254 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+let
+
+  dataDir = "/var/lib/consul";
+  cfg = config.services.consul;
+
+  configOptions = {
+    data_dir = dataDir;
+    ui = cfg.webUi;
+  } // cfg.extraConfig;
+
+  configFiles = [ "/etc/consul.json" "/etc/consul-addrs.json" ]
+    ++ cfg.extraConfigFiles;
+
+  devices = attrValues (filterAttrs (_: i: i != null) cfg.interface);
+  systemdDevices = forEach devices
+    (i: "sys-subsystem-net-devices-${utils.escapeSystemdPath i}.device");
+in
+{
+  options = {
+
+    services.consul = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enables the consul daemon.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.consul;
+        defaultText = "pkgs.consul";
+        description = ''
+          The package used for the Consul agent and CLI.
+        '';
+      };
+
+
+      webUi = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enables the web interface on the consul http port.
+        '';
+      };
+
+      leaveOnStop = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If enabled, causes a leave action to be sent when closing consul.
+          This allows a clean termination of the node, but permanently removes
+          it from the cluster. You probably don't want this option unless you
+          are running a node which going offline in a permanent / semi-permanent
+          fashion.
+        '';
+      };
+
+      interface = {
+
+        advertise = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            The name of the interface to pull the advertise_addr from.
+          '';
+        };
+
+        bind = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            The name of the interface to pull the bind_addr from.
+          '';
+        };
+
+      };
+
+      forceIpv4 = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether we should force the interfaces to only pull ipv4 addresses.
+        '';
+      };
+
+      dropPrivileges = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether the consul agent should be run as a non-root consul user.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = { };
+        description = ''
+          Extra configuration options which are serialized to json and added
+          to the config.json file.
+        '';
+      };
+
+      extraConfigFiles = mkOption {
+        default = [ ];
+        type = types.listOf types.str;
+        description = ''
+          Additional configuration files to pass to consul
+          NOTE: These will not trigger the service to be restarted when altered.
+        '';
+      };
+
+      alerts = {
+        enable = mkEnableOption "consul-alerts";
+
+        package = mkOption {
+          description = "Package to use for consul-alerts.";
+          default = pkgs.consul-alerts;
+          defaultText = "pkgs.consul-alerts";
+          type = types.package;
+        };
+
+        listenAddr = mkOption {
+          description = "Api listening address.";
+          default = "localhost:9000";
+          type = types.str;
+        };
+
+        consulAddr = mkOption {
+          description = "Consul api listening adddress";
+          default = "localhost:8500";
+          type = types.str;
+        };
+
+        watchChecks = mkOption {
+          description = "Whether to enable check watcher.";
+          default = true;
+          type = types.bool;
+        };
+
+        watchEvents = mkOption {
+          description = "Whether to enable event watcher.";
+          default = true;
+          type = types.bool;
+        };
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable (
+    mkMerge [{
+
+      users.users.consul = {
+        description = "Consul agent daemon user";
+        uid = config.ids.uids.consul;
+        # The shell is needed for health checks
+        shell = "/run/current-system/sw/bin/bash";
+      };
+
+      environment = {
+        etc."consul.json".text = builtins.toJSON configOptions;
+        # We need consul.d to exist for consul to start
+        etc."consul.d/dummy.json".text = "{ }";
+        systemPackages = [ cfg.package ];
+      };
+
+      systemd.services.consul = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ] ++ systemdDevices;
+        bindsTo = systemdDevices;
+        restartTriggers = [ config.environment.etc."consul.json".source ]
+          ++ mapAttrsToList (_: d: d.source)
+            (filterAttrs (n: _: hasPrefix "consul.d/" n) config.environment.etc);
+
+        serviceConfig = {
+          ExecStart = "@${cfg.package}/bin/consul consul agent -config-dir /etc/consul.d"
+            + concatMapStrings (n: " -config-file ${n}") configFiles;
+          ExecReload = "${cfg.package}/bin/consul reload";
+          PermissionsStartOnly = true;
+          User = if cfg.dropPrivileges then "consul" else null;
+          Restart = "on-failure";
+          TimeoutStartSec = "infinity";
+        } // (optionalAttrs (cfg.leaveOnStop) {
+          ExecStop = "${cfg.package}/bin/consul leave";
+        });
+
+        path = with pkgs; [ iproute gnugrep gawk consul ];
+        preStart = ''
+          mkdir -m 0700 -p ${dataDir}
+          chown -R consul ${dataDir}
+
+          # Determine interface addresses
+          getAddrOnce () {
+            ip addr show dev "$1" \
+              | grep 'inet${optionalString (cfg.forceIpv4) " "}.*scope global' \
+              | awk -F '[ /\t]*' '{print $3}' | head -n 1
+          }
+          getAddr () {
+            ADDR="$(getAddrOnce $1)"
+            LEFT=60 # Die after 1 minute
+            while [ -z "$ADDR" ]; do
+              sleep 1
+              LEFT=$(expr $LEFT - 1)
+              if [ "$LEFT" -eq "0" ]; then
+                echo "Address lookup timed out"
+                exit 1
+              fi
+              ADDR="$(getAddrOnce $1)"
+            done
+            echo "$ADDR"
+          }
+          echo "{" > /etc/consul-addrs.json
+          delim=" "
+        ''
+        + concatStrings (flip mapAttrsToList cfg.interface (name: i:
+          optionalString (i != null) ''
+            echo "$delim \"${name}_addr\": \"$(getAddr "${i}")\"" >> /etc/consul-addrs.json
+            delim=","
+          ''))
+        + ''
+          echo "}" >> /etc/consul-addrs.json
+        '';
+      };
+    }
+
+    (mkIf (cfg.alerts.enable) {
+      systemd.services.consul-alerts = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "consul.service" ];
+
+        path = [ cfg.package ];
+
+        serviceConfig = {
+          ExecStart = ''
+            ${cfg.alerts.package}/bin/consul-alerts start \
+              --alert-addr=${cfg.alerts.listenAddr} \
+              --consul-addr=${cfg.alerts.consulAddr} \
+              ${optionalString cfg.alerts.watchChecks "--watch-checks"} \
+              ${optionalString cfg.alerts.watchEvents "--watch-events"}
+          '';
+          User = if cfg.dropPrivileges then "consul" else null;
+          Restart = "on-failure";
+        };
+      };
+    })
+
+  ]);
+}
diff --git a/nixpkgs/nixos/modules/services/networking/coredns.nix b/nixpkgs/nixos/modules/services/networking/coredns.nix
new file mode 100644
index 000000000000..afb2b547a465
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/coredns.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.coredns;
+  configFile = pkgs.writeText "Corefile" cfg.config;
+in {
+  options.services.coredns = {
+    enable = mkEnableOption "Coredns dns server";
+
+    config = mkOption {
+      default = "";
+      example = ''
+        . {
+          whoami
+        }
+      '';
+      type = types.lines;
+      description = "Verbatim Corefile to use. See <link xlink:href=\"https://coredns.io/manual/toc/#configuration\"/> for details.";
+    };
+
+    package = mkOption {
+      default = pkgs.coredns;
+      defaultText = "pkgs.coredns";
+      type = types.package;
+      description = "Coredns package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.coredns = {
+      description = "Coredns dns server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        PermissionsStartOnly = true;
+        LimitNPROC = 512;
+        LimitNOFILE = 1048576;
+        CapabilityBoundingSet = "cap_net_bind_service";
+        AmbientCapabilities = "cap_net_bind_service";
+        NoNewPrivileges = true;
+        DynamicUser = true;
+        ExecStart = "${getBin cfg.package}/bin/coredns -conf=${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -SIGUSR1 $MAINPID";
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/corerad.nix b/nixpkgs/nixos/modules/services/networking/corerad.nix
new file mode 100644
index 000000000000..5d73c0a0d779
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/corerad.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.corerad;
+
+  writeTOML = name: x:
+    pkgs.runCommandNoCCLocal name {
+      passAsFile = ["config"];
+      config = builtins.toJSON x;
+      buildInputs = [ pkgs.go-toml ];
+    } "jsontoml < $configPath > $out";
+
+in {
+  meta.maintainers = with maintainers; [ mdlayher ];
+
+  options.services.corerad = {
+    enable = mkEnableOption "CoreRAD IPv6 NDP RA daemon";
+
+    settings = mkOption {
+      type = types.uniq types.attrs;
+      example = literalExample ''
+        {
+          interfaces = [
+            # eth0 is an upstream interface monitoring for IPv6 router advertisements.
+            {
+              name = "eth0";
+              monitor = true;
+            }
+            # eth1 is a downstream interface advertising IPv6 prefixes for SLAAC.
+            {
+              name = "eth1";
+              advertise = true;
+              prefix = [{ prefix = "::/64"; }];
+            }
+          ];
+          # Optionally enable Prometheus metrics.
+          debug = {
+            address = "localhost:9430";
+            prometheus = true;
+          };
+        }
+      '';
+      description = ''
+        Configuration for CoreRAD, see <link xlink:href="https://github.com/mdlayher/corerad/blob/master/internal/config/default.toml"/>
+        for supported values. Ignored if configFile is set.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.path;
+      example = literalExample "\"\${pkgs.corerad}/etc/corerad/corerad.toml\"";
+      description = "Path to CoreRAD TOML configuration file.";
+    };
+
+    package = mkOption {
+      default = pkgs.corerad;
+      defaultText = literalExample "pkgs.corerad";
+      type = types.package;
+      description = "CoreRAD package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # Prefer the config file over settings if both are set.
+    services.corerad.configFile = mkDefault (writeTOML "corerad.toml" cfg.settings);
+
+    systemd.services.corerad = {
+      description = "CoreRAD IPv6 NDP RA daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        LimitNPROC = 512;
+        LimitNOFILE = 1048576;
+        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
+        AmbientCapabilities = "CAP_NET_ADMIN CAP_NET_RAW";
+        NoNewPrivileges = true;
+        DynamicUser = true;
+        ExecStart = "${getBin cfg.package}/bin/corerad -c=${cfg.configFile}";
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/coturn.nix b/nixpkgs/nixos/modules/services/networking/coturn.nix
new file mode 100644
index 000000000000..1bfbc307c59d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/coturn.nix
@@ -0,0 +1,334 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.coturn;
+  pidfile = "/run/turnserver/turnserver.pid";
+  configFile = pkgs.writeText "turnserver.conf" ''
+listening-port=${toString cfg.listening-port}
+tls-listening-port=${toString cfg.tls-listening-port}
+alt-listening-port=${toString cfg.alt-listening-port}
+alt-tls-listening-port=${toString cfg.alt-tls-listening-port}
+${concatStringsSep "\n" (map (x: "listening-ip=${x}") cfg.listening-ips)}
+${concatStringsSep "\n" (map (x: "relay-ip=${x}") cfg.relay-ips)}
+min-port=${toString cfg.min-port}
+max-port=${toString cfg.max-port}
+${lib.optionalString cfg.lt-cred-mech "lt-cred-mech"}
+${lib.optionalString cfg.no-auth "no-auth"}
+${lib.optionalString cfg.use-auth-secret "use-auth-secret"}
+${lib.optionalString (cfg.static-auth-secret != null) ("static-auth-secret=${cfg.static-auth-secret}")}
+realm=${cfg.realm}
+${lib.optionalString cfg.no-udp "no-udp"}
+${lib.optionalString cfg.no-tcp "no-tcp"}
+${lib.optionalString cfg.no-tls "no-tls"}
+${lib.optionalString cfg.no-dtls "no-dtls"}
+${lib.optionalString cfg.no-udp-relay "no-udp-relay"}
+${lib.optionalString cfg.no-tcp-relay "no-tcp-relay"}
+${lib.optionalString (cfg.cert != null) "cert=${cfg.cert}"}
+${lib.optionalString (cfg.pkey != null) "pkey=${cfg.pkey}"}
+${lib.optionalString (cfg.dh-file != null) ("dh-file=${cfg.dh-file}")}
+no-stdout-log
+syslog
+pidfile=${pidfile}
+${lib.optionalString cfg.secure-stun "secure-stun"}
+${lib.optionalString cfg.no-cli "no-cli"}
+cli-ip=${cfg.cli-ip}
+cli-port=${toString cfg.cli-port}
+${lib.optionalString (cfg.cli-password != null) ("cli-password=${cfg.cli-password}")}
+${cfg.extraConfig}
+'';
+in {
+  options = {
+    services.coturn = {
+      enable = mkEnableOption "coturn TURN server";
+      listening-port = mkOption {
+        type = types.int;
+        default = 3478;
+        description = ''
+          TURN listener port for UDP and TCP.
+          Note: actually, TLS and DTLS sessions can connect to the
+          "plain" TCP and UDP port(s), too - if allowed by configuration.
+        '';
+      };
+      tls-listening-port = mkOption {
+        type = types.int;
+        default = 5349;
+        description = ''
+          TURN listener port for TLS.
+          Note: actually, "plain" TCP and UDP sessions can connect to the TLS and
+          DTLS port(s), too - if allowed by configuration. The TURN server
+          "automatically" recognizes the type of traffic. Actually, two listening
+          endpoints (the "plain" one and the "tls" one) are equivalent in terms of
+          functionality; but we keep both endpoints to satisfy the RFC 5766 specs.
+          For secure TCP connections, we currently support SSL version 3 and
+          TLS version 1.0, 1.1 and 1.2.
+          For secure UDP connections, we support DTLS version 1.
+        '';
+      };
+      alt-listening-port = mkOption {
+        type = types.int;
+        default = cfg.listening-port + 1;
+        defaultText = "listening-port + 1";
+        description = ''
+          Alternative listening port for UDP and TCP listeners;
+          default (or zero) value means "listening port plus one".
+          This is needed for RFC 5780 support
+          (STUN extension specs, NAT behavior discovery). The TURN Server
+          supports RFC 5780 only if it is started with more than one
+          listening IP address of the same family (IPv4 or IPv6).
+          RFC 5780 is supported only by UDP protocol, other protocols
+          are listening to that endpoint only for "symmetry".
+        '';
+      };
+      alt-tls-listening-port = mkOption {
+        type = types.int;
+        default = cfg.tls-listening-port + 1;
+        defaultText = "tls-listening-port + 1";
+        description = ''
+          Alternative listening port for TLS and DTLS protocols.
+        '';
+      };
+      listening-ips = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "203.0.113.42" "2001:DB8::42" ];
+        description = ''
+          Listener IP addresses of relay server.
+          If no IP(s) specified in the config file or in the command line options,
+          then all IPv4 and IPv6 system IPs will be used for listening.
+        '';
+      };
+      relay-ips = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "203.0.113.42" "2001:DB8::42" ];
+        description = ''
+          Relay address (the local IP address that will be used to relay the
+          packets to the peer).
+          Multiple relay addresses may be used.
+          The same IP(s) can be used as both listening IP(s) and relay IP(s).
+
+          If no relay IP(s) specified, then the turnserver will apply the default
+          policy: it will decide itself which relay addresses to be used, and it
+          will always be using the client socket IP address as the relay IP address
+          of the TURN session (if the requested relay address family is the same
+          as the family of the client socket).
+        '';
+      };
+      min-port = mkOption {
+        type = types.int;
+        default = 49152;
+        description = ''
+          Lower bound of UDP relay endpoints
+        '';
+      };
+      max-port = mkOption {
+        type = types.int;
+        default = 65535;
+        description = ''
+          Upper bound of UDP relay endpoints
+        '';
+      };
+      lt-cred-mech = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Use long-term credential mechanism.
+        '';
+      };
+      no-auth = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          This option is opposite to lt-cred-mech.
+          (TURN Server with no-auth option allows anonymous access).
+          If neither option is defined, and no users are defined,
+          then no-auth is default. If at least one user is defined,
+          in this file or in command line or in usersdb file, then
+          lt-cred-mech is default.
+        '';
+      };
+      use-auth-secret = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          TURN REST API flag.
+          Flag that sets a special authorization option that is based upon authentication secret.
+          This feature can be used with the long-term authentication mechanism, only.
+          This feature purpose is to support "TURN Server REST API", see
+          "TURN REST API" link in the project's page
+          https://github.com/coturn/coturn/
+
+          This option is used with timestamp:
+
+          usercombo -> "timestamp:userid"
+          turn user -> usercombo
+          turn password -> base64(hmac(secret key, usercombo))
+
+          This allows TURN credentials to be accounted for a specific user id.
+          If you don't have a suitable id, the timestamp alone can be used.
+          This option is just turning on secret-based authentication.
+          The actual value of the secret is defined either by option static-auth-secret,
+          or can be found in the turn_secret table in the database.
+        '';
+      };
+      static-auth-secret = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          'Static' authentication secret value (a string) for TURN REST API only.
+          If not set, then the turn server
+          will try to use the 'dynamic' value in turn_secret table
+          in user database (if present). The database-stored  value can be changed on-the-fly
+          by a separate program, so this is why that other mode is 'dynamic'.
+        '';
+      };
+      realm = mkOption {
+        type = types.str;
+        default = config.networking.hostName;
+        example = "example.com";
+        description = ''
+          The default realm to be used for the users when no explicit
+          origin/realm relationship was found in the database, or if the TURN
+          server is not using any database (just the commands-line settings
+          and the userdb file). Must be used with long-term credentials
+          mechanism or with TURN REST API.
+        '';
+      };
+      cert = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/acme/example.com/fullchain.pem";
+        description = ''
+          Certificate file in PEM format.
+        '';
+      };
+      pkey = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/acme/example.com/key.pem";
+        description = ''
+          Private key file in PEM format.
+        '';
+      };
+      dh-file = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Use custom DH TLS key, stored in PEM format in the file.
+        '';
+      };
+      secure-stun = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Require authentication of the STUN Binding request.
+          By default, the clients are allowed anonymous access to the STUN Binding functionality.
+        '';
+      };
+      no-cli = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Turn OFF the CLI support.
+        '';
+      };
+      cli-ip = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = ''
+          Local system IP address to be used for CLI server endpoint.
+        '';
+      };
+      cli-port = mkOption {
+        type = types.int;
+        default = 5766;
+        description = ''
+          CLI server port.
+        '';
+      };
+      cli-password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          CLI access password.
+          For the security reasons, it is recommended to use the encrypted
+          for of the password (see the -P command in the turnadmin utility).
+        '';
+      };
+      no-udp = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable UDP client listener";
+      };
+      no-tcp = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable TCP client listener";
+      };
+      no-tls = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable TLS client listener";
+      };
+      no-dtls = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable DTLS client listener";
+      };
+      no-udp-relay = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable UDP relay endpoints";
+      };
+      no-tcp-relay = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable TCP relay endpoints";
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional configuration options";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.turnserver =
+      { uid = config.ids.uids.turnserver;
+        description = "coturn TURN server user";
+      };
+    users.groups.turnserver =
+      { gid = config.ids.gids.turnserver;
+        members = [ "turnserver" ];
+      };
+
+    systemd.services.coturn = {
+      description = "coturn TURN server";
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      unitConfig = {
+        Documentation = "man:coturn(1) man:turnadmin(1) man:turnserver(1)";
+      };
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.coturn}/bin/turnserver -c ${configFile}";
+        RuntimeDirectory = "turnserver";
+        User = "turnserver";
+        Group = "turnserver";
+        AmbientCapabilities =
+          mkIf (
+            cfg.listening-port < 1024 ||
+            cfg.alt-listening-port < 1024 ||
+            cfg.tls-listening-port < 1024 ||
+            cfg.alt-tls-listening-port < 1024 ||
+            cfg.min-port < 1024
+          ) "cap_net_bind_service";
+        Restart = "on-abort";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dante.nix b/nixpkgs/nixos/modules/services/networking/dante.nix
new file mode 100644
index 000000000000..20d4faa1cdb1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dante.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.services.dante;
+  confFile = pkgs.writeText "dante-sockd.conf" ''
+    user.privileged: root
+    user.unprivileged: dante
+    logoutput: syslog
+
+    ${cfg.config}
+  '';
+in
+
+{
+  meta = {
+    maintainers = with maintainers; [ arobyn ];
+  };
+
+  options = {
+    services.dante = {
+      enable = mkEnableOption "Dante SOCKS proxy";
+
+      config = mkOption {
+        type        = types.lines;
+        description = ''
+          Contents of Dante's configuration file.
+          NOTE: user.privileged, user.unprivileged and logoutput are set by the service.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion   = cfg.config != "";
+        message     = "please provide Dante configuration file contents";
+      }
+    ];
+
+    users.users.dante = {
+      description   = "Dante SOCKS proxy daemon user";
+      isSystemUser  = true;
+      group         = "dante";
+    };
+    users.groups.dante = {};
+
+    systemd.services.dante = {
+      description   = "Dante SOCKS v4 and v5 compatible proxy server";
+      after         = [ "network-online.target" ];
+      wantedBy      = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type        = "simple";
+        ExecStart   = "${pkgs.dante}/bin/sockd -f ${confFile}";
+        ExecReload  = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        # Can crash sometimes; see https://github.com/NixOS/nixpkgs/pull/39005#issuecomment-381828708
+        Restart     = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ddclient.nix b/nixpkgs/nixos/modules/services/networking/ddclient.nix
new file mode 100644
index 000000000000..053efe712709
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ddclient.nix
@@ -0,0 +1,209 @@
+{ config, pkgs, lib, ... }:
+
+let
+  cfg = config.services.ddclient;
+  boolToStr = bool: if bool then "yes" else "no";
+  dataDir = "/var/lib/ddclient";
+
+  configText = ''
+    # This file can be used as a template for configFile or is automatically generated by Nix options.
+    cache=${dataDir}/ddclient.cache
+    foreground=YES
+    use=${cfg.use}
+    login=${cfg.username}
+    password=${cfg.password}
+    protocol=${cfg.protocol}
+    ${lib.optionalString (cfg.script != "") "script=${cfg.script}"}
+    ${lib.optionalString (cfg.server != "") "server=${cfg.server}"}
+    ${lib.optionalString (cfg.zone != "")   "zone=${cfg.zone}"}
+    ssl=${boolToStr cfg.ssl}
+    wildcard=YES
+    quiet=${boolToStr cfg.quiet}
+    verbose=${boolToStr cfg.verbose}
+    ${cfg.extraConfig}
+    ${lib.concatStringsSep "," cfg.domains}
+  '';
+
+in
+
+with lib;
+
+{
+
+  imports = [
+    (mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]
+      (config:
+        let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
+        in if value != "" then [ value ] else []))
+    (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.ddclient = with lib.types; {
+
+      enable = mkOption {
+        default = false;
+        type = bool;
+        description = ''
+          Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org).
+        '';
+      };
+
+      domains = mkOption {
+        default = [ "" ];
+        type = listOf str;
+        description = ''
+          Domain name(s) to synchronize.
+        '';
+      };
+
+      username = mkOption {
+        default = "";
+        type = str;
+        description = ''
+          User name.
+        '';
+      };
+
+      password = mkOption {
+        default = "";
+        type = str;
+        description = ''
+          Password. WARNING: The password becomes world readable in the Nix store.
+        '';
+      };
+
+      interval = mkOption {
+        default = "10min";
+        type = str;
+        description = ''
+          The interval at which to run the check and update.
+          See <command>man 7 systemd.time</command> for the format.
+        '';
+      };
+
+      configFile = mkOption {
+        default = "/etc/ddclient.conf";
+        type = path;
+        description = ''
+          Path to configuration file.
+          When set to the default '/etc/ddclient.conf' it will be populated with the various other options in this module. When it is changed (for example: '/root/nixos/secrets/ddclient.conf') the file read directly to configure ddclient. This is a source of impurity.
+          The purpose of this is to avoid placing secrets into the store.
+        '';
+        example = "/root/nixos/secrets/ddclient.conf";
+      };
+
+      protocol = mkOption {
+        default = "dyndns2";
+        type = str;
+        description = ''
+          Protocol to use with dynamic DNS provider (see https://sourceforge.net/p/ddclient/wiki/protocols).
+        '';
+      };
+
+      server = mkOption {
+        default = "";
+        type = str;
+        description = ''
+          Server address.
+        '';
+      };
+
+      ssl = mkOption {
+        default = true;
+        type = bool;
+        description = ''
+          Whether to use to use SSL/TLS to connect to dynamic DNS provider.
+        '';
+      };
+
+
+      quiet = mkOption {
+        default = false;
+        type = bool;
+        description = ''
+          Print no messages for unnecessary updates.
+        '';
+      };
+
+      script = mkOption {
+        default = "";
+        type = str;
+        description = ''
+          script as required by some providers.
+        '';
+      };
+
+      use = mkOption {
+        default = "web, web=checkip.dyndns.com/, web-skip='Current IP Address: '";
+        type = str;
+        description = ''
+          Method to determine the IP address to send to the dynamic DNS provider.
+        '';
+      };
+
+      verbose = mkOption {
+        default = true;
+        type = bool;
+        description = ''
+          Print verbose information.
+        '';
+      };
+
+      zone = mkOption {
+        default = "";
+        type = str;
+        description = ''
+          zone as required by some providers.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = lines;
+        description = ''
+          Extra configuration. Contents will be added verbatim to the configuration file.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.ddclient.enable {
+    environment.etc."ddclient.conf" = {
+      enable = cfg.configFile == "/etc/ddclient.conf";
+      mode = "0600";
+      text = configText;
+    };
+
+    systemd.services.ddclient = {
+      description = "Dynamic DNS Client";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      restartTriggers = [ config.environment.etc."ddclient.conf".source ];
+
+      serviceConfig = rec {
+        DynamicUser = true;
+        RuntimeDirectory = StateDirectory;
+        StateDirectory = builtins.baseNameOf dataDir;
+        Type = "oneshot";
+        ExecStartPre = "!${lib.getBin pkgs.coreutils}/bin/install -m666 ${cfg.configFile} /run/${RuntimeDirectory}/ddclient.conf";
+        ExecStart = "${lib.getBin pkgs.ddclient}/bin/ddclient -file /run/${RuntimeDirectory}/ddclient.conf";
+      };
+    };
+
+    systemd.timers.ddclient = {
+      description = "Run ddclient";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnBootSec = cfg.interval;
+        OnUnitInactiveSec = cfg.interval;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dhcpcd.nix b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
new file mode 100644
index 000000000000..0507b739d499
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dhcpcd.nix
@@ -0,0 +1,225 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  dhcpcd = if !config.boot.isContainer then pkgs.dhcpcd else pkgs.dhcpcd.override { udev = null; };
+
+  cfg = config.networking.dhcpcd;
+
+  interfaces = attrValues config.networking.interfaces;
+
+  enableDHCP = config.networking.dhcpcd.enable &&
+        (config.networking.useDHCP || any (i: i.useDHCP == true) interfaces);
+
+  # Don't start dhcpcd on explicitly configured interfaces or on
+  # interfaces that are part of a bridge, bond or sit device.
+  ignoredInterfaces =
+    map (i: i.name) (filter (i: if i.useDHCP != null then !i.useDHCP else i.ipv4.addresses != [ ]) interfaces)
+    ++ mapAttrsToList (i: _: i) config.networking.sits
+    ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bridges))
+    ++ flatten (concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues config.networking.vswitches))
+    ++ concatLists (attrValues (mapAttrs (n: v: v.interfaces) config.networking.bonds))
+    ++ config.networking.dhcpcd.denyInterfaces;
+
+  arrayAppendOrNull = a1: a2: if a1 == null && a2 == null then null
+    else if a1 == null then a2 else if a2 == null then a1
+      else a1 ++ a2;
+
+  # If dhcp is disabled but explicit interfaces are enabled,
+  # we need to provide dhcp just for those interfaces.
+  allowInterfaces = arrayAppendOrNull cfg.allowInterfaces
+    (if !config.networking.useDHCP && enableDHCP then
+      map (i: i.name) (filter (i: i.useDHCP == true) interfaces) else null);
+
+  # Config file adapted from the one that ships with dhcpcd.
+  dhcpcdConf = pkgs.writeText "dhcpcd.conf"
+    ''
+      # Inform the DHCP server of our hostname for DDNS.
+      hostname
+
+      # A list of options to request from the DHCP server.
+      option domain_name_servers, domain_name, domain_search, host_name
+      option classless_static_routes, ntp_servers, interface_mtu
+
+      # A ServerID is required by RFC2131.
+      # Commented out because of many non-compliant DHCP servers in the wild :(
+      #require dhcp_server_identifier
+
+      # A hook script is provided to lookup the hostname if not set by
+      # the DHCP server, but it should not be run by default.
+      nohook lookup-hostname
+
+      # Ignore peth* devices; on Xen, they're renamed physical
+      # Ethernet cards used for bridging.  Likewise for vif* and tap*
+      # (Xen) and virbr* and vnet* (libvirt).
+      denyinterfaces ${toString ignoredInterfaces} lo peth* vif* tap* tun* virbr* vnet* vboxnet* sit*
+
+      # Use the list of allowed interfaces if specified
+      ${optionalString (allowInterfaces != null) "allowinterfaces ${toString allowInterfaces}"}
+
+      # Immediately fork to background if specified, otherwise wait for IP address to be assigned
+      ${{
+        background = "background";
+        any = "waitip";
+        ipv4 = "waitip 4";
+        ipv6 = "waitip 6";
+        both = "waitip 4\nwaitip 6";
+        if-carrier-up = "";
+      }.${cfg.wait}}
+
+      ${cfg.extraConfig}
+    '';
+
+  exitHook = pkgs.writeText "dhcpcd.exit-hook"
+    ''
+      if [ "$reason" = BOUND -o "$reason" = REBOOT ]; then
+          # Restart ntpd.  We need to restart it to make sure that it
+          # will actually do something: if ntpd cannot resolve the
+          # server hostnames in its config file, then it will never do
+          # anything ever again ("couldn't resolve ..., giving up on
+          # it"), so we silently lose time synchronisation. This also
+          # applies to openntpd.
+          /run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service || true
+      fi
+
+      ${cfg.runHook}
+    '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    networking.dhcpcd.enable = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to enable dhcpcd for device configuration. This is mainly to
+        explicitly disable dhcpcd (for example when using networkd).
+      '';
+    };
+
+    networking.dhcpcd.persistent = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+          Whenever to leave interfaces configured on dhcpcd daemon
+          shutdown. Set to true if you have your root or store mounted
+          over the network or this machine accepts SSH connections
+          through DHCP interfaces and clients should be notified when
+          it shuts down.
+      '';
+    };
+
+    networking.dhcpcd.denyInterfaces = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+         Disable the DHCP client for any interface whose name matches
+         any of the shell glob patterns in this list. The purpose of
+         this option is to blacklist virtual interfaces such as those
+         created by Xen, libvirt, LXC, etc.
+      '';
+    };
+
+    networking.dhcpcd.allowInterfaces = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = ''
+         Enable the DHCP client for any interface whose name matches
+         any of the shell glob patterns in this list. Any interface not
+         explicitly matched by this pattern will be denied. This pattern only
+         applies when non-null.
+      '';
+    };
+
+    networking.dhcpcd.extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+         Literal string to append to the config file generated for dhcpcd.
+      '';
+    };
+
+    networking.dhcpcd.runHook = mkOption {
+      type = types.lines;
+      default = "";
+      example = "if [[ $reason =~ BOUND ]]; then echo $interface: Routers are $new_routers - were $old_routers; fi";
+      description = ''
+         Shell code that will be run after all other hooks. See
+         `man dhcpcd-run-hooks` for details on what is possible.
+      '';
+    };
+
+    networking.dhcpcd.wait = mkOption {
+      type = types.enum [ "background" "any" "ipv4" "ipv6" "both" "if-carrier-up" ];
+      default = "any";
+      description = ''
+        This option specifies when the dhcpcd service will fork to background.
+        If set to "background", dhcpcd will fork to background immediately.
+        If set to "ipv4" or "ipv6", dhcpcd will wait for the corresponding IP
+        address to be assigned. If set to "any", dhcpcd will wait for any type
+        (IPv4 or IPv6) to be assigned. If set to "both", dhcpcd will wait for
+        both an IPv4 and an IPv6 address before forking.
+        The option "if-carrier-up" is equivalent to "any" if either ethernet
+        is plugged nor WiFi is powered, and to "background" otherwise.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf enableDHCP {
+
+    systemd.services.dhcpcd = let
+      cfgN = config.networking;
+      hasDefaultGatewaySet = (cfgN.defaultGateway != null && cfgN.defaultGateway.address != "")
+                          && (!cfgN.enableIPv6 || (cfgN.defaultGateway6 != null && cfgN.defaultGateway6.address != ""));
+    in
+      { description = "DHCP Client";
+
+        wantedBy = [ "multi-user.target" ] ++ optional (!hasDefaultGatewaySet) "network-online.target";
+        wants = [ "network.target" "systemd-udev-settle.service" ];
+        before = [ "network-online.target" ];
+        after = [ "systemd-udev-settle.service" ];
+
+        restartTriggers = [ exitHook ];
+
+        # Stopping dhcpcd during a reconfiguration is undesirable
+        # because it brings down the network interfaces configured by
+        # dhcpcd.  So do a "systemctl restart" instead.
+        stopIfChanged = false;
+
+        path = [ dhcpcd pkgs.nettools pkgs.openresolv ];
+
+        unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+
+        serviceConfig =
+          { Type = "forking";
+            PIDFile = "/run/dhcpcd.pid";
+            ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd --quiet ${optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}";
+            ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind";
+            Restart = "always";
+          };
+      };
+
+    environment.systemPackages = [ dhcpcd ];
+
+    environment.etc."dhcpcd.exit-hook".source = exitHook;
+
+    powerManagement.resumeCommands = mkIf config.systemd.services.dhcpcd.enable
+      ''
+        # Tell dhcpcd to rebind its interfaces if it's running.
+        /run/current-system/systemd/bin/systemctl reload dhcpcd.service
+      '';
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dhcpd.nix b/nixpkgs/nixos/modules/services/networking/dhcpd.nix
new file mode 100644
index 000000000000..67f7d8118870
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dhcpd.nix
@@ -0,0 +1,214 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg4 = config.services.dhcpd4;
+  cfg6 = config.services.dhcpd6;
+
+  writeConfig = cfg: pkgs.writeText "dhcpd.conf"
+    ''
+      default-lease-time 600;
+      max-lease-time 7200;
+      authoritative;
+      ddns-update-style interim;
+      log-facility local1; # see dhcpd.nix
+
+      ${cfg.extraConfig}
+
+      ${lib.concatMapStrings
+          (machine: ''
+            host ${machine.hostName} {
+              hardware ethernet ${machine.ethernetAddress};
+              fixed-address ${machine.ipAddress};
+            }
+          '')
+          cfg.machines
+      }
+    '';
+
+  dhcpdService = postfix: cfg: optionalAttrs cfg.enable {
+    "dhcpd${postfix}" = {
+      description = "DHCPv${postfix} server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        mkdir -m 755 -p ${cfg.stateDir}
+        chown dhcpd:nogroup ${cfg.stateDir}
+        touch ${cfg.stateDir}/dhcpd.leases
+      '';
+
+      serviceConfig =
+        let
+          configFile = if cfg.configFile != null then cfg.configFile else writeConfig cfg;
+          args = [ "@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}"
+                   "-pf" "/run/dhcpd${postfix}/dhcpd.pid"
+                   "-cf" "${configFile}"
+                   "-lf" "${cfg.stateDir}/dhcpd.leases"
+                   "-user" "dhcpd" "-group" "nogroup"
+                 ] ++ cfg.extraFlags
+                   ++ cfg.interfaces;
+
+        in {
+          ExecStart = concatMapStringsSep " " escapeShellArg args;
+          Type = "forking";
+          Restart = "always";
+          RuntimeDirectory = [ "dhcpd${postfix}" ];
+          PIDFile = "/run/dhcpd${postfix}/dhcpd.pid";
+        };
+    };
+  };
+
+  machineOpts = { ... }: {
+
+    options = {
+
+      hostName = mkOption {
+        type = types.str;
+        example = "foo";
+        description = ''
+          Hostname which is assigned statically to the machine.
+        '';
+      };
+
+      ethernetAddress = mkOption {
+        type = types.str;
+        example = "00:16:76:9a:32:1d";
+        description = ''
+          MAC address of the machine.
+        '';
+      };
+
+      ipAddress = mkOption {
+        type = types.str;
+        example = "192.168.1.10";
+        description = ''
+          IP address of the machine.
+        '';
+      };
+
+    };
+  };
+
+  dhcpConfig = postfix: {
+
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable the DHCPv${postfix} server.
+      '';
+    };
+
+    stateDir = mkOption {
+      type = types.path;
+      # We use /var/lib/dhcp for DHCPv4 to save backwards compatibility.
+      default = "/var/lib/dhcp${if postfix == "4" then "" else postfix}";
+      description = ''
+        State directory for the DHCP server.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        option subnet-mask 255.255.255.0;
+        option broadcast-address 192.168.1.255;
+        option routers 192.168.1.5;
+        option domain-name-servers 130.161.158.4, 130.161.33.17, 130.161.180.1;
+        option domain-name "example.org";
+        subnet 192.168.1.0 netmask 255.255.255.0 {
+          range 192.168.1.100 192.168.1.200;
+        }
+      '';
+      description = ''
+        Extra text to be appended to the DHCP server configuration
+        file. Currently, you almost certainly need to specify something
+        there, such as the options specifying the subnet mask, DNS servers,
+        etc.
+      '';
+    };
+
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Additional command line flags to be passed to the dhcpd daemon.
+      '';
+    };
+
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        The path of the DHCP server configuration file.  If no file
+        is specified, a file is generated using the other options.
+      '';
+    };
+
+    interfaces = mkOption {
+      type = types.listOf types.str;
+      default = ["eth0"];
+      description = ''
+        The interfaces on which the DHCP server should listen.
+      '';
+    };
+
+    machines = mkOption {
+      type = with types; listOf (submodule machineOpts);
+      default = [];
+      example = [
+        { hostName = "foo";
+          ethernetAddress = "00:16:76:9a:32:1d";
+          ipAddress = "192.168.1.10";
+        }
+        { hostName = "bar";
+          ethernetAddress = "00:19:d1:1d:c4:9a";
+          ipAddress = "192.168.1.11";
+        }
+      ];
+      description = ''
+        A list mapping Ethernet addresses to IPv${postfix} addresses for the
+        DHCP server.
+      '';
+    };
+
+  };
+
+in
+
+{
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ])
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.dhcpd4 = dhcpConfig "4";
+    services.dhcpd6 = dhcpConfig "6";
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf (cfg4.enable || cfg6.enable) {
+
+    users = {
+      users.dhcpd = {
+        uid = config.ids.uids.dhcpd;
+        description = "DHCP daemon user";
+      };
+    };
+
+    systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnscache.nix b/nixpkgs/nixos/modules/services/networking/dnscache.nix
new file mode 100644
index 000000000000..d06032daecc7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnscache.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dnscache;
+
+  dnscache-root = pkgs.runCommand "dnscache-root" { preferLocalBuild = true; } ''
+    mkdir -p $out/{servers,ip}
+
+    ${concatMapStrings (ip: ''
+      touch "$out/ip/"${lib.escapeShellArg ip}
+    '') cfg.clientIps}
+
+    ${concatStrings (mapAttrsToList (host: ips: ''
+      ${concatMapStrings (ip: ''
+        echo ${lib.escapeShellArg ip} >> "$out/servers/"${lib.escapeShellArg host}
+      '') ips}
+    '') cfg.domainServers)}
+
+    # if a list of root servers was not provided in config, copy it
+    # over. (this is also done by dnscache-conf, but we 'rm -rf
+    # /var/lib/dnscache/root' below & replace it wholesale with this,
+    # so we have to ensure servers/@ exists ourselves.)
+    if [ ! -e $out/servers/@ ]; then
+      # symlink does not work here, due chroot
+      cp ${pkgs.djbdns}/etc/dnsroots.global $out/servers/@;
+    fi
+  '';
+
+in {
+
+  ###### interface
+
+  options = {
+    services.dnscache = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to run the dnscache caching dns server.";
+      };
+
+      ip = mkOption {
+        default = "0.0.0.0";
+        type = types.str;
+        description = "IP address on which to listen for connections.";
+      };
+
+      clientIps = mkOption {
+        default = [ "127.0.0.1" ];
+        type = types.listOf types.str;
+        description = "Client IP addresses (or prefixes) from which to accept connections.";
+        example = ["192.168" "172.23.75.82"];
+      };
+
+      domainServers = mkOption {
+        default = { };
+        type = types.attrsOf (types.listOf types.str);
+        description = ''
+          Table of {hostname: server} pairs to use as authoritative servers for hosts (and subhosts).
+          If entry for @ is not specified predefined list of root servers is used.
+        '';
+        example = literalExample ''
+          {
+            "@" = ["8.8.8.8" "8.8.4.4"];
+            "example.com" = ["192.168.100.100"];
+          }
+        '';
+      };
+
+      forwardOnly = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to treat root servers (for @) as caching
+          servers, requesting addresses the same way a client does. This is
+          needed if you want to use e.g. Google DNS as your upstream DNS.
+        '';
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.dnscache.enable {
+    environment.systemPackages = [ pkgs.djbdns ];
+    users.users.dnscache.isSystemUser = true;
+
+    systemd.services.dnscache = {
+      description = "djbdns dnscache server";
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [ bash daemontools djbdns ];
+      preStart = ''
+        rm -rf /var/lib/dnscache
+        dnscache-conf dnscache dnscache /var/lib/dnscache ${config.services.dnscache.ip}
+        rm -rf /var/lib/dnscache/root
+        ln -sf ${dnscache-root} /var/lib/dnscache/root
+      '';
+      script = ''
+        cd /var/lib/dnscache/
+        ${optionalString cfg.forwardOnly "export FORWARDONLY=1"}
+        exec ./run
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix
new file mode 100644
index 000000000000..28691e838277
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-proxy2.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }: with lib;
+
+let
+  cfg = config.services.dnscrypt-proxy2;
+in
+
+{
+  options.services.dnscrypt-proxy2 = {
+    enable = mkEnableOption "dnscrypt-proxy2";
+
+    settings = mkOption {
+      description = ''
+        Attrset that is converted and passed as TOML config file.
+        For available params, see: <link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>
+      '';
+      example = literalExample ''
+        {
+          sources.public-resolvers = {
+            urls = [ "https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md" ];
+            cache_file = "public-resolvers.md";
+            minisign_key = "RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3";
+            refresh_delay = 72;
+          };
+        }
+      '';
+      type = types.attrs;
+      default = {};
+    };
+
+    configFile = mkOption {
+      description = ''
+        Path to TOML config file. See: <link xlink:href="https://github.com/DNSCrypt/dnscrypt-proxy/blob/master/dnscrypt-proxy/example-dnscrypt-proxy.toml"/>
+        If this option is set, it will override any configuration done in options.services.dnscrypt-proxy2.settings.
+      '';
+      example = "/etc/dnscrypt-proxy/dnscrypt-proxy.toml";
+      type = types.path;
+      default = pkgs.runCommand "dnscrypt-proxy.toml" {
+        json = builtins.toJSON cfg.settings;
+        passAsFile = [ "json" ];
+      } ''
+        ${pkgs.remarshal}/bin/json2toml < $jsonPath > $out
+      '';
+      defaultText = literalExample "TOML file generated from services.dnscrypt-proxy2.settings";
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    networking.nameservers = lib.mkDefault [ "127.0.0.1" ];
+
+    systemd.services.dnscrypt-proxy2 = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        DynamicUser = true;
+        ExecStart = "${pkgs.dnscrypt-proxy2}/bin/dnscrypt-proxy -config ${cfg.configFile}";
+        Restart = "always";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
new file mode 100644
index 000000000000..b9333cd19a2a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnscrypt-wrapper.nix
@@ -0,0 +1,281 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg     = config.services.dnscrypt-wrapper;
+  dataDir = "/var/lib/dnscrypt-wrapper";
+
+  mkPath = path: default:
+    if path != null
+      then toString path
+      else default;
+
+  publicKey = mkPath cfg.providerKey.public "${dataDir}/public.key";
+  secretKey = mkPath cfg.providerKey.secret "${dataDir}/secret.key";
+
+  daemonArgs = with cfg; [
+    "--listen-address=${address}:${toString port}"
+    "--resolver-address=${upstream.address}:${toString upstream.port}"
+    "--provider-name=${providerName}"
+    "--provider-publickey-file=${publicKey}"
+    "--provider-secretkey-file=${secretKey}"
+    "--provider-cert-file=${providerName}.crt"
+    "--crypt-secretkey-file=${providerName}.key"
+  ];
+
+  genKeys = ''
+    # generates time-limited keypairs
+    keyGen() {
+      dnscrypt-wrapper --gen-crypt-keypair \
+        --crypt-secretkey-file=${cfg.providerName}.key
+
+      dnscrypt-wrapper --gen-cert-file \
+        --crypt-secretkey-file=${cfg.providerName}.key \
+        --provider-cert-file=${cfg.providerName}.crt \
+        --provider-publickey-file=${publicKey} \
+        --provider-secretkey-file=${secretKey} \
+        --cert-file-expire-days=${toString cfg.keys.expiration}
+    }
+
+    cd ${dataDir}
+
+    # generate provider keypair (first run only)
+    ${optionalString (cfg.providerKey.public == null || cfg.providerKey.secret == null) ''
+      if [ ! -f ${publicKey} ] || [ ! -f ${secretKey} ]; then
+        dnscrypt-wrapper --gen-provider-keypair
+      fi
+    ''}
+
+    # generate new keys for rotation
+    if [ ! -f ${cfg.providerName}.key ] || [ ! -f ${cfg.providerName}.crt ]; then
+      keyGen
+    fi
+  '';
+
+  rotateKeys = ''
+    # check if keys are not expired
+    keyValid() {
+      fingerprint=$(dnscrypt-wrapper --show-provider-publickey | awk '{print $(NF)}')
+      dnscrypt-proxy --test=${toString (cfg.keys.checkInterval + 1)} \
+        --resolver-address=127.0.0.1:${toString cfg.port} \
+        --provider-name=${cfg.providerName} \
+        --provider-key=$fingerprint
+    }
+
+    cd ${dataDir}
+
+    # archive old keys and restart the service
+    if ! keyValid; then
+      echo "certificate soon to become invalid; backing up old cert"
+      mkdir -p oldkeys
+      mv -v ${cfg.providerName}.key oldkeys/${cfg.providerName}-$(date +%F-%T).key
+      mv -v ${cfg.providerName}.crt oldkeys/${cfg.providerName}-$(date +%F-%T).crt
+      systemctl restart dnscrypt-wrapper
+    fi
+  '';
+
+
+  # This is the fork of the original dnscrypt-proxy maintained by Dyne.org.
+  # dnscrypt-proxy2 doesn't provide the `--test` feature that is needed to
+  # correctly implement key rotation of dnscrypt-wrapper ephemeral keys.
+  dnscrypt-proxy1 = pkgs.callPackage
+    ({ stdenv, fetchFromGitHub, autoreconfHook
+    , pkgconfig, libsodium, ldns, openssl, systemd }:
+
+    stdenv.mkDerivation rec {
+      pname = "dnscrypt-proxy";
+      version = "2019-08-20";
+
+      src = fetchFromGitHub {
+        owner = "dyne";
+        repo = "dnscrypt-proxy";
+        rev = "07ac3825b5069adc28e2547c16b1d983a8ed8d80";
+        sha256 = "0c4mq741q4rpmdn09agwmxap32kf0vgfz7pkhcdc5h54chc3g3xy";
+      };
+
+      configureFlags = optional stdenv.isLinux "--with-systemd";
+
+      nativeBuildInputs = [ autoreconfHook pkgconfig ];
+
+      # <ldns/ldns.h> depends on <openssl/ssl.h>
+      buildInputs = [ libsodium openssl.dev ldns ] ++ optional stdenv.isLinux systemd;
+
+      postInstall = ''
+        # Previous versions required libtool files to load plugins; they are
+        # now strictly optional.
+        rm $out/lib/dnscrypt-proxy/*.la
+      '';
+
+      meta = {
+        description = "A tool for securing communications between a client and a DNS resolver";
+        homepage = "https://github.com/dyne/dnscrypt-proxy";
+        license = licenses.isc;
+        maintainers = with maintainers; [ rnhmjoj ];
+        platforms = platforms.linux;
+      };
+    }) { };
+
+in {
+
+
+  ###### interface
+
+  options.services.dnscrypt-wrapper = {
+    enable = mkEnableOption "DNSCrypt wrapper";
+
+    address = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = ''
+        The DNSCrypt wrapper will bind to this IP address.
+      '';
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 5353;
+      description = ''
+        The DNSCrypt wrapper will listen for DNS queries on this port.
+      '';
+    };
+
+    providerName = mkOption {
+      type = types.str;
+      default = "2.dnscrypt-cert.${config.networking.hostName}";
+      example = "2.dnscrypt-cert.myresolver";
+      description = ''
+        The name that will be given to this DNSCrypt resolver.
+        Note: the resolver name must start with <literal>2.dnscrypt-cert.</literal>.
+      '';
+    };
+
+    providerKey.public = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/etc/secrets/public.key";
+      description = ''
+        The filepath to the provider public key. If not given a new
+        provider key pair will be generated on the first run.
+      '';
+    };
+
+    providerKey.secret = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/etc/secrets/secret.key";
+      description = ''
+        The filepath to the provider secret key. If not given a new
+        provider key pair will be generated on the first run.
+      '';
+    };
+
+    upstream.address = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = ''
+        The IP address of the upstream DNS server DNSCrypt will "wrap".
+      '';
+    };
+
+    upstream.port = mkOption {
+      type = types.int;
+      default = 53;
+      description = ''
+        The port of the upstream DNS server DNSCrypt will "wrap".
+      '';
+    };
+
+    keys.expiration = mkOption {
+      type = types.int;
+      default = 30;
+      description = ''
+        The duration (in days) of the time-limited secret key.
+        This will be automatically rotated before expiration.
+      '';
+    };
+
+    keys.checkInterval = mkOption {
+      type = types.int;
+      default = 1440;
+      description = ''
+        The time interval (in minutes) between key expiration checks.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.dnscrypt-wrapper = {
+      description = "dnscrypt-wrapper daemon user";
+      home = "${dataDir}";
+      createHome = true;
+      isSystemUser = true;
+    };
+    users.groups.dnscrypt-wrapper = { };
+
+    security.polkit.extraConfig = ''
+      // Allow dnscrypt-wrapper user to restart dnscrypt-wrapper.service
+      polkit.addRule(function(action, subject) {
+          if (action.id == "org.freedesktop.systemd1.manage-units" &&
+              action.lookup("unit") == "dnscrypt-wrapper.service" &&
+              subject.user == "dnscrypt-wrapper") {
+              return polkit.Result.YES;
+          }
+        });
+    '';
+
+    systemd.services.dnscrypt-wrapper = {
+      description = "dnscrypt-wrapper daemon";
+      after    = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path     = [ pkgs.dnscrypt-wrapper ];
+
+      serviceConfig = {
+        User = "dnscrypt-wrapper";
+        WorkingDirectory = dataDir;
+        Restart   = "on-failure";
+        ExecStart = "${pkgs.dnscrypt-wrapper}/bin/dnscrypt-wrapper ${toString daemonArgs}";
+      };
+
+      preStart = genKeys;
+    };
+
+
+    systemd.services.dnscrypt-wrapper-rotate = {
+      after    = [ "network.target" ];
+      requires = [ "dnscrypt-wrapper.service" ];
+      description = "Rotates DNSCrypt wrapper keys if soon to expire";
+
+      path   = with pkgs; [ dnscrypt-wrapper dnscrypt-proxy1 gawk ];
+      script = rotateKeys;
+      serviceConfig.User = "dnscrypt-wrapper";
+    };
+
+
+    systemd.timers.dnscrypt-wrapper-rotate = {
+      description = "Periodically check DNSCrypt wrapper keys for expiration";
+      wantedBy = [ "multi-user.target" ];
+
+      timerConfig = {
+        Unit = "dnscrypt-wrapper-rotate.service";
+        OnBootSec = "1min";
+        OnUnitActiveSec = cfg.keys.checkInterval * 60;
+      };
+    };
+
+    assertions = with cfg; [
+      { assertion = (providerKey.public == null && providerKey.secret == null) ||
+                    (providerKey.secret != null && providerKey.public != null);
+        message = "The secret and public provider key must be set together.";
+      }
+    ];
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnsdist.nix b/nixpkgs/nixos/modules/services/networking/dnsdist.nix
new file mode 100644
index 000000000000..8249da69bc1a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnsdist.nix
@@ -0,0 +1,60 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dnsdist;
+  configFile = pkgs.writeText "dndist.conf" ''
+    setLocal('${cfg.listenAddress}:${toString cfg.listenPort}')
+    ${cfg.extraConfig}
+    '';
+in {
+  options = {
+    services.dnsdist = {
+      enable = mkEnableOption "dnsdist domain name server";
+
+      listenAddress = mkOption {
+        type = types.str;
+        description = "Listen IP Address";
+        default = "0.0.0.0";
+      };
+      listenPort = mkOption {
+        type = types.int;
+        description = "Listen port";
+        default = 53;
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = ''
+        '';
+        description = ''
+          Extra lines to be added verbatim to dnsdist.conf.
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.dnsdist.enable {
+    systemd.services.dnsdist = {
+      description = "dnsdist load balancer";
+      wantedBy = [ "multi-user.target" ];
+      after = ["network.target"];
+
+      serviceConfig = {
+        Restart="on-failure";
+        RestartSec="1";
+        DynamicUser = true;
+        StartLimitInterval="0";
+        PrivateDevices=true;
+        AmbientCapabilities="CAP_NET_BIND_SERVICE";
+        CapabilityBoundingSet="CAP_NET_BIND_SERVICE";
+        ExecStart = "${pkgs.dnsdist}/bin/dnsdist --supervised --disable-syslog --config ${configFile}";
+        ProtectHome=true;
+        RestrictAddressFamilies="AF_UNIX AF_INET AF_INET6";
+        LimitNOFILE="16384";
+        TasksMax="8192";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/dnsmasq.nix b/nixpkgs/nixos/modules/services/networking/dnsmasq.nix
new file mode 100644
index 000000000000..377d7bc57058
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/dnsmasq.nix
@@ -0,0 +1,128 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.dnsmasq;
+  dnsmasq = pkgs.dnsmasq;
+  stateDir = "/var/lib/dnsmasq";
+
+  dnsmasqConf = pkgs.writeText "dnsmasq.conf" ''
+    dhcp-leasefile=${stateDir}/dnsmasq.leases
+    ${optionalString cfg.resolveLocalQueries ''
+      conf-file=/etc/dnsmasq-conf.conf
+      resolv-file=/etc/dnsmasq-resolv.conf
+    ''}
+    ${flip concatMapStrings cfg.servers (server: ''
+      server=${server}
+    '')}
+    ${cfg.extraConfig}
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.dnsmasq = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run dnsmasq.
+        '';
+      };
+
+      resolveLocalQueries = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether dnsmasq should resolve local queries (i.e. add 127.0.0.1 to
+          /etc/resolv.conf).
+        '';
+      };
+
+      servers = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "8.8.8.8" "8.8.4.4" ];
+        description = ''
+          The DNS servers which dnsmasq should query.
+        '';
+      };
+
+      alwaysKeepRunning = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If enabled, systemd will always respawn dnsmasq even if shut down manually. The default, disabled, will only restart it on error.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration directives that should be added to
+          <literal>dnsmasq.conf</literal>.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    networking.nameservers =
+      optional cfg.resolveLocalQueries "127.0.0.1";
+
+    services.dbus.packages = [ dnsmasq ];
+
+    users.users.dnsmasq = {
+      uid = config.ids.uids.dnsmasq;
+      description = "Dnsmasq daemon user";
+    };
+
+    networking.resolvconf = mkIf cfg.resolveLocalQueries {
+      useLocalResolver = mkDefault true;
+
+      extraConfig = ''
+        dnsmasq_conf=/etc/dnsmasq-conf.conf
+        dnsmasq_resolv=/etc/dnsmasq-resolv.conf
+      '';
+    };
+
+    systemd.services.dnsmasq = {
+        description = "Dnsmasq Daemon";
+        after = [ "network.target" "systemd-resolved.service" ];
+        wantedBy = [ "multi-user.target" ];
+        path = [ dnsmasq ];
+        preStart = ''
+          mkdir -m 755 -p ${stateDir}
+          touch ${stateDir}/dnsmasq.leases
+          chown -R dnsmasq ${stateDir}
+          touch /etc/dnsmasq-{conf,resolv}.conf
+          dnsmasq --test
+        '';
+        serviceConfig = {
+          Type = "dbus";
+          BusName = "uk.org.thekelleys.dnsmasq";
+          ExecStart = "${dnsmasq}/bin/dnsmasq -k --enable-dbus --user=dnsmasq -C ${dnsmasqConf}";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+          PrivateTmp = true;
+          ProtectSystem = true;
+          ProtectHome = true;
+          Restart = if cfg.alwaysKeepRunning then "always" else "on-failure";
+        };
+        restartTriggers = [ config.environment.etc.hosts.source ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ejabberd.nix b/nixpkgs/nixos/modules/services/networking/ejabberd.nix
new file mode 100644
index 000000000000..a5af25b983b9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ejabberd.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.ejabberd;
+
+  ctlcfg = pkgs.writeText "ejabberdctl.cfg" ''
+    ERL_EPMD_ADDRESS=127.0.0.1
+    ${cfg.ctlConfig}
+  '';
+
+  ectl = ''${cfg.package}/bin/ejabberdctl ${optionalString (cfg.configFile != null) "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"'';
+
+  dumps = lib.escapeShellArgs cfg.loadDumps;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.ejabberd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable ejabberd server";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.ejabberd;
+        defaultText = "pkgs.ejabberd";
+        description = "ejabberd server package to use";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "ejabberd";
+        description = "User under which ejabberd is ran";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "ejabberd";
+        description = "Group under which ejabberd is ran";
+      };
+
+      spoolDir = mkOption {
+        type = types.path;
+        default = "/var/lib/ejabberd";
+        description = "Location of the spooldir of ejabberd";
+      };
+
+      logsDir = mkOption {
+        type = types.path;
+        default = "/var/log/ejabberd";
+        description = "Location of the logfile directory of ejabberd";
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        description = "Configuration file for ejabberd in YAML format";
+        default = null;
+      };
+
+      ctlConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Configuration of ejabberdctl";
+      };
+
+      loadDumps = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = "Configuration dumps that should be loaded on the first startup";
+        example = literalExample "[ ./myejabberd.dump ]";
+      };
+
+      imagemagick = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Add ImageMagick to server's path; allows for image thumbnailing";
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    users.users = optionalAttrs (cfg.user == "ejabberd") {
+      ejabberd = {
+        group = cfg.group;
+        home = cfg.spoolDir;
+        createHome = true;
+        uid = config.ids.uids.ejabberd;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "ejabberd") {
+      ejabberd.gid = config.ids.gids.ejabberd;
+    };
+
+    systemd.services.ejabberd = {
+      description = "ejabberd server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pkgs.findutils pkgs.coreutils ] ++ lib.optional cfg.imagemagick pkgs.imagemagick;
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${ectl} foreground";
+        ExecStop = "${ectl} stop";
+        ExecReload = "${ectl} reload_config";
+      };
+
+      preStart = ''
+        if [ -z "$(ls -A '${cfg.spoolDir}')" ]; then
+          touch "${cfg.spoolDir}/.firstRun"
+        fi
+      '';
+
+      postStart = ''
+        while ! ${ectl} status >/dev/null 2>&1; do
+          if ! kill -0 "$MAINPID"; then exit 1; fi
+          sleep 0.1
+        done
+
+        if [ -e "${cfg.spoolDir}/.firstRun" ]; then
+          rm "${cfg.spoolDir}/.firstRun"
+          for src in ${dumps}; do
+            find "$src" -type f | while read dump; do
+              echo "Loading configuration dump at $dump"
+              ${ectl} load "$dump"
+            done
+          done
+        fi
+      '';
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logsDir}' 0750 ${cfg.user} ${cfg.group} -"
+      "d '${cfg.spoolDir}' 0700 ${cfg.user} ${cfg.group} -"
+    ];
+
+    security.pam.services.ejabberd = {};
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/epmd.nix b/nixpkgs/nixos/modules/services/networking/epmd.nix
new file mode 100644
index 000000000000..692b75e4f086
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/epmd.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.epmd;
+
+in
+
+{
+  ###### interface
+  options.services.epmd = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable socket activation for Erlang Port Mapper Daemon (epmd),
+        which acts as a name server on all hosts involved in distributed
+        Erlang computations.
+      '';
+    };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.erlang;
+      description = ''
+        The Erlang package to use to get epmd binary. That way you can re-use
+        an Erlang runtime that is already installed for other purposes.
+      '';
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    systemd.sockets.epmd = rec {
+      description = "Erlang Port Mapper Daemon Activation Socket";
+      wantedBy = [ "sockets.target" ];
+      before = wantedBy;
+      socketConfig = {
+        ListenStream = "4369";
+        Accept = "false";
+      };
+    };
+
+    systemd.services.epmd = {
+      description = "Erlang Port Mapper Daemon";
+      after = [ "network.target" ];
+      requires = [ "epmd.socket" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/epmd -systemd";
+        Type = "notify";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ergo.nix b/nixpkgs/nixos/modules/services/networking/ergo.nix
new file mode 100644
index 000000000000..c52de30dc361
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ergo.nix
@@ -0,0 +1,141 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.ergo;
+
+  inherit (lib) mkEnableOption mkIf mkOption optionalString types;
+
+  configFile = pkgs.writeText "ergo.conf" (''
+ergo {
+  directory = "${cfg.dataDir}"
+  node {
+    mining = false
+  }
+  wallet.secretStorage.secretDir = "${cfg.dataDir}/wallet/keystore"
+}
+
+scorex {
+  network {
+    bindAddress = "${cfg.listen.ip}:${toString cfg.listen.port}"
+  }
+'' + optionalString (cfg.api.keyHash != null) ''
+ restApi {
+    apiKeyHash = "${cfg.api.keyHash}"
+    bindAddress = "${cfg.api.listen.ip}:${toString cfg.api.listen.port}"
+ }
+'' + ''
+}
+'');
+
+in {
+
+  options = {
+
+    services.ergo = {
+      enable = mkEnableOption "Ergo service";
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/ergo";
+        description = "The data directory for the Ergo node.";
+      };
+
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = "IP address on which the Ergo node should listen.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 9006;
+          description = "Listen port for the Ergo node.";
+        };
+      };
+
+      api = {
+       keyHash = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "324dcf027dd4a30a932c441f365a25e86b173defa4b8e58948253471b81b72cf";
+        description = "Hex-encoded Blake2b256 hash of an API key as a 64-chars long Base16 string.";
+       };
+
+       listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = "IP address that the Ergo node API should listen on if <option>api.keyHash</option> is defined.";
+          };
+
+        port = mkOption {
+          type = types.port;
+          default = 9052;
+          description = "Listen port for the API endpoint if <option>api.keyHash</option> is defined.";
+        };
+       };
+      };
+
+      testnet = mkOption {
+         type = types.bool;
+         default = false;
+         description = "Connect to testnet network instead of the default mainnet.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "ergo";
+        description = "The user as which to run the Ergo node.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = cfg.user;
+        description = "The group as which to run the Ergo node.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open ports in the firewall for the Ergo node as well as the API.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
+    ];
+
+    systemd.services.ergo = {
+      description = "ergo server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = ''${pkgs.ergo}/bin/ergo \
+                      ${optionalString (!cfg.testnet)
+                      "--mainnet"} \
+                      -c ${configFile}'';
+      };
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ] ++ [ cfg.api.listen.port ];
+    };
+
+    users.users.${cfg.user} = {
+      name = cfg.user;
+      group = cfg.group;
+      description = "Ergo daemon user";
+      home = cfg.dataDir;
+      isSystemUser = true;
+    };
+
+    users.groups.${cfg.group} = {};
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix b/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix
new file mode 100644
index 000000000000..a2e5b30dc0f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/eternal-terminal.nix
@@ -0,0 +1,95 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.eternal-terminal;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.eternal-terminal = {
+
+      enable = mkEnableOption "Eternal Terminal server";
+
+      port = mkOption {
+        default = 2022;
+        type = types.int;
+        description = ''
+          The port the server should listen on. Will use the server's default (2022) if not specified.
+
+          Make sure to open this port in the firewall if necessary.
+        '';
+      };
+
+      verbosity = mkOption {
+        default = 0;
+        type = types.enum (lib.range 0 9);
+        description = ''
+          The verbosity level (0-9).
+        '';
+      };
+
+      silent = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If enabled, disables all logging.
+        '';
+      };
+
+      logSize = mkOption {
+        default = 20971520;
+        type = types.int;
+        description = ''
+          The maximum log size.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    # We need to ensure the et package is fully installed because
+    # the (remote) et client runs the `etterminal` binary when it
+    # connects.
+    environment.systemPackages = [ pkgs.eternal-terminal ];
+
+    systemd.services = {
+      eternal-terminal = {
+        description = "Eternal Terminal server.";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "syslog.target" "network.target" ];
+        serviceConfig = {
+          Type = "forking";
+          ExecStart = "${pkgs.eternal-terminal}/bin/etserver --daemon --cfgfile=${pkgs.writeText "et.cfg" ''
+            ; et.cfg : Config file for Eternal Terminal
+            ;
+
+            [Networking]
+            port = ${toString cfg.port}
+
+            [Debug]
+            verbose = ${toString cfg.verbosity}
+            silent = ${if cfg.silent then "1" else "0"}
+            logsize = ${toString cfg.logSize}
+          ''}";
+          Restart = "on-failure";
+          KillMode = "process";
+        };
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ pingiun ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/fakeroute.nix b/nixpkgs/nixos/modules/services/networking/fakeroute.nix
new file mode 100644
index 000000000000..7916ad4098a7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/fakeroute.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.fakeroute;
+  routeConf = pkgs.writeText "route.conf" (concatStringsSep "\n" cfg.route);
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.fakeroute = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the fakeroute service.
+        '';
+      };
+
+      route = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [
+          "216.102.187.130"
+          "4.0.1.122"
+          "198.116.142.34"
+          "63.199.8.242"
+        ];
+        description = ''
+         Fake route that will appear after the real
+         one to any host running a traceroute.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.fakeroute = {
+      description = "Fakeroute Daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "forking";
+        User = "root";
+        ExecStart = "${pkgs.fakeroute}/bin/fakeroute -f ${routeConf}";
+      };
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ferm.nix b/nixpkgs/nixos/modules/services/networking/ferm.nix
new file mode 100644
index 000000000000..07338ccf4d9c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ferm.nix
@@ -0,0 +1,63 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ferm;
+
+  configFile = pkgs.stdenv.mkDerivation {
+    name = "ferm.conf";
+    text = cfg.config;
+    preferLocalBuild = true;
+    buildCommand = ''
+      echo -n "$text" > $out
+      ${cfg.package}/bin/ferm --noexec $out
+    '';
+  };
+in {
+  options = {
+    services.ferm = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to enable Ferm Firewall.
+          *Warning*: Enabling this service WILL disable the existing NixOS
+          firewall! Default firewall rules provided by packages are not
+          considered at the moment.
+        '';
+      };
+      config = mkOption {
+        description = "Verbatim ferm.conf configuration.";
+        default = "";
+        defaultText = "empty firewall, allows any traffic";
+        type = types.lines;
+      };
+      package = mkOption {
+        description = "The ferm package.";
+        type = types.package;
+        default = pkgs.ferm;
+        defaultText = "pkgs.ferm";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.firewall.enable = false;
+    systemd.services.ferm = {
+      description = "Ferm Firewall";
+      after = [ "ipset.target" ];
+      before = [ "network-pre.target" ];
+      wants = [ "network-pre.target" ];
+      wantedBy = [ "multi-user.target" ];
+      reloadIfChanged = true;
+      serviceConfig = {
+        Type="oneshot";
+        RemainAfterExit = "yes";
+        ExecStart = "${cfg.package}/bin/ferm ${configFile}";
+        ExecReload = "${cfg.package}/bin/ferm ${configFile}";
+        ExecStop = "${cfg.package}/bin/ferm -F ${configFile}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix b/nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix
new file mode 100644
index 000000000000..6842aa735617
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/firefox/sync-server.nix
@@ -0,0 +1,183 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.firefox.syncserver;
+
+  defaultDbLocation = "/var/db/firefox-sync-server/firefox-sync-server.db";
+  defaultSqlUri = "sqlite:///${defaultDbLocation}";
+
+  syncServerIni = pkgs.writeText "syncserver.ini" ''
+    [DEFAULT]
+    overrides = ${cfg.privateConfig}
+
+    [server:main]
+    use = egg:gunicorn
+    host = ${cfg.listen.address}
+    port = ${toString cfg.listen.port}
+
+    [app:main]
+    use = egg:syncserver
+
+    [syncserver]
+    public_url = ${cfg.publicUrl}
+    ${optionalString (cfg.sqlUri != "") "sqluri = ${cfg.sqlUri}"}
+    allow_new_users = ${boolToString cfg.allowNewUsers}
+
+    [browserid]
+    backend = tokenserver.verifiers.LocalVerifier
+    audiences = ${removeSuffix "/" cfg.publicUrl}
+  '';
+
+  user = "syncserver";
+  group = "syncserver";
+in
+
+{
+  meta.maintainers = with lib.maintainers; [ nadrieril ];
+
+  options = {
+    services.firefox.syncserver = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable a Firefox Sync Server, this give the opportunity to
+          Firefox users to store all synchronized data on their own server. To use this
+          server, Firefox users should visit the <option>about:config</option>, and
+          replicate the following change
+
+          <screen>
+          services.sync.tokenServerURI: http://localhost:5000/token/1.0/sync/1.5
+          </screen>
+
+          where <option>http://localhost:5000/</option> corresponds to the
+          public url of the server.
+        '';
+      };
+
+      listen.address = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        example = "0.0.0.0";
+        description = ''
+          Address on which the sync server listen to.
+        '';
+      };
+
+      listen.port = mkOption {
+        type = types.int;
+        default = 5000;
+        description = ''
+          Port on which the sync server listen to.
+        '';
+      };
+
+      publicUrl = mkOption {
+        type = types.str;
+        default = "http://localhost:5000/";
+        example = "http://sync.example.com/";
+        description = ''
+          Public URL with which firefox users can use to access the sync server.
+        '';
+      };
+
+      allowNewUsers = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to allow new-user signups on the server. Only request by
+          existing accounts will be honored.
+        '';
+      };
+
+      sqlUri = mkOption {
+        type = types.str;
+        default = defaultSqlUri;
+        example = "postgresql://scott:tiger@localhost/test";
+        description = ''
+          The location of the database. This URL is composed of
+          <option>dialect[+driver]://user:password@host/dbname[?key=value..]</option>,
+          where <option>dialect</option> is a database name such as
+          <option>mysql</option>, <option>oracle</option>, <option>postgresql</option>,
+          etc., and <option>driver</option> the name of a DBAPI, such as
+          <option>psycopg2</option>, <option>pyodbc</option>, <option>cx_oracle</option>,
+          etc. The <link
+          xlink:href="http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html#database-urls">
+          SQLAlchemy documentation</link> provides more examples and describe the syntax of
+          the expected URL.
+        '';
+      };
+
+      privateConfig = mkOption {
+        type = types.str;
+        default = "/etc/firefox/syncserver-secret.ini";
+        description = ''
+          The private config file is used to extend the generated config with confidential
+          information, such as the <option>syncserver.sqlUri</option> setting if it contains a
+          password, and the <option>syncserver.secret</option> setting is used by the server to
+          generate cryptographically-signed authentication tokens.
+
+          If this file does not exists, then it is created with a generated
+          <option>syncserver.secret</option> settings.
+       '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.syncserver = {
+      after = [ "network.target" ];
+      description = "Firefox Sync Server";
+      wantedBy = [ "multi-user.target" ];
+      path = [
+        pkgs.coreutils
+        (pkgs.python.withPackages (ps: [ pkgs.syncserver ps.gunicorn ]))
+      ];
+
+      serviceConfig = {
+        User = user;
+        Group = group;
+        PermissionsStartOnly = true;
+      };
+
+      preStart = ''
+        if ! test -e ${cfg.privateConfig}; then
+          mkdir -p $(dirname ${cfg.privateConfig})
+          echo  > ${cfg.privateConfig} '[syncserver]'
+          chmod 600 ${cfg.privateConfig}
+          echo >> ${cfg.privateConfig} "secret = $(head -c 20 /dev/urandom | sha1sum | tr -d ' -')"
+        fi
+        chmod 600 ${cfg.privateConfig}
+        chmod 755 $(dirname ${cfg.privateConfig})
+        chown ${user}:${group} ${cfg.privateConfig}
+
+      '' + optionalString (cfg.sqlUri == defaultSqlUri) ''
+        if ! test -e $(dirname ${defaultDbLocation}); then
+          mkdir -m 700 -p $(dirname ${defaultDbLocation})
+          chown ${user}:${group} $(dirname ${defaultDbLocation})
+        fi
+
+        # Move previous database file if it exists
+        oldDb="/var/db/firefox-sync-server.db"
+        if test -f $oldDb; then
+          mv $oldDb ${defaultDbLocation}
+          chown ${user}:${group} ${defaultDbLocation}
+        fi
+      '';
+
+      script = ''
+        gunicorn --paste ${syncServerIni}
+      '';
+    };
+
+    users.users.${user} = {
+      inherit group;
+      isSystemUser = true;
+    };
+
+    users.groups.${group} = {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/fireqos.nix b/nixpkgs/nixos/modules/services/networking/fireqos.nix
new file mode 100644
index 000000000000..0b34f0b6b8b4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/fireqos.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.fireqos;
+  fireqosConfig = pkgs.writeText "fireqos.conf" "${cfg.config}";
+in {
+  options.services.fireqos = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If enabled, FireQOS will be launched with the specified
+        configuration given in `config`.
+      '';
+    };
+
+    config = mkOption {
+      type = types.str;
+      default = "";
+      example = ''
+        interface wlp3s0 world-in input rate 10mbit ethernet
+          class web commit 50kbit
+            match tcp ports 80,443
+
+        interface wlp3s0 world-out input rate 10mbit ethernet
+          class web commit 50kbit
+            match tcp ports 80,443
+      '';
+      description = ''
+        The FireQOS configuration goes here.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.fireqos = {
+      description = "FireQOS";
+      after = [ "network.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "${pkgs.firehol}/bin/fireqos start ${fireqosConfig}";
+        ExecStop = [
+          "${pkgs.firehol}/bin/fireqos stop"
+          "${pkgs.firehol}/bin/fireqos clear_all_qos"
+        ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/firewall.nix b/nixpkgs/nixos/modules/services/networking/firewall.nix
new file mode 100644
index 000000000000..cdc3a172ea70
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/firewall.nix
@@ -0,0 +1,585 @@
+/* This module enables a simple firewall.
+
+   The firewall can be customised in arbitrary ways by setting
+   ‘networking.firewall.extraCommands’.  For modularity, the firewall
+   uses several chains:
+
+   - ‘nixos-fw’ is the main chain for input packet processing.
+
+   - ‘nixos-fw-accept’ is called for accepted packets.  If you want
+     additional logging, or want to reject certain packets anyway, you
+     can insert rules at the start of this chain.
+
+   - ‘nixos-fw-log-refuse’ and ‘nixos-fw-refuse’ are called for
+     refused packets.  (The former jumps to the latter after logging
+     the packet.)  If you want additional logging, or want to accept
+     certain packets anyway, you can insert rules at the start of
+     this chain.
+
+   - ‘nixos-fw-rpfilter’ is used as the main chain in the raw table,
+     called from the built-in ‘PREROUTING’ chain.  If the kernel
+     supports it and `cfg.checkReversePath` is set this chain will
+     perform a reverse path filter test.
+
+   - ‘nixos-drop’ is used while reloading the firewall in order to drop
+     all traffic.  Since reloading isn't implemented in an atomic way
+     this'll prevent any traffic from leaking through while reloading
+     the firewall.  However, if the reloading fails, the ‘firewall-stop’
+     script will be called which in return will effectively disable the
+     complete firewall (in the default configuration).
+
+*/
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.networking.firewall;
+
+  inherit (config.boot.kernelPackages) kernel;
+
+  kernelHasRPFilter = ((kernel.config.isEnabled or (x: false)) "IP_NF_MATCH_RPFILTER") || (kernel.features.netfilterRPFilter or false);
+
+  helpers = import ./helpers.nix { inherit config lib; };
+
+  writeShScript = name: text: let dir = pkgs.writeScriptBin name ''
+    #! ${pkgs.runtimeShell} -e
+    ${text}
+  ''; in "${dir}/bin/${name}";
+
+  defaultInterface = { default = mapAttrs (name: value: cfg.${name}) commonOptions; };
+  allInterfaces = defaultInterface // cfg.interfaces;
+
+  startScript = writeShScript "firewall-start" ''
+    ${helpers}
+
+    # Flush the old firewall rules.  !!! Ideally, updating the
+    # firewall would be atomic.  Apparently that's possible
+    # with iptables-restore.
+    ip46tables -D INPUT -j nixos-fw 2> /dev/null || true
+    for chain in nixos-fw nixos-fw-accept nixos-fw-log-refuse nixos-fw-refuse; do
+      ip46tables -F "$chain" 2> /dev/null || true
+      ip46tables -X "$chain" 2> /dev/null || true
+    done
+
+
+    # The "nixos-fw-accept" chain just accepts packets.
+    ip46tables -N nixos-fw-accept
+    ip46tables -A nixos-fw-accept -j ACCEPT
+
+
+    # The "nixos-fw-refuse" chain rejects or drops packets.
+    ip46tables -N nixos-fw-refuse
+
+    ${if cfg.rejectPackets then ''
+      # Send a reset for existing TCP connections that we've
+      # somehow forgotten about.  Send ICMP "port unreachable"
+      # for everything else.
+      ip46tables -A nixos-fw-refuse -p tcp ! --syn -j REJECT --reject-with tcp-reset
+      ip46tables -A nixos-fw-refuse -j REJECT
+    '' else ''
+      ip46tables -A nixos-fw-refuse -j DROP
+    ''}
+
+
+    # The "nixos-fw-log-refuse" chain performs logging, then
+    # jumps to the "nixos-fw-refuse" chain.
+    ip46tables -N nixos-fw-log-refuse
+
+    ${optionalString cfg.logRefusedConnections ''
+      ip46tables -A nixos-fw-log-refuse -p tcp --syn -j LOG --log-level info --log-prefix "refused connection: "
+    ''}
+    ${optionalString (cfg.logRefusedPackets && !cfg.logRefusedUnicastsOnly) ''
+      ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type broadcast \
+        -j LOG --log-level info --log-prefix "refused broadcast: "
+      ip46tables -A nixos-fw-log-refuse -m pkttype --pkt-type multicast \
+        -j LOG --log-level info --log-prefix "refused multicast: "
+    ''}
+    ip46tables -A nixos-fw-log-refuse -m pkttype ! --pkt-type unicast -j nixos-fw-refuse
+    ${optionalString cfg.logRefusedPackets ''
+      ip46tables -A nixos-fw-log-refuse \
+        -j LOG --log-level info --log-prefix "refused packet: "
+    ''}
+    ip46tables -A nixos-fw-log-refuse -j nixos-fw-refuse
+
+
+    # The "nixos-fw" chain does the actual work.
+    ip46tables -N nixos-fw
+
+    # Clean up rpfilter rules
+    ip46tables -t raw -D PREROUTING -j nixos-fw-rpfilter 2> /dev/null || true
+    ip46tables -t raw -F nixos-fw-rpfilter 2> /dev/null || true
+    ip46tables -t raw -X nixos-fw-rpfilter 2> /dev/null || true
+
+    ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
+      # Perform a reverse-path test to refuse spoofers
+      # For now, we just drop, as the raw table doesn't have a log-refuse yet
+      ip46tables -t raw -N nixos-fw-rpfilter 2> /dev/null || true
+      ip46tables -t raw -A nixos-fw-rpfilter -m rpfilter --validmark ${optionalString (cfg.checkReversePath == "loose") "--loose"} -j RETURN
+
+      # Allows this host to act as a DHCP4 client without first having to use APIPA
+      iptables -t raw -A nixos-fw-rpfilter -p udp --sport 67 --dport 68 -j RETURN
+
+      # Allows this host to act as a DHCPv4 server
+      iptables -t raw -A nixos-fw-rpfilter -s 0.0.0.0 -d 255.255.255.255 -p udp --sport 68 --dport 67 -j RETURN
+
+      ${optionalString cfg.logReversePathDrops ''
+        ip46tables -t raw -A nixos-fw-rpfilter -j LOG --log-level info --log-prefix "rpfilter drop: "
+      ''}
+      ip46tables -t raw -A nixos-fw-rpfilter -j DROP
+
+      ip46tables -t raw -A PREROUTING -j nixos-fw-rpfilter
+    ''}
+
+    # Accept all traffic on the trusted interfaces.
+    ${flip concatMapStrings cfg.trustedInterfaces (iface: ''
+      ip46tables -A nixos-fw -i ${iface} -j nixos-fw-accept
+    '')}
+
+    # Accept packets from established or related connections.
+    ip46tables -A nixos-fw -m conntrack --ctstate ESTABLISHED,RELATED -j nixos-fw-accept
+
+    # Accept connections to the allowed TCP ports.
+    ${concatStrings (mapAttrsToList (iface: cfg:
+      concatMapStrings (port:
+        ''
+          ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+        ''
+      ) cfg.allowedTCPPorts
+    ) allInterfaces)}
+
+    # Accept connections to the allowed TCP port ranges.
+    ${concatStrings (mapAttrsToList (iface: cfg:
+      concatMapStrings (rangeAttr:
+        let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
+        ''
+          ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+        ''
+      ) cfg.allowedTCPPortRanges
+    ) allInterfaces)}
+
+    # Accept packets on the allowed UDP ports.
+    ${concatStrings (mapAttrsToList (iface: cfg:
+      concatMapStrings (port:
+        ''
+          ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+        ''
+      ) cfg.allowedUDPPorts
+    ) allInterfaces)}
+
+    # Accept packets on the allowed UDP port ranges.
+    ${concatStrings (mapAttrsToList (iface: cfg:
+      concatMapStrings (rangeAttr:
+        let range = toString rangeAttr.from + ":" + toString rangeAttr.to; in
+        ''
+          ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
+        ''
+      ) cfg.allowedUDPPortRanges
+    ) allInterfaces)}
+
+    # Accept IPv4 multicast.  Not a big security risk since
+    # probably nobody is listening anyway.
+    #iptables -A nixos-fw -d 224.0.0.0/4 -j nixos-fw-accept
+
+    # Optionally respond to ICMPv4 pings.
+    ${optionalString cfg.allowPing ''
+      iptables -w -A nixos-fw -p icmp --icmp-type echo-request ${optionalString (cfg.pingLimit != null)
+        "-m limit ${cfg.pingLimit} "
+      }-j nixos-fw-accept
+    ''}
+
+    ${optionalString config.networking.enableIPv6 ''
+      # Accept all ICMPv6 messages except redirects and node
+      # information queries (type 139).  See RFC 4890, section
+      # 4.4.
+      ip6tables -A nixos-fw -p icmpv6 --icmpv6-type redirect -j DROP
+      ip6tables -A nixos-fw -p icmpv6 --icmpv6-type 139 -j DROP
+      ip6tables -A nixos-fw -p icmpv6 -j nixos-fw-accept
+
+      # Allow this host to act as a DHCPv6 client
+      ip6tables -A nixos-fw -d fe80::/64 -p udp --dport 546 -j nixos-fw-accept
+    ''}
+
+    ${cfg.extraCommands}
+
+    # Reject/drop everything else.
+    ip46tables -A nixos-fw -j nixos-fw-log-refuse
+
+
+    # Enable the firewall.
+    ip46tables -A INPUT -j nixos-fw
+  '';
+
+  stopScript = writeShScript "firewall-stop" ''
+    ${helpers}
+
+    # Clean up in case reload fails
+    ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
+
+    # Clean up after added ruleset
+    ip46tables -D INPUT -j nixos-fw 2>/dev/null || true
+
+    ${optionalString (kernelHasRPFilter && (cfg.checkReversePath != false)) ''
+      ip46tables -t raw -D PREROUTING -j nixos-fw-rpfilter 2>/dev/null || true
+    ''}
+
+    ${cfg.extraStopCommands}
+  '';
+
+  reloadScript = writeShScript "firewall-reload" ''
+    ${helpers}
+
+    # Create a unique drop rule
+    ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
+    ip46tables -F nixos-drop 2>/dev/null || true
+    ip46tables -X nixos-drop 2>/dev/null || true
+    ip46tables -N nixos-drop
+    ip46tables -A nixos-drop -j DROP
+
+    # Don't allow traffic to leak out until the script has completed
+    ip46tables -A INPUT -j nixos-drop
+
+    ${cfg.extraStopCommands}
+
+    if ${startScript}; then
+      ip46tables -D INPUT -j nixos-drop 2>/dev/null || true
+    else
+      echo "Failed to reload firewall... Stopping"
+      ${stopScript}
+      exit 1
+    fi
+  '';
+
+  canonicalizePortList =
+    ports: lib.unique (builtins.sort builtins.lessThan ports);
+
+  commonOptions = {
+    allowedTCPPorts = mkOption {
+      type = types.listOf types.port;
+      default = [ ];
+      apply = canonicalizePortList;
+      example = [ 22 80 ];
+      description =
+        ''
+          List of TCP ports on which incoming connections are
+          accepted.
+        '';
+    };
+
+    allowedTCPPortRanges = mkOption {
+      type = types.listOf (types.attrsOf types.port);
+      default = [ ];
+      example = [ { from = 8999; to = 9003; } ];
+      description =
+        ''
+          A range of TCP ports on which incoming connections are
+          accepted.
+        '';
+    };
+
+    allowedUDPPorts = mkOption {
+      type = types.listOf types.port;
+      default = [ ];
+      apply = canonicalizePortList;
+      example = [ 53 ];
+      description =
+        ''
+          List of open UDP ports.
+        '';
+    };
+
+    allowedUDPPortRanges = mkOption {
+      type = types.listOf (types.attrsOf types.port);
+      default = [ ];
+      example = [ { from = 60000; to = 61000; } ];
+      description =
+        ''
+          Range of open UDP ports.
+        '';
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    networking.firewall = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          ''
+            Whether to enable the firewall.  This is a simple stateful
+            firewall that blocks connection attempts to unauthorised TCP
+            or UDP ports on this machine.  It does not affect packet
+            forwarding.
+          '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.iptables;
+        defaultText = "pkgs.iptables";
+        example = literalExample "pkgs.iptables-nftables-compat";
+        description =
+          ''
+            The iptables package to use for running the firewall service."
+          '';
+      };
+
+      logRefusedConnections = mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          ''
+            Whether to log rejected or dropped incoming connections.
+          '';
+      };
+
+      logRefusedPackets = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          ''
+            Whether to log all rejected or dropped incoming packets.
+            This tends to give a lot of log messages, so it's mostly
+            useful for debugging.
+          '';
+      };
+
+      logRefusedUnicastsOnly = mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          ''
+            If <option>networking.firewall.logRefusedPackets</option>
+            and this option are enabled, then only log packets
+            specifically directed at this machine, i.e., not broadcasts
+            or multicasts.
+          '';
+      };
+
+      rejectPackets = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          ''
+            If set, refused packets are rejected rather than dropped
+            (ignored).  This means that an ICMP "port unreachable" error
+            message is sent back to the client (or a TCP RST packet in
+            case of an existing connection).  Rejecting packets makes
+            port scanning somewhat easier.
+          '';
+      };
+
+      trustedInterfaces = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "enp0s2" ];
+        description =
+          ''
+            Traffic coming in from these interfaces will be accepted
+            unconditionally.  Traffic from the loopback (lo) interface
+            will always be accepted.
+          '';
+      };
+
+      allowPing = mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          ''
+            Whether to respond to incoming ICMPv4 echo requests
+            ("pings").  ICMPv6 pings are always allowed because the
+            larger address space of IPv6 makes network scanning much
+            less effective.
+          '';
+      };
+
+      pingLimit = mkOption {
+        type = types.nullOr (types.separatedString " ");
+        default = null;
+        example = "--limit 1/minute --limit-burst 5";
+        description =
+          ''
+            If pings are allowed, this allows setting rate limits
+            on them.  If non-null, this option should be in the form of
+            flags like "--limit 1/minute --limit-burst 5"
+          '';
+      };
+
+      checkReversePath = mkOption {
+        type = types.either types.bool (types.enum ["strict" "loose"]);
+        default = kernelHasRPFilter;
+        example = "loose";
+        description =
+          ''
+            Performs a reverse path filter test on a packet.  If a reply
+            to the packet would not be sent via the same interface that
+            the packet arrived on, it is refused.
+
+            If using asymmetric routing or other complicated routing, set
+            this option to loose mode or disable it and setup your own
+            counter-measures.
+
+            This option can be either true (or "strict"), "loose" (only
+            drop the packet if the source address is not reachable via any
+            interface) or false.  Defaults to the value of
+            kernelHasRPFilter.
+
+            (needs kernel 3.3+)
+          '';
+      };
+
+      logReversePathDrops = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          ''
+            Logs dropped packets failing the reverse path filter test if
+            the option networking.firewall.checkReversePath is enabled.
+          '';
+      };
+
+      connectionTrackingModules = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "ftp" "irc" "sane" "sip" "tftp" "amanda" "h323" "netbios_sn" "pptp" "snmp" ];
+        description =
+          ''
+            List of connection-tracking helpers that are auto-loaded.
+            The complete list of possible values is given in the example.
+
+            As helpers can pose as a security risk, it is advised to
+            set this to an empty list and disable the setting
+            networking.firewall.autoLoadConntrackHelpers unless you
+            know what you are doing. Connection tracking is disabled
+            by default.
+
+            Loading of helpers is recommended to be done through the
+            CT target.  More info:
+            https://home.regit.org/netfilter-en/secure-use-of-helpers/
+          '';
+      };
+
+      autoLoadConntrackHelpers = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          ''
+            Whether to auto-load connection-tracking helpers.
+            See the description at networking.firewall.connectionTrackingModules
+
+            (needs kernel 3.5+)
+          '';
+      };
+
+      extraCommands = mkOption {
+        type = types.lines;
+        default = "";
+        example = "iptables -A INPUT -p icmp -j ACCEPT";
+        description =
+          ''
+            Additional shell commands executed as part of the firewall
+            initialisation script.  These are executed just before the
+            final "reject" firewall rule is added, so they can be used
+            to allow packets that would otherwise be refused.
+          '';
+      };
+
+      extraPackages = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        example = literalExample "[ pkgs.ipset ]";
+        description =
+          ''
+            Additional packages to be included in the environment of the system
+            as well as the path of networking.firewall.extraCommands.
+          '';
+      };
+
+      extraStopCommands = mkOption {
+        type = types.lines;
+        default = "";
+        example = "iptables -P INPUT ACCEPT";
+        description =
+          ''
+            Additional shell commands executed as part of the firewall
+            shutdown script.  These are executed just after the removal
+            of the NixOS input rule, or if the service enters a failed
+            state.
+          '';
+      };
+
+      interfaces = mkOption {
+        default = { };
+        type = with types; attrsOf (submodule [ { options = commonOptions; } ]);
+        description =
+          ''
+            Interface-specific open ports.
+          '';
+      };
+    } // commonOptions;
+
+  };
+
+
+  ###### implementation
+
+  # FIXME: Maybe if `enable' is false, the firewall should still be
+  # built but not started by default?
+  config = mkIf cfg.enable {
+
+    networking.firewall.trustedInterfaces = [ "lo" ];
+
+    environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
+
+    boot.kernelModules = (optional cfg.autoLoadConntrackHelpers "nf_conntrack")
+      ++ map (x: "nf_conntrack_${x}") cfg.connectionTrackingModules;
+    boot.extraModprobeConfig = optionalString cfg.autoLoadConntrackHelpers ''
+      options nf_conntrack nf_conntrack_helper=1
+    '';
+
+    assertions = [
+      # This is approximately "checkReversePath -> kernelHasRPFilter",
+      # but the checkReversePath option can include non-boolean
+      # values.
+      { assertion = cfg.checkReversePath == false || kernelHasRPFilter;
+        message = "This kernel does not support rpfilter"; }
+    ];
+
+    systemd.services.firewall = {
+      description = "Firewall";
+      wantedBy = [ "sysinit.target" ];
+      wants = [ "network-pre.target" ];
+      before = [ "network-pre.target" ];
+      after = [ "systemd-modules-load.service" ];
+
+      path = [ cfg.package ] ++ cfg.extraPackages;
+
+      # FIXME: this module may also try to load kernel modules, but
+      # containers don't have CAP_SYS_MODULE.  So the host system had
+      # better have all necessary modules already loaded.
+      unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+      unitConfig.DefaultDependencies = false;
+
+      reloadIfChanged = true;
+
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = "@${startScript} firewall-start";
+        ExecReload = "@${reloadScript} firewall-reload";
+        ExecStop = "@${stopScript} firewall-stop";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/flannel.nix b/nixpkgs/nixos/modules/services/networking/flannel.nix
new file mode 100644
index 000000000000..4c040112d28d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/flannel.nix
@@ -0,0 +1,191 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.flannel;
+
+  networkConfig = filterAttrs (n: v: v != null) {
+    Network = cfg.network;
+    SubnetLen = cfg.subnetLen;
+    SubnetMin = cfg.subnetMin;
+    SubnetMax = cfg.subnetMax;
+    Backend = cfg.backend;
+  };
+in {
+  options.services.flannel = {
+    enable = mkEnableOption "flannel";
+
+    package = mkOption {
+      description = "Package to use for flannel";
+      type = types.package;
+      default = pkgs.flannel;
+      defaultText = "pkgs.flannel";
+    };
+
+    publicIp = mkOption {
+      description = ''
+        IP accessible by other nodes for inter-host communication.
+        Defaults to the IP of the interface being used for communication.
+      '';
+      type = types.nullOr types.str;
+      default = null;
+    };
+
+    iface = mkOption {
+      description = ''
+        Interface to use (IP or name) for inter-host communication.
+        Defaults to the interface for the default route on the machine.
+      '';
+      type = types.nullOr types.str;
+      default = null;
+    };
+
+    etcd = {
+      endpoints = mkOption {
+        description = "Etcd endpoints";
+        type = types.listOf types.str;
+        default = ["http://127.0.0.1:2379"];
+      };
+
+      prefix = mkOption {
+        description = "Etcd key prefix";
+        type = types.str;
+        default = "/coreos.com/network";
+      };
+
+      caFile = mkOption {
+        description = "Etcd certificate authority file";
+        type = types.nullOr types.path;
+        default = null;
+      };
+
+      certFile = mkOption {
+        description = "Etcd cert file";
+        type = types.nullOr types.path;
+        default = null;
+      };
+
+      keyFile = mkOption {
+        description = "Etcd key file";
+        type = types.nullOr types.path;
+        default = null;
+      };
+    };
+
+    kubeconfig = mkOption {
+      description = ''
+        Path to kubeconfig to use for storing flannel config using the
+        Kubernetes API
+      '';
+      type = types.nullOr types.path;
+      default = null;
+    };
+
+    network = mkOption {
+      description = " IPv4 network in CIDR format to use for the entire flannel network.";
+      type = types.str;
+    };
+
+    nodeName = mkOption {
+      description = ''
+        Needed when running with Kubernetes as backend as this cannot be auto-detected";
+      '';
+      type = types.nullOr types.str;
+      default = with config.networking; (hostName + optionalString (domain != null) ".${domain}");
+      example = "node1.example.com";
+    };
+
+    storageBackend = mkOption {
+      description = "Determines where flannel stores its configuration at runtime";
+      type = types.enum ["etcd" "kubernetes"];
+      default = "etcd";
+    };
+
+    subnetLen = mkOption {
+      description = ''
+        The size of the subnet allocated to each host. Defaults to 24 (i.e. /24)
+        unless the Network was configured to be smaller than a /24 in which case
+        it is one less than the network.
+      '';
+      type = types.int;
+      default = 24;
+    };
+
+    subnetMin = mkOption {
+      description = ''
+        The beginning of IP range which the subnet allocation should start with.
+        Defaults to the first subnet of Network.
+      '';
+      type = types.nullOr types.str;
+      default = null;
+    };
+
+    subnetMax = mkOption {
+      description = ''
+        The end of IP range which the subnet allocation should start with.
+        Defaults to the last subnet of Network.
+      '';
+      type = types.nullOr types.str;
+      default = null;
+    };
+
+    backend = mkOption {
+      description = "Type of backend to use and specific configurations for that backend.";
+      type = types.attrs;
+      default = {
+        Type = "vxlan";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.flannel = {
+      description = "Flannel Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      environment = {
+        FLANNELD_PUBLIC_IP = cfg.publicIp;
+        FLANNELD_IFACE = cfg.iface;
+      } // optionalAttrs (cfg.storageBackend == "etcd") {
+        FLANNELD_ETCD_ENDPOINTS = concatStringsSep "," cfg.etcd.endpoints;
+        FLANNELD_ETCD_KEYFILE = cfg.etcd.keyFile;
+        FLANNELD_ETCD_CERTFILE = cfg.etcd.certFile;
+        FLANNELD_ETCD_CAFILE = cfg.etcd.caFile;
+        ETCDCTL_CERT_FILE = cfg.etcd.certFile;
+        ETCDCTL_KEY_FILE = cfg.etcd.keyFile;
+        ETCDCTL_CA_FILE = cfg.etcd.caFile;
+        ETCDCTL_PEERS = concatStringsSep "," cfg.etcd.endpoints;
+      } // optionalAttrs (cfg.storageBackend == "kubernetes") {
+        FLANNELD_KUBE_SUBNET_MGR = "true";
+        FLANNELD_KUBECONFIG_FILE = cfg.kubeconfig;
+        NODE_NAME = cfg.nodeName;
+      };
+      path = [ pkgs.iptables ];
+      preStart = ''
+        mkdir -p /run/flannel
+        touch /run/flannel/docker
+      '' + optionalString (cfg.storageBackend == "etcd") ''
+        echo "setting network configuration"
+        until ${pkgs.etcdctl}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
+        do
+          echo "setting network configuration, retry"
+          sleep 1
+        done
+      '';
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/flannel";
+        Restart = "always";
+        RestartSec = "10s";
+      };
+    };
+
+    services.etcd.enable = mkDefault (cfg.storageBackend == "etcd" && cfg.etcd.endpoints == ["http://127.0.0.1:2379"]);
+
+    # for some reason, flannel doesn't let you configure this path
+    # see: https://github.com/coreos/flannel/blob/master/Documentation/configuration.md#configuration
+    environment.etc."kube-flannel/net-conf.json" = mkIf (cfg.storageBackend == "kubernetes") {
+      source = pkgs.writeText "net-conf.json" (builtins.toJSON networkConfig);
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/flashpolicyd.nix b/nixpkgs/nixos/modules/services/networking/flashpolicyd.nix
new file mode 100644
index 000000000000..7f25083307c7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/flashpolicyd.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.flashpolicyd;
+
+  flashpolicyd = pkgs.stdenv.mkDerivation {
+    name = "flashpolicyd-0.6";
+
+    src = pkgs.fetchurl {
+      name = "flashpolicyd_v0.6.zip";
+      url = "https://download.adobe.com/pub/adobe/devnet/flashplayer/articles/socket_policy_files/flashpolicyd_v0.6.zip";
+      sha256 = "16zk237233npwfq1m4ksy4g5lzy1z9fp95w7pz0cdlpmv0fv9sm3";
+    };
+
+    buildInputs = [ pkgs.unzip pkgs.perl ];
+
+    installPhase = "mkdir $out; cp -pr * $out/; chmod +x $out/*/*.pl";
+  };
+
+  flashpolicydWrapper = pkgs.writeScriptBin "flashpolicyd"
+    ''
+      #! ${pkgs.runtimeShell}
+      exec ${flashpolicyd}/Perl_xinetd/in.flashpolicyd.pl \
+        --file=${pkgs.writeText "flashpolixy.xml" cfg.policy} \
+        2> /dev/null
+    '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.flashpolicyd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          ''
+            Whether to enable the Flash Policy server.  This is
+            necessary if you want Flash applications to make
+            connections to your server.
+          '';
+      };
+
+      policy = mkOption {
+        default =
+          ''
+            <?xml version="1.0"?>
+            <!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
+            <cross-domain-policy>
+              <site-control permitted-cross-domain-policies="master-only"/>
+              <allow-access-from domain="*" to-ports="*" />
+            </cross-domain-policy>
+          '';
+        description = "The policy to be served.  The default is to allow connections from any domain to any port.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.xinetd.enable = true;
+
+    services.xinetd.services = singleton
+      { name = "flashpolicy";
+        port = 843;
+        unlisted = true;
+        server = "${flashpolicydWrapper}/bin/flashpolicyd";
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/freenet.nix b/nixpkgs/nixos/modules/services/networking/freenet.nix
new file mode 100644
index 000000000000..3da3ab0c7df4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/freenet.nix
@@ -0,0 +1,64 @@
+# NixOS module for Freenet daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.freenet;
+  varDir = "/var/lib/freenet";
+
+in
+
+{
+
+  ### configuration
+
+  options = {
+
+    services.freenet = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable the Freenet daemon";
+      };
+
+      nice = mkOption {
+        type = types.int;
+        default = 10;
+        description = "Set the nice level for the Freenet daemon";
+      };
+
+    };
+
+  };
+
+  ### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.freenet = {
+      description = "Freenet daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${pkgs.freenet}/bin/freenet";
+      serviceConfig.User = "freenet";
+      serviceConfig.UMask = "0007";
+      serviceConfig.WorkingDirectory = varDir;
+      serviceConfig.Nice = cfg.nice;
+    };
+
+    users.users.freenet = {
+      group = "freenet";
+      description = "Freenet daemon user";
+      home = varDir;
+      createHome = true;
+      uid = config.ids.uids.freenet;
+    };
+
+    users.groups.freenet.gid = config.ids.gids.freenet;
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/freeradius.nix b/nixpkgs/nixos/modules/services/networking/freeradius.nix
new file mode 100644
index 000000000000..f3fdd576b65c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/freeradius.nix
@@ -0,0 +1,84 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.freeradius;
+
+  freeradiusService = cfg:
+  {
+    description = "FreeRadius server";
+    wantedBy = ["multi-user.target"];
+    after = ["network.target"];
+    wants = ["network.target"];
+    preStart = ''
+      ${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout
+    '';
+
+    serviceConfig = {
+        ExecStart = "${pkgs.freeradius}/bin/radiusd -f -d ${cfg.configDir} -l stdout" +
+                    optionalString cfg.debug " -xx";
+        ExecReload = [
+          "${pkgs.freeradius}/bin/radiusd -C -d ${cfg.configDir} -l stdout"
+          "${pkgs.coreutils}/bin/kill -HUP $MAINPID"
+        ];
+        User = "radius";
+        ProtectSystem = "full";
+        ProtectHome = "on";
+        Restart = "on-failure";
+        RestartSec = 2;
+    };
+  };
+
+  freeradiusConfig = {
+    enable = mkEnableOption "the freeradius server";
+
+    configDir = mkOption {
+      type = types.path;
+      default = "/etc/raddb";
+      description = ''
+        The path of the freeradius server configuration directory.
+      '';
+    };
+
+    debug = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable debug logging for freeradius (-xx
+        option). This should not be left on, since it includes
+        sensitive data such as passwords in the logs.
+      '';
+    };
+
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.freeradius = freeradiusConfig;
+  };
+
+
+  ###### implementation
+
+  config = mkIf (cfg.enable) {
+
+    users = {
+      users.radius = {
+        /*uid = config.ids.uids.radius;*/
+        description = "Radius daemon user";
+      };
+    };
+
+    systemd.services.freeradius = freeradiusService cfg;
+    warnings = optional cfg.debug "Freeradius debug logging is enabled. This will log passwords in plaintext to the journal!";
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gale.nix b/nixpkgs/nixos/modules/services/networking/gale.nix
new file mode 100644
index 000000000000..cb954fd836bc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gale.nix
@@ -0,0 +1,181 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gale;
+  # we convert the path to a string to avoid it being copied to the nix store,
+  # otherwise users could read the private key as all files in the store are
+  # world-readable
+  keyPath = toString cfg.keyPath;
+  # ...but we refer to the pubkey file using a path so that we can ensure the
+  # config gets rebuilt if the public key changes (we can assume the private key
+  # will never change without the public key having changed)
+  gpubFile = cfg.keyPath + "/${cfg.domain}.gpub";
+  home = "/var/lib/gale";
+  keysPrepared = cfg.keyPath != null && lib.pathExists cfg.keyPath;
+in
+{
+  options = {
+    services.gale = {
+      enable = mkEnableOption "the Gale messaging daemon";
+
+      user = mkOption {
+        default = "gale";
+        type = types.str;
+        description = "Username for the Gale daemon.";
+      };
+
+      group = mkOption {
+        default = "gale";
+        type = types.str;
+        description = "Group name for the Gale daemon.";
+      };
+
+      setuidWrapper = mkOption {
+        default = null;
+        description = "Configuration for the Gale gksign setuid wrapper.";
+      };
+
+      domain = mkOption {
+        default = "";
+        type = types.str;
+        description = "Domain name for the Gale system.";
+      };
+
+      keyPath = mkOption {
+        default = null;
+        type = types.nullOr types.path;
+        description = ''
+          Directory containing the key pair for this Gale domain.  The expected
+          filename will be taken from the domain option with ".gpri" and ".gpub"
+          appended.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional text to be added to <filename>/etc/gale/conf</filename>.
+        '';
+      };
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+       assertions = [{
+         assertion = cfg.domain != "";
+         message = "A domain must be set for Gale.";
+       }];
+
+       warnings = mkIf (!keysPrepared) [
+         "You must run gale-install in order to generate a domain key."
+       ];
+
+       system.activationScripts.gale = mkIf cfg.enable (
+         stringAfter [ "users" "groups" ] ''
+           chmod 755 ${home}
+           mkdir -m 0777 -p ${home}/auth/cache
+           mkdir -m 1777 -p ${home}/auth/local # GALE_DOMAIN.gpub
+           mkdir -m 0700 -p ${home}/auth/private # ROOT.gpub
+           mkdir -m 0755 -p ${home}/auth/trusted # ROOT
+           mkdir -m 0700 -p ${home}/.gale
+           mkdir -m 0700 -p ${home}/.gale/auth
+           mkdir -m 0700 -p ${home}/.gale/auth/private # GALE_DOMAIN.gpri
+
+           ln -sf ${pkgs.gale}/etc/gale/auth/trusted/ROOT "${home}/auth/trusted/ROOT"
+           chown ${cfg.user}:${cfg.group} ${home} ${home}/auth ${home}/auth/*
+           chown ${cfg.user}:${cfg.group} ${home}/.gale ${home}/.gale/auth ${home}/.gale/auth/private
+         ''
+       );
+
+       environment = {
+         etc = {
+           "gale/auth".source = home + "/auth"; # symlink /var/lib/gale/auth
+           "gale/conf".text = ''
+             GALE_USER ${cfg.user}
+             GALE_DOMAIN ${cfg.domain}
+             ${cfg.extraConfig}
+           '';
+         };
+
+         systemPackages = [ pkgs.gale ];
+       };
+
+       users.users.${cfg.user} = {
+         description = "Gale daemon";
+         uid = config.ids.uids.gale;
+         group = cfg.group;
+         home = home;
+         createHome = true;
+       };
+
+       users.groups = [{
+         name = cfg.group;
+         gid = config.ids.gids.gale;
+       }];
+    })
+    (mkIf (cfg.enable && keysPrepared) {
+       assertions = [
+         {
+           assertion = cfg.keyPath != null
+                    && lib.pathExists (cfg.keyPath + "/${cfg.domain}.gpub");
+           message = "Couldn't find a Gale public key for ${cfg.domain}.";
+         }
+         {
+           assertion = cfg.keyPath != null
+                    && lib.pathExists (cfg.keyPath + "/${cfg.domain}.gpri");
+           message = "Couldn't find a Gale private key for ${cfg.domain}.";
+         }
+       ];
+
+       services.gale.setuidWrapper = {
+         program = "gksign";
+         source = "${pkgs.gale}/bin/gksign";
+         owner = cfg.user;
+         group = cfg.group;
+         setuid = true;
+         setgid = false;
+       };
+
+       security.wrappers.gksign = cfg.setuidWrapper;
+
+       systemd.services.gale-galed = {
+         description = "Gale messaging daemon";
+         wantedBy = [ "multi-user.target" ];
+         wants = [ "gale-gdomain.service" ];
+         after = [ "network.target" ];
+
+         preStart = ''
+           install -m 0640 -o ${cfg.user} -g ${cfg.group} ${keyPath}/${cfg.domain}.gpri "${home}/.gale/auth/private/"
+           install -m 0644 -o ${cfg.user} -g ${cfg.group} ${gpubFile} "${home}/.gale/auth/private/${cfg.domain}.gpub"
+           install -m 0644 -o ${cfg.user} -g ${cfg.group} ${gpubFile} "${home}/auth/local/${cfg.domain}.gpub"
+         '';
+
+         serviceConfig = {
+           Type = "forking";
+           ExecStart = "@${pkgs.gale}/bin/galed galed";
+           User = cfg.user;
+           Group = cfg.group;
+           PermissionsStartOnly = true;
+         };
+       };
+
+       systemd.services.gale-gdomain = {
+         description = "Gale AKD daemon";
+         wantedBy = [ "multi-user.target" ];
+         requires = [ "gale-galed.service" ];
+         after = [ "gale-galed.service" ];
+
+         serviceConfig = {
+           Type = "forking";
+           ExecStart = "@${pkgs.gale}/bin/gdomain gdomain";
+           User = cfg.user;
+           Group = cfg.group;
+         };
+       };
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gateone.nix b/nixpkgs/nixos/modules/services/networking/gateone.nix
new file mode 100644
index 000000000000..4456a95402ed
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gateone.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ...}:
+with lib;
+let
+  cfg = config.services.gateone;
+in
+{
+options = {
+    services.gateone = {
+      enable = mkEnableOption "GateOne server";
+      pidDir = mkOption {
+        default = "/run/gateone";
+        type = types.path;
+        description = ''Path of pid files for GateOne.'';
+      };
+      settingsDir = mkOption {
+        default = "/var/lib/gateone";
+        type = types.path;
+        description = ''Path of configuration files for GateOne.'';
+      };
+    };
+};
+config = mkIf cfg.enable {
+  environment.systemPackages = with pkgs.pythonPackages; [
+    gateone pkgs.openssh pkgs.procps pkgs.coreutils pkgs.cacert];
+
+  users.users.gateone = {
+    description = "GateOne privilege separation user";
+    uid = config.ids.uids.gateone;
+    home = cfg.settingsDir;
+  };
+  users.groups.gateone.gid = config.ids.gids.gateone;
+
+  systemd.services.gateone = with pkgs; {
+    description = "GateOne web-based terminal";
+    path = [ pythonPackages.gateone nix openssh procps coreutils ];
+    preStart = ''
+      if [ ! -d ${cfg.settingsDir} ] ; then
+        mkdir -m 0750 -p ${cfg.settingsDir}
+        chown -R gateone.gateone ${cfg.settingsDir}
+      fi
+      if [ ! -d ${cfg.pidDir} ] ; then
+        mkdir -m 0750 -p ${cfg.pidDir}
+        chown -R gateone.gateone ${cfg.pidDir}
+      fi
+      '';
+    #unitConfig.RequiresMountsFor = "${cfg.settingsDir}";
+    serviceConfig = {
+      ExecStart = ''${pythonPackages.gateone}/bin/gateone --settings_dir=${cfg.settingsDir} --pid_file=${cfg.pidDir}/gateone.pid --gid=${toString config.ids.gids.gateone} --uid=${toString config.ids.uids.gateone}'';
+      User = "gateone";
+      Group = "gateone";
+      WorkingDirectory = cfg.settingsDir;
+    };
+
+    wantedBy = [ "multi-user.target" ];
+    requires = [ "network.target" ];
+  };
+};
+}
+  
diff --git a/nixpkgs/nixos/modules/services/networking/gdomap.nix b/nixpkgs/nixos/modules/services/networking/gdomap.nix
new file mode 100644
index 000000000000..3d829cb69135
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gdomap.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  #
+  # interface
+  #
+  options = {
+    services.gdomap = {
+      enable = mkEnableOption "GNUstep Distributed Objects name server";
+   };
+  };
+
+  #
+  # implementation
+  #
+  config = mkIf config.services.gdomap.enable {
+    # NOTE: gdomap runs as root
+    # TODO: extra user for gdomap?
+    systemd.services.gdomap = {
+      description = "gdomap server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path  = [ pkgs.gnustep.base ];
+      serviceConfig.ExecStart = "${pkgs.gnustep.base}/bin/gdomap -f";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/git-daemon.nix b/nixpkgs/nixos/modules/services/networking/git-daemon.nix
new file mode 100644
index 000000000000..52c895215fbe
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/git-daemon.nix
@@ -0,0 +1,130 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+
+  cfg = config.services.gitDaemon;
+
+in
+{
+
+  ###### interface
+
+  options = {
+    services.gitDaemon = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable Git daemon, which allows public hosting of git repositories
+          without any access controls. This is mostly intended for read-only access.
+
+          You can allow write access by setting daemon.receivepack configuration
+          item of the repository to true. This is solely meant for a closed LAN setting
+          where everybody is friendly.
+
+          If you need any access controls, use something else.
+        '';
+      };
+
+      basePath = mkOption {
+        type = types.str;
+        default = "";
+        example = "/srv/git/";
+        description = ''
+          Remap all the path requests as relative to the given path. For example,
+          if you set base-path to /srv/git, then if you later try to pull
+          git://example.com/hello.git, Git daemon will interpret the path as /srv/git/hello.git.
+        '';
+      };
+
+      exportAll = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Publish all directories that look like Git repositories (have the objects
+          and refs subdirectories), even if they do not have the git-daemon-export-ok file.
+
+          If disabled, you need to touch .git/git-daemon-export-ok in each repository
+          you want the daemon to publish.
+
+          Warning: enabling this without a repository whitelist or basePath
+          publishes every git repository you have.
+        '';
+      };
+
+      repositories = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "/srv/git" "/home/user/git/repo2" ];
+        description = ''
+          A whitelist of paths of git repositories, or directories containing repositories
+          all of which would be published. Paths must not end in "/".
+
+          Warning: leaving this empty and enabling exportAll publishes all
+          repositories in your filesystem or basePath if specified.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "";
+        example = "example.com";
+        description = "Listen on a specific IP address or hostname.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 9418;
+        description = "Port to listen on.";
+      };
+
+      options = mkOption {
+        type = types.str;
+        default = "";
+        description = "Extra configuration options to be passed to Git daemon.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "git";
+        description = "User under which Git daemon would be running.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "git";
+        description = "Group under which Git daemon would be running.";
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = optionalAttrs (cfg.user == "git") {
+      git = {
+        uid = config.ids.uids.git;
+        description = "Git daemon user";
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "git") {
+      git.gid = config.ids.gids.git;
+    };
+
+    systemd.services.git-daemon = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      script = "${pkgs.git}/bin/git daemon --reuseaddr "
+        + (optionalString (cfg.basePath != "") "--base-path=${cfg.basePath} ")
+        + (optionalString (cfg.listenAddress != "") "--listen=${cfg.listenAddress} ")
+        + "--port=${toString cfg.port} --user=${cfg.user} --group=${cfg.group} ${cfg.options} "
+        + "--verbose " + (optionalString cfg.exportAll "--export-all ")  + concatStringsSep " " cfg.repositories;
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gnunet.nix b/nixpkgs/nixos/modules/services/networking/gnunet.nix
new file mode 100644
index 000000000000..69d4ed047756
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gnunet.nix
@@ -0,0 +1,166 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.gnunet;
+
+  homeDir = "/var/lib/gnunet";
+
+  configFile = with cfg; pkgs.writeText "gnunetd.conf"
+    ''
+      [PATHS]
+      SERVICEHOME = ${homeDir}
+
+      [ats]
+      WAN_QUOTA_IN = ${toString load.maxNetDownBandwidth} b
+      WAN_QUOTA_OUT = ${toString load.maxNetUpBandwidth} b
+
+      [datastore]
+      QUOTA = ${toString fileSharing.quota} MB
+
+      [transport-udp]
+      PORT = ${toString udp.port}
+      ADVERTISED_PORT = ${toString udp.port}
+
+      [transport-tcp]
+      PORT = ${toString tcp.port}
+      ADVERTISED_PORT = ${toString tcp.port}
+
+      ${extraOptions}
+    '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.gnunet = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run the GNUnet daemon.  GNUnet is GNU's anonymous
+          peer-to-peer communication and file sharing framework.
+        '';
+      };
+
+      fileSharing = {
+        quota = mkOption {
+          type = types.int;
+          default = 1024;
+          description = ''
+            Maximum file system usage (in MiB) for file sharing.
+          '';
+        };
+      };
+
+      udp = {
+        port = mkOption {
+          type = types.port;
+          default = 2086;  # assigned by IANA
+          description = ''
+            The UDP port for use by GNUnet.
+          '';
+        };
+      };
+
+      tcp = {
+        port = mkOption {
+          type = types.port;
+          default = 2086;  # assigned by IANA
+          description = ''
+            The TCP port for use by GNUnet.
+          '';
+        };
+      };
+
+      load = {
+        maxNetDownBandwidth = mkOption {
+          type = types.int;
+          default = 50000;
+          description = ''
+            Maximum bandwidth usage (in bits per second) for GNUnet
+            when downloading data.
+          '';
+        };
+
+        maxNetUpBandwidth = mkOption {
+          type = types.int;
+          default = 50000;
+          description = ''
+            Maximum bandwidth usage (in bits per second) for GNUnet
+            when downloading data.
+          '';
+        };
+
+        hardNetUpBandwidth = mkOption {
+          type = types.int;
+          default = 0;
+          description = ''
+            Hard bandwidth limit (in bits per second) when uploading
+            data.
+          '';
+        };
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.gnunet;
+        defaultText = "pkgs.gnunet";
+        description = "Overridable attribute of the gnunet package to use.";
+        example = literalExample "pkgs.gnunet_git";
+      };
+
+      extraOptions = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional options that will be copied verbatim in `gnunet.conf'.
+          See `gnunet.conf(5)' for details.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnunet.enable {
+
+    users.users.gnunet = {
+      group = "gnunet";
+      description = "GNUnet User";
+      home = homeDir;
+      createHome = true;
+      uid = config.ids.uids.gnunet;
+    };
+
+    users.groups.gnunet.gid = config.ids.gids.gnunet;
+
+    # The user tools that talk to `gnunetd' should come from the same source,
+    # so install them globally.
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.gnunet = {
+      description = "GNUnet";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ cfg.package pkgs.miniupnpc ];
+      environment.TMPDIR = "/tmp";
+      serviceConfig.PrivateTmp = true;
+      serviceConfig.ExecStart = "${cfg.package}/lib/gnunet/libexec/gnunet-service-arm -c ${configFile}";
+      serviceConfig.User = "gnunet";
+      serviceConfig.UMask = "0007";
+      serviceConfig.WorkingDirectory = homeDir;
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/go-neb.nix b/nixpkgs/nixos/modules/services/networking/go-neb.nix
new file mode 100644
index 000000000000..991ae38f30a5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/go-neb.nix
@@ -0,0 +1,53 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.go-neb;
+
+  configFile = pkgs.writeText "config.yml" (builtins.toJSON cfg.config);
+in {
+  options.services.go-neb = {
+    enable = mkEnableOption "Extensible matrix bot written in Go";
+
+    bindAddress = mkOption {
+      type = types.str;
+      description = "Port (and optionally address) to listen on.";
+      default = ":4050";
+    };
+
+    baseUrl = mkOption {
+      type = types.str;
+      description = "Public-facing endpoint that can receive webhooks.";
+    };
+
+    config = mkOption {
+      type = types.uniq types.attrs;
+      description = ''
+        Your <filename>config.yaml</filename> as a Nix attribute set.
+        See <link xlink:href="https://github.com/matrix-org/go-neb/blob/master/config.sample.yaml">config.sample.yaml</link>
+        for possible options.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.go-neb = {
+      description = "Extensible matrix bot written in Go";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        BASE_URL = cfg.baseUrl;
+        BIND_ADDRESS = cfg.bindAddress;
+        CONFIG_FILE = configFile;
+      };
+
+      serviceConfig = {
+        ExecStart = "${pkgs.go-neb}/bin/go-neb";
+        DynamicUser = true;
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ hexa maralorn ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/go-shadowsocks2.nix b/nixpkgs/nixos/modules/services/networking/go-shadowsocks2.nix
new file mode 100644
index 000000000000..afbd7ea27c65
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/go-shadowsocks2.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.go-shadowsocks2.server;
+in {
+  options.services.go-shadowsocks2.server = {
+    enable = mkEnableOption "go-shadowsocks2 server";
+
+    listenAddress = mkOption {
+      type = types.str;
+      description = "Server listen address or URL";
+      example = "ss://AEAD_CHACHA20_POLY1305:your-password@:8488";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.go-shadowsocks2-server = {
+      description = "go-shadowsocks2 server";
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.go-shadowsocks2}/bin/go-shadowsocks2 -s '${cfg.listenAddress}'";
+        DynamicUser = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gogoclient.nix b/nixpkgs/nixos/modules/services/networking/gogoclient.nix
new file mode 100644
index 000000000000..99455b183144
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gogoclient.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.gogoclient;
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.gogoclient = {
+      enable = mkOption {
+        default = false;
+        type =  types.bool;
+        description = ''
+          Enable the gogoCLIENT IPv6 tunnel.
+        '';
+      };
+      autorun = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to automatically start the tunnel.
+        '';
+      };
+
+      username = mkOption {
+        default = "";
+        description = ''
+          Your Gateway6 login name, if any.
+        '';
+      };
+
+      password = mkOption {
+        default = "";
+        type = types.str;
+        description = ''
+          Path to a file (as a string), containing your gogoNET password, if any.
+        '';
+      };
+
+      server = mkOption {
+        default = "anonymous.freenet6.net";
+        example = "broker.freenet6.net";
+        description = "The Gateway6 server to be used.";
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "tun" ];
+
+    networking.enableIPv6 = true;
+
+    systemd.services.gogoclient = {
+      description = "ipv6 tunnel";
+
+      after = [ "network.target" ];
+      requires = [ "network.target" ];
+
+      unitConfig.RequiresMountsFor = "/var/lib/gogoc";
+
+      script = let authMethod = if cfg.password == "" then "anonymous" else "any"; in ''
+        mkdir -p -m 700 /var/lib/gogoc
+        cat ${pkgs.gogoclient}/share/${pkgs.gogoclient.name}/gogoc.conf.sample | \
+          ${pkgs.gnused}/bin/sed \
+            -e "s|^userid=|&${cfg.username}|" \
+            -e "s|^passwd=|&${optionalString (cfg.password != "") "$(cat ${cfg.password})"}|" \
+            -e "s|^server=.*|server=${cfg.server}|" \
+            -e "s|^auth_method=.*|auth_method=${authMethod}|" \
+            -e "s|^#log_file=|log_file=1|" > /var/lib/gogoc/gogoc.conf
+        cd /var/lib/gogoc
+        exec ${pkgs.gogoclient}/bin/gogoc -y -f /var/lib/gogoc/gogoc.conf
+      '';
+    } // optionalAttrs cfg.autorun {
+      wantedBy = [ "multi-user.target" ];
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/gvpe.nix b/nixpkgs/nixos/modules/services/networking/gvpe.nix
new file mode 100644
index 000000000000..92e87cd4640d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/gvpe.nix
@@ -0,0 +1,124 @@
+# GNU Virtual Private Ethernet
+
+{config, pkgs, lib, ...}:
+
+let
+  inherit (lib) mkOption mkIf;
+
+  cfg = config.services.gvpe;
+
+  finalConfig = if cfg.configFile != null then
+    cfg.configFile
+  else if cfg.configText != null then
+    pkgs.writeTextFile {
+      name = "gvpe.conf";
+      text = cfg.configText;
+    }
+  else
+    throw "You must either specify contents of the config file or the config file itself for GVPE";
+
+  ifupScript = if cfg.ipAddress == null || cfg.subnet == null then
+     throw "Specify IP address and subnet (with mask) for GVPE"
+   else if cfg.nodename == null then
+     throw "You must set node name for GVPE"
+   else
+   (pkgs.writeTextFile {
+    name = "gvpe-if-up";
+    text = ''
+      #! /bin/sh
+
+      export PATH=$PATH:${pkgs.iproute}/sbin
+
+      ip link set $IFNAME up
+      ip address add ${cfg.ipAddress} dev $IFNAME
+      ip route add ${cfg.subnet} dev $IFNAME
+
+      ${cfg.customIFSetup}
+    '';
+    executable = true;
+  });
+in
+
+{
+  options = {
+    services.gvpe = {
+      enable = lib.mkEnableOption "gvpe";
+
+      nodename = mkOption {
+        default = null;
+        description =''
+          GVPE node name
+        '';
+      };
+      configText = mkOption {
+        default = null;
+        example = ''
+          tcp-port = 655
+          udp-port = 655
+          mtu = 1480
+          ifname = vpn0
+
+          node = alpha
+          hostname = alpha.example.org
+          connect = always
+          enable-udp = true
+          enable-tcp = true
+          on alpha if-up = if-up-0
+          on alpha pid-file = /var/gvpe/gvpe.pid
+        '';
+        description = ''
+          GVPE config contents
+        '';
+      };
+      configFile = mkOption {
+        default = null;
+        example = "/root/my-gvpe-conf";
+        description = ''
+          GVPE config file, if already present
+        '';
+      };
+      ipAddress = mkOption {
+        default = null;
+        description = ''
+          IP address to assign to GVPE interface
+        '';
+      };
+      subnet = mkOption {
+        default = null;
+        example = "10.0.0.0/8";
+        description = ''
+          IP subnet assigned to GVPE network
+        '';
+      };
+      customIFSetup = mkOption {
+        default = "";
+        description = ''
+          Additional commands to apply in ifup script
+        '';
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+    systemd.services.gvpe = {
+      description = "GNU Virtual Private Ethernet node";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        mkdir -p /var/gvpe
+        mkdir -p /var/gvpe/pubkey
+        chown root /var/gvpe
+        chmod 700 /var/gvpe
+        cp ${finalConfig} /var/gvpe/gvpe.conf
+        cp ${ifupScript} /var/gvpe/if-up
+      '';
+
+      script = "${pkgs.gvpe}/sbin/gvpe -c /var/gvpe -D ${cfg.nodename} "
+        + " ${cfg.nodename}.pid-file=/var/gvpe/gvpe.pid"
+        + " ${cfg.nodename}.if-up=if-up"
+        + " &> /var/log/gvpe";
+
+      serviceConfig.Restart = "always";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hans.nix b/nixpkgs/nixos/modules/services/networking/hans.nix
new file mode 100644
index 000000000000..8334dc68d623
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hans.nix
@@ -0,0 +1,145 @@
+# NixOS module for hans, ip over icmp daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.hans;
+
+  hansUser = "hans";
+
+in
+{
+
+  ### configuration
+
+  options = {
+
+    services.hans = {
+      clients = mkOption {
+        default = {};
+        description = ''
+          Each attribute of this option defines a systemd service that
+          runs hans. Many or none may be defined.
+          The name of each service is
+          <literal>hans-<replaceable>name</replaceable></literal>
+          where <replaceable>name</replaceable> is the name of the
+          corresponding attribute name.
+        '';
+        example = literalExample ''
+        {
+          foo = {
+            server = "192.0.2.1";
+            extraConfig = "-v";
+          }
+        }
+        '';
+        type = types.attrsOf (types.submodule (
+        {
+          options = {
+            server = mkOption {
+              type = types.str;
+              default = "";
+              description = "IP address of server running hans";
+              example = "192.0.2.1";
+            };
+
+            extraConfig = mkOption {
+              type = types.str;
+              default = "";
+              description = "Additional command line parameters";
+              example = "-v";
+            };
+
+            passwordFile = mkOption {
+              type = types.str;
+              default = "";
+              description = "File that containts password";
+            };
+
+          };
+        }));
+      };
+
+      server = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = "enable hans server";
+        };
+
+        ip = mkOption {
+          type = types.str;
+          default = "";
+          description = "The assigned ip range";
+          example = "198.51.100.0";
+        };
+
+        respondToSystemPings = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Force hans respond to ordinary pings";
+        };
+
+        extraConfig = mkOption {
+          type = types.str;
+          default = "";
+          description = "Additional command line parameters";
+          example = "-v";
+        };
+
+        passwordFile = mkOption {
+          type = types.str;
+          default = "";
+          description = "File that containts password";
+        };
+      };
+
+    };
+  };
+
+  ### implementation
+
+  config = mkIf (cfg.server.enable || cfg.clients != {}) {
+    boot.kernel.sysctl = optionalAttrs cfg.server.respondToSystemPings {
+      "net.ipv4.icmp_echo_ignore_all" = 1;
+    };
+
+    boot.kernelModules = [ "tun" ];
+
+    systemd.services =
+    let
+      createHansClientService = name: cfg:
+      {
+        description = "hans client - ${name}";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        script = "${pkgs.hans}/bin/hans -f -u ${hansUser} ${cfg.extraConfig} -c ${cfg.server} ${optionalString (cfg.passwordFile != "") "-p $(cat \"${cfg.passwordFile}\")"}";
+        serviceConfig = {
+          RestartSec = "30s";
+          Restart = "always";
+        };
+      };
+    in
+    listToAttrs (
+      mapAttrsToList
+        (name: value: nameValuePair "hans-${name}" (createHansClientService name value))
+        cfg.clients
+    ) // {
+      hans = mkIf (cfg.server.enable) {
+        description = "hans, ip over icmp server daemon";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        script = "${pkgs.hans}/bin/hans -f -u ${hansUser} ${cfg.server.extraConfig} -s ${cfg.server.ip} ${optionalString cfg.server.respondToSystemPings "-r"} ${optionalString (cfg.server.passwordFile != "") "-p $(cat \"${cfg.server.passwordFile}\")"}";
+      };
+    };
+
+    users.users.${hansUser} = {
+      description = "Hans daemon user";
+      isSystemUser = true;
+    };
+  };
+
+  meta.maintainers = with maintainers; [ gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/haproxy.nix b/nixpkgs/nixos/modules/services/networking/haproxy.nix
new file mode 100644
index 000000000000..e9d72b35499d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/haproxy.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.haproxy;
+
+  haproxyCfg = pkgs.writeText "haproxy.conf" ''
+    global
+      # needed for hot-reload to work without dropping packets in multi-worker mode
+      stats socket /run/haproxy/haproxy.sock mode 600 expose-fd listeners level user
+
+    ${cfg.config}
+  '';
+
+in
+with lib;
+{
+  options = {
+    services.haproxy = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable HAProxy, the reliable, high performance TCP/HTTP
+          load balancer.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "haproxy";
+        description = "User account under which haproxy runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "haproxy";
+        description = "Group account under which haproxy runs.";
+      };
+
+      config = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = ''
+          Contents of the HAProxy configuration file,
+          <filename>haproxy.conf</filename>.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [{
+      assertion = cfg.config != null;
+      message = "You must provide services.haproxy.config.";
+    }];
+
+    # configuration file indirection is needed to support reloading
+    environment.etc."haproxy.cfg".source = haproxyCfg;
+
+    systemd.services.haproxy = {
+      description = "HAProxy";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        Type = "notify";
+        ExecStartPre = [
+          # when the master process receives USR2, it reloads itself using exec(argv[0]),
+          # so we create a symlink there and update it before reloading
+          "${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy"
+          # when running the config test, don't be quiet so we can see what goes wrong
+          "/run/haproxy/haproxy -c -f ${haproxyCfg}"
+        ];
+        ExecStart = "/run/haproxy/haproxy -Ws -f /etc/haproxy.cfg -p /run/haproxy/haproxy.pid";
+        # support reloading
+        ExecReload = [
+          "${pkgs.haproxy}/sbin/haproxy -c -f ${haproxyCfg}"
+          "${pkgs.coreutils}/bin/ln -sf ${pkgs.haproxy}/sbin/haproxy /run/haproxy/haproxy"
+          "${pkgs.coreutils}/bin/kill -USR2 $MAINPID"
+        ];
+        KillMode = "mixed";
+        SuccessExitStatus = "143";
+        Restart = "always";
+        RuntimeDirectory = "haproxy";
+        # upstream hardening options
+        NoNewPrivileges = true;
+        ProtectHome = true;
+        ProtectSystem = "strict";
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        SystemCallFilter= "~@cpu-emulation @keyring @module @obsolete @raw-io @reboot @swap @sync";
+        # needed in case we bind to port < 1024
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == "haproxy") {
+      haproxy = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "haproxy") {
+      haproxy = {};
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/helpers.nix b/nixpkgs/nixos/modules/services/networking/helpers.nix
new file mode 100644
index 000000000000..d7d42de0e3a8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/helpers.nix
@@ -0,0 +1,11 @@
+{ config, lib, ... }: ''
+  # Helper command to manipulate both the IPv4 and IPv6 tables.
+  ip46tables() {
+    iptables -w "$@"
+    ${
+      lib.optionalString config.networking.enableIPv6 ''
+        ip6tables -w "$@"
+      ''
+    }
+  }
+''
diff --git a/nixpkgs/nixos/modules/services/networking/heyefi.nix b/nixpkgs/nixos/modules/services/networking/heyefi.nix
new file mode 100644
index 000000000000..fc2b5a848578
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/heyefi.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.heyefi;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.heyefi = {
+
+      enable = mkEnableOption "heyefi";
+
+      cardMacaddress = mkOption {
+        default = "";
+        description = ''
+          An Eye-Fi card MAC address.
+          '';
+      };
+
+      uploadKey = mkOption {
+        default = "";
+        description = ''
+          An Eye-Fi card's upload key.
+          '';
+      };
+
+      uploadDir = mkOption {
+        example = "/home/username/pictures";
+        description = ''
+          The directory to upload the files to.
+          '';
+      };
+
+      user = mkOption {
+        default = "root";
+        description = ''
+          heyefi will be run under this user (user must exist,
+          this can be your user name).
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.heyefi =
+      {
+        description = "heyefi service";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = "${cfg.user}";
+          Restart = "always";
+          ExecStart = "${pkgs.heyefi}/bin/heyefi";
+        };
+
+      };
+
+    environment.etc."heyefi/heyefi.config".text =
+      ''
+        # /etc/heyefi/heyefi.conf: DO NOT EDIT -- this file has been generated automatically.
+        cards = [["${config.services.heyefi.cardMacaddress}","${config.services.heyefi.uploadKey}"]]
+        upload_dir = "${toString config.services.heyefi.uploadDir}"
+      '';
+
+    environment.systemPackages = [ pkgs.heyefi ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hostapd.nix b/nixpkgs/nixos/modules/services/networking/hostapd.nix
new file mode 100644
index 000000000000..5d73038363a9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hostapd.nix
@@ -0,0 +1,218 @@
+{ config, lib, pkgs, utils, ... }:
+
+# TODO:
+#
+# asserts
+#   ensure that the nl80211 module is loaded/compiled in the kernel
+#   wpa_supplicant and hostapd on the same wireless interface doesn't make any sense
+
+with lib;
+
+let
+
+  cfg = config.services.hostapd;
+
+  escapedInterface = utils.escapeSystemdPath cfg.interface;
+
+  configFile = pkgs.writeText "hostapd.conf" ''
+    interface=${cfg.interface}
+    driver=${cfg.driver}
+    ssid=${cfg.ssid}
+    hw_mode=${cfg.hwMode}
+    channel=${toString cfg.channel}
+    ${optionalString (cfg.countryCode != null) ''country_code=${cfg.countryCode}''}
+    ${optionalString (cfg.countryCode != null) ''ieee80211d=1''}
+
+    # logging (debug level)
+    logger_syslog=-1
+    logger_syslog_level=${toString cfg.logLevel}
+    logger_stdout=-1
+    logger_stdout_level=${toString cfg.logLevel}
+
+    ctrl_interface=/run/hostapd
+    ctrl_interface_group=${cfg.group}
+
+    ${optionalString cfg.wpa ''
+      wpa=2
+      wpa_passphrase=${cfg.wpaPassphrase}
+    ''}
+    ${optionalString cfg.noScan "noscan=1"}
+
+    ${cfg.extraConfig}
+  '' ;
+
+in
+
+{
+  ###### interface
+
+  options = {
+
+    services.hostapd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable putting a wireless interface into infrastructure mode,
+          allowing other wireless devices to associate with the wireless
+          interface and do wireless networking. A simple access point will
+          <option>enable hostapd.wpa</option>,
+          <option>hostapd.wpaPassphrase</option>, and
+          <option>hostapd.ssid</option>, as well as DHCP on the wireless
+          interface to provide IP addresses to the associated stations, and
+          NAT (from the wireless interface to an upstream interface).
+        '';
+      };
+
+      interface = mkOption {
+        default = "";
+        example = "wlp2s0";
+        description = ''
+          The interfaces <command>hostapd</command> will use.
+        '';
+      };
+
+      noScan = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Do not scan for overlapping BSSs in HT40+/- mode.
+          Caution: turning this on will violate regulatory requirements!
+        '';
+      };
+
+      driver = mkOption {
+        default = "nl80211";
+        example = "hostapd";
+        type = types.str;
+        description = ''
+          Which driver <command>hostapd</command> will use.
+          Most applications will probably use the default.
+        '';
+      };
+
+      ssid = mkOption {
+        default = "nixos";
+        example = "mySpecialSSID";
+        type = types.str;
+        description = "SSID to be used in IEEE 802.11 management frames.";
+      };
+
+      hwMode = mkOption {
+        default = "g";
+        type = types.enum [ "a" "b" "g" ];
+        description = ''
+          Operation mode.
+          (a = IEEE 802.11a, b = IEEE 802.11b, g = IEEE 802.11g).
+        '';
+      };
+
+      channel = mkOption {
+        default = 7;
+        example = 11;
+        type = types.int;
+        description = ''
+          Channel number (IEEE 802.11)
+          Please note that some drivers do not use this value from
+          <command>hostapd</command> and the channel will need to be configured
+          separately with <command>iwconfig</command>.
+        '';
+      };
+
+      group = mkOption {
+        default = "wheel";
+        example = "network";
+        type = types.str;
+        description = ''
+          Members of this group can control <command>hostapd</command>.
+        '';
+      };
+
+      wpa = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enable WPA (IEEE 802.11i/D3.0) to authenticate with the access point.
+        '';
+      };
+
+      wpaPassphrase = mkOption {
+        default = "my_sekret";
+        example = "any_64_char_string";
+        type = types.str;
+        description = ''
+          WPA-PSK (pre-shared-key) passphrase. Clients will need this
+          passphrase to associate with this access point.
+          Warning: This passphrase will get put into a world-readable file in
+          the Nix store!
+        '';
+      };
+
+      logLevel = mkOption {
+        default = 2;
+        type = types.int;
+        description = ''
+          Levels (minimum value for logged events):
+          0 = verbose debugging
+          1 = debugging
+          2 = informational messages
+          3 = notification
+          4 = warning
+        '';
+      };
+
+      countryCode = mkOption {
+        default = null;
+        example = "US";
+        type = with types; nullOr str;
+        description = ''
+          Country code (ISO/IEC 3166-1). Used to set regulatory domain.
+          Set as needed to indicate country in which device is operating.
+          This can limit available channels and transmit power.
+          These two octets are used as the first two octets of the Country String
+          (dot11CountryString).
+          If set this enables IEEE 802.11d. This advertises the countryCode and
+          the set of allowed channels and transmit power levels based on the
+          regulatory limits.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        example = ''
+          auth_algo=0
+          ieee80211n=1
+          ht_capab=[HT40-][SHORT-GI-40][DSSS_CCK-40]
+          '';
+        type = types.lines;
+        description = "Extra configuration options to put in hostapd.conf.";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages =  [ pkgs.hostapd ];
+
+    services.udev.packages = optional (cfg.countryCode != null) [ pkgs.crda ];
+
+    systemd.services.hostapd =
+      { description = "hostapd wireless AP";
+
+        path = [ pkgs.hostapd ];
+        after = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
+        bindsTo = [ "sys-subsystem-net-devices-${escapedInterface}.device" ];
+        requiredBy = [ "network-link-${cfg.interface}.service" ];
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig =
+          { ExecStart = "${pkgs.hostapd}/bin/hostapd ${configFile}";
+            Restart = "always";
+          };
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/htpdate.nix b/nixpkgs/nixos/modules/services/networking/htpdate.nix
new file mode 100644
index 000000000000..6954e5b060c4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/htpdate.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  inherit (pkgs) htpdate;
+
+  cfg = config.services.htpdate;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.htpdate = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable htpdate daemon.
+        '';
+      };
+
+      extraOptions = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Additional command line arguments to pass to htpdate.
+        '';
+      };
+
+      servers = mkOption {
+        type = types.listOf types.str;
+        default = [ "www.google.com" ];
+        description = ''
+          HTTP servers to use for time synchronization.
+        '';
+      };
+
+      proxy = mkOption {
+        type = types.str;
+        default = "";
+        example = "127.0.0.1:8118";
+        description = ''
+          HTTP proxy used for requests.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.htpdate = {
+      description = "htpdate daemon";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "forking";
+        PIDFile = "/run/htpdate.pid";
+        ExecStart = concatStringsSep " " [
+          "${htpdate}/bin/htpdate"
+          "-D -u nobody"
+          "-a -s"
+          "-l"
+          "${optionalString (cfg.proxy != "") "-P ${cfg.proxy}"}"
+          "${cfg.extraOptions}"
+          "${concatStringsSep " " cfg.servers}"
+        ];
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/default.nix b/nixpkgs/nixos/modules/services/networking/hylafax/default.nix
new file mode 100644
index 000000000000..d8ffa3fc04d2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/default.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+  imports = [
+    ./options.nix
+    ./systemd.nix
+  ];
+
+  config = lib.modules.mkIf config.services.hylafax.enable {
+    environment.systemPackages = [ pkgs.hylafaxplus ];
+    users.users.uucp = {
+      uid = config.ids.uids.uucp;
+      group = "uucp";
+      description = "Unix-to-Unix CoPy system";
+      isSystemUser = true;
+      inherit (config.users.users.nobody) home;
+    };
+    assertions = [{
+      assertion = config.services.hylafax.modems != {};
+      message = ''
+        HylaFAX cannot be used without modems.
+        Please define at least one modem with
+        <option>config.services.hylafax.modems</option>.
+      '';
+    }];
+  };
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/faxq-default.nix b/nixpkgs/nixos/modules/services/networking/hylafax/faxq-default.nix
new file mode 100644
index 000000000000..9b634650cf79
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/faxq-default.nix
@@ -0,0 +1,12 @@
+{ ... }:
+
+# see man:hylafax-config(5)
+
+{
+
+  ModemGroup = [ ''"any:0:.*"'' ];
+  ServerTracing = "0x78701";
+  SessionTracing = "0x78701";
+  UUCPLockDir = "/var/lock";
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/faxq-wait.sh b/nixpkgs/nixos/modules/services/networking/hylafax/faxq-wait.sh
new file mode 100755
index 000000000000..8c39e9d20c18
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/faxq-wait.sh
@@ -0,0 +1,29 @@
+#! @shell@ -e
+
+# skip this if there are no modems at all
+if ! stat -t "@spoolAreaPath@"/etc/config.* >/dev/null 2>&1
+then
+  exit 0
+fi
+
+echo "faxq started, waiting for modem(s) to initialize..."
+
+for i in `seq @timeoutSec@0 -1 0`  # gracefully timeout
+do
+  sleep 0.1
+  # done if status files exist, but don't mention initialization
+  if \
+    stat -t "@spoolAreaPath@"/status/* >/dev/null 2>&1 \
+    && \
+    ! grep --silent --ignore-case 'initializing server' \
+    "@spoolAreaPath@"/status/*
+  then
+    echo "modem(s) apparently ready"
+    exit 0
+  fi
+  # if i reached 0, modems probably failed to initialize
+  if test $i -eq 0
+  then
+    echo "warning: modem initialization timed out"
+  fi
+done
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/hfaxd-default.nix b/nixpkgs/nixos/modules/services/networking/hylafax/hfaxd-default.nix
new file mode 100644
index 000000000000..8999dae57f41
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/hfaxd-default.nix
@@ -0,0 +1,10 @@
+{ ... }:
+
+# see man:hfaxd(8)
+
+{
+
+  ServerTracing = "0x91";
+  XferLogFile = "/clientlog";
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/modem-default.nix b/nixpkgs/nixos/modules/services/networking/hylafax/modem-default.nix
new file mode 100644
index 000000000000..7529b5b0aafd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/modem-default.nix
@@ -0,0 +1,22 @@
+{ pkgs, ... }:
+
+# see man:hylafax-config(5)
+
+{
+
+  TagLineFont = "etc/LiberationSans-25.pcf";
+  TagLineLocale = ''en_US.UTF-8'';
+
+  AdminGroup = "root";  # groups that can change server config
+  AnswerRotary = "fax";  # don't accept anything else but faxes
+  LogFileMode = "0640";
+  PriorityScheduling = true;
+  RecvFileMode = "0640";
+  ServerTracing = "0x78701";
+  SessionTracing = "0x78701";
+  UUCPLockDir = "/var/lock";
+
+  SendPageCmd = ''${pkgs.coreutils}/bin/false'';  # prevent pager transmit
+  SendUUCPCmd = ''${pkgs.coreutils}/bin/false'';  # prevent UUCP transmit
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/options.nix b/nixpkgs/nixos/modules/services/networking/hylafax/options.nix
new file mode 100644
index 000000000000..4ac6d3fa8432
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/options.nix
@@ -0,0 +1,375 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib.options) literalExample mkEnableOption mkOption;
+  inherit (lib.types) bool enum int lines loaOf nullOr path str submodule;
+  inherit (lib.modules) mkDefault mkIf mkMerge;
+
+  commonDescr = ''
+    Values can be either strings or integers
+    (which will be added to the config file verbatimly)
+    or lists thereof
+    (which will be translated to multiple
+    lines with the same configuration key).
+    Boolean values are translated to "Yes" or "No".
+    The default contains some reasonable
+    configuration to yield an operational system.
+  '';
+
+  str1 = lib.types.addCheck str (s: s!="");  # non-empty string
+  int1 = lib.types.addCheck int (i: i>0);  # positive integer
+
+  configAttrType =
+    # Options in HylaFAX configuration files can be
+    # booleans, strings, integers, or list thereof
+    # representing multiple config directives with the same key.
+    # This type definition resolves all
+    # those types into a list of strings.
+    let
+      inherit (lib.types) attrsOf coercedTo listOf;
+      innerType = coercedTo bool (x: if x then "Yes" else "No")
+        (coercedTo int (toString) str);
+    in
+      attrsOf (coercedTo innerType lib.singleton (listOf innerType));
+
+  cfg = config.services.hylafax;
+
+  modemConfigOptions = { name, config, ... }: {
+    options = {
+      name = mkOption {
+        type = str1;
+        example = "ttyS1";
+        description = ''
+          Name of modem device,
+          will be searched for in <filename>/dev</filename>.
+        '';
+      };
+      type = mkOption {
+        type = str1;
+        example = "cirrus";
+        description = ''
+          Name of modem configuration file,
+          will be searched for in <filename>config</filename>
+          in the spooling area directory.
+        '';
+      };
+      config = mkOption {
+        type = configAttrType;
+        example = {
+          AreaCode = "49";
+          LocalCode = "30";
+          FAXNumber = "123456";
+          LocalIdentifier = "LostInBerlin";
+        };
+        description = ''
+          Attribute set of values for the given modem.
+          ${commonDescr}
+          Options defined here override options in
+          <option>commonModemConfig</option> for this modem.
+        '';
+      };
+    };
+    config.name = mkDefault name;
+    config.config.Include = [ "config/${config.type}" ];
+  };
+
+  defaultConfig =
+    let
+      inherit (config.security) wrapperDir;
+      inherit (config.services.mail.sendmailSetuidWrapper) program;
+      mkIfDefault = cond: value: mkIf cond (mkDefault value);
+      noWrapper = config.services.mail.sendmailSetuidWrapper==null;
+      # If a sendmail setuid wrapper exists,
+      # we add the path to the default configuration file.
+      # Otherwise, we use `false` to provoke
+      # an error if hylafax tries to use it.
+      c.sendmailPath = mkMerge [
+        (mkIfDefault noWrapper ''${pkgs.coreutils}/bin/false'')
+        (mkIfDefault (!noWrapper) ''${wrapperDir}/${program}'')
+      ];
+      importDefaultConfig = file:
+        lib.attrsets.mapAttrs
+        (lib.trivial.const mkDefault)
+        (import file { inherit pkgs; });
+      c.commonModemConfig = importDefaultConfig ./modem-default.nix;
+      c.faxqConfig = importDefaultConfig ./faxq-default.nix;
+      c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix;
+    in
+      c;
+
+  localConfig =
+    let
+      c.hfaxdConfig.UserAccessFile = cfg.userAccessFile;
+      c.faxqConfig = lib.attrsets.mapAttrs
+        (lib.trivial.const (v: mkIf (v!=null) v))
+        {
+          AreaCode = cfg.areaCode;
+          CountryCode = cfg.countryCode;
+          LongDistancePrefix = cfg.longDistancePrefix;
+          InternationalPrefix = cfg.internationalPrefix;
+        };
+      c.commonModemConfig = c.faxqConfig;
+    in
+      c;
+
+in
+
+
+{
+
+
+  options.services.hylafax = {
+
+    enable = mkEnableOption ''HylaFAX server'';
+
+    autostart = mkOption {
+      type = bool;
+      default = true;
+      example = false;
+      description = ''
+        Autostart the HylaFAX queue manager at system start.
+        If this is <literal>false</literal>, the queue manager
+        will still be started if there are pending
+        jobs or if a user tries to connect to it.
+      '';
+    };
+
+    countryCode = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "49";
+      description = ''Country code for server and all modems.'';
+    };
+
+    areaCode = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "30";
+      description = ''Area code for server and all modems.'';
+    };
+
+    longDistancePrefix = mkOption {
+      type = nullOr str;
+      default = null;
+      example = "0";
+      description = ''Long distance prefix for server and all modems.'';
+    };
+
+    internationalPrefix = mkOption {
+      type = nullOr str;
+      default = null;
+      example = "00";
+      description = ''International prefix for server and all modems.'';
+    };
+
+    spoolAreaPath = mkOption {
+      type = path;
+      default = "/var/spool/fax";
+      description = ''
+        The spooling area will be created/maintained
+        at the location given here.
+      '';
+    };
+
+    userAccessFile = mkOption {
+      type = path;
+      default = "/etc/hosts.hfaxd";
+      description = ''
+        The <filename>hosts.hfaxd</filename>
+        file entry in the spooling area
+        will be symlinked to the location given here.
+        This file must exist and be
+        readable only by the <literal>uucp</literal> user.
+        See hosts.hfaxd(5) for details.
+        This configuration permits access for all users:
+        <literal>
+          environment.etc."hosts.hfaxd" = {
+            mode = "0600";
+            user = "uucp";
+            text = ".*";
+          };
+        </literal>
+        Note that host-based access can be controlled with
+        <option>config.systemd.sockets.hylafax-hfaxd.listenStreams</option>;
+        by default, only 127.0.0.1 is permitted to connect.
+      '';
+    };
+
+    sendmailPath = mkOption {
+      type = path;
+      example = literalExample "''${pkgs.postfix}/bin/sendmail";
+      # '' ;  # fix vim
+      description = ''
+        Path to <filename>sendmail</filename> program.
+        The default uses the local sendmail wrapper
+        (see <option>config.services.mail.sendmailSetuidWrapper</option>),
+        otherwise the <filename>false</filename>
+        binary to cause an error if used.
+      '';
+    };
+
+    hfaxdConfig = mkOption {
+      type = configAttrType;
+      example.RecvqProtection = "0400";
+      description = ''
+        Attribute set of lines for the global
+        hfaxd config file <filename>etc/hfaxd.conf</filename>.
+        ${commonDescr}
+      '';
+    };
+
+    faxqConfig = mkOption {
+      type = configAttrType;
+      example = {
+        InternationalPrefix = "00";
+        LongDistancePrefix = "0";
+      };
+      description = ''
+        Attribute set of lines for the global
+        faxq config file <filename>etc/config</filename>.
+        ${commonDescr}
+      '';
+    };
+
+    commonModemConfig = mkOption {
+      type = configAttrType;
+      example = {
+        InternationalPrefix = "00";
+        LongDistancePrefix = "0";
+      };
+      description = ''
+        Attribute set of default values for
+        modem config files <filename>etc/config.*</filename>.
+        ${commonDescr}
+        Think twice before changing
+        paths of fax-processing scripts.
+      '';
+    };
+
+    modems = mkOption {
+      type = loaOf (submodule [ modemConfigOptions ]);
+      default = {};
+      example.ttyS1 = {
+        type = "cirrus";
+        config = {
+          FAXNumber = "123456";
+          LocalIdentifier = "Smith";
+        };
+      };
+      description = ''
+        Description of installed modems.
+        At least on modem must be defined
+        to enable the HylaFAX server.
+      '';
+    };
+
+    spoolExtraInit = mkOption {
+      type = lines;
+      default = "";
+      example = ''chmod 0755 .  # everyone may read my faxes'';
+      description = ''
+        Additional shell code that is executed within the
+        spooling area directory right after its setup.
+      '';
+    };
+
+    faxcron.enable.spoolInit = mkEnableOption ''
+      Purge old files from the spooling area with
+      <filename>faxcron</filename>
+      each time the spooling area is initialized.
+    '';
+    faxcron.enable.frequency = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "daily";
+      description = ''
+        Purge old files from the spooling area with
+        <filename>faxcron</filename> with the given frequency
+        (see systemd.time(7)).
+      '';
+    };
+    faxcron.infoDays = mkOption {
+      type = int1;
+      default = 30;
+      description = ''
+        Set the expiration time for data in the
+        remote machine information directory in days.
+      '';
+    };
+    faxcron.logDays = mkOption {
+      type = int1;
+      default = 30;
+      description = ''
+        Set the expiration time for
+        session trace log files in days.
+      '';
+    };
+    faxcron.rcvDays = mkOption {
+      type = int1;
+      default = 7;
+      description = ''
+        Set the expiration time for files in
+        the received facsimile queue in days.
+      '';
+    };
+
+    faxqclean.enable.spoolInit = mkEnableOption ''
+      Purge old files from the spooling area with
+      <filename>faxqclean</filename>
+      each time the spooling area is initialized.
+    '';
+    faxqclean.enable.frequency = mkOption {
+      type = nullOr str1;
+      default = null;
+      example = "daily";
+      description = ''
+        Purge old files from the spooling area with
+        <filename>faxcron</filename> with the given frequency
+        (see systemd.time(7)).
+      '';
+    };
+    faxqclean.archiving = mkOption {
+      type = enum [ "never" "as-flagged" "always" ];
+      default = "as-flagged";
+      example = "always";
+      description = ''
+        Enable or suppress job archiving:
+        <literal>never</literal> disables job archiving,
+        <literal>as-flagged</literal> archives jobs that
+        have been flagged for archiving by sendfax,
+        <literal>always</literal> forces archiving of all jobs.
+        See also sendfax(1) and faxqclean(8).
+      '';
+    };
+    faxqclean.doneqMinutes = mkOption {
+      type = int1;
+      default = 15;
+      example = literalExample ''24*60'';
+      description = ''
+        Set the job
+        age threshold (in minutes) that controls how long
+        jobs may reside in the doneq directory.
+      '';
+    };
+    faxqclean.docqMinutes = mkOption {
+      type = int1;
+      default = 60;
+      example = literalExample ''24*60'';
+      description = ''
+        Set the document
+        age threshold (in minutes) that controls how long
+        unreferenced files may reside in the docq directory.
+      '';
+    };
+
+  };
+
+
+  config.services.hylafax =
+    mkIf
+    (config.services.hylafax.enable)
+    (mkMerge [ defaultConfig localConfig ])
+  ;
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/spool.sh b/nixpkgs/nixos/modules/services/networking/hylafax/spool.sh
new file mode 100755
index 000000000000..31e930e8c597
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/spool.sh
@@ -0,0 +1,111 @@
+#! @shell@ -e
+
+# The following lines create/update the HylaFAX spool directory:
+# Subdirectories/files with persistent data are kept,
+# other directories/files are removed/recreated,
+# mostly from the template spool
+# directory in the HylaFAX package.
+
+# This block explains how the spool area is
+# derived from the spool template in the HylaFAX package:
+#
+#                  + capital letter: directory; file otherwise
+#                  + P/p: persistent directory
+#                  + F/f: directory with symlinks per entry
+#                  + T/t: temporary data
+#                  + S/s: single symlink into package
+#                  |
+#                  | + u: change ownership to uucp:uucp
+#                  | + U: ..also change access mode to user-only
+#                  | |
+# archive          P U
+# bin              S
+# client           T u  (client connection info)
+# config           S
+# COPYRIGHT        s
+# dev              T u  (maybe some FIFOs)
+# docq             P U
+# doneq            P U
+# etc              F    contains customized config files!
+# etc/hosts.hfaxd  f
+# etc/xferfaxlog   f
+# info             P u  (database of called devices)
+# log              P u  (communication logs)
+# pollq            P U
+# recvq            P u
+# sendq            P U
+# status           T u  (modem status info files)
+# tmp              T U
+
+
+shopt -s dotglob  # if bash sees "*", it also includes dot files
+lnsym () { ln --symbol "$@" ; }
+lnsymfrc () { ln --symbolic --force "$@" ; }
+cprd () { cp --remove-destination "$@" ; }
+update () { install --owner=@faxuser@ --group=@faxgroup@ "$@" ; }
+
+
+## create/update spooling area
+
+update --mode=0750 -d "@spoolAreaPath@"
+cd "@spoolAreaPath@"
+
+persist=(archive docq doneq info log pollq recvq sendq)
+
+# remove entries that don't belong here
+touch dummy  # ensure "*" resolves to something
+for k in *
+do
+  keep=0
+  for j in "${persist[@]}" xferfaxlog clientlog faxcron.lastrun
+  do
+    if test "$k" == "$j"
+    then
+      keep=1
+      break
+    fi
+  done
+  if test "$keep" == "0"
+  then
+    rm --recursive "$k"
+  fi
+done
+
+# create persistent data directories (unless they exist already)
+update --mode=0700 -d "${persist[@]}"
+chmod 0755 info log recvq
+
+# create ``xferfaxlog``, ``faxcron.lastrun``, ``clientlog``
+touch clientlog faxcron.lastrun xferfaxlog
+chown @faxuser@:@faxgroup@ clientlog faxcron.lastrun xferfaxlog
+
+# create symlinks for frozen directories/files
+lnsym --target-directory=. "@hylafax@"/spool/{COPYRIGHT,bin,config}
+
+# create empty temporary directories
+update --mode=0700 -d client dev status
+update -d tmp
+
+
+## create and fill etc
+
+install -d "@spoolAreaPath@/etc"
+cd "@spoolAreaPath@/etc"
+
+# create symlinks to all files in template's etc
+lnsym --target-directory=. "@hylafax@/spool/etc"/*
+
+# set LOCKDIR in setup.cache
+sed --regexp-extended 's|^(UUCP_LOCKDIR=).*$|\1'"'@lockPath@'|g" --in-place setup.cache
+
+# etc/{xferfaxlog,lastrun} are stored in the spool root
+lnsymfrc --target-directory=. ../xferfaxlog
+lnsymfrc --no-target-directory ../faxcron.lastrun lastrun
+
+# etc/hosts.hfaxd is provided by the NixOS configuration
+lnsymfrc --no-target-directory "@userAccessFile@" hosts.hfaxd
+
+# etc/config and etc/config.${DEVID} must be copied:
+# hfaxd reads these file after locking itself up in a chroot
+cprd --no-target-directory "@globalConfigPath@" config
+cprd --target-directory=. "@modemConfigPath@"/*
diff --git a/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix b/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix
new file mode 100644
index 000000000000..b9b9b9dca4f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/hylafax/systemd.nix
@@ -0,0 +1,249 @@
+{ config, lib, pkgs, ... }:
+
+
+let
+
+  inherit (lib) mkIf mkMerge;
+  inherit (lib) concatStringsSep optionalString;
+
+  cfg = config.services.hylafax;
+  mapModems = lib.forEach (lib.attrValues cfg.modems);
+
+  mkConfigFile = name: conf:
+    # creates hylafax config file,
+    # makes sure "Include" is listed *first*
+    let
+      mkLines = conf:
+        (lib.concatLists
+        (lib.flip lib.mapAttrsToList conf
+        (k: map (v: ''${k}: ${v}'')
+      )));
+      include = mkLines { Include = conf.Include or []; };
+      other = mkLines ( conf // { Include = []; } );
+    in
+      pkgs.writeText ''hylafax-config${name}''
+      (concatStringsSep "\n" (include ++ other));
+
+  globalConfigPath = mkConfigFile "" cfg.faxqConfig;
+
+  modemConfigPath =
+    let
+      mkModemConfigFile = { config, name, ... }:
+        mkConfigFile ''.${name}''
+        (cfg.commonModemConfig // config);
+      mkLine = { name, type, ... }@modem: ''
+        # check if modem config file exists:
+        test -f "${pkgs.hylafaxplus}/spool/config/${type}"
+        ln \
+          --symbolic \
+          --no-target-directory \
+          "${mkModemConfigFile modem}" \
+          "$out/config.${name}"
+      '';
+    in
+      pkgs.runCommand "hylafax-config-modems" { preferLocalBuild = true; }
+      ''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}'';
+
+  setupSpoolScript = pkgs.substituteAll {
+    name = "hylafax-setup-spool.sh";
+    src = ./spool.sh;
+    isExecutable = true;
+    inherit (pkgs.stdenv) shell;
+    hylafax = pkgs.hylafaxplus;
+    faxuser = "uucp";
+    faxgroup = "uucp";
+    lockPath = "/var/lock";
+    inherit globalConfigPath modemConfigPath;
+    inherit (cfg) sendmailPath spoolAreaPath userAccessFile;
+  };
+
+  waitFaxqScript = pkgs.substituteAll {
+    # This script checks the modems status files
+    # and waits until all modems report readiness.
+    name = "hylafax-faxq-wait-start.sh";
+    src = ./faxq-wait.sh;
+    isExecutable = true;
+    timeoutSec = toString 10;
+    inherit (pkgs.stdenv) shell;
+    inherit (cfg) spoolAreaPath;
+  };
+
+  sockets.hylafax-hfaxd = {
+    description = "HylaFAX server socket";
+    documentation = [ "man:hfaxd(8)" ];
+    wantedBy = [ "multi-user.target" ];
+    listenStreams = [ "127.0.0.1:4559" ];
+    socketConfig.FreeBind = true;
+    socketConfig.Accept = true;
+  };
+
+  paths.hylafax-faxq = {
+    description = "HylaFAX queue manager sendq watch";
+    documentation = [ "man:faxq(8)" "man:sendq(5)" ];
+    wantedBy = [ "multi-user.target" ];
+    pathConfig.PathExistsGlob = [ ''${cfg.spoolAreaPath}/sendq/q*'' ];
+  };
+
+  timers = mkMerge [
+    (
+      mkIf (cfg.faxcron.enable.frequency!=null)
+      { hylafax-faxcron.timerConfig.Persistent = true; }
+    )
+    (
+      mkIf (cfg.faxqclean.enable.frequency!=null)
+      { hylafax-faxqclean.timerConfig.Persistent = true; }
+    )
+  ];
+
+  hardenService =
+    # Add some common systemd service hardening settings,
+    # but allow each service (here) to override
+    # settings by explicitely setting those to `null`.
+    # More hardening would be nice but makes
+    # customizing hylafax setups very difficult.
+    # If at all, it should only be added along
+    # with some options to customize it.
+    let
+      hardening = {
+        PrivateDevices = true;  # breaks /dev/tty...
+        PrivateNetwork = true;
+        PrivateTmp = true;
+        ProtectControlGroups = true;
+        #ProtectHome = true;  # breaks custom spool dirs
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        #ProtectSystem = "strict";  # breaks custom spool dirs
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+      };
+      filter = key: value: (value != null) || ! (lib.hasAttr key hardening);
+      apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {}));
+    in
+      service: service // { serviceConfig = apply service; };
+
+  services.hylafax-spool = {
+    description = "HylaFAX spool area preparation";
+    documentation = [ "man:hylafax-server(4)" ];
+    script = ''
+      ${setupSpoolScript}
+      cd "${cfg.spoolAreaPath}"
+      ${cfg.spoolExtraInit}
+      if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd"
+      then
+        echo hosts.hfaxd is missing
+        exit 1
+      fi
+    '';
+    serviceConfig.ExecStop = ''${setupSpoolScript}'';
+    serviceConfig.RemainAfterExit = true;
+    serviceConfig.Type = "oneshot";
+    unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ];
+  };
+
+  services.hylafax-faxq = {
+    description = "HylaFAX queue manager";
+    documentation = [ "man:faxq(8)" ];
+    requires = [ "hylafax-spool.service" ];
+    after = [ "hylafax-spool.service" ];
+    wants = mapModems ( { name, ... }: ''hylafax-faxgetty@${name}.service'' );
+    wantedBy = mkIf cfg.autostart [ "multi-user.target" ];
+    serviceConfig.Type = "forking";
+    serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"'';
+    # This delays the "readiness" of this service until
+    # all modems are initialized (or a timeout is reached).
+    # Otherwise, sending a fax with the fax service
+    # stopped will always yield a failed send attempt:
+    # The fax service is started when the job is created with
+    # `sendfax`, but modems need some time to initialize.
+    serviceConfig.ExecStartPost = [ ''${waitFaxqScript}'' ];
+    # faxquit fails if the pipe is already gone
+    # (e.g. the service is already stopping)
+    serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"'';
+    # disable some systemd hardening settings
+    serviceConfig.PrivateDevices = null;
+    serviceConfig.RestrictRealtime = null;
+  };
+
+  services."hylafax-hfaxd@" = {
+    description = "HylaFAX server";
+    documentation = [ "man:hfaxd(8)" ];
+    after = [ "hylafax-faxq.service" ];
+    requires = [ "hylafax-faxq.service" ];
+    serviceConfig.StandardInput = "socket";
+    serviceConfig.StandardOutput = "socket";
+    serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I'';
+    unitConfig.RequiresMountsFor = [ cfg.userAccessFile ];
+    # disable some systemd hardening settings
+    serviceConfig.PrivateDevices = null;
+    serviceConfig.PrivateNetwork = null;
+  };
+
+  services.hylafax-faxcron = rec {
+    description = "HylaFAX spool area maintenance";
+    documentation = [ "man:faxcron(8)" ];
+    after = [ "hylafax-spool.service" ];
+    requires = [ "hylafax-spool.service" ];
+    wantedBy = mkIf cfg.faxcron.enable.spoolInit requires;
+    startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency;
+    serviceConfig.ExecStart = concatStringsSep " " [
+      ''${pkgs.hylafaxplus}/spool/bin/faxcron''
+      ''-q "${cfg.spoolAreaPath}"''
+      ''-info ${toString cfg.faxcron.infoDays}''
+      ''-log  ${toString cfg.faxcron.logDays}''
+      ''-rcv  ${toString cfg.faxcron.rcvDays}''
+    ];
+  };
+
+  services.hylafax-faxqclean = rec {
+    description = "HylaFAX spool area queue cleaner";
+    documentation = [ "man:faxqclean(8)" ];
+    after = [ "hylafax-spool.service" ];
+    requires = [ "hylafax-spool.service" ];
+    wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires;
+    startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency;
+    serviceConfig.ExecStart = concatStringsSep " " [
+      ''${pkgs.hylafaxplus}/spool/bin/faxqclean''
+      ''-q "${cfg.spoolAreaPath}"''
+      ''-v''
+      (optionalString (cfg.faxqclean.archiving!="never") ''-a'')
+      (optionalString (cfg.faxqclean.archiving=="always")  ''-A'')
+      ''-j ${toString (cfg.faxqclean.doneqMinutes*60)}''
+      ''-d ${toString (cfg.faxqclean.docqMinutes*60)}''
+    ];
+  };
+
+  mkFaxgettyService = { name, ... }:
+    lib.nameValuePair ''hylafax-faxgetty@${name}'' rec {
+      description = "HylaFAX faxgetty for %I";
+      documentation = [ "man:faxgetty(8)" ];
+      bindsTo = [ "dev-%i.device" ];
+      requires = [ "hylafax-spool.service" ];
+      after = bindsTo ++ requires;
+      before = [ "hylafax-faxq.service" "getty.target" ];
+      unitConfig.StopWhenUnneeded = true;
+      unitConfig.AssertFileNotEmpty = ''${cfg.spoolAreaPath}/etc/config.%I'';
+      serviceConfig.UtmpIdentifier = "%I";
+      serviceConfig.TTYPath = "/dev/%I";
+      serviceConfig.Restart = "always";
+      serviceConfig.KillMode = "process";
+      serviceConfig.IgnoreSIGPIPE = false;
+      serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I'';
+      # faxquit fails if the pipe is already gone
+      # (e.g. the service is already stopping)
+      serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I'';
+      # disable some systemd hardening settings
+      serviceConfig.PrivateDevices = null;
+      serviceConfig.RestrictRealtime = null;
+    };
+
+  modemServices =
+    lib.listToAttrs (mapModems mkFaxgettyService);
+
+in
+
+{
+  config.systemd = mkIf cfg.enable {
+    inherit sockets timers paths;
+    services = lib.mapAttrs (lib.const hardenService) (services // modemServices);
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/i2p.nix b/nixpkgs/nixos/modules/services/networking/i2p.nix
new file mode 100644
index 000000000000..3b6010531f13
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/i2p.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.i2p;
+  homeDir = "/var/lib/i2p";
+in {
+  ###### interface
+  options.services.i2p.enable = mkEnableOption "I2P router";
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    users.users.i2p = {
+      group = "i2p";
+      description = "i2p User";
+      home = homeDir;
+      createHome = true;
+      uid = config.ids.uids.i2p;
+    };
+    users.groups.i2p.gid = config.ids.gids.i2p;
+    systemd.services.i2p = {
+      description = "I2P router with administration interface for hidden services";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        User = "i2p";
+        WorkingDirectory = homeDir;
+        Restart = "on-abort";
+        ExecStart = "${pkgs.i2p}/bin/i2prouter-plain";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/i2pd.nix b/nixpkgs/nixos/modules/services/networking/i2pd.nix
new file mode 100644
index 000000000000..93a21fd4c97e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/i2pd.nix
@@ -0,0 +1,684 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.i2pd;
+
+  homeDir = "/var/lib/i2pd";
+
+  strOpt = k: v: k + " = " + v;
+  boolOpt = k: v: k + " = " + boolToString v;
+  intOpt = k: v: k + " = " + toString v;
+  lstOpt = k: xs: k + " = " + concatStringsSep "," xs;
+  optionalNullString = o: s: optional (s != null) (strOpt o s);
+  optionalNullBool = o: b: optional (b != null) (boolOpt o b);
+  optionalNullInt = o: i: optional (i != null) (intOpt o i);
+  optionalEmptyList = o: l: optional ([] != l) (lstOpt o l);
+
+  mkEnableTrueOption = name: mkEnableOption name // { default = true; };
+
+  mkEndpointOpt = name: addr: port: {
+    enable = mkEnableOption name;
+    name = mkOption {
+      type = types.str;
+      default = name;
+      description = "The endpoint name.";
+    };
+    address = mkOption {
+      type = types.str;
+      default = addr;
+      description = "Bind address for ${name} endpoint.";
+    };
+    port = mkOption {
+      type = types.int;
+      default = port;
+      description = "Bind port for ${name} endoint.";
+    };
+  };
+
+  i2cpOpts = name: {
+    length = mkOption {
+      type = types.int;
+      description = "Guaranteed minimum hops for ${name} tunnels.";
+      default = 3;
+    };
+    quantity = mkOption {
+      type = types.int;
+      description = "Number of simultaneous ${name} tunnels.";
+      default = 5;
+    };
+  };
+
+  mkKeyedEndpointOpt = name: addr: port: keyloc:
+    (mkEndpointOpt name addr port) // {
+      keys = mkOption {
+        type = with types; nullOr str;
+        default = keyloc;
+        description = ''
+          File to persist ${lib.toUpper name} keys.
+        '';
+      };
+      inbound = i2cpOpts name;
+      outbound = i2cpOpts name;
+      latency.min = mkOption {
+        type = with types; nullOr int;
+        description = "Min latency for tunnels.";
+        default = null;
+      };
+      latency.max = mkOption {
+        type = with types; nullOr int;
+        description = "Max latency for tunnels.";
+        default = null;
+      };
+    };
+
+  commonTunOpts = name: {
+    outbound = i2cpOpts name;
+    inbound = i2cpOpts name;
+    crypto.tagsToSend = mkOption {
+      type = types.int;
+      description = "Number of ElGamal/AES tags to send.";
+      default = 40;
+    };
+    destination = mkOption {
+      type = types.str;
+      description = "Remote endpoint, I2P hostname or b32.i2p address.";
+    };
+    keys = mkOption {
+      type = types.str;
+      default = name + "-keys.dat";
+      description = "Keyset used for tunnel identity.";
+    };
+  } // mkEndpointOpt name "127.0.0.1" 0;
+
+  sec = name: "\n[" + name + "]";
+  notice = "# DO NOT EDIT -- this file has been generated automatically.";
+  i2pdConf = let
+    opts = [
+      notice
+      (strOpt "loglevel" cfg.logLevel)
+      (boolOpt "logclftime" cfg.logCLFTime)
+      (boolOpt "ipv4" cfg.enableIPv4)
+      (boolOpt "ipv6" cfg.enableIPv6)
+      (boolOpt "notransit" cfg.notransit)
+      (boolOpt "floodfill" cfg.floodfill)
+      (intOpt "netid" cfg.netid)
+    ] ++ (optionalNullInt "bandwidth" cfg.bandwidth)
+      ++ (optionalNullInt "port" cfg.port)
+      ++ (optionalNullString "family" cfg.family)
+      ++ (optionalNullString "datadir" cfg.dataDir)
+      ++ (optionalNullInt "share" cfg.share)
+      ++ (optionalNullBool "ssu" cfg.ssu)
+      ++ (optionalNullBool "ntcp" cfg.ntcp)
+      ++ (optionalNullString "ntcpproxy" cfg.ntcpProxy)
+      ++ (optionalNullString "ifname" cfg.ifname)
+      ++ (optionalNullString "ifname4" cfg.ifname4)
+      ++ (optionalNullString "ifname6" cfg.ifname6)
+      ++ [
+      (sec "limits")
+      (intOpt "transittunnels" cfg.limits.transittunnels)
+      (intOpt "coresize" cfg.limits.coreSize)
+      (intOpt "openfiles" cfg.limits.openFiles)
+      (intOpt "ntcphard" cfg.limits.ntcpHard)
+      (intOpt "ntcpsoft" cfg.limits.ntcpSoft)
+      (intOpt "ntcpthreads" cfg.limits.ntcpThreads)
+      (sec "upnp")
+      (boolOpt "enabled" cfg.upnp.enable)
+      (sec "precomputation")
+      (boolOpt "elgamal" cfg.precomputation.elgamal)
+      (sec "reseed")
+      (boolOpt "verify" cfg.reseed.verify)
+    ] ++ (optionalNullString "file" cfg.reseed.file)
+      ++ (optionalEmptyList "urls" cfg.reseed.urls)
+      ++ (optionalNullString "floodfill" cfg.reseed.floodfill)
+      ++ (optionalNullString "zipfile" cfg.reseed.zipfile)
+      ++ (optionalNullString "proxy" cfg.reseed.proxy)
+      ++ [
+      (sec "trust")
+      (boolOpt "enabled" cfg.trust.enable)
+      (boolOpt "hidden" cfg.trust.hidden)
+    ] ++ (optionalEmptyList "routers" cfg.trust.routers)
+      ++ (optionalNullString "family" cfg.trust.family)
+      ++ [
+      (sec "websockets")
+      (boolOpt "enabled" cfg.websocket.enable)
+      (strOpt "address" cfg.websocket.address)
+      (intOpt "port" cfg.websocket.port)
+      (sec "exploratory")
+      (intOpt "inbound.length" cfg.exploratory.inbound.length)
+      (intOpt "inbound.quantity" cfg.exploratory.inbound.quantity)
+      (intOpt "outbound.length" cfg.exploratory.outbound.length)
+      (intOpt "outbound.quantity" cfg.exploratory.outbound.quantity)
+      (sec "ntcp2")
+      (boolOpt "enabled" cfg.ntcp2.enable)
+      (boolOpt "published" cfg.ntcp2.published)
+      (intOpt "port" cfg.ntcp2.port)
+      (sec "addressbook")
+      (strOpt "defaulturl" cfg.addressbook.defaulturl)
+    ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
+      ++ (flip map
+      (collect (proto: proto ? port && proto ? address) cfg.proto)
+      (proto: let protoOpts = [
+        (sec proto.name)
+        (boolOpt "enabled" proto.enable)
+        (strOpt "address" proto.address)
+        (intOpt "port" proto.port)
+        ] ++ (if proto ? keys then optionalNullString "keys" proto.keys else [])
+        ++ (if proto ? auth then optionalNullBool "auth" proto.auth else [])
+        ++ (if proto ? user then optionalNullString "user" proto.user else [])
+        ++ (if proto ? pass then optionalNullString "pass" proto.pass else [])
+        ++ (if proto ? strictHeaders then optionalNullBool "strictheaders" proto.strictHeaders else [])
+        ++ (if proto ? hostname then optionalNullString "hostname" proto.hostname else [])
+        ++ (if proto ? outproxy then optionalNullString "outproxy" proto.outproxy else [])
+        ++ (if proto ? outproxyPort then optionalNullInt "outproxyport" proto.outproxyPort else [])
+        ++ (if proto ? outproxyEnable then optionalNullBool "outproxy.enabled" proto.outproxyEnable else []);
+        in (concatStringsSep "\n" protoOpts)
+      ));
+  in
+    pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts);
+
+  tunnelConf = let opts = [
+    notice
+    (flip map
+      (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
+      (tun: let outTunOpts = [
+        (sec tun.name)
+        "type = client"
+        (intOpt "port" tun.port)
+        (strOpt "destination" tun.destination)
+        ] ++ (if tun ? destinationPort then optionalNullInt "destinationport" tun.destinationPort else [])
+        ++ (if tun ? keys then
+            optionalNullString "keys" tun.keys else [])
+        ++ (if tun ? address then
+            optionalNullString "address" tun.address else [])
+        ++ (if tun ? inbound.length then
+            optionalNullInt "inbound.length" tun.inbound.length else [])
+        ++ (if tun ? inbound.quantity then
+            optionalNullInt "inbound.quantity" tun.inbound.quantity else [])
+        ++ (if tun ? outbound.length then
+            optionalNullInt "outbound.length" tun.outbound.length else [])
+        ++ (if tun ? outbound.quantity then
+            optionalNullInt "outbound.quantity" tun.outbound.quantity else [])
+        ++ (if tun ? crypto.tagsToSend then
+            optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend else []);
+        in concatStringsSep "\n" outTunOpts))
+    (flip map
+      (collect (tun: tun ? port && tun ? address) cfg.inTunnels)
+      (tun: let inTunOpts = [
+        (sec tun.name)
+        "type = server"
+        (intOpt "port" tun.port)
+        (strOpt "host" tun.address)
+      ] ++ (if tun ? destination then
+            optionalNullString "destination" tun.destination else [])
+        ++ (if tun ? keys then
+            optionalNullString "keys" tun.keys else [])
+        ++ (if tun ? inPort then
+            optionalNullInt "inport" tun.inPort else [])
+        ++ (if tun ? accessList then
+            optionalEmptyList "accesslist" tun.accessList else []);
+        in concatStringsSep "\n" inTunOpts))];
+    in pkgs.writeText "i2pd-tunnels.conf" opts;
+
+  i2pdSh = pkgs.writeScriptBin "i2pd" ''
+    #!/bin/sh
+    exec ${pkgs.i2pd}/bin/i2pd \
+      ${if cfg.address == null then "" else "--host="+cfg.address} \
+      --service \
+      --conf=${i2pdConf} \
+      --tunconf=${tunnelConf}
+  '';
+
+in
+
+{
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ])
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.i2pd = {
+
+      enable = mkEnableOption "I2Pd daemon" // {
+        description = ''
+          Enables I2Pd as a running service upon activation.
+          Please read http://i2pd.readthedocs.io/en/latest/ for further
+          configuration help.
+        '';
+      };
+
+      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>.
+        '';
+      };
+
+      logCLFTime = mkEnableOption "Full CLF-formatted date and time to log";
+
+      address = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Your external IP or hostname.
+        '';
+      };
+
+      family = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Specify a family the router belongs to.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Alternative path to storage of i2pd data (RI, keys, peer profiles, ...)
+        '';
+      };
+
+      share = mkOption {
+        type = types.int;
+        default = 100;
+        description = ''
+          Limit of transit traffic from max bandwidth in percents.
+        '';
+      };
+
+      ifname = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Network interface to bind to.
+        '';
+      };
+
+      ifname4 = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          IPv4 interface to bind to.
+        '';
+      };
+
+      ifname6 = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          IPv6 interface to bind to.
+        '';
+      };
+
+      ntcpProxy = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Proxy URL for NTCP transport.
+        '';
+      };
+
+      ntcp = mkEnableTrueOption "ntcp";
+      ssu = mkEnableTrueOption "ssu";
+
+      notransit = mkEnableOption "notransit" // {
+        description = ''
+          Tells the router to not accept transit tunnels during startup.
+        '';
+      };
+
+      floodfill = mkEnableOption "floodfill" // {
+        description = ''
+          If the router is declared to be unreachable and needs introduction nodes.
+        '';
+      };
+
+      netid = mkOption {
+        type = types.int;
+        default = 2;
+        description = ''
+          I2P overlay netid.
+        '';
+      };
+
+      bandwidth = mkOption {
+        type = with types; nullOr int;
+        default = null;
+        description = ''
+           Set a router bandwidth limit integer in KBps.
+           If not set, <command>i2pd</command> defaults to 32KBps.
+        '';
+      };
+
+      port = mkOption {
+        type = with types; nullOr int;
+        default = null;
+        description = ''
+          I2P listen port. If no one is given the router will pick between 9111 and 30777.
+        '';
+      };
+
+      enableIPv4 = mkEnableTrueOption "IPv4 connectivity";
+      enableIPv6 = mkEnableOption "IPv6 connectivity";
+      nat = mkEnableTrueOption "NAT bypass";
+
+      upnp.enable = mkEnableOption "UPnP service discovery";
+      upnp.name = mkOption {
+        type = types.str;
+        default = "I2Pd";
+        description = ''
+          Name i2pd appears in UPnP forwardings list.
+        '';
+      };
+
+      precomputation.elgamal = mkEnableTrueOption "Precomputed ElGamal tables" // {
+        description = ''
+          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.
+        '';
+      };
+
+      reseed.verify = mkEnableOption "SU3 signature verification";
+
+      reseed.file = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Full path to SU3 file to reseed from.
+        '';
+      };
+
+      reseed.urls = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = ''
+          Reseed URLs.
+        '';
+      };
+
+      reseed.floodfill = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Path to router info of floodfill to reseed from.
+        '';
+      };
+
+      reseed.zipfile = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Path to local .zip file to reseed from.
+        '';
+      };
+
+      reseed.proxy = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          URL for reseed proxy, supports http/socks.
+        '';
+      };
+
+     addressbook.defaulturl = mkOption {
+        type = types.str;
+        default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
+        description = ''
+          AddressBook subscription URL for initial setup
+        '';
+      };
+     addressbook.subscriptions = mkOption {
+        type = with types; listOf str;
+        default = [
+          "http://inr.i2p/export/alive-hosts.txt"
+          "http://i2p-projekt.i2p/hosts.txt"
+          "http://stats.i2p/cgi-bin/newhosts.txt"
+        ];
+        description = ''
+          AddressBook subscription URLs
+        '';
+      };
+
+      trust.enable = mkEnableOption "Explicit trust options";
+
+      trust.family = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Router Familiy to trust for first hops.
+        '';
+      };
+
+      trust.routers = mkOption {
+        type = with types; listOf str;
+        default = [];
+        description = ''
+          Only connect to the listed routers.
+        '';
+      };
+
+      trust.hidden = mkEnableOption "Router concealment";
+
+      websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
+
+      exploratory.inbound = i2cpOpts "exploratory";
+      exploratory.outbound = i2cpOpts "exploratory";
+
+      ntcp2.enable = mkEnableTrueOption "NTCP2.";
+      ntcp2.published = mkEnableOption "NTCP2 publication";
+      ntcp2.port = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Port to listen for incoming NTCP2 connections (0=auto).
+        '';
+      };
+
+      limits.transittunnels = mkOption {
+        type = types.int;
+        default = 2500;
+        description = ''
+          Maximum number of active transit sessions.
+        '';
+      };
+
+      limits.coreSize = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Maximum size of corefile in Kb (0 - use system limit).
+        '';
+      };
+
+      limits.openFiles = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Maximum number of open files (0 - use system default).
+        '';
+      };
+
+      limits.ntcpHard = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Maximum number of active transit sessions.
+        '';
+      };
+
+      limits.ntcpSoft = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          Threshold to start probabalistic backoff with ntcp sessions (default: use system limit).
+        '';
+      };
+
+      limits.ntcpThreads = mkOption {
+        type = types.int;
+        default = 1;
+        description = ''
+          Maximum number of threads used by NTCP DH worker.
+        '';
+      };
+
+      proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
+
+        auth = mkEnableOption "Webconsole authentication";
+
+        user = mkOption {
+          type = types.str;
+          default = "i2pd";
+          description = ''
+            Username for webconsole access
+          '';
+        };
+
+        pass = mkOption {
+          type = types.str;
+          default = "i2pd";
+          description = ''
+            Password for webconsole access.
+          '';
+        };
+
+        strictHeaders = mkOption {
+          type = with types; nullOr bool;
+          default = null;
+          description = ''
+            Enable strict host checking on WebUI.
+          '';
+        };
+
+        hostname = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = ''
+            Expected hostname for WebUI.
+          '';
+        };
+      };
+
+      proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat")
+      // {
+        outproxy = mkOption {
+          type = with types; nullOr str;
+          default = null;
+          description = "Upstream outproxy bind address.";
+        };
+      };
+      proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat")
+      // {
+        outproxyEnable = mkEnableOption "SOCKS outproxy";
+        outproxy = mkOption {
+          type = types.str;
+          default = "127.0.0.1";
+          description = "Upstream outproxy bind address.";
+        };
+        outproxyPort = mkOption {
+          type = types.int;
+          default = 4444;
+          description = "Upstream outproxy bind port.";
+        };
+      };
+
+      proto.sam = mkEndpointOpt "sam" "127.0.0.1" 7656;
+      proto.bob = mkEndpointOpt "bob" "127.0.0.1" 2827;
+      proto.i2cp = mkEndpointOpt "i2cp" "127.0.0.1" 7654;
+      proto.i2pControl = mkEndpointOpt "i2pcontrol" "127.0.0.1" 7650;
+
+      outTunnels = mkOption {
+        default = {};
+        type = with types; attrsOf (submodule (
+          { name, ... }: {
+            options = {
+              destinationPort = mkOption {
+                type = with types; nullOr int;
+                default = null;
+                description = "Connect to particular port at destination.";
+              };
+            } // commonTunOpts name;
+            config = {
+              name = mkDefault name;
+            };
+          }
+        ));
+        description = ''
+          Connect to someone as a client and establish a local accept endpoint
+        '';
+      };
+
+      inTunnels = mkOption {
+        default = {};
+        type = with types; attrsOf (submodule (
+          { name, ... }: {
+            options = {
+              inPort = mkOption {
+                type = types.int;
+                default = 0;
+                description = "Service port. Default to the tunnel's listen port.";
+              };
+              accessList = mkOption {
+                type = with types; listOf str;
+                default = [];
+                description = "I2P nodes that are allowed to connect to this service.";
+              };
+            } // commonTunOpts name;
+            config = {
+              name = mkDefault name;
+            };
+          }
+        ));
+        description = ''
+          Serve something on I2P network at port and delegate requests to address inPort.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.i2pd = {
+      group = "i2pd";
+      description = "I2Pd User";
+      home = homeDir;
+      createHome = true;
+      uid = config.ids.uids.i2pd;
+    };
+
+    users.groups.i2pd.gid = config.ids.gids.i2pd;
+
+    systemd.services.i2pd = {
+      description = "Minimal I2P router";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig =
+      {
+        User = "i2pd";
+        WorkingDirectory = homeDir;
+        Restart = "on-abort";
+        ExecStart = "${i2pdSh}/bin/i2pd";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/iodine.nix b/nixpkgs/nixos/modules/services/networking/iodine.nix
new file mode 100644
index 000000000000..46051d7044b5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/iodine.nix
@@ -0,0 +1,197 @@
+# NixOS module for iodine, ip over dns daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.iodine;
+
+  iodinedUser = "iodined";
+
+  /* is this path made unreadable by ProtectHome = true ? */
+  isProtected = x: hasPrefix "/root" x || hasPrefix "/home" x;
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "iodined" "enable" ] [ "services" "iodine" "server" "enable" ])
+    (mkRenamedOptionModule [ "services" "iodined" "domain" ] [ "services" "iodine" "server" "domain" ])
+    (mkRenamedOptionModule [ "services" "iodined" "ip" ] [ "services" "iodine" "server" "ip" ])
+    (mkRenamedOptionModule [ "services" "iodined" "extraConfig" ] [ "services" "iodine" "server" "extraConfig" ])
+    (mkRemovedOptionModule [ "services" "iodined" "client" ] "")
+  ];
+
+  ### configuration
+
+  options = {
+
+    services.iodine = {
+      clients = mkOption {
+        default = {};
+        description = ''
+          Each attribute of this option defines a systemd service that
+          runs iodine. Many or none may be defined.
+          The name of each service is
+          <literal>iodine-<replaceable>name</replaceable></literal>
+          where <replaceable>name</replaceable> is the name of the
+          corresponding attribute name.
+        '';
+        example = literalExample ''
+          {
+            foo = {
+              server = "tunnel.mdomain.com";
+              relay = "8.8.8.8";
+              extraConfig = "-v";
+            }
+          }
+        '';
+        type = types.attrsOf (
+          types.submodule (
+            {
+              options = {
+                server = mkOption {
+                  type = types.str;
+                  default = "";
+                  description = "Hostname of server running iodined";
+                  example = "tunnel.mydomain.com";
+                };
+
+                relay = mkOption {
+                  type = types.str;
+                  default = "";
+                  description = "DNS server to use as an intermediate relay to the iodined server";
+                  example = "8.8.8.8";
+                };
+
+                extraConfig = mkOption {
+                  type = types.str;
+                  default = "";
+                  description = "Additional command line parameters";
+                  example = "-l 192.168.1.10 -p 23";
+                };
+
+                passwordFile = mkOption {
+                  type = types.str;
+                  default = "";
+                  description = "Path to a file containing the password.";
+                };
+              };
+            }
+          )
+        );
+      };
+
+      server = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = "enable iodined server";
+        };
+
+        ip = mkOption {
+          type = types.str;
+          default = "";
+          description = "The assigned ip address or ip range";
+          example = "172.16.10.1/24";
+        };
+
+        domain = mkOption {
+          type = types.str;
+          default = "";
+          description = "Domain or subdomain of which nameservers point to us";
+          example = "tunnel.mydomain.com";
+        };
+
+        extraConfig = mkOption {
+          type = types.str;
+          default = "";
+          description = "Additional command line parameters";
+          example = "-l 192.168.1.10 -p 23";
+        };
+
+        passwordFile = mkOption {
+          type = types.str;
+          default = "";
+          description = "File that contains password";
+        };
+      };
+
+    };
+  };
+
+  ### implementation
+
+  config = mkIf (cfg.server.enable || cfg.clients != {}) {
+    environment.systemPackages = [ pkgs.iodine ];
+    boot.kernelModules = [ "tun" ];
+
+    systemd.services =
+      let
+        createIodineClientService = name: cfg:
+          {
+            description = "iodine client - ${name}";
+            after = [ "network.target" ];
+            wantedBy = [ "multi-user.target" ];
+            script = "exec ${pkgs.iodine}/bin/iodine -f -u ${iodinedUser} ${cfg.extraConfig} ${optionalString (cfg.passwordFile != "") "< \"${builtins.toString cfg.passwordFile}\""} ${cfg.relay} ${cfg.server}";
+            serviceConfig = {
+              RestartSec = "30s";
+              Restart = "always";
+
+              # hardening :
+              # Filesystem access
+              ProtectSystem = "strict";
+              ProtectHome = if isProtected cfg.passwordFile then "read-only" else "true" ;
+              PrivateTmp = true;
+              ReadWritePaths = "/dev/net/tun";
+              PrivateDevices = false;
+              ProtectKernelTunables = true;
+              ProtectKernelModules = true;
+              ProtectControlGroups = true;
+              # Caps
+              NoNewPrivileges = true;
+              # Misc.
+              LockPersonality = true;
+              RestrictRealtime = true;
+              PrivateMounts = true;
+              MemoryDenyWriteExecute = true;
+            };
+          };
+      in
+        listToAttrs (
+          mapAttrsToList
+            (name: value: nameValuePair "iodine-${name}" (createIodineClientService name value))
+            cfg.clients
+        ) // {
+          iodined = mkIf (cfg.server.enable) {
+            description = "iodine, ip over dns server daemon";
+            after = [ "network.target" ];
+            wantedBy = [ "multi-user.target" ];
+            script = "exec ${pkgs.iodine}/bin/iodined -f -u ${iodinedUser} ${cfg.server.extraConfig} ${optionalString (cfg.server.passwordFile != "") "< \"${builtins.toString cfg.server.passwordFile}\""} ${cfg.server.ip} ${cfg.server.domain}";
+            serviceConfig = {
+              # Filesystem access
+              ProtectSystem = "strict";
+              ProtectHome = if isProtected cfg.server.passwordFile then "read-only" else "true" ;
+              PrivateTmp = true;
+              ReadWritePaths = "/dev/net/tun";
+              PrivateDevices = false;
+              ProtectKernelTunables = true;
+              ProtectKernelModules = true;
+              ProtectControlGroups = true;
+              # Caps
+              NoNewPrivileges = true;
+              # Misc.
+              LockPersonality = true;
+              RestrictRealtime = true;
+              PrivateMounts = true;
+              MemoryDenyWriteExecute = true;
+            };
+          };
+        };
+
+    users.users.${iodinedUser} = {
+      uid = config.ids.uids.iodined;
+      description = "Iodine daemon user";
+    };
+    users.groups.iodined.gid = config.ids.gids.iodined;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/iperf3.nix b/nixpkgs/nixos/modules/services/networking/iperf3.nix
new file mode 100644
index 000000000000..0fe378b225d7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/iperf3.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }: with lib;
+let
+  cfg = config.services.iperf3;
+
+  api = {
+    enable = mkEnableOption "iperf3 network throughput testing server";
+    port = mkOption {
+      type        = types.ints.u16;
+      default     = 5201;
+      description = "Server port to listen on for iperf3 client requsts.";
+    };
+    affinity = mkOption {
+      type        = types.nullOr types.ints.unsigned;
+      default     = null;
+      description = "CPU affinity for the process.";
+    };
+    bind = mkOption {
+      type        = types.nullOr types.str;
+      default     = null;
+      description = "Bind to the specific interface associated with the given address.";
+    };
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Open ports in the firewall for iperf3.";
+    };
+    verbose = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = "Give more detailed output.";
+    };
+    forceFlush = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = "Force flushing output at every interval.";
+    };
+    debug = mkOption {
+      type        = types.bool;
+      default     = false;
+      description = "Emit debugging output.";
+    };
+    rsaPrivateKey = mkOption {
+      type        = types.nullOr types.path;
+      default     = null;
+      description = "Path to the RSA private key (not password-protected) used to decrypt authentication credentials from the client.";
+    };
+    authorizedUsersFile = mkOption {
+      type        = types.nullOr types.path;
+      default     = null;
+      description = "Path to the configuration file containing authorized users credentials to run iperf tests.";
+    };
+    extraFlags = mkOption {
+      type        = types.listOf types.str;
+      default     = [ ];
+      description = "Extra flags to pass to iperf3(1).";
+    };
+  };
+
+  imp = {
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.port ];
+    };
+
+    systemd.services.iperf3 = {
+      description = "iperf3 daemon";
+      unitConfig.Documentation = "man:iperf3(1) https://iperf.fr/iperf-doc.php";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        Restart = "on-failure";
+        RestartSec = 2;
+        DynamicUser = true;
+        PrivateDevices = true;
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        ExecStart = ''
+          ${pkgs.iperf3}/bin/iperf \
+            --server \
+            --port ${toString cfg.port} \
+            ${optionalString (cfg.affinity != null) "--affinity ${toString cfg.affinity}"} \
+            ${optionalString (cfg.bind != null) "--bind ${cfg.bind}"} \
+            ${optionalString (cfg.rsaPrivateKey != null) "--rsa-private-key-path ${cfg.rsaPrivateKey}"} \
+            ${optionalString (cfg.authorizedUsersFile != null) "--authorized-users-path ${cfg.authorizedUsersFile}"} \
+            ${optionalString cfg.verbose "--verbose"} \
+            ${optionalString cfg.debug "--debug"} \
+            ${optionalString cfg.forceFlush "--forceflush"} \
+            ${escapeShellArgs cfg.extraFlags}
+        '';
+      };
+    };
+  };
+in {
+  options.services.iperf3 = api;
+  config = mkIf cfg.enable imp;
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh
new file mode 100644
index 000000000000..38312210df25
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/builder.sh
@@ -0,0 +1,31 @@
+source $stdenv/setup
+
+doSub() {
+    local src=$1
+    local dst=$2
+    mkdir -p $(dirname $dst)
+    substituteAll $src $dst
+}
+
+subDir=/
+for i in $scripts; do
+    if test "$(echo $i | cut -c1-2)" = "=>"; then
+        subDir=$(echo $i | cut -c3-)
+    else
+        dst=$out/$subDir/$(stripHash $i | sed 's/\.in//')
+        doSub $i $dst
+        chmod +x $dst # !!!
+    fi
+done
+
+subDir=/
+for i in $substFiles; do
+    if test "$(echo $i | cut -c1-2)" = "=>"; then
+        subDir=$(echo $i | cut -c3-)
+    else
+        dst=$out/$subDir/$(stripHash $i | sed 's/\.in//')
+        doSub $i $dst
+    fi
+done
+
+mkdir -p $out/bin
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/control.in b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/control.in
new file mode 100644
index 000000000000..312dfaada329
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/control.in
@@ -0,0 +1,26 @@
+#! @shell@ -e
+
+# Make sure that the environment is deterministic.
+export PATH=@coreutils@/bin
+
+if test "$1" = "start"; then
+	if ! @procps@/bin/pgrep ircd; then
+	if @ipv6Enabled@; then 
+		while ! @iproute@/sbin/ip addr | 
+			@gnugrep@/bin/grep inet6 | 
+			@gnugrep@/bin/grep global; do
+			sleep 1;
+		done;
+	fi;
+	rm -rf /home/ircd
+	mkdir -p /home/ircd
+	chown ircd: /home/ircd
+	cd /home/ircd
+    env - HOME=/homeless-shelter $extraEnv \
+        @su@/bin/su ircd --shell=/bin/sh -c ' @ircdHybrid@/bin/ircd -configfile @out@/conf/ircd.conf </dev/null -logfile /home/ircd/ircd.log' 2>&1 >/var/log/ircd-hybrid.out
+	fi;
+fi
+
+if test "$1" = "stop" ; then 
+	@procps@/bin/pkill ircd;
+fi;
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix
new file mode 100644
index 000000000000..91d0bf437d69
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/default.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.ircdHybrid;
+
+  ircdService = pkgs.stdenv.mkDerivation rec {
+    name = "ircd-hybrid-service";
+    scripts = [ "=>/bin" ./control.in ];
+    substFiles = [ "=>/conf" ./ircd.conf ];
+    inherit (pkgs) ircdHybrid coreutils su iproute gnugrep procps;
+
+    ipv6Enabled = boolToString config.networking.enableIPv6;
+
+    inherit (cfg) serverName sid description adminEmail
+            extraPort;
+
+    cryptoSettings =
+      (optionalString (cfg.rsaKey != null) "rsa_private_key_file = \"${cfg.rsaKey}\";\n") +
+      (optionalString (cfg.certificate != null) "ssl_certificate_file = \"${cfg.certificate}\";\n");
+
+    extraListen = map (ip: "host = \""+ip+"\";\nport = 6665 .. 6669, "+extraPort+"; ") cfg.extraIPs;
+
+    builder = ./builder.sh;
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.ircdHybrid = {
+
+      enable = mkEnableOption "IRCD";
+
+      serverName = mkOption {
+        default = "hades.arpa";
+        description = "
+          IRCD server name.
+        ";
+      };
+
+      sid = mkOption {
+        default = "0NL";
+        description = "
+          IRCD server unique ID in a net of servers.
+        ";
+      };
+
+      description = mkOption {
+        default = "Hybrid-7 IRC server.";
+        description = "
+          IRCD server description.
+        ";
+      };
+
+      rsaKey = mkOption {
+        default = null;
+        example = literalExample "/root/certificates/irc.key";
+        description = "
+          IRCD server RSA key.
+        ";
+      };
+
+      certificate = mkOption {
+        default = null;
+        example = literalExample "/root/certificates/irc.pem";
+        description = "
+          IRCD server SSL certificate. There are some limitations - read manual.
+        ";
+      };
+
+      adminEmail = mkOption {
+        default = "<bit-bucket@example.com>";
+        example = "<name@domain.tld>";
+        description = "
+          IRCD server administrator e-mail.
+        ";
+      };
+
+      extraIPs = mkOption {
+        default = [];
+        example = ["127.0.0.1"];
+        description = "
+          Extra IP's to bind.
+        ";
+      };
+
+      extraPort = mkOption {
+        default = "7117";
+        description = "
+          Extra port to avoid filtering.
+        ";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.ircdHybrid.enable {
+
+    users.users.ircd =
+      { description = "IRCD owner";
+        group = "ircd";
+        uid = config.ids.uids.ircd;
+      };
+
+    users.groups.ircd.gid = config.ids.gids.ircd;
+
+    systemd.services.ircd-hybrid = {
+      description = "IRCD Hybrid server";
+      after = [ "started networking" ];
+      wantedBy = [ "multi-user.target" ];
+      script = "${ircdService}/bin/control start";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ircd-hybrid/ircd.conf b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/ircd.conf
new file mode 100644
index 000000000000..17ef203840af
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ircd-hybrid/ircd.conf
@@ -0,0 +1,1051 @@
+/* doc/example.conf - ircd-hybrid-7 Example configuration file
+ * Copyright (C) 2000-2006 Hybrid Development Team
+ *
+ * Written by ejb, wcampbel, db, leeh and others
+ * Other example configurations can be found in the source dir under
+ * etc/.
+ *
+ * $Id: example.conf 639 2006-06-01 14:12:21Z michael $
+ */
+
+/* IMPORTANT NOTES:
+ *
+ * auth {} blocks MUST be specified in order of precedence.  The first one
+ * that matches a user will be used.  So place spoofs first, then specials,
+ * then general access.
+ *
+ * Shell style (#), C++ style (//) and C style comments are supported.
+ *
+ * Files may be included by either:
+ *        .include "filename"
+ *        .include <filename>
+ *
+ * Times/durations are written as:
+ *        12 hours 30 minutes 1 second
+ *        
+ * Valid units of time:
+ *        month, week, day, hour, minute, second
+ *
+ * Valid units of size:
+ *        megabyte/mbyte/mb, kilobyte/kbyte/kb, byte
+ *
+ * Sizes and times may be singular or plural.  
+ */ 
+
+/* EFNET NOTE:
+ *
+ * This config file is NOT suitable for EFNet.  EFNet admins should use
+ * example.efnet.conf
+ */
+ 
+/*
+ * serverinfo {}:  contains information about the server. (OLD M:)
+ */
+serverinfo {
+	/*
+	 * name: the name of our server.  This cannot be changed at runtime.
+	 */
+	name = "@serverName@";
+
+	/*
+	 * sid: a server's unique ID.  This is three characters long and must
+	 * be in the form [0-9][A-Z0-9][A-Z0-9].  The first character must be
+	 * a digit, followed by 2 alpha-numerical letters.
+	 * NOTE: The letters must be capitalized.  This cannot be changed at runtime.
+	 */
+	sid = "@sid@";
+
+	/*
+	 * description: the description of the server.  '[' and ']' may not
+	 * be used here for compatibility with older servers.
+	 */
+	description = "@description@";
+
+	/*
+	 * network info: the name and description of the network this server
+	 * is on.  Shown in the 005 reply and used with serverhiding.
+	 */
+	network_name = "JustIRCNetwork";
+	network_desc = "This is My Network";
+
+	/*
+	 * hub: allow this server to act as a hub and have multiple servers
+	 * connected to it.  This may not be changed if there are active
+	 * LazyLink servers.
+	 */
+	hub = no;
+
+	/*
+	 * vhost: the IP to bind to when we connect outward to ipv4 servers.
+	 * This should be an ipv4 IP only, or "* for INADDR_ANY.
+	 */
+	#vhost = "192.169.0.1";
+
+	/*
+	 * vhost6: the IP to bind to when we connect outward to ipv6 servers.
+	 * This should be an ipv6 IP only, or "* for INADDR_ANY.
+	 */
+	#vhost6 = "3ffe:80e8:546::2";
+
+	/* max_clients: the maximum number of clients allowed to connect */
+	max_clients = 512;
+
+	/*
+	 * rsa key: the path to the file containing our rsa key for cryptlink.
+	 *
+	 * Example command to store a 2048 bit RSA keypair in
+	 * rsa.key, and the public key in rsa.pub:
+	 * 
+	 * 	openssl genrsa -out rsa.key 2048
+	 *	openssl rsa -in rsa.key -pubout -out rsa.pub
+	 *	chown <ircd-user>.<ircd.group> rsa.key rsa.pub
+	 *	chmod 0600 rsa.key
+	 *	chmod 0644 rsa.pub
+	 */
+	#rsa_private_key_file = "/usr/local/ircd/etc/rsa.key";
+
+	/*
+	 * ssl certificate: the path to the file containing our ssl certificate
+	 * for encrypted client connection.
+	 *
+	 * This assumes your private RSA key is stored in rsa.key. You
+	 * MUST have an RSA key in order to generate the certificate
+	 *
+	 *	openssl req -new -days 365 -x509 -key rsa.key -out cert.pem
+	 *
+	 * See http://www.openssl.org/docs/HOWTO/certificates.txt
+	 *
+	 * Please use the following values when generating the cert
+	 *
+	 *	Organization Name: Network Name
+	 *	Organization Unit Name: changme.someirc.net
+	 *	Common Name: irc.someirc.net
+	 *	E-mail: you@domain.com
+	 */
+	#ssl_certificate_file = "/usr/local/ircd/etc/cert.pem";
+
+	@cryptoSettings@
+};
+
+/*
+ * admin {}:  contains admin information about the server. (OLD A:)
+ */
+admin {
+	name = "Anonymous Hero";
+	description = "Main Server Administrator";
+	email = "@adminEmail@";
+};
+
+/*
+ * log {}:  contains information about logfiles.
+ */
+log {
+	/* Do you want to enable logging to ircd.log? */
+	use_logging = yes;
+
+	/*
+	 * logfiles: the logfiles to use for user connects, /oper uses,
+	 * and failed /oper.  These files must exist for logging to be used.
+	 */
+	fname_userlog = "/home/ircd/logs/userlog";
+	fname_operlog = "/home/ircd/logs/operlog";
+	fname_killlog = "/home/ircd/logs/kill";
+	fname_klinelog = "/home/ircd/logs/kline";
+	fname_glinelog = "/home/ircd/logs/gline";
+
+	/*
+	 * log_level: the amount of detail to log in ircd.log.  The
+	 * higher, the more information is logged.  May be changed
+	 * once the server is running via /quote SET LOG.  Either:
+	 * L_CRIT, L_ERROR, L_WARN, L_NOTICE, L_TRACE, L_INFO or L_DEBUG
+	 */
+	log_level = L_INFO;
+};
+
+/*
+ * class {}:  contains information about classes for users (OLD Y:)
+ */
+class {
+	/* name: the name of the class.  classes are text now */
+	name = "users";
+
+	/*
+	 * ping_time: how often a client must reply to a PING from the
+	 * server before they are dropped.
+	 */
+	ping_time = 90 seconds;
+
+	/*
+	 * number_per_ip: how many local users are allowed to connect
+	 * from one IP  (optional)
+	 */
+	number_per_ip = 10;
+
+	/*
+	 * max_local: how many local users are allowed to connect
+	 * from one ident@host  (optional)
+	 */
+	max_local = 50;
+
+	/*
+	 * max_global: network-wide limit of users per ident@host  (optional)
+	 */
+	max_global = 50;
+
+	/*
+	 * max_number: the maximum number of users allowed in this class (optional)
+	 */
+	max_number = 10000;
+
+	/*
+	 * the following lines are optional and allow you to define
+	 * how many users can connect from one /NN subnet
+	 */
+	/*cidr_bitlen_ipv4 = 24;
+	 *cidr_bitlen_ipv6 = 120;
+	 *number_per_cidr = 16;*/
+
+	/*
+	 * sendq: the amount of data allowed in a clients queue before
+	 * they are dropped.
+	 */
+	sendq = 100 kbytes;
+};
+
+class {
+	name = "opers";
+	ping_time = 90 seconds;
+	number_per_ip = 10;
+	max_number = 100;
+	sendq = 100kbytes;
+};
+
+class {
+	name = "server";
+	ping_time = 90 seconds;
+
+	/*
+	 * ping_warning: how fast a server must reply to a PING before
+	 * a warning to opers is generated.
+	 */
+	ping_warning = 15 seconds;
+
+	/*
+	 * connectfreq: only used in server classes.  Specifies the delay
+	 * between autoconnecting to servers.
+	 */
+	connectfreq = 5 minutes;
+
+	/* max number: the amount of servers to autoconnect to */
+	max_number = 1;
+
+	/* sendq: servers need a higher sendq as they send more data */
+	sendq = 2 megabytes;
+};
+
+/*
+ * listen {}:  contains information about the ports ircd listens on (OLD P:)
+ */
+listen {
+	/*
+	 * port: the specific port to listen on.  If no host is specified
+	 * before, it will listen on all available IPs.
+	 *
+	 * Ports are separated via a comma, a range may be specified using ".."
+	 */
+	
+	/* port: listen on all available IPs, ports 6665 to 6669 */
+	port = 6665 .. 6669;
+
+	/*
+	 * Listen on 192.168.0.1/6697 with ssl enabled and hidden from STATS P
+	 * unless you are an administrator.
+	 *
+	 * NOTE: The "flags" directive has to come before "port".  Always!
+	 */
+	#flags = hidden, ssl;
+	#host = "192.168.0.1";
+	#port = 6697;
+
+	/*
+	 * host: set a specific IP/host the ports after the line will listen 
+	 * on.  This may be ipv4 or ipv6.
+	 */
+	#host = "1.2.3.4";
+	#port = 7000, 7001;
+
+	#host = "3ffe:1234:a:b:c::d";
+	#port = 7002;
+	
+	@extraListen@
+};
+
+auth {
+	user = "*@*";
+	class = "users";
+	#flags = need_ident;
+};
+
+/*
+ * operator {}:  defines ircd operators. (OLD O:)
+ *
+ * ircd-hybrid no longer supports local operators, privileges are
+ * controlled via flags.
+ */
+operator {
+	/* name: the name of the oper */
+	/* NOTE: operator "opername"{} is also supported */
+	name = "god";
+
+	/*
+	 * user: the user@host required for this operator.  CIDR is not
+	 * supported.  Multiple user="" lines are supported.
+	 */
+	user = "*god@*";
+	user = "*@127.0.0.1";
+
+	/*
+	 * password: the password required to oper.  By default this will
+	 * need to be encrypted using 'mkpasswd'.  MD5 is supported.
+	 */
+	password = "iamoperator";
+
+	/*
+	 * encrypted: controls whether the oper password above has been
+	 * encrypted.  (OLD CRYPT_OPER_PASSWORD now optional per operator)
+	 */
+	encrypted = no;
+
+	/*
+	 * rsa_public_key_file: the public key for this oper when using Challenge.
+	 * A password should not be defined when this is used, see 
+	 * doc/challenge.txt for more information.
+	 */
+#	rsa_public_key_file = "/usr/local/ircd/etc/oper.pub";
+
+	/* class: the class the oper joins when they successfully /oper */
+	class = "opers";
+
+	/*
+	 * umodes: default usermodes opers get when they /oper.  If defined,
+	 * it will override oper_umodes settings in general {}.
+	 * Available usermodes:
+	 *
+	 * +b - bots         - See bot and drone flooding notices
+	 * +c - cconn        - Client connection/quit notices
+	 * +D - deaf         - Don't receive channel messages
+	 * +d - debug        - See debugging notices
+	 * +f - full         - See I: line full notices
+	 * +G - softcallerid - Server Side Ignore for users not on your channels
+	 * +g - callerid     - Server Side Ignore (for privmsgs etc)
+	 * +i - invisible    - Not shown in NAMES or WHO unless you share a
+	 *                     a channel
+	 * +k - skill        - See server generated KILL messages
+	 * +l - locops       - See LOCOPS messages
+	 * +n - nchange      - See client nick changes
+	 * +r - rej          - See rejected client notices
+	 * +s - servnotice   - See general server notices
+	 * +u - unauth       - See unauthorized client notices
+	 * +w - wallop       - See server generated WALLOPS
+	 * +x - external     - See remote server connection and split notices
+	 * +y - spy          - See LINKS, STATS, TRACE notices etc.
+	 * +z - operwall     - See oper generated WALLOPS
+	 */
+#	umodes = locops, servnotice, operwall, wallop;
+
+	/*
+	 * privileges: controls the activities and commands an oper is 
+	 * allowed to do on the server.  All options default to no.
+	 * Available options:
+	 *
+	 * global_kill:  allows remote users to be /KILL'd (OLD 'O' flag)
+	 * remote:       allows remote SQUIT and CONNECT   (OLD 'R' flag)
+	 * remoteban:    allows remote KLINE/UNKLINE
+	 * kline:        allows KILL, KLINE and DLINE      (OLD 'K' flag)
+	 * unkline:      allows UNKLINE and UNDLINE        (OLD 'U' flag)
+	 * gline:        allows GLINE                      (OLD 'G' flag)
+	 * xline:         allows XLINE                     (OLD 'X' flag)
+	 * operwall:     allows OPERWALL
+	 * nick_changes: allows oper to see nickchanges    (OLD 'N' flag)
+	 *               via usermode +n
+	 * rehash:       allows oper to REHASH config      (OLD 'H' flag)
+	 * die:          allows DIE and RESTART            (OLD 'D' flag)
+	 * admin:        gives admin privileges.  admins
+	 *               may (un)load modules and see the
+	 *               real IPs of servers.
+	 * hidden_admin: same as 'admin', but noone can recognize you as
+	 *               being an admin
+	 * hidden_oper:  not shown in /stats p (except for other operators)
+	 */
+	/* You can either use
+	 * die = yes;
+	 * rehash = yes;
+	 *
+	 * or in a flags statement i.e.
+	 * flags = die, rehash;
+	 *
+	 * You can also negate a flag with ~ i.e.
+	 * flags = ~remote;
+	 *
+	 */
+	flags = global_kill, remote, kline, unkline, xline,
+		die, rehash, nick_changes, admin, operwall;
+};
+
+/*
+ * shared {}: users that are allowed to remote kline (OLD U:)
+ *
+ * NOTE: This can be effectively used for remote klines.
+ *       Please note that there is no password authentication
+ *       for users setting remote klines.  You must also be
+ *       /oper'd in order to issue a remote kline.
+ */
+shared {
+	/*
+	 * name: the server the user must be on to set klines.  If this is not
+	 * specified, the user will be allowed to kline from all servers.
+	 */
+	name = "irc2.some.server";
+
+	/*
+	 * user: the user@host mask that is allowed to set klines.  If this is
+	 * not specified, all users on the server above will be allowed to set
+	 * a remote kline.
+	 */
+	user = "oper@my.host.is.spoofed";
+
+	/*
+	 * type: list of what to share, options are as follows:
+	 *	kline	- allow oper/server to kline
+	 *	tkline	- allow temporary klines
+	 *	unkline	- allow oper/server to unkline
+	 *	xline	- allow oper/server to xline
+	 * 	txline	- allow temporary xlines
+	 *	unxline	- allow oper/server to unxline
+	 *	resv	- allow oper/server to resv
+	 * 	tresv	- allow temporary resvs
+	 *	unresv	- allow oper/server to unresv
+	 *      locops  - allow oper/server to locops - only used for servers that cluster
+	 *	all	- allow oper/server to do all of the above (default)
+	 */
+	type = kline, unkline, resv;
+};
+
+/*
+ * kill {}:  users that are not allowed to connect (OLD K:)
+ * Oper issued klines will be added to the specified kline config
+ */
+kill {
+	user = "bad@*.hacked.edu";
+	reason = "Obviously hacked account";
+};
+
+kill {
+	user = "^O[[:alpha:]]?[[:digit:]]+(x\.o|\.xo)$@^[[:alnum:]]{4}\.evilnet.org$";
+	type = regex;
+};
+
+/*
+ * deny {}:  IPs that are not allowed to connect (before DNS/ident lookup)
+ * Oper issued dlines will be added to the specified dline config
+ */
+deny {
+	ip = "10.0.1.0/24";
+	reason = "Reconnecting vhosted bots";
+};
+
+/*
+ * exempt {}: IPs that are exempt from deny {} and Dlines. (OLD d:)
+ */
+exempt {
+	ip = "192.168.0.0/16";
+};
+
+/*
+ * resv {}:  nicks and channels users may not use/join (OLD Q:)
+ */
+resv {
+	/* reason: the reason for the proceeding resv's */
+	reason = "There are no services on this network";
+
+	/* resv: the nicks and channels users may not join/use */
+	nick = "nickserv";
+	nick = "chanserv";
+	channel = "#services";
+
+	/* resv: wildcard masks are also supported in nicks only */
+	reason = "Clone bots";
+	nick = "clone*";
+};
+
+/*
+ * gecos {}:  The X: replacement, used for banning users based on
+ * their "realname".
+ */
+gecos {
+	name = "*sex*";
+	reason = "Possible spambot";
+};
+
+gecos {
+	name = "sub7server";
+	reason = "Trojan drone";
+};
+
+gecos {
+	name = "*http*";
+	reason = "Spambot";
+};
+
+gecos {
+	name = "^\[J[0o]hn Do[3e]\]-[0-9]{2,5}$";
+	type = regex;
+};
+
+/*
+ * channel {}:  The channel block contains options pertaining to channels
+ */
+channel {
+	/*
+	 * disable_fake_channels: this option, if set to 'yes', will
+	 * disallow clients to create or join channels that have one
+	 * of the following ASCII characters in their name:
+	 *
+	 *   2 | bold
+	 *   3 | mirc color
+         *  15 | plain text
+	 *  22 | reverse
+	 *  31 | underline
+	 * 160 | non-breaking space
+	 */
+	disable_fake_channels = yes;
+
+	/*
+	 * restrict_channels: reverse channel RESVs logic, only reserved
+	 * channels are allowed
+	 */
+	restrict_channels = no;
+
+	/*
+	 * disable_local_channels: prevent users from joining &channels.
+	 */
+	disable_local_channels = no;
+
+	/*
+	 * use_invex: Enable/disable channel mode +I, a n!u@h list of masks
+	 * that can join a +i channel without an invite.
+	 */
+	use_invex = yes;
+
+	/*
+	 * use_except: Enable/disable channel mode +e, a n!u@h list of masks
+	 * that can join a channel through a ban (+b).
+	 */
+	use_except = yes;
+
+	/*
+	 * use_knock: Allows users to request an invite to a channel that
+	 * is locked somehow (+ikl).  If the channel is +p or you are banned
+	 * the knock will not be sent.
+	 */
+	use_knock = yes;
+
+	/*
+	 * knock_delay: The amount of time a user must wait between issuing
+	 * the knock command.
+	 */
+	knock_delay = 1 minutes;
+
+	/*
+	 * knock_delay_channel: How often a knock to any specific channel
+	 * is permitted, regardless of the user sending the knock.
+	 */
+	knock_delay_channel = 1 minute;
+
+	/*
+	 * burst_topicwho: enable sending of who set topic on topicburst
+	 * default is yes
+	 */
+	burst_topicwho = yes;
+
+	/*
+	 * max_chans_per_user: The maximum number of channels a user can
+	 * join/be on.
+	 */
+	max_chans_per_user = 25;
+
+	/* quiet_on_ban: stop banned people talking in channels. */
+	quiet_on_ban = yes;
+
+	/* max_bans: maximum number of +b/e/I modes in a channel */
+	max_bans = 1000;
+
+	/*
+	 * how many joins in how many seconds constitute a flood, use 0 to
+	 * disable. +b opers will be notified (changeable via /set)
+	 */
+	join_flood_count = 100;
+	join_flood_time = 10 seconds;
+
+	/*
+	 * splitcode: The ircd will now check splitmode every few seconds.
+	 *
+	 * Either split users or split servers can activate splitmode, but
+	 * both conditions must be met for the ircd to deactivate splitmode.
+	 * 
+	 * You may force splitmode to be permanent by /quote set splitmode on
+	 */
+
+	/*
+	 * default_split_user_count: when the usercount is lower than this level,
+	 * consider ourselves split.  This must be set for automatic splitmode.
+	 */
+	default_split_user_count = 0;
+
+	/*
+	 * default_split_server_count: when the servercount is lower than this,
+	 * consider ourselves split.  This must be set for automatic splitmode.
+	 */
+	default_split_server_count = 0;
+
+	/* split no create: disallow users creating channels on split. */
+	no_create_on_split = yes;
+
+	/* split: no join: disallow users joining channels at all on a split */
+	no_join_on_split = no;
+};
+
+/*
+ * serverhide {}:  The serverhide block contains the options regarding
+ * serverhiding
+ */
+serverhide {
+	/*
+	 * flatten_links: this option will show all servers in /links appear
+	 * that they are linked to this current server
+	 */
+	flatten_links = no;
+
+	/*
+	 * links_delay: how often to update the links file when it is
+	 * flattened.
+	 */
+	links_delay = 5 minutes;
+
+	/*
+	 * hidden: hide this server from a /links output on servers that
+	 * support it.  This allows hub servers to be hidden etc.
+	 */
+	hidden = no;
+
+	/*
+	 * disable_hidden: prevent servers hiding themselves from a
+	 * /links output.
+	 */
+	disable_hidden = no;
+
+	/*
+	 * hide_servers: hide remote servernames everywhere and instead use
+	 * hidden_name and network_desc.
+	 */
+	hide_servers = no;
+
+	/*
+	 * Use this as the servername users see if hide_servers = yes.
+	 */
+	hidden_name = "*.hidden.com";
+
+	/*
+	 * hide_server_ips: If this is disabled, opers will be unable to see servers
+	 * ips and will be shown a masked ip, admins will be shown the real ip.
+	 *
+	 * If this is enabled, nobody can see a servers ip.  *This is a kludge*, it
+	 * has the side effect of hiding the ips everywhere, including logfiles.
+	 *
+	 * We recommend you leave this disabled, and just take care with who you
+	 * give admin=yes; to.
+	 */
+	hide_server_ips = no;
+};
+
+/*
+ * general {}:  The general block contains many of the options that were once
+ * compiled in options in config.h.  The general block is read at start time.
+ */
+general {
+	/*
+	 * gline_min_cidr: the minimum required length of a CIDR bitmask
+	 * for IPv4 based glines
+	 */
+	gline_min_cidr = 16;
+
+	/*
+	 * gline_min_cidr6: the minimum required length of a CIDR bitmask
+	 * for IPv6 based glines
+	 */
+	gline_min_cidr6 = 48;
+
+	/*
+	 * Whether to automatically set mode +i on connecting users.
+	 */
+	invisible_on_connect = yes;
+
+	/*
+	 * If you don't explicitly specify burst_away in your connect blocks, then
+	 * they will default to the burst_away value below.
+	 */
+	burst_away = no;
+
+	/*
+	 * Show "actually using host <ip>" on /whois when possible.
+	 */
+	use_whois_actually = yes;
+
+	/*
+	 * Max time from the nickname change that still causes KILL
+	 * automatically to switch for the current nick of that user. (seconds)
+	 */
+	kill_chase_time_limit = 90;
+
+	/*
+	 * If hide_spoof_ips is disabled, opers will be allowed to see the real IP of spoofed
+	 * users in /trace etc.  If this is defined they will be shown a masked IP.
+	 */
+	hide_spoof_ips = yes;
+
+	/*
+	 * Ignore bogus timestamps from other servers.  Yes, this will desync
+	 * the network, but it will allow chanops to resync with a valid non TS 0
+	 *
+	 * This should be enabled network wide, or not at all.
+	 */
+	ignore_bogus_ts = no;
+
+	/*
+	 * disable_auth: completely disable ident lookups; if you enable this,
+	 * be careful of what you set need_ident to in your auth {} blocks
+	 */
+	disable_auth = no;
+
+	/* disable_remote_commands: disable users doing commands on remote servers */
+	disable_remote_commands = no;
+
+	/*
+	 * tkline_expire_notices: enables or disables temporary kline/xline
+	 * expire notices.
+	 */
+	tkline_expire_notices = no;
+
+	/*
+	 * default_floodcount: the default value of floodcount that is configurable
+	 * via /quote set floodcount.  This is the amount of lines a user
+	 * may send to any other user/channel in one second.
+	 */
+	default_floodcount = 10;
+
+	/*
+	 * failed_oper_notice: send a notice to all opers on the server when 
+	 * someone tries to OPER and uses the wrong password, host or ident.
+	 */
+	failed_oper_notice = yes;
+
+	/*
+	 * dots_in_ident: the amount of '.' characters permitted in an ident
+	 * reply before the user is rejected.
+	 */
+	dots_in_ident = 2;
+
+	/*
+	 * dot_in_ip6_addr: ircd-hybrid-6.0 and earlier will disallow hosts 
+	 * without a '.' in them.  This will add one to the end.  Only needed
+	 * for older servers.
+	 */
+	dot_in_ip6_addr = no;
+
+	/*
+	 * min_nonwildcard: the minimum non wildcard characters in k/d/g lines
+	 * placed via the server.  klines hand placed are exempt from limits.
+	 * wildcard chars: '.' ':' '*' '?' '@' '!' '#'
+	 */
+	min_nonwildcard = 4;
+
+	/*
+	 * min_nonwildcard_simple: the minimum non wildcard characters in 
+	 * gecos bans.  wildcard chars: '*' '?' '#'
+	 */
+	min_nonwildcard_simple = 3;
+
+	/* max_accept: maximum allowed /accept's for +g usermode */
+	max_accept = 20;
+
+	/* anti_nick_flood: enable the nickflood control code */
+	anti_nick_flood = yes;
+
+	/* nick flood: the nick changes allowed in the specified period */
+	max_nick_time = 20 seconds;
+	max_nick_changes = 5;
+
+	/*
+	 * anti_spam_exit_message_time: the minimum time a user must be connected
+	 * before custom quit messages are allowed.
+	 */
+	anti_spam_exit_message_time = 5 minutes;
+
+	/*
+	 * ts delta: the time delta allowed between server clocks before
+	 * a warning is given, or before the link is dropped.  all servers
+	 * should run ntpdate/rdate to keep clocks in sync
+	 */
+	ts_warn_delta = 30 seconds;
+	ts_max_delta = 5 minutes;
+
+	/*
+	 * kline_with_reason: show the user the reason why they are k/d/glined 
+	 * on exit.  May give away who set k/dline when set via tcm.
+	 */
+	kline_with_reason = yes;
+
+	/*
+	 * kline_reason: show this message to users on channel
+	 * instead of the oper reason.
+	 */
+	kline_reason = "Connection closed";
+
+	/*
+	 * reject_hold_time: wait this amount of time before disconnecting
+	 * a rejected client. Use 0 to disable.
+	 */
+	reject_hold_time = 0;
+
+	/*
+	 * warn_no_nline: warn opers about servers that try to connect but
+	 * we don't have a connect {} block for.  Twits with misconfigured 
+	 * servers can get really annoying with this enabled.
+	 */
+	warn_no_nline = yes;
+
+	/*
+	 * stats_e_disabled: set this to 'yes' to disable "STATS e" for both
+	 * operators and administrators.  Doing so is a good idea in case
+	 * there are any exempted (exempt{}) server IPs you don't want to
+	 * see leaked.
+	 */
+	stats_e_disabled = no;
+
+	/* stats_o_oper only: make stats o (opers) oper only */
+	stats_o_oper_only = yes;
+
+	/* stats_P_oper_only: make stats P (ports) oper only */
+	stats_P_oper_only = yes;
+
+	/*
+	 * stats i oper only: make stats i (auth {}) oper only. set to:
+	 *     yes:    show users no auth blocks, made oper only.
+	 *     masked: show users first matching auth block
+	 *     no:     show users all auth blocks.
+	 */
+	stats_i_oper_only = yes;
+
+	/*
+	 * stats_k_oper_only: make stats k/K (klines) oper only.  set to:
+	 *     yes:    show users no auth blocks, made oper only
+	 *     masked: show users first matching auth block
+	 *     no:     show users all auth blocks.
+	 */
+	stats_k_oper_only = yes;
+
+	/*
+	 * caller_id_wait: time between notifying a +g user that somebody
+	 * is messaging them.
+	 */
+	caller_id_wait = 1 minute;
+
+	/*
+	 * opers_bypass_callerid: allows operators to bypass +g and message
+	 * anyone who has it set (useful if you use services).
+	 */
+	opers_bypass_callerid = no;
+
+	/*
+	 * pace_wait_simple: time between use of less intensive commands
+	 * (ADMIN, HELP, (L)USERS, VERSION, remote WHOIS)
+	 */
+	pace_wait_simple = 1 second;
+
+	/*
+	 * pace_wait: time between more intensive commands
+	 * (INFO, LINKS, LIST, MAP, MOTD, STATS, WHO, wildcard WHOIS, WHOWAS)
+	 */
+	pace_wait = 10 seconds;
+
+	/*
+	 * short_motd: send clients a notice telling them to read the motd
+	 * instead of forcing a motd to clients who may simply ignore it.
+	 */
+	short_motd = no;
+
+	/*
+	 * ping_cookie: require clients to respond exactly to a ping command,
+	 * can help block certain types of drones and FTP PASV mode spoofing.
+	 */
+	ping_cookie = no;
+
+	/* no_oper_flood: increase flood limits for opers. */
+	no_oper_flood = yes;
+
+	/*
+	 * true_no_oper_flood: completely eliminate flood limits for opers
+	 * and for clients with can_flood = yes in their auth {} blocks
+	 */
+	true_no_oper_flood = yes;
+
+	/* oper_pass_resv: allow opers to over-ride RESVs on nicks/channels */
+	oper_pass_resv = yes;
+
+	/*
+	 * idletime: the maximum amount of time a user may idle before
+	 * they are disconnected
+	 */
+	idletime = 0;
+
+	/* REMOVE ME.  The following line checks you've been reading. */
+	#havent_read_conf = 1;
+
+	/*
+	 * max_targets: the maximum amount of targets in a single 
+	 * PRIVMSG/NOTICE.  Set to 999 NOT 0 for unlimited.
+	 */
+	max_targets = 4;
+
+	/*
+	 * client_flood: maximum amount of data in a clients queue before
+	 * they are dropped for flooding.
+	 */
+	client_flood = 2560 bytes;
+
+	/*
+	 * message_locale: the default message locale
+	 * Use "standard" for the compiled in defaults.
+	 * To install the translated messages, go into messages/ in the
+	 * source directory and run `make install'.
+	 */
+	message_locale = "standard";
+
+	/*
+	 * usermodes configurable: a list of usermodes for the options below
+	 *
+	 * +b - bots         - See bot and drone flooding notices
+	 * +c - cconn        - Client connection/quit notices
+	 * +D - deaf         - Don't receive channel messages
+	 * +d - debug        - See debugging notices
+	 * +f - full         - See I: line full notices
+	 * +G - softcallerid - Server Side Ignore for users not on your channels
+	 * +g - callerid     - Server Side Ignore (for privmsgs etc)
+	 * +i - invisible    - Not shown in NAMES or WHO unless you share a 
+	 *                     a channel
+	 * +k - skill        - See server generated KILL messages
+	 * +l - locops       - See LOCOPS messages
+	 * +n - nchange      - See client nick changes
+	 * +r - rej          - See rejected client notices
+	 * +s - servnotice   - See general server notices
+	 * +u - unauth       - See unauthorized client notices
+	 * +w - wallop       - See server generated WALLOPS
+	 * +x - external     - See remote server connection and split notices
+	 * +y - spy          - See LINKS, STATS, TRACE notices etc.
+	 * +z - operwall     - See oper generated WALLOPS
+	 */
+
+	/* oper_only_umodes: usermodes only opers may set */
+	oper_only_umodes = bots, cconn, debug, full, skill, nchange, 
+			   rej, spy, external, operwall, locops, unauth;
+
+	/* oper_umodes: default usermodes opers get when they /oper */
+	oper_umodes = bots, locops, servnotice, operwall, wallop;
+
+	/*
+	 * servlink_path: path to 'servlink' program used by ircd to handle
+	 * encrypted/compressed server <-> server links.
+	 *
+	 * only define if servlink is not in same directory as ircd itself.
+	 */
+	#servlink_path = "/usr/local/ircd/bin/servlink";
+
+	/*
+	 * default_cipher_preference: default cipher to use for cryptlink when none is
+	 * specified in connect block.
+	 */
+	#default_cipher_preference = "BF/168";
+
+	/*
+	 * use_egd: if your system does not have *random devices yet you
+	 * want to use OpenSSL and encrypted links, enable this.  Beware -
+	 * EGD is *very* CPU intensive when gathering data for its pool
+	 */
+#	use_egd = yes;
+
+	/*
+	 * egdpool_path: path to EGD pool. Not necessary for OpenSSL >= 0.9.7
+	 * which automatically finds the path.
+	 */
+#	egdpool_path = "/run/egd-pool";
+
+
+	/*
+	 * compression_level: level of compression for compressed links between
+	 * servers.  
+	 *
+	 * values are between: 1 (least compression, fastest)
+	 *                and: 9 (most compression, slowest).
+	 */
+#	compression_level = 6;
+
+	/*
+	 * throttle_time: the minimum amount of time between connections from
+	 * the same ip.  exempt {} blocks are excluded from this throttling.
+	 * Offers protection against flooders who reconnect quickly.  
+	 * Set to 0 to disable.
+	 */
+	throttle_time = 10;
+};
+
+glines {
+	/* enable: enable glines, network wide temp klines */
+	enable = yes;
+
+	/*
+	 * duration: the amount of time a gline will remain on your
+	 * server before expiring
+	 */
+	duration = 1 day;
+
+	/*
+	 * logging: which types of rules you want to log when triggered
+	 * (choose reject or block)
+	 */
+	logging = reject, block;
+
+	/*
+	 * NOTE: gline ACLs can cause a desync of glines throughout the
+	 * network, meaning some servers may have a gline triggered, and
+	 * others may not. Also, you only need insert rules for glines
+	 * that you want to block and/or reject. If you want to accept and
+	 * propagate the gline, do NOT put a rule for it.
+	 */
+
+	/* user@host for rule to apply to */
+	user = "god@I.still.hate.packets";
+	/* server for rule to apply to */
+	name = "hades.arpa";
+
+	/*
+	 * action: action to take when a matching gline is found. options are:
+	 *  reject	- do not apply the gline locally
+	 *  block	- do not propagate the gline
+	 */
+	action = reject, block;
+
+	user = "god@*";
+	name = "*";
+	action = block;
+};
+
diff --git a/nixpkgs/nixos/modules/services/networking/iwd.nix b/nixpkgs/nixos/modules/services/networking/iwd.nix
new file mode 100644
index 000000000000..6be67a8b96f4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/iwd.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.networking.wireless.iwd;
+in {
+  options.networking.wireless.iwd.enable = mkEnableOption "iwd";
+
+  config = mkIf cfg.enable {
+    assertions = [{
+      assertion = !config.networking.wireless.enable;
+      message = ''
+        Only one wireless daemon is allowed at the time: networking.wireless.enable and networking.wireless.iwd.enable are mutually exclusive.
+      '';
+    }];
+
+    # for iwctl
+    environment.systemPackages =  [ pkgs.iwd ];
+
+    services.dbus.packages = [ pkgs.iwd ];
+
+    systemd.packages = [ pkgs.iwd ];
+
+    systemd.services.iwd.wantedBy = [ "multi-user.target" ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ mic92 dtzWill ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keepalived/default.nix b/nixpkgs/nixos/modules/services/networking/keepalived/default.nix
new file mode 100644
index 000000000000..c9ac2ee25990
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keepalived/default.nix
@@ -0,0 +1,303 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.keepalived;
+
+  keepalivedConf = pkgs.writeText "keepalived.conf" ''
+    global_defs {
+      ${optionalString cfg.enableScriptSecurity "enable_script_security"}
+      ${snmpGlobalDefs}
+      ${cfg.extraGlobalDefs}
+    }
+
+    ${vrrpScriptStr}
+    ${vrrpInstancesStr}
+    ${cfg.extraConfig}
+  '';
+
+  snmpGlobalDefs = with cfg.snmp; optionalString enable (
+    optionalString (socket != null) "snmp_socket ${socket}\n"
+    + optionalString enableKeepalived "enable_snmp_keepalived\n"
+    + optionalString enableChecker "enable_snmp_checker\n"
+    + optionalString enableRfc "enable_snmp_rfc\n"
+    + optionalString enableRfcV2 "enable_snmp_rfcv2\n"
+    + optionalString enableRfcV3 "enable_snmp_rfcv3\n"
+    + optionalString enableTraps "enable_traps"
+  );
+
+  vrrpScriptStr = concatStringsSep "\n" (map (s:
+    ''
+      vrrp_script ${s.name} {
+        script "${s.script}"
+        interval ${toString s.interval}
+        fall ${toString s.fall}
+        rise ${toString s.rise}
+        timeout ${toString s.timeout}
+        weight ${toString s.weight}
+        user ${s.user} ${optionalString (s.group != null) s.group}
+
+        ${s.extraConfig}
+      }
+    ''
+  ) vrrpScripts);
+
+  vrrpInstancesStr = concatStringsSep "\n" (map (i:
+    ''
+      vrrp_instance ${i.name} {
+        interface ${i.interface}
+        state ${i.state}
+        virtual_router_id ${toString i.virtualRouterId}
+        priority ${toString i.priority}
+        ${optionalString i.noPreempt "nopreempt"}
+
+        ${optionalString i.useVmac (
+          "use_vmac" + optionalString (i.vmacInterface != null) " ${i.vmacInterface}"
+        )}
+        ${optionalString i.vmacXmitBase "vmac_xmit_base"}
+
+        ${optionalString (i.unicastSrcIp != null) "unicast_src_ip ${i.unicastSrcIp}"}
+        unicast_peer {
+          ${concatStringsSep "\n" i.unicastPeers}
+        }
+
+        virtual_ipaddress {
+          ${concatMapStringsSep "\n" virtualIpLine i.virtualIps}
+        }
+
+        ${optionalString (builtins.length i.trackScripts > 0) ''
+          track_script {
+            ${concatStringsSep "\n" i.trackScripts}
+          }
+        ''}
+
+        ${optionalString (builtins.length i.trackInterfaces > 0) ''
+          track_interface {
+            ${concatStringsSep "\n" i.trackInterfaces}
+          }
+        ''}
+
+        ${i.extraConfig}
+      }
+    ''
+  ) vrrpInstances);
+
+  virtualIpLine = (ip:
+    ip.addr
+    + optionalString (notNullOrEmpty ip.brd) " brd ${ip.brd}"
+    + optionalString (notNullOrEmpty ip.dev) " dev ${ip.dev}"
+    + optionalString (notNullOrEmpty ip.scope) " scope ${ip.scope}"
+    + optionalString (notNullOrEmpty ip.label) " label ${ip.label}"
+  );
+
+  notNullOrEmpty = s: !(s == null || s == "");
+
+  vrrpScripts = mapAttrsToList (name: config:
+    {
+      inherit name;
+    } // config
+  ) cfg.vrrpScripts;
+
+  vrrpInstances = mapAttrsToList (iName: iConfig:
+    {
+      name = iName;
+    } // iConfig
+  ) cfg.vrrpInstances;
+
+  vrrpInstanceAssertions = i: [
+    { assertion = i.interface != "";
+      message = "services.keepalived.vrrpInstances.${i.name}.interface option cannot be empty.";
+    }
+    { assertion = i.virtualRouterId >= 0 && i.virtualRouterId <= 255;
+      message = "services.keepalived.vrrpInstances.${i.name}.virtualRouterId must be an integer between 0..255.";
+    }
+    { assertion = i.priority >= 0 && i.priority <= 255;
+      message = "services.keepalived.vrrpInstances.${i.name}.priority must be an integer between 0..255.";
+    }
+    { assertion = i.vmacInterface == null || i.useVmac;
+      message = "services.keepalived.vrrpInstances.${i.name}.vmacInterface has no effect when services.keepalived.vrrpInstances.${i.name}.useVmac is not set.";
+    }
+    { assertion = !i.vmacXmitBase || i.useVmac;
+      message = "services.keepalived.vrrpInstances.${i.name}.vmacXmitBase has no effect when services.keepalived.vrrpInstances.${i.name}.useVmac is not set.";
+    }
+  ] ++ flatten (map (virtualIpAssertions i.name) i.virtualIps)
+    ++ flatten (map (vrrpScriptAssertion i.name) i.trackScripts);
+
+  virtualIpAssertions = vrrpName: ip: [
+    { assertion = ip.addr != "";
+      message = "The 'addr' option for an services.keepalived.vrrpInstances.${vrrpName}.virtualIps entry cannot be empty.";
+    }
+  ];
+
+  vrrpScriptAssertion = vrrpName: scriptName: {
+    assertion = builtins.hasAttr scriptName cfg.vrrpScripts;
+    message = "services.keepalived.vrrpInstances.${vrrpName} trackscript ${scriptName} is not defined in services.keepalived.vrrpScripts.";
+  };
+
+  pidFile = "/run/keepalived.pid";
+
+in
+{
+
+  options = {
+    services.keepalived = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable Keepalived.
+        '';
+      };
+
+      enableScriptSecurity = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Don't run scripts configured to be run as root if any part of the path is writable by a non-root user.
+        '';
+      };
+
+      snmp = {
+
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to enable the builtin AgentX subagent.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Socket to use for connecting to SNMP master agent. If this value is
+            set to null, keepalived's default will be used, which is
+            unix:/var/agentx/master, unless using a network namespace, when the
+            default is udp:localhost:705.
+          '';
+        };
+
+        enableKeepalived = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Enable SNMP handling of vrrp element of KEEPALIVED MIB.
+          '';
+        };
+
+        enableChecker = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Enable SNMP handling of checker element of KEEPALIVED MIB.
+          '';
+        };
+
+        enableRfc = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Enable SNMP handling of RFC2787 and RFC6527 VRRP MIBs.
+          '';
+        };
+
+        enableRfcV2 = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Enable SNMP handling of RFC2787 VRRP MIB.
+          '';
+        };
+
+        enableRfcV3 = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Enable SNMP handling of RFC6527 VRRP MIB.
+          '';
+        };
+
+        enableTraps = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Enable SNMP traps.
+          '';
+        };
+
+      };
+
+      vrrpScripts = mkOption {
+        type = types.attrsOf (types.submodule (import ./vrrp-script-options.nix {
+          inherit lib;
+        }));
+        default = {};
+        description = "Declarative vrrp script config";
+      };
+
+      vrrpInstances = mkOption {
+        type = types.attrsOf (types.submodule (import ./vrrp-instance-options.nix {
+          inherit lib;
+        }));
+        default = {};
+        description = "Declarative vhost config";
+      };
+
+      extraGlobalDefs = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra lines to be added verbatim to the 'global_defs' block of the
+          configuration file
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra lines to be added verbatim to the configuration file.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = flatten (map vrrpInstanceAssertions vrrpInstances);
+
+    systemd.timers.keepalived-boot-delay = {
+      description = "Keepalive Daemon delay to avoid instant transition to MASTER state";
+      after = [ "network.target" "network-online.target" "syslog.target" ];
+      requires = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      timerConfig = {
+        OnActiveSec = "5s";
+        Unit = "keepalived.service";
+      };
+    };
+
+    systemd.services.keepalived = {
+      description = "Keepalive Daemon (LVS and VRRP)";
+      after = [ "network.target" "network-online.target" "syslog.target" ];
+      wants = [ "network-online.target" ];
+      serviceConfig = {
+        Type = "forking";
+        PIDFile = pidFile;
+        KillMode = "process";
+        ExecStart = "${pkgs.keepalived}/sbin/keepalived"
+          + " -f ${keepalivedConf}"
+          + " -p ${pidFile}"
+          + optionalString cfg.snmp.enable " --snmp";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+        RestartSec = "1s";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keepalived/virtual-ip-options.nix b/nixpkgs/nixos/modules/services/networking/keepalived/virtual-ip-options.nix
new file mode 100644
index 000000000000..1b8889b1b472
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keepalived/virtual-ip-options.nix
@@ -0,0 +1,50 @@
+{ lib } :
+
+with lib;
+{
+  options = {
+
+    addr = mkOption {
+      type = types.str;
+      description = ''
+        IP address, optionally with a netmask: IPADDR[/MASK]
+      '';
+    };
+
+    brd = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The broadcast address on the interface.
+      '';
+    };
+
+    dev = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The name of the device to add the address to.
+      '';
+    };
+
+    scope = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The scope of the area where this address is valid.
+      '';
+    };
+
+    label = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Each address may be tagged with a label string. In order to preserve
+        compatibility with Linux-2.0 net aliases, this string must coincide with
+        the name of the device or must be prefixed with the device name followed
+        by colon.
+      '';
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix b/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
new file mode 100644
index 000000000000..85b9bc337726
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-instance-options.nix
@@ -0,0 +1,135 @@
+{ lib } :
+
+with lib;
+{
+  options = {
+
+    interface = mkOption {
+      type = types.str;
+      description = ''
+        Interface for inside_network, bound by vrrp.
+      '';
+    };
+
+    state = mkOption {
+      type = types.enum [ "MASTER" "BACKUP" ];
+      default = "BACKUP";
+      description = ''
+        Initial state. As soon as the other machine(s) come up, an election will
+        be held and the machine with the highest "priority" will become MASTER.
+        So the entry here doesn't matter a whole lot.
+      '';
+    };
+
+    virtualRouterId = mkOption {
+      type = types.int;
+      description = ''
+        Arbitrary unique number 0..255. Used to differentiate multiple instances
+        of vrrpd running on the same NIC (and hence same socket).
+      '';
+    };
+
+    priority = mkOption {
+      type = types.int;
+      default = 100;
+      description = ''
+        For electing MASTER, highest priority wins. To be MASTER, make 50 more
+        than other machines.
+      '';
+    };
+
+    noPreempt = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        VRRP will normally preempt a lower priority machine when a higher
+        priority machine comes online. "nopreempt" allows the lower priority
+        machine to maintain the master role, even when a higher priority machine
+        comes back online. NOTE: For this to work, the initial state of this
+        entry must be BACKUP.
+      '';
+    };
+
+    useVmac = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Use VRRP Virtual MAC.
+      '';
+    };
+
+    vmacInterface = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+         Name of the vmac interface to use. keepalived will come up with a name
+         if you don't specify one.
+      '';
+    };
+
+    vmacXmitBase = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Send/Recv VRRP messages from base interface instead of VMAC interface.
+      '';
+    };
+
+    unicastSrcIp = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+         Default IP for binding vrrpd is the primary IP on interface. If you
+         want to hide location of vrrpd, use this IP as src_addr for unicast
+         vrrp packets.
+      '';
+    };
+
+    unicastPeers = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Do not send VRRP adverts over VRRP multicast group. Instead it sends
+        adverts to the following list of ip addresses using unicast design
+        fashion. It can be cool to use VRRP FSM and features in a networking
+        environment where multicast is not supported! IP Addresses specified can
+        IPv4 as well as IPv6.
+      '';
+    };
+
+    virtualIps = mkOption {
+      type = types.listOf (types.submodule (import ./virtual-ip-options.nix {
+        inherit lib;
+      }));
+      default = [];
+      example = literalExample ''
+        TODO: Example
+      '';
+      description = "Declarative vhost config";
+    };
+
+    trackScripts = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "chk_cmd1" "chk_cmd2" ];
+      description = "List of script names to invoke for health tracking.";
+    };
+
+    trackInterfaces = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "eth0" "eth1" ];
+      description = "List of network interfaces to monitor for health tracking.";
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Extra lines to be added verbatim to the vrrp_instance section.
+      '';
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-script-options.nix b/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
new file mode 100644
index 000000000000..a3f794c40a89
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keepalived/vrrp-script-options.nix
@@ -0,0 +1,64 @@
+{ lib } :
+
+with lib;
+with lib.types;
+{
+  options = {
+
+    script = mkOption {
+      type = str;
+      example = "\${pkgs.curl} -f http://localhost:80";
+      description = "(Path of) Script command to execute followed by args, i.e. cmd [args]...";
+    };
+
+    interval = mkOption {
+      type = int;
+      default = 1;
+      description = "Seconds between script invocations.";
+    };
+
+    timeout = mkOption {
+      type = int;
+      default = 5;
+      description = "Seconds after which script is considered to have failed.";
+    };
+
+    weight = mkOption {
+      type = int;
+      default = 0;
+      description = "Following a failure, adjust the priority by this weight.";
+    };
+
+    rise = mkOption {
+      type = int;
+      default = 5;
+      description = "Required number of successes for OK transition.";
+    };
+
+    fall = mkOption {
+      type = int;
+      default = 3;
+      description = "Required number of failures for KO transition.";
+    };
+
+    user = mkOption {
+      type = str;
+      default = "keepalived_script";
+      description = "Name of user to run the script under.";
+    };
+
+    group = mkOption {
+      type = nullOr str;
+      default = null;
+      description = "Name of group to run the script under. Defaults to user group.";
+    };
+
+    extraConfig = mkOption {
+      type = lines;
+      default = "";
+      description = "Extra lines to be added verbatim to the vrrp_script section.";
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/keybase.nix b/nixpkgs/nixos/modules/services/networking/keybase.nix
new file mode 100644
index 000000000000..495102cb7eee
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/keybase.nix
@@ -0,0 +1,47 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.keybase;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.keybase = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to start the Keybase service.";
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    # Upstream: https://github.com/keybase/client/blob/master/packaging/linux/systemd/keybase.service
+    systemd.user.services.keybase = {
+      description = "Keybase service";
+      unitConfig.ConditionUser = "!@system";
+      environment.KEYBASE_SERVICE_TYPE = "systemd";
+      serviceConfig = {
+        Type = "notify";
+        EnvironmentFile = [
+          "-%E/keybase/keybase.autogen.env"
+          "-%E/keybase/keybase.env"
+        ];
+        ExecStart = "${pkgs.keybase}/bin/keybase service";
+        Restart = "on-failure";
+        PrivateTmp = true;
+      };
+      wantedBy = [ "default.target" ];
+    };
+
+    environment.systemPackages = [ pkgs.keybase ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/kippo.nix b/nixpkgs/nixos/modules/services/networking/kippo.nix
new file mode 100644
index 000000000000..553415a2f329
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/kippo.nix
@@ -0,0 +1,117 @@
+# NixOS module for kippo honeypot ssh server
+# See all the options for configuration details.
+#
+# Default port is 2222. Recommend using something like this for port redirection to default SSH port:
+# networking.firewall.extraCommands = ''
+#      iptables -t nat -A PREROUTING -i IN_IFACE -p tcp --dport 22 -j REDIRECT --to-port 2222'';
+#
+# Lastly: use this service at your own risk. I am working on a way to run this inside a VM.
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.kippo;
+in
+{
+  options = {
+    services.kippo = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''Enable the kippo honeypot ssh server.'';
+      };
+      port = mkOption {
+        default = 2222;
+        type = types.int;
+        description = ''TCP port number for kippo to bind to.'';
+      };
+      hostname = mkOption {
+        default = "nas3";
+        type = types.str;
+        description = ''Hostname for kippo to present to SSH login'';
+      };
+      varPath = mkOption {
+        default = "/var/lib/kippo";
+        type = types.path;
+        description = ''Path of read/write files needed for operation and configuration.'';
+      };
+      logPath = mkOption {
+        default = "/var/log/kippo";
+        type = types.path;
+        description = ''Path of log files needed for operation and configuration.'';
+      };
+      pidPath = mkOption {
+        default = "/run/kippo";
+        type = types.path;
+        description = ''Path of pid files needed for operation.'';
+      };
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''Extra verbatim configuration added to the end of kippo.cfg.'';
+      };
+    };
+
+  };
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs.pythonPackages; [
+      python pkgs.kippo.twisted pycrypto pyasn1 ];
+
+    environment.etc."kippo.cfg".text = ''
+        # Automatically generated by NixOS.
+        # See ${pkgs.kippo}/src/kippo.cfg for details.
+        [honeypot]
+        log_path = ${cfg.logPath}
+        download_path = ${cfg.logPath}/dl
+        filesystem_file = ${cfg.varPath}/honeyfs
+        filesystem_file = ${cfg.varPath}/fs.pickle
+        data_path = ${cfg.varPath}/data
+        txtcmds_path = ${cfg.varPath}/txtcmds
+        public_key = ${cfg.varPath}/keys/public.key
+        private_key = ${cfg.varPath}/keys/private.key
+        ssh_port = ${toString cfg.port}
+        hostname = ${cfg.hostname}
+        ${cfg.extraConfig}
+    '';
+
+    users.users.kippo = {
+      description = "kippo web server privilege separation user";
+      uid = 108; # why does config.ids.uids.kippo give an error?
+    };
+    users.groups.kippo.gid = 108;
+
+    systemd.services.kippo = with pkgs; {
+      description = "Kippo Web Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment.PYTHONPATH = "${pkgs.kippo}/src/:${pkgs.pythonPackages.pycrypto}/lib/python2.7/site-packages/:${pkgs.pythonPackages.pyasn1}/lib/python2.7/site-packages/:${pkgs.pythonPackages.python}/lib/python2.7/site-packages/:${pkgs.kippo.twisted}/lib/python2.7/site-packages/:.";
+      preStart = ''
+        if [ ! -d ${cfg.varPath}/ ] ; then
+            mkdir -p ${cfg.logPath}/tty
+            mkdir -p ${cfg.logPath}/dl
+            mkdir -p ${cfg.varPath}/keys
+            cp ${pkgs.kippo}/src/honeyfs ${cfg.varPath} -r
+            cp ${pkgs.kippo}/src/fs.pickle ${cfg.varPath}/fs.pickle
+            cp ${pkgs.kippo}/src/data ${cfg.varPath} -r
+            cp ${pkgs.kippo}/src/txtcmds ${cfg.varPath} -r
+
+            chmod u+rw ${cfg.varPath} -R
+            chown kippo.kippo ${cfg.varPath} -R
+            chown kippo.kippo ${cfg.logPath} -R
+            chmod u+rw ${cfg.logPath} -R
+        fi
+        if [ ! -d ${cfg.pidPath}/ ] ; then
+            mkdir -p ${cfg.pidPath}
+            chmod u+rw ${cfg.pidPath}
+            chown kippo.kippo ${cfg.pidPath}
+        fi
+      '';
+
+      serviceConfig.ExecStart = "${pkgs.kippo.twisted}/bin/twistd -y ${pkgs.kippo}/src/kippo.tac --syslog --rundir=${cfg.varPath}/ --pidfile=${cfg.pidPath}/kippo.pid --prefix=kippo -n";
+      serviceConfig.PermissionsStartOnly = true;
+      serviceConfig.User = "kippo";
+      serviceConfig.Group = "kippo";
+    };
+};
+}
+
+
diff --git a/nixpkgs/nixos/modules/services/networking/knot.nix b/nixpkgs/nixos/modules/services/networking/knot.nix
new file mode 100644
index 000000000000..12ff89fe8492
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/knot.nix
@@ -0,0 +1,117 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.knot;
+
+  configFile = pkgs.writeTextFile {
+    name = "knot.conf";
+    text = (concatMapStringsSep "\n" (file: "include: ${file}") cfg.keyFiles) + "\n" +
+           cfg.extraConfig;
+    checkPhase = lib.optionalString (cfg.keyFiles == []) ''
+      ${cfg.package}/bin/knotc --config=$out conf-check
+    '';
+  };
+
+  socketFile = "/run/knot/knot.sock";
+
+  knot-cli-wrappers = pkgs.stdenv.mkDerivation {
+    name = "knot-cli-wrappers";
+    buildInputs = [ pkgs.makeWrapper ];
+    buildCommand = ''
+      mkdir -p $out/bin
+      makeWrapper ${cfg.package}/bin/knotc "$out/bin/knotc" \
+        --add-flags "--config=${configFile}" \
+        --add-flags "--socket=${socketFile}"
+      makeWrapper ${cfg.package}/bin/keymgr "$out/bin/keymgr" \
+        --add-flags "--config=${configFile}"
+      for executable in kdig khost kjournalprint knsec3hash knsupdate kzonecheck
+      do
+        ln -s "${cfg.package}/bin/$executable" "$out/bin/$executable"
+      done
+      mkdir -p "$out/share"
+      ln -s '${cfg.package}/share/man' "$out/share/"
+    '';
+  };
+in {
+  options = {
+    services.knot = {
+      enable = mkEnableOption "Knot authoritative-only DNS server";
+
+      extraArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          List of additional command line paramters for knotd
+        '';
+      };
+
+      keyFiles = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          A list of files containing additional configuration
+          to be included using the include directive. This option
+          allows to include configuration like TSIG keys without
+          exposing them to the nix store readable to any process.
+          Note that using this option will also disable configuration
+          checks at build time.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra lines to be added verbatim to knot.conf
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.knot-dns;
+        defaultText = "pkgs.knot-dns";
+        description = ''
+          Which Knot DNS package to use
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.knot.enable {
+    users.users.knot = {
+      isSystemUser = true;
+      group = "knot";
+      description = "Knot daemon user";
+    };
+
+    users.groups.knot.gid = null;
+    systemd.services.knot = {
+      unitConfig.Documentation = "man:knotd(8) man:knot.conf(5) man:knotc(8) https://www.knot-dns.cz/docs/${cfg.package.version}/html/";
+      description = cfg.package.meta.description;
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network.target" ];
+      after = ["network.target" ];
+
+      serviceConfig = {
+        Type = "notify";
+        ExecStart = "${cfg.package}/bin/knotd --config=${configFile} --socket=${socketFile} ${concatStringsSep " " cfg.extraArgs}";
+        ExecReload = "${knot-cli-wrappers}/bin/knotc reload";
+        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETPCAP";
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETPCAP";
+        NoNewPrivileges = true;
+        User = "knot";
+        RuntimeDirectory = "knot";
+        StateDirectory = "knot";
+        StateDirectoryMode = "0700";
+        PrivateDevices = true;
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+        SystemCallArchitectures = "native";
+        Restart = "on-abort";
+      };
+    };
+
+    environment.systemPackages = [ knot-cli-wrappers ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/kresd.nix b/nixpkgs/nixos/modules/services/networking/kresd.nix
new file mode 100644
index 000000000000..c5a84eebd46f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/kresd.nix
@@ -0,0 +1,145 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.kresd;
+
+  # Convert systemd-style address specification to kresd config line(s).
+  # On Nix level we don't attempt to precisely validate the address specifications.
+  mkListen = kind: addr: let
+    al_v4 = builtins.match "([0-9.]\+):([0-9]\+)" addr;
+    al_v6 = builtins.match "\\[(.\+)]:([0-9]\+)" addr;
+    al_portOnly = builtins.match "()([0-9]\+)" addr;
+    al = findFirst (a: a != null)
+      (throw "services.kresd.*: incorrect address specification '${addr}'")
+      [ al_v4 al_v6 al_portOnly ];
+    port = last al;
+    addrSpec = if al_portOnly == null then "'${head al}'" else "{'::', '127.0.0.1'}";
+    in # freebind is set for compatibility with earlier kresd services;
+       # it could be configurable, for example.
+      ''
+        net.listen(${addrSpec}, ${port}, { kind = '${kind}', freebind = true })
+      '';
+
+  configFile = pkgs.writeText "kresd.conf" (
+    optionalString (cfg.listenDoH != []) ''
+      modules.load('http')
+    ''
+    + concatMapStrings (mkListen "dns") cfg.listenPlain
+    + concatMapStrings (mkListen "tls") cfg.listenTLS
+    + concatMapStrings (mkListen "doh") cfg.listenDoH
+    + cfg.extraConfig
+  );
+
+  package = if cfg.listenDoH == []
+    then pkgs.knot-resolver # never force `extraFeatures = false`
+    else pkgs.knot-resolver.override { extraFeatures = true; };
+in {
+  meta.maintainers = [ maintainers.vcunat /* upstream developer */ ];
+
+  imports = [
+    (mkChangedOptionModule [ "services" "kresd" "interfaces" ] [ "services" "kresd" "listenPlain" ]
+      (config:
+        let value = getAttrFromPath [ "services" "kresd" "interfaces" ] config;
+        in map
+          (iface: if elem ":" (stringToCharacters iface) then "[${iface}]:53" else "${iface}:53") # Syntax depends on being IPv6 or IPv4.
+          value
+      )
+    )
+    (mkRemovedOptionModule [ "services" "kresd" "cacheDir" ] "Please use (bind-)mounting instead.")
+  ];
+
+  ###### interface
+  options.services.kresd = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable knot-resolver domain name server.
+        DNSSEC validation is turned on by default.
+        You can run <literal>sudo nc -U /run/knot-resolver/control/1</literal>
+        and give commands interactively to kresd@1.service.
+      '';
+    };
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Extra lines to be added verbatim to the generated configuration file.
+      '';
+    };
+    listenPlain = mkOption {
+      type = with types; listOf str;
+      default = [ "[::1]:53" "127.0.0.1:53" ];
+      example = [ "53" ];
+      description = ''
+        What addresses and ports the server should listen on.
+        For detailed syntax see ListenStream in man systemd.socket.
+      '';
+    };
+    listenTLS = mkOption {
+      type = with types; listOf str;
+      default = [];
+      example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ];
+      description = ''
+        Addresses and ports on which kresd should provide DNS over TLS (see RFC 7858).
+        For detailed syntax see ListenStream in man systemd.socket.
+      '';
+    };
+    listenDoH = mkOption {
+      type = with types; listOf str;
+      default = [];
+      example = [ "198.51.100.1:443" "[2001:db8::1]:443" "443" ];
+      description = ''
+        Addresses and ports on which kresd should provide DNS over HTTPS (see RFC 8484).
+        For detailed syntax see ListenStream in man systemd.socket.
+      '';
+    };
+    instances = mkOption {
+      type = types.ints.unsigned;
+      default = 1;
+      description = ''
+        The number of instances to start.  They will be called kresd@{1,2,...}.service.
+        Knot Resolver uses no threads, so this is the way to scale.
+        You can dynamically start/stop them at will, so this is just system default.
+      '';
+    };
+    # TODO: perhaps options for more common stuff like cache size or forwarding
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    environment.etc."knot-resolver/kresd.conf".source = configFile; # not required
+
+    users.users.knot-resolver =
+      { isSystemUser = true;
+        group = "knot-resolver";
+        description = "Knot-resolver daemon user";
+      };
+    users.groups.knot-resolver.gid = null;
+
+    systemd.packages = [ package ]; # the units are patched inside the package a bit
+
+    systemd.targets.kresd = { # configure units started by default
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "kres-cache-gc.service" ]
+        ++ map (i: "kresd@${toString i}.service") (range 1 cfg.instances);
+    };
+    systemd.services."kresd@".serviceConfig = {
+      ExecStart = "${package}/bin/kresd --noninteractive "
+        + "-c ${package}/lib/knot-resolver/distro-preconfig.lua -c ${configFile}";
+      # Ensure correct ownership in case UID or GID changes.
+      CacheDirectory = "knot-resolver";
+      CacheDirectoryMode = "0750";
+    };
+
+    environment.etc."tmpfiles.d/knot-resolver.conf".source =
+      "${package}/lib/tmpfiles.d/knot-resolver.conf";
+
+    # Try cleaning up the previously default location of cache file.
+    # Note that /var/cache/* should always be safe to remove.
+    # TODO: remove later, probably between 20.09 and 21.03
+    systemd.tmpfiles.rules = [ "R /var/cache/kresd" ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/lambdabot.nix b/nixpkgs/nixos/modules/services/networking/lambdabot.nix
new file mode 100644
index 000000000000..b7c8bd008fe1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/lambdabot.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.lambdabot;
+
+  rc = builtins.toFile "script.rc" cfg.script;
+
+in
+
+{
+
+  ### configuration
+
+  options = {
+
+    services.lambdabot = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable the Lambdabot IRC bot";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.lambdabot;
+        defaultText = "pkgs.lambdabot";
+        description = "Used lambdabot package";
+      };
+
+      script = mkOption {
+        type = types.str;
+        default = "";
+        description = "Lambdabot script";
+      };
+
+    };
+
+  };
+
+  ### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.lambdabot = {
+      description = "Lambdabot daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      # Workaround for https://github.com/lambdabot/lambdabot/issues/117
+      script = ''
+        mkdir -p ~/.lambdabot
+        cd ~/.lambdabot
+        mkfifo /run/lambdabot/offline
+        (
+          echo 'rc ${rc}'
+          while true; do
+            cat /run/lambdabot/offline
+          done
+        ) | ${cfg.package}/bin/lambdabot
+      '';
+      serviceConfig = {
+        User = "lambdabot";
+        RuntimeDirectory = [ "lambdabot" ];
+      };
+    };
+
+    users.users.lambdabot = {
+      group = "lambdabot";
+      description = "Lambdabot daemon user";
+      home = "/var/lib/lambdabot";
+      createHome = true;
+      uid = config.ids.uids.lambdabot;
+    };
+
+    users.groups.lambdabot.gid = config.ids.gids.lambdabot;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/libreswan.nix b/nixpkgs/nixos/modules/services/networking/libreswan.nix
new file mode 100644
index 000000000000..280158b89f61
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/libreswan.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.libreswan;
+
+  libexec = "${pkgs.libreswan}/libexec/ipsec";
+  ipsec = "${pkgs.libreswan}/sbin/ipsec";
+
+  trim = chars: str: let
+      nonchars = filter (x : !(elem x.value chars))
+                  (imap0 (i: v: {ind = i; value = v;}) (stringToCharacters str));
+    in
+      if length nonchars == 0 then ""
+      else substring (head nonchars).ind (add 1 (sub (last nonchars).ind (head nonchars).ind)) str;
+  indent = str: concatStrings (concatMap (s: ["  " (trim [" " "\t"] s) "\n"]) (splitString "\n" str));
+  configText = indent (toString cfg.configSetup);
+  connectionText = concatStrings (mapAttrsToList (n: v:
+    ''
+      conn ${n}
+      ${indent v}
+
+    '') cfg.connections);
+  configFile = pkgs.writeText "ipsec.conf"
+    ''
+      config setup
+      ${configText}
+
+      ${connectionText}
+    '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.libreswan = {
+
+      enable = mkEnableOption "libreswan ipsec service";
+
+      configSetup = mkOption {
+        type = types.lines;
+        default = ''
+            protostack=netkey
+            nat_traversal=yes
+            virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v4:100.64.0.0/10,%v6:fd00::/8,%v6:fe80::/10
+        '';
+        example = ''
+            secretsfile=/root/ipsec.secrets
+            protostack=netkey
+            nat_traversal=yes
+            virtual_private=%v4:10.0.0.0/8,%v4:192.168.0.0/16,%v4:172.16.0.0/12,%v4:25.0.0.0/8,%v4:100.64.0.0/10,%v6:fd00::/8,%v6:fe80::/10
+        '';
+        description = "Options to go in the 'config setup' section of the libreswan ipsec configuration";
+      };
+
+      connections = mkOption {
+        type = types.attrsOf types.lines;
+        default = {};
+        example = {
+          myconnection = ''
+            auto=add
+            left=%defaultroute
+            leftid=@user
+
+            right=my.vpn.com
+
+            ikev2=no
+            ikelifetime=8h
+          '';
+        };
+        description = "A set of connections to define for the libreswan ipsec service";
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.libreswan pkgs.iproute ];
+
+    systemd.services.ipsec = {
+      description = "Internet Key Exchange (IKE) Protocol Daemon for IPsec";
+      path = [
+        "${pkgs.libreswan}"
+        "${pkgs.iproute}"
+        "${pkgs.procps}"
+        "${pkgs.nssTools}"
+        "${pkgs.iptables}"
+        "${pkgs.nettools}"
+      ];
+
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        Restart = "always";
+        EnvironmentFile = "-${pkgs.libreswan}/etc/sysconfig/pluto";
+        ExecStartPre = [
+          "${libexec}/addconn --config ${configFile} --checkconfig"
+          "${libexec}/_stackmanager start"
+          "${ipsec} --checknss"
+          "${ipsec} --checknflog"
+        ];
+        ExecStart = "${libexec}/pluto --config ${configFile} --nofork \$PLUTO_OPTIONS";
+        ExecStop = "${libexec}/whack --shutdown";
+        ExecStopPost = [
+          "${pkgs.iproute}/bin/ip xfrm policy flush"
+          "${pkgs.iproute}/bin/ip xfrm state flush"
+          "${ipsec} --stopnflog"
+        ];
+        ExecReload = "${libexec}/whack --listen";
+      };
+
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/lldpd.nix b/nixpkgs/nixos/modules/services/networking/lldpd.nix
new file mode 100644
index 000000000000..d5de9c45d84b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/lldpd.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lldpd;
+
+in
+
+{
+  options.services.lldpd = {
+    enable = mkEnableOption "Link Layer Discovery Protocol Daemon";
+
+    extraArgs = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "-c" "-k" "-I eth0" ];
+      description = "List of command line parameters for lldpd";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users._lldpd = {
+      description = "lldpd user";
+      group = "_lldpd";
+      home = "/run/lldpd";
+      isSystemUser = true;
+    };
+    users.groups._lldpd = {};
+
+    environment.systemPackages = [ pkgs.lldpd ];
+    systemd.packages = [ pkgs.lldpd ];
+
+    systemd.services.lldpd = {
+      wantedBy = [ "multi-user.target" ];
+      environment.LLDPD_OPTIONS = concatStringsSep " " cfg.extraArgs;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/logmein-hamachi.nix b/nixpkgs/nixos/modules/services/networking/logmein-hamachi.nix
new file mode 100644
index 000000000000..11cbdda2f845
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/logmein-hamachi.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.logmein-hamachi;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.logmein-hamachi.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description =
+        ''
+          Whether to enable LogMeIn Hamachi, a proprietary
+          (closed source) commercial VPN software.
+        '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.logmein-hamachi = {
+      description = "LogMeIn Hamachi Daemon";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.logmein-hamachi}/bin/hamachid";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.logmein-hamachi ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix b/nixpkgs/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
new file mode 100644
index 000000000000..09d357cd2b6e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/magic-wormhole-mailbox-server.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.magic-wormhole-mailbox-server;
+  dataDir = "/var/lib/magic-wormhole-mailbox-server;";
+  python = pkgs.python3.withPackages (py: [ py.magic-wormhole-mailbox-server py.twisted ]);
+in
+{
+  options.services.magic-wormhole-mailbox-server = {
+    enable = mkEnableOption "Enable Magic Wormhole Mailbox Server";
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.magic-wormhole-mailbox-server = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${python}/bin/twistd --nodaemon wormhole-mailbox";
+        WorkingDirectory = dataDir;
+        StateDirectory = baseNameOf dataDir;
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mailpile.nix b/nixpkgs/nixos/modules/services/networking/mailpile.nix
new file mode 100644
index 000000000000..b79ee11d17db
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mailpile.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.mailpile;
+
+  hostname = cfg.hostname;
+  port = cfg.port;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.mailpile = {
+      enable = mkEnableOption "Mailpile the mail client";
+
+      hostname = mkOption {
+        default = "localhost";
+        description = "Listen to this hostname or ip.";
+      };
+      port = mkOption {
+        default = "33411";
+        description = "Listen on this port.";
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.mailpile.enable {
+
+    users.users.mailpile =
+      { uid = config.ids.uids.mailpile;
+        description = "Mailpile user";
+        createHome = true;
+        home = "/var/lib/mailpile";
+      };
+
+    users.groups.mailpile =
+      { gid = config.ids.gids.mailpile;
+      };
+
+    systemd.services.mailpile =
+      {
+        description = "Mailpile server.";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = "mailpile";
+          ExecStart = "${pkgs.mailpile}/bin/mailpile --www ${hostname}:${port} --wait";
+          # mixed - first send SIGINT to main process,
+          # then after 2min send SIGKILL to whole group if neccessary
+          KillMode = "mixed";
+          KillSignal = "SIGINT";  # like Ctrl+C - safe mailpile shutdown
+          TimeoutSec = 120;  # wait 2min untill SIGKILL
+        };
+        environment.MAILPILE_HOME = "/var/lib/mailpile/.local/share/Mailpile";
+      };
+
+    environment.systemPackages = [ pkgs.mailpile ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/matterbridge.nix b/nixpkgs/nixos/modules/services/networking/matterbridge.nix
new file mode 100644
index 000000000000..b8b4f37c84a8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/matterbridge.nix
@@ -0,0 +1,120 @@
+{ options, config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.matterbridge;
+
+  matterbridgeConfToml =
+    if cfg.configPath == null then
+      pkgs.writeText "matterbridge.toml" (cfg.configFile)
+    else
+      cfg.configPath;
+
+in
+
+{
+  options = {
+    services.matterbridge = {
+      enable = mkEnableOption "Matterbridge chat platform bridge";
+
+      configPath = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        example = "/etc/nixos/matterbridge.toml";
+        description = ''
+          The path to the matterbridge configuration file.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.str;
+        example = ''
+          # WARNING: as this file contains credentials, do not use this option!
+          # It is kept only for backwards compatibility, and would cause your
+          # credentials to be in the nix-store, thus with the world-readable
+          # permission bits.
+          # Use services.matterbridge.configPath instead.
+
+          [irc]
+              [irc.freenode]
+              Server="irc.freenode.net:6667"
+              Nick="matterbot"
+
+          [mattermost]
+              [mattermost.work]
+               # Do not prefix it with http:// or https://
+               Server="yourmattermostserver.domain"
+               Team="yourteam"
+               Login="yourlogin"
+               Password="yourpass"
+               PrefixMessagesWithNick=true
+
+          [[gateway]]
+          name="gateway1"
+          enable=true
+              [[gateway.inout]]
+              account="irc.freenode"
+              channel="#testing"
+
+              [[gateway.inout]]
+              account="mattermost.work"
+              channel="off-topic"
+        '';
+        description = ''
+          WARNING: THIS IS INSECURE, as your password will end up in
+          <filename>/nix/store</filename>, thus publicly readable. Use
+          <literal>services.matterbridge.configPath</literal> instead.
+
+          The matterbridge configuration file in the TOML file format.
+        '';
+      };
+      user = mkOption {
+        type = types.str;
+        default = "matterbridge";
+        description = ''
+          User which runs the matterbridge service.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "matterbridge";
+        description = ''
+          Group which runs the matterbridge service.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    warnings = optional options.services.matterbridge.configFile.isDefined
+      "The option services.matterbridge.configFile is insecure and should be replaced with services.matterbridge.configPath";
+
+    users.users = optionalAttrs (cfg.user == "matterbridge")
+      { matterbridge = {
+          group = "matterbridge";
+          isSystemUser = true;
+        };
+      };
+
+    users.groups = optionalAttrs (cfg.group == "matterbridge")
+      { matterbridge = { };
+      };
+
+    systemd.services.matterbridge = {
+      description = "Matterbridge chat platform bridge";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${pkgs.matterbridge}/bin/matterbridge -conf ${matterbridgeConfToml}";
+        Restart = "always";
+        RestartSec = "10";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/minidlna.nix b/nixpkgs/nixos/modules/services/networking/minidlna.nix
new file mode 100644
index 000000000000..c580ba47dad3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/minidlna.nix
@@ -0,0 +1,193 @@
+# Module for MiniDLNA, a simple DLNA server.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.minidlna;
+  port = 8200;
+in
+
+{
+  ###### interface
+  options = {
+    services.minidlna.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description =
+        ''
+          Whether to enable MiniDLNA, a simple DLNA server.  It serves
+          media files such as video and music to DLNA client devices
+          such as televisions and media players.
+        '';
+    };
+
+    services.minidlna.mediaDirs = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "/data/media" "V,/home/alice/video" ];
+      description =
+        ''
+          Directories to be scanned for media files.  The prefixes
+          <literal>A,</literal>, <literal>V,</literal> and
+          <literal>P,</literal> restrict a directory to audio, video
+          or image files.  The directories must be accessible to the
+          <literal>minidlna</literal> user account.
+        '';
+    };
+
+    services.minidlna.friendlyName = mkOption {
+      type = types.str;
+      default = "${config.networking.hostName} MiniDLNA";
+      defaultText = "$HOSTNAME MiniDLNA";
+      example = "rpi3";
+      description =
+        ''
+          Name that the DLNA server presents to clients.
+        '';
+    };
+
+    services.minidlna.rootContainer = mkOption {
+      type = types.str;
+      default = ".";
+      example = "B";
+      description =
+        ''
+          Use a different container as the root of the directory tree presented
+          to clients. The possible values are:
+          - "." - standard container
+          - "B" - "Browse Directory"
+          - "M" - "Music"
+          - "P" - "Pictures"
+          - "V" - "Video"
+          - Or, you can specify the ObjectID of your desired root container
+            (eg. 1$F for Music/Playlists)
+          If you specify "B" and the client device is audio-only then
+          "Music/Folders" will be used as root.
+         '';
+    };
+
+    services.minidlna.loglevel = mkOption {
+      type = types.str;
+      default = "warn";
+      example = "general,artwork,database,inotify,scanner,metadata,http,ssdp,tivo=warn";
+      description =
+        ''
+          Defines the type of messages that should be logged, and down to
+          which level of importance they should be considered.
+
+          The possible types are “artwork”, “database”, “general”, “http”,
+          “inotify”, “metadata”, “scanner”, “ssdp” and “tivo”.
+
+          The levels are “off”, “fatal”, “error”, “warn”, “info” and
+          “debug”, listed here in order of decreasing importance.  “off”
+          turns off logging messages entirely, “fatal” logs the most
+          critical messages only, and so on down to “debug” that logs every
+          single messages.
+
+          The types are comma-separated, followed by an equal sign (‘=’),
+          followed by a level that applies to the preceding types. This can
+          be repeated, separating each of these constructs with a comma.
+
+          Defaults to “general,artwork,database,inotify,scanner,metadata,
+          http,ssdp,tivo=warn” which logs every type of message at the
+          “warn” level.
+        '';
+    };
+
+    services.minidlna.announceInterval = mkOption {
+      type = types.int;
+      default = 895;
+      description =
+        ''
+          The interval between announces (in seconds).
+
+          By default miniDLNA will announce its presence on the network
+          approximately every 15 minutes.
+
+          Many people prefer shorter announce intervals (e.g. 60 seconds)
+          on their home networks, especially when DLNA clients are
+          started on demand.
+        '';
+    };
+
+    services.minidlna.config = mkOption {
+      type = types.lines;
+      description =
+      ''
+        The contents of MiniDLNA's configuration file.
+        When the service is activated, a basic template is generated
+        from the current options opened here.
+      '';
+    };
+
+    services.minidlna.extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        # Not exhaustive example
+        # Support for streaming .jpg and .mp3 files to a TiVo supporting HMO.
+        enable_tivo=no
+        # SSDP notify interval, in seconds.
+        notify_interval=10
+        # maximum number of simultaneous connections
+        # note: many clients open several simultaneous connections while
+        # streaming
+        max_connections=50
+        # set this to yes to allow symlinks that point outside user-defined
+        # media_dirs.
+        wide_links=yes
+      '';
+      description =
+      ''
+        Extra minidlna options not yet opened for configuration here
+        (strict_dlna, model_number, model_name, etc...).  This is appended
+        to the current service already provided.
+      '';
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.minidlna.config =
+      ''
+        port=${toString port}
+        friendly_name=${cfg.friendlyName}
+        db_dir=/var/cache/minidlna
+        log_level=${cfg.loglevel}
+        inotify=yes
+        root_container=${cfg.rootContainer}
+        ${concatMapStrings (dir: ''
+          media_dir=${dir}
+        '') cfg.mediaDirs}
+        notify_interval=${toString cfg.announceInterval}
+        ${cfg.extraConfig}
+      '';
+
+    users.users.minidlna = {
+      description = "MiniDLNA daemon user";
+      group = "minidlna";
+      uid = config.ids.uids.minidlna;
+    };
+
+    users.groups.minidlna.gid = config.ids.gids.minidlna;
+
+    systemd.services.minidlna =
+      { description = "MiniDLNA Server";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+
+        serviceConfig =
+          { User = "minidlna";
+            Group = "minidlna";
+            CacheDirectory = "minidlna";
+            RuntimeDirectory = "minidlna";
+            PIDFile = "/run/minidlna/pid";
+            ExecStart =
+              "${pkgs.minidlna}/sbin/minidlnad -S -P /run/minidlna/pid" +
+              " -f ${pkgs.writeText "minidlna.conf" cfg.config}";
+          };
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/miniupnpd.nix b/nixpkgs/nixos/modules/services/networking/miniupnpd.nix
new file mode 100644
index 000000000000..c095d9948546
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/miniupnpd.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.miniupnpd;
+  configFile = pkgs.writeText "miniupnpd.conf" ''
+    ext_ifname=${cfg.externalInterface}
+    enable_natpmp=${if cfg.natpmp then "yes" else "no"}
+    enable_upnp=${if cfg.upnp then "yes" else "no"}
+
+    ${concatMapStrings (range: ''
+      listening_ip=${range}
+    '') cfg.internalIPs}
+
+    ${cfg.appendConfig}
+  '';
+in
+{
+  options = {
+    services.miniupnpd = {
+      enable = mkEnableOption "MiniUPnP daemon";
+
+      externalInterface = mkOption {
+        type = types.str;
+        description = ''
+          Name of the external interface.
+        '';
+      };
+
+      internalIPs = mkOption {
+        type = types.listOf types.str;
+        example = [ "192.168.1.1/24" "enp1s0" ];
+        description = ''
+          The IP address ranges to listen on.
+        '';
+      };
+
+      natpmp = mkEnableOption "NAT-PMP support";
+
+      upnp = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Whether to enable UPNP support.
+        '';
+      };
+
+      appendConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Configuration lines appended to the MiniUPnP config.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    networking.firewall.extraCommands = ''
+      ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_init.sh -i ${cfg.externalInterface}
+    '';
+
+    networking.firewall.extraStopCommands = ''
+      ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_removeall.sh -i ${cfg.externalInterface}
+    '';
+
+    systemd.services.miniupnpd = {
+      description = "MiniUPnP daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.miniupnpd}/bin/miniupnpd -f ${configFile}";
+        PIDFile = "/run/miniupnpd.pid";
+        Type = "forking";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/miredo.nix b/nixpkgs/nixos/modules/services/networking/miredo.nix
new file mode 100644
index 000000000000..2c8393fb5b41
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/miredo.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.miredo;
+  pidFile = "/run/miredo.pid";
+  miredoConf = pkgs.writeText "miredo.conf" ''
+    InterfaceName ${cfg.interfaceName}
+    ServerAddress ${cfg.serverAddress}
+    ${optionalString (cfg.bindAddress != null) "BindAddress ${cfg.bindAddress}"}
+    ${optionalString (cfg.bindPort != null) "BindPort ${cfg.bindPort}"}
+  '';
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.miredo = {
+
+      enable = mkEnableOption "the Miredo IPv6 tunneling service";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.miredo;
+        defaultText = "pkgs.miredo";
+        description = ''
+          The package to use for the miredo daemon's binary.
+        '';
+      };
+
+      serverAddress = mkOption {
+        default = "teredo.remlab.net";
+        type = types.str;
+        description = ''
+          The hostname or primary IPv4 address of the Teredo server.
+          This setting is required if Miredo runs as a Teredo client.
+          "teredo.remlab.net" is an experimental service for testing only.
+          Please use another server for production and/or large scale deployments.
+        '';
+      };
+
+      interfaceName = mkOption {
+        default = "teredo";
+        type = types.str;
+        description = ''
+          Name of the network tunneling interface.
+        '';
+      };
+
+      bindAddress = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = ''
+          Depending on the local firewall/NAT rules, you might need to force
+          Miredo to use a fixed UDP port and or IPv4 address.
+        '';
+      };
+
+      bindPort = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = ''
+          Depending on the local firewall/NAT rules, you might need to force
+          Miredo to use a fixed UDP port and or IPv4 address.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.miredo = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      description = "Teredo IPv6 Tunneling Daemon";
+      serviceConfig = {
+        Restart = "always";
+        RestartSec = "5s";
+        ExecStart = "${cfg.package}/bin/miredo -c ${miredoConf} -p ${pidFile} -f";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix b/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix
new file mode 100644
index 000000000000..dbc35e2e71c0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mjpg-streamer.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.mjpg-streamer;
+
+in {
+
+  options = {
+
+    services.mjpg-streamer = {
+
+      enable = mkEnableOption "mjpg-streamer webcam streamer";
+
+      inputPlugin = mkOption {
+        type = types.str;
+        default = "input_uvc.so";
+        description = ''
+          Input plugin. See plugins documentation for more information.
+        '';
+      };
+
+      outputPlugin = mkOption {
+        type = types.str;
+        default = "output_http.so -w @www@ -n -p 5050";
+        description = ''
+          Output plugin. <literal>@www@</literal> is substituted for default mjpg-streamer www directory.
+          See plugins documentation for more information.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "mjpg-streamer";
+        description = "mjpg-streamer user name.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "video";
+        description = "mjpg-streamer group name.";
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users = optionalAttrs (cfg.user == "mjpg-streamer") {
+      mjpg-streamer = {
+        uid = config.ids.uids.mjpg-streamer;
+        group = cfg.group;
+      };
+    };
+
+    systemd.services.mjpg-streamer = {
+      description = "mjpg-streamer webcam streamer";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        Restart = "on-failure";
+        RestartSec = 1;
+      };
+
+      script = ''
+        IPLUGIN="${cfg.inputPlugin}"
+        OPLUGIN="${cfg.outputPlugin}"
+        OPLUGIN="''${OPLUGIN//@www@/${pkgs.mjpg-streamer}/share/mjpg-streamer/www}"
+        exec ${pkgs.mjpg-streamer}/bin/mjpg_streamer -i "$IPLUGIN" -o "$OPLUGIN"
+      '';
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/monero.nix b/nixpkgs/nixos/modules/services/networking/monero.nix
new file mode 100644
index 000000000000..97af29978397
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/monero.nix
@@ -0,0 +1,238 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg     = config.services.monero;
+  dataDir = "/var/lib/monero";
+
+  listToConf = option: list:
+    concatMapStrings (value: "${option}=${value}\n") list;
+
+  login = (cfg.rpc.user != null && cfg.rpc.password != null);
+
+  configFile = with cfg; pkgs.writeText "monero.conf" ''
+    log-file=/dev/stdout
+    data-dir=${dataDir}
+
+    ${optionalString mining.enable ''
+      start-mining=${mining.address}
+      mining-threads=${toString mining.threads}
+    ''}
+
+    rpc-bind-ip=${rpc.address}
+    rpc-bind-port=${toString rpc.port}
+    ${optionalString login ''
+      rpc-login=${rpc.user}:${rpc.password}
+    ''}
+    ${optionalString rpc.restricted ''
+      restricted-rpc=1
+    ''}
+
+    limit-rate-up=${toString limits.upload}
+    limit-rate-down=${toString limits.download}
+    max-concurrency=${toString limits.threads}
+    block-sync-size=${toString limits.syncSize}
+
+    ${listToConf "add-peer" extraNodes}
+    ${listToConf "add-priority-node" priorityNodes}
+    ${listToConf "add-exclusive-node" exclusiveNodes}
+
+    ${extraConfig}
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.monero = {
+
+      enable = mkEnableOption "Monero node daemon";
+
+      mining.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to mine moneroj.
+        '';
+      };
+
+      mining.address = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Monero address where to send mining rewards.
+        '';
+      };
+
+      mining.threads = mkOption {
+        type = types.addCheck types.int (x: x>=0);
+        default = 0;
+        description = ''
+          Number of threads used for mining.
+          Set to <literal>0</literal> to use all available.
+        '';
+      };
+
+      rpc.user = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          User name for RPC connections.
+        '';
+      };
+
+      rpc.password = mkOption {
+        type = types.str;
+        default = null;
+        description = ''
+          Password for RPC connections.
+        '';
+      };
+
+      rpc.address = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = ''
+          IP address the RPC server will bind to.
+        '';
+      };
+
+      rpc.port = mkOption {
+        type = types.int;
+        default = 18081;
+        description = ''
+          Port the RPC server will bind to.
+        '';
+      };
+
+      rpc.restricted = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to restrict RPC to view only commands.
+        '';
+      };
+
+      limits.upload = mkOption {
+        type = types.addCheck types.int (x: x>=-1);
+        default = -1;
+        description = ''
+          Limit of the upload rate in kB/s.
+          Set to <literal>-1</literal> to leave unlimited.
+        '';
+      };
+
+      limits.download = mkOption {
+        type = types.addCheck types.int (x: x>=-1);
+        default = -1;
+        description = ''
+          Limit of the download rate in kB/s.
+          Set to <literal>-1</literal> to leave unlimited.
+        '';
+      };
+
+      limits.threads = mkOption {
+        type = types.addCheck types.int (x: x>=0);
+        default = 0;
+        description = ''
+          Maximum number of threads used for a parallel job.
+          Set to <literal>0</literal> to leave unlimited.
+        '';
+      };
+
+      limits.syncSize = mkOption {
+        type = types.addCheck types.int (x: x>=0);
+        default = 0;
+        description = ''
+          Maximum number of blocks to sync at once.
+          Set to <literal>0</literal> for adaptive.
+        '';
+      };
+
+      extraNodes = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = ''
+          List of additional peer IP addresses to add to the local list.
+        '';
+      };
+
+      priorityNodes = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = ''
+          List of peer IP addresses to connect to and
+          attempt to keep the connection open.
+        '';
+      };
+
+      exclusiveNodes = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = ''
+          List of peer IP addresses to connect to *only*.
+          If given the other peer options will be ignored.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra lines to be added verbatim to monerod configuration.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.monero = {
+      uid  = config.ids.uids.monero;
+      description = "Monero daemon user";
+      home = dataDir;
+      createHome = true;
+    };
+
+    users.groups.monero = {
+      gid = config.ids.gids.monero;
+    };
+
+    systemd.services.monero = {
+      description = "monero daemon";
+      after    = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User  = "monero";
+        Group = "monero";
+        ExecStart = "${pkgs.monero}/bin/monerod --config-file=${configFile} --non-interactive";
+        Restart = "always";
+        SuccessExitStatus = [ 0 1 ];
+      };
+    };
+
+    assertions = singleton {
+      assertion = cfg.mining.enable -> cfg.mining.address != "";
+      message   = ''
+       You need a Monero address to receive mining rewards:
+       specify one using option monero.mining.address.
+      '';
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/morty.nix b/nixpkgs/nixos/modules/services/networking/morty.nix
new file mode 100644
index 000000000000..e3a6444c1163
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/morty.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.morty;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.morty = {
+
+      enable = mkEnableOption
+        "Morty proxy server. See https://github.com/asciimoo/morty";
+
+      ipv6 = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Allow IPv6 HTTP requests?";
+        defaultText = "Allow IPv6 HTTP requests.";
+      };
+
+      key = mkOption {
+        type = types.str;
+        default = "";
+        description = "HMAC url validation key (hexadecimal encoded).
+	Leave blank to disable. Without validation key, anyone can
+	submit proxy requests. Leave blank to disable.";
+        defaultText = "No HMAC url validation. Generate with echo -n somevalue | openssl dgst -sha1 -hmac somekey";
+      };
+
+      timeout = mkOption {
+        type = types.int;
+        default = 2;
+        description = "Request timeout in seconds.";
+        defaultText = "A resource now gets 2 seconds to respond.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.morty;
+        defaultText = "pkgs.morty";
+        description = "morty package to use.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 3000;
+        description = "Listing port";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "The address on which the service listens";
+        defaultText = "127.0.0.1 (localhost)";
+      };
+
+    };
+
+  };
+
+  ###### Service definition
+
+  config = mkIf config.services.morty.enable {
+
+    users.users.morty =
+      { description = "Morty user";
+        createHome = true;
+        home = "/var/lib/morty";
+        isSystemUser = true;
+      };
+
+    systemd.services.morty =
+      {
+        description = "Morty sanitizing proxy server.";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = "morty";
+          ExecStart = ''${cfg.package}/bin/morty              \
+	    -listen ${cfg.listenAddress}:${toString cfg.port} \
+	    ${optionalString cfg.ipv6 "-ipv6"}                \
+	    ${optionalString (cfg.key != "") "-key " + cfg.key} \
+	  '';
+        };
+      };
+    environment.systemPackages = [ cfg.package ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mosquitto.nix b/nixpkgs/nixos/modules/services/networking/mosquitto.nix
new file mode 100644
index 000000000000..d2feb93e2b72
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mosquitto.nix
@@ -0,0 +1,231 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfg = config.services.mosquitto;
+
+  listenerConf = optionalString cfg.ssl.enable ''
+    listener ${toString cfg.ssl.port} ${cfg.ssl.host}
+    cafile ${cfg.ssl.cafile}
+    certfile ${cfg.ssl.certfile}
+    keyfile ${cfg.ssl.keyfile}
+  '';
+
+  passwordConf = optionalString cfg.checkPasswords ''
+    password_file ${cfg.dataDir}/passwd
+  '';
+
+  mosquittoConf = pkgs.writeText "mosquitto.conf" ''
+    acl_file ${aclFile}
+    persistence true
+    allow_anonymous ${boolToString cfg.allowAnonymous}
+    bind_address ${cfg.host}
+    port ${toString cfg.port}
+    ${passwordConf}
+    ${listenerConf}
+    ${cfg.extraConf}
+  '';
+
+  userAcl = (concatStringsSep "\n\n" (mapAttrsToList (n: c:
+    "user ${n}\n" + (concatStringsSep "\n" c.acl)) cfg.users
+  ));
+
+  aclFile = pkgs.writeText "mosquitto.acl" ''
+    ${cfg.aclExtraConf}
+    ${userAcl}
+  '';
+
+in
+
+{
+
+  ###### Interface
+
+  options = {
+    services.mosquitto = {
+      enable = mkEnableOption "the MQTT Mosquitto broker";
+
+      host = mkOption {
+        default = "127.0.0.1";
+        example = "0.0.0.0";
+        type = types.str;
+        description = ''
+          Host to listen on without SSL.
+        '';
+      };
+
+      port = mkOption {
+        default = 1883;
+        example = 1883;
+        type = types.int;
+        description = ''
+          Port on which to listen without SSL.
+        '';
+      };
+
+      ssl = {
+        enable = mkEnableOption "SSL listener";
+
+        cafile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          description = "Path to PEM encoded CA certificates.";
+        };
+
+        certfile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          description = "Path to PEM encoded server certificate.";
+        };
+
+        keyfile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          description = "Path to PEM encoded server key.";
+        };
+
+        host = mkOption {
+          default = "0.0.0.0";
+          example = "localhost";
+          type = types.str;
+          description = ''
+            Host to listen on with SSL.
+          '';
+        };
+
+        port = mkOption {
+          default = 8883;
+          example = 8883;
+          type = types.int;
+          description = ''
+            Port on which to listen with SSL.
+          '';
+        };
+      };
+
+      dataDir = mkOption {
+        default = "/var/lib/mosquitto";
+        type = types.path;
+        description = ''
+          The data directory.
+        '';
+      };
+
+      users = mkOption {
+        type = types.attrsOf (types.submodule {
+          options = {
+            password = mkOption {
+              type = with types; uniq (nullOr str);
+              default = null;
+              description = ''
+                Specifies the (clear text) password for the MQTT User.
+              '';
+            };
+
+            hashedPassword = mkOption {
+              type = with types; uniq (nullOr str);
+              default = null;
+              description = ''
+                Specifies the hashed password for the MQTT User.
+                <option>hashedPassword</option> overrides <option>password</option>.
+                To generate hashed password install <literal>mosquitto</literal>
+                package and use <literal>mosquitto_passwd</literal>.
+              '';
+            };
+
+            acl = mkOption {
+              type = types.listOf types.str;
+              example = [ "topic read A/B" "topic A/#" ];
+              description = ''
+                Control client access to topics on the broker.
+              '';
+            };
+          };
+        });
+        example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
+        description = ''
+          A set of users and their passwords and ACLs.
+        '';
+      };
+
+      allowAnonymous = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Allow clients to connect without authentication.
+        '';
+      };
+
+      checkPasswords = mkOption {
+        default = false;
+        example = true;
+        type = types.bool;
+        description = ''
+          Refuse connection when clients provide incorrect passwords.
+        '';
+      };
+
+      extraConf = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra config to append to `mosquitto.conf` file.
+        '';
+      };
+
+      aclExtraConf = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra config to prepend to the ACL file.
+        '';
+      };
+
+    };
+  };
+
+
+  ###### Implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.mosquitto = {
+      description = "Mosquitto MQTT Broker Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        Type = "notify";
+        NotifyAccess = "main";
+        User = "mosquitto";
+        Group = "mosquitto";
+        RuntimeDirectory = "mosquitto";
+        WorkingDirectory = cfg.dataDir;
+        Restart = "on-failure";
+        ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${mosquittoConf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+      preStart = ''
+        rm -f ${cfg.dataDir}/passwd
+        touch ${cfg.dataDir}/passwd
+      '' + concatStringsSep "\n" (
+        mapAttrsToList (n: c:
+          if c.hashedPassword != null then
+            "echo '${n}:${c.hashedPassword}' >> ${cfg.dataDir}/passwd"
+          else optionalString (c.password != null)
+            "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} '${c.password}'"
+        ) cfg.users);
+    };
+
+    users.users.mosquitto = {
+      description = "Mosquitto MQTT Broker Daemon owner";
+      group = "mosquitto";
+      uid = config.ids.uids.mosquitto;
+      home = cfg.dataDir;
+      createHome = true;
+    };
+
+    users.groups.mosquitto.gid = config.ids.gids.mosquitto;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mstpd.nix b/nixpkgs/nixos/modules/services/networking/mstpd.nix
new file mode 100644
index 000000000000..5d1fc4a65427
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mstpd.nix
@@ -0,0 +1,33 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.mstpd;
+in
+with lib;
+{
+  options.services.mstpd = {
+    
+    enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable the multiple spanning tree protocol daemon.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.mstpd ];
+
+    systemd.services.mstpd = {
+      description = "Multiple Spanning Tree Protocol Daemon";
+      wantedBy = [ "network.target" ];
+      unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "@${pkgs.mstpd}/bin/mstpd mstpd";
+        PIDFile = "/run/mstpd.pid";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix b/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix
new file mode 100644
index 000000000000..d896f227b82c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mtprotoproxy.nix
@@ -0,0 +1,110 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.mtprotoproxy;
+
+  configOpts = {
+    PORT = cfg.port;
+    USERS = cfg.users;
+    SECURE_ONLY = cfg.secureOnly;
+  } // lib.optionalAttrs (cfg.adTag != null) { AD_TAG = cfg.adTag; }
+    // cfg.extraConfig;
+
+  convertOption = opt:
+    if isString opt || isInt opt then
+      builtins.toJSON opt
+    else if isBool opt then
+      if opt then "True" else "False"
+    else if isList opt then
+      "[" + concatMapStringsSep "," convertOption opt + "]"
+    else if isAttrs opt then
+      "{" + concatStringsSep "," (mapAttrsToList (name: opt: "${builtins.toJSON name}: ${convertOption opt}") opt) + "}"
+    else
+      throw "Invalid option type";
+
+  configFile = pkgs.writeText "config.py" (concatStringsSep "\n" (mapAttrsToList (name: opt: "${name} = ${convertOption opt}") configOpts));
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.mtprotoproxy = {
+
+      enable = mkEnableOption "mtprotoproxy";
+
+      port = mkOption {
+        type = types.int;
+        default = 3256;
+        description = ''
+          TCP port to accept mtproto connections on.
+        '';
+      };
+
+      users = mkOption {
+        type = types.attrsOf types.str;
+        example = {
+          tg = "00000000000000000000000000000000";
+          tg2 = "0123456789abcdef0123456789abcdef";
+        };
+        description = ''
+          Allowed users and their secrets. A secret is a 32 characters long hex string.
+        '';
+      };
+
+      secureOnly = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Don't allow users to connect in non-secure mode (without random padding).
+        '';
+      };
+
+      adTag = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        # Taken from mtproxyproto's repo.
+        example = "3c09c680b76ee91a4c25ad51f742267d";
+        description = ''
+          Tag for advertising that can be obtained from @MTProxybot.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        example = {
+          STATS_PRINT_PERIOD = 600;
+        };
+        description = ''
+          Extra configuration options for mtprotoproxy.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.mtprotoproxy = {
+      description = "MTProto Proxy Daemon";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.mtprotoproxy}/bin/mtprotoproxy ${configFile}";
+        DynamicUser = true;
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix b/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix
new file mode 100644
index 000000000000..cc98414257ca
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mullvad-vpn.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+let
+  cfg = config.services.mullvad-vpn;
+in
+with lib;
+{
+  options.services.mullvad-vpn.enable = mkOption {
+    type = types.bool;
+    default = false;
+    description = ''
+      This option enables Mullvad VPN daemon.
+    '';
+  };
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "tun" ];
+
+    systemd.services.mullvad-daemon = {
+      description = "Mullvad VPN daemon";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network.target" ];
+      after = [
+        "network-online.target"
+        "NetworkManager.service"
+        "systemd-resolved.service"
+      ];
+      path = [
+        pkgs.iproute
+        # Needed for ping
+        "/run/wrappers"
+      ];
+      serviceConfig = {
+        StartLimitBurst = 5;
+        StartLimitIntervalSec = 20;
+        ExecStart = "${pkgs.mullvad-vpn}/bin/mullvad-daemon -v --disable-stdout-timestamps";
+        Restart = "always";
+        RestartSec = 1;
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.xfix ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/murmur.nix b/nixpkgs/nixos/modules/services/networking/murmur.nix
new file mode 100644
index 000000000000..3054ae1b201f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/murmur.nix
@@ -0,0 +1,270 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.murmur;
+  forking = cfg.logFile != null;
+  configFile = pkgs.writeText "murmurd.ini" ''
+    database=/var/lib/murmur/murmur.sqlite
+    dbDriver=QSQLITE
+
+    autobanAttempts=${toString cfg.autobanAttempts}
+    autobanTimeframe=${toString cfg.autobanTimeframe}
+    autobanTime=${toString cfg.autobanTime}
+
+    logfile=${optionalString (cfg.logFile != null) cfg.logFile}
+    ${optionalString forking "pidfile=/run/murmur/murmurd.pid"}
+
+    welcometext="${cfg.welcometext}"
+    port=${toString cfg.port}
+
+    ${if cfg.hostName == "" then "" else "host="+cfg.hostName}
+    ${if cfg.password == "" then "" else "serverpassword="+cfg.password}
+
+    bandwidth=${toString cfg.bandwidth}
+    users=${toString cfg.users}
+
+    textmessagelength=${toString cfg.textMsgLength}
+    imagemessagelength=${toString cfg.imgMsgLength}
+    allowhtml=${boolToString cfg.allowHtml}
+    logdays=${toString cfg.logDays}
+    bonjour=${boolToString cfg.bonjour}
+    sendversion=${boolToString cfg.sendVersion}
+
+    ${if cfg.registerName     == "" then "" else "registerName="+cfg.registerName}
+    ${if cfg.registerPassword == "" then "" else "registerPassword="+cfg.registerPassword}
+    ${if cfg.registerUrl      == "" then "" else "registerUrl="+cfg.registerUrl}
+    ${if cfg.registerHostname == "" then "" else "registerHostname="+cfg.registerHostname}
+
+    certrequired=${boolToString cfg.clientCertRequired}
+    ${if cfg.sslCert == "" then "" else "sslCert="+cfg.sslCert}
+    ${if cfg.sslKey  == "" then "" else "sslKey="+cfg.sslKey}
+    ${if cfg.sslCa   == "" then "" else "sslCA="+cfg.sslCa}
+
+    ${cfg.extraConfig}
+  '';
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "murmur" "welcome" ] [ "services" "murmur" "welcometext" ])
+    (mkRemovedOptionModule [ "services" "murmur" "pidfile" ] "Hardcoded to /run/murmur/murmurd.pid now")
+  ];
+
+  options = {
+    services.murmur = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If enabled, start the Murmur Mumble server.";
+      };
+
+      autobanAttempts = mkOption {
+        type = types.int;
+        default = 10;
+        description = ''
+          Number of attempts a client is allowed to make in
+          <literal>autobanTimeframe</literal> seconds, before being
+          banned for <literal>autobanTime</literal>.
+        '';
+      };
+
+      autobanTimeframe = mkOption {
+        type = types.int;
+        default = 120;
+        description = ''
+          Timeframe in which a client can connect without being banned
+          for repeated attempts (in seconds).
+        '';
+      };
+
+      autobanTime = mkOption {
+        type = types.int;
+        default = 300;
+        description = "The amount of time an IP ban lasts (in seconds).";
+      };
+
+      logFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/log/murmur/murmurd.log";
+        description = "Path to the log file for Murmur daemon. Empty means log to journald.";
+      };
+
+      welcometext = mkOption {
+        type = types.str;
+        default = "";
+        description = "Welcome message for connected clients.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 64738;
+        description = "Ports to bind to (UDP and TCP).";
+      };
+
+      hostName = mkOption {
+        type = types.str;
+        default = "";
+        description = "Host to bind to. Defaults binding on all addresses.";
+      };
+
+      password = mkOption {
+        type = types.str;
+        default = "";
+        description = "Required password to join server, if specified.";
+      };
+
+      bandwidth = mkOption {
+        type = types.int;
+        default = 72000;
+        description = ''
+          Maximum bandwidth (in bits per second) that clients may send
+          speech at.
+        '';
+      };
+
+      users = mkOption {
+        type = types.int;
+        default = 100;
+        description = "Maximum number of concurrent clients allowed.";
+      };
+
+      textMsgLength = mkOption {
+        type = types.int;
+        default = 5000;
+        description = "Max length of text messages. Set 0 for no limit.";
+      };
+
+      imgMsgLength = mkOption {
+        type = types.int;
+        default = 131072;
+        description = "Max length of image messages. Set 0 for no limit.";
+      };
+
+      allowHtml = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Allow HTML in client messages, comments, and channel
+          descriptions.
+        '';
+      };
+
+      logDays = mkOption {
+        type = types.int;
+        default = 31;
+        description = ''
+          How long to store RPC logs for in the database. Set 0 to
+          keep logs forever, or -1 to disable DB logging.
+        '';
+      };
+
+      bonjour = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable Bonjour auto-discovery, which allows clients over
+          your LAN to automatically discover Murmur servers.
+        '';
+      };
+
+      sendVersion = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Send Murmur version in UDP response.";
+      };
+
+      registerName = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Public server registration name, and also the name of the
+          Root channel. Even if you don't publicly register your
+          server, you probably still want to set this.
+        '';
+      };
+
+      registerPassword = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Public server registry password, used authenticate your
+          server to the registry to prevent impersonation; required for
+          subsequent registry updates.
+        '';
+      };
+
+      registerUrl = mkOption {
+        type = types.str;
+        default = "";
+        description = "URL website for your server.";
+      };
+
+      registerHostname = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          DNS hostname where your server can be reached. This is only
+          needed if you want your server to be accessed by its
+          hostname and not IP - but the name *must* resolve on the
+          internet properly.
+        '';
+      };
+
+      clientCertRequired = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Require clients to authenticate via certificates.";
+      };
+
+      sslCert = mkOption {
+        type = types.str;
+        default = "";
+        description = "Path to your SSL certificate.";
+      };
+
+      sslKey = mkOption {
+        type = types.str;
+        default = "";
+        description = "Path to your SSL key.";
+      };
+
+      sslCa = mkOption {
+        type = types.str;
+        default = "";
+        description = "Path to your SSL CA certificate.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra configuration to put into murmur.ini.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.murmur = {
+      description     = "Murmur Service user";
+      home            = "/var/lib/murmur";
+      createHome      = true;
+      uid             = config.ids.uids.murmur;
+    };
+
+    systemd.services.murmur = {
+      description = "Murmur Chat Service";
+      wantedBy    = [ "multi-user.target" ];
+      after       = [ "network-online.target "];
+
+      serviceConfig = {
+        # murmurd doesn't fork when logging to the console.
+        Type      = if forking then "forking" else "simple";
+        PIDFile   = mkIf forking "/run/murmur/murmurd.pid";
+        RuntimeDirectory = mkIf forking "murmur";
+        User      = "murmur";
+        ExecStart = "${pkgs.murmur}/bin/murmurd -ini ${configFile}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/mxisd.nix b/nixpkgs/nixos/modules/services/networking/mxisd.nix
new file mode 100644
index 000000000000..482d6ff456b1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/mxisd.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  isMa1sd =
+    package:
+    lib.hasPrefix "ma1sd" package.name;
+
+  isMxisd =
+    package:
+    lib.hasPrefix "mxisd" package.name;
+
+  cfg = config.services.mxisd;
+
+  server = optionalAttrs (cfg.server.name != null) { inherit (cfg.server) name; }
+        // optionalAttrs (cfg.server.port != null) { inherit (cfg.server) port; };
+
+  baseConfig = {
+    matrix.domain = cfg.matrix.domain;
+    key.path = "${cfg.dataDir}/signing.key";
+    storage = {
+      provider.sqlite.database = if isMa1sd cfg.package
+                                 then "${cfg.dataDir}/ma1sd.db"
+                                 else "${cfg.dataDir}/mxisd.db";
+    };
+  } // optionalAttrs (server != {}) { inherit server; };
+
+  # merges baseConfig and extraConfig into a single file
+  fullConfig = recursiveUpdate baseConfig cfg.extraConfig;
+
+  configFile = if isMa1sd cfg.package
+               then pkgs.writeText "ma1sd-config.yaml" (builtins.toJSON fullConfig)
+               else pkgs.writeText "mxisd-config.yaml" (builtins.toJSON fullConfig);
+
+in {
+  options = {
+    services.mxisd = {
+      enable = mkEnableOption "matrix federated identity server";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.mxisd;
+        defaultText = "pkgs.mxisd";
+        description = "The mxisd/ma1sd package to use";
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/mxisd";
+        description = "Where data mxisd/ma1sd uses resides";
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        description = "Extra options merged into the mxisd/ma1sd configuration";
+      };
+
+      matrix = {
+
+        domain = mkOption {
+          type = types.str;
+          description = ''
+            the domain of the matrix homeserver
+          '';
+        };
+
+      };
+
+      server = {
+
+        name = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Public hostname of mxisd/ma1sd, if different from the Matrix domain.
+          '';
+        };
+
+        port = mkOption {
+          type = types.nullOr types.int;
+          default = null;
+          description = ''
+            HTTP port to listen on (unencrypted)
+          '';
+        };
+
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.mxisd =
+      {
+        group = "mxisd";
+        home = cfg.dataDir;
+        createHome = true;
+        shell = "${pkgs.bash}/bin/bash";
+        uid = config.ids.uids.mxisd;
+      };
+
+    users.groups.mxisd =
+      {
+        gid = config.ids.gids.mxisd;
+      };
+
+    systemd.services.mxisd = {
+      description = "a federated identity server for the matrix ecosystem";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = let
+        executable = if isMa1sd cfg.package then "ma1sd" else "mxisd";
+      in {
+        Type = "simple";
+        User = "mxisd";
+        Group = "mxisd";
+        ExecStart = "${cfg.package}/bin/${executable} -c ${configFile}";
+        WorkingDirectory = cfg.dataDir;
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/namecoind.nix b/nixpkgs/nixos/modules/services/networking/namecoind.nix
new file mode 100644
index 000000000000..6ca99e1321bd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/namecoind.nix
@@ -0,0 +1,199 @@
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg     = config.services.namecoind;
+  dataDir = "/var/lib/namecoind";
+  useSSL  = (cfg.rpc.certificate != null) && (cfg.rpc.key != null);
+  useRPC  = (cfg.rpc.user != null) && (cfg.rpc.password != null);
+
+  listToConf = option: list:
+    concatMapStrings (value :"${option}=${value}\n") list;
+
+  configFile = pkgs.writeText "namecoin.conf" (''
+    server=1
+    daemon=0
+    txindex=1
+    txprevcache=1
+    walletpath=${cfg.wallet}
+    gen=${if cfg.generate then "1" else "0"}
+    ${listToConf "addnode" cfg.extraNodes}
+    ${listToConf "connect" cfg.trustedNodes}
+  '' + optionalString useRPC ''
+    rpcbind=${cfg.rpc.address}
+    rpcport=${toString cfg.rpc.port}
+    rpcuser=${cfg.rpc.user}
+    rpcpassword=${cfg.rpc.password}
+    ${listToConf "rpcallowip" cfg.rpc.allowFrom}
+  '' + optionalString useSSL ''
+    rpcssl=1
+    rpcsslcertificatechainfile=${cfg.rpc.certificate}
+    rpcsslprivatekeyfile=${cfg.rpc.key}
+    rpcsslciphers=TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH
+  '');
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.namecoind = {
+
+      enable = mkEnableOption "namecoind, Namecoin client";
+
+      wallet = mkOption {
+        type = types.path;
+        default = "${dataDir}/wallet.dat";
+        description = ''
+          Wallet file. The ownership of the file has to be
+          namecoin:namecoin, and the permissions must be 0640.
+        '';
+      };
+
+      generate = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to generate (mine) Namecoins.
+        '';
+      };
+
+      extraNodes = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = ''
+          List of additional peer IP addresses to connect to.
+        '';
+      };
+
+      trustedNodes = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        description = ''
+          List of the only peer IP addresses to connect to. If specified
+          no other connection will be made.
+        '';
+      };
+
+      rpc.user = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          User name for RPC connections.
+        '';
+      };
+
+      rpc.password = mkOption {
+        type = types.str;
+        default = null;
+        description = ''
+          Password for RPC connections.
+        '';
+      };
+
+      rpc.address = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = ''
+          IP address the RPC server will bind to.
+        '';
+      };
+
+      rpc.port = mkOption {
+        type = types.int;
+        default = 8332;
+        description = ''
+          Port the RPC server will bind to.
+        '';
+      };
+
+      rpc.certificate = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/lib/namecoind/server.cert";
+        description = ''
+          Certificate file for securing RPC connections.
+        '';
+      };
+
+      rpc.key = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/lib/namecoind/server.pem";
+        description = ''
+          Key file for securing RPC connections.
+        '';
+      };
+
+
+      rpc.allowFrom = mkOption {
+        type = types.listOf types.str;
+        default = [ "127.0.0.1" ];
+        description = ''
+          List of IP address ranges allowed to use the RPC API.
+          Wiledcards (*) can be user to specify a range.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.namecoin = {
+      uid  = config.ids.uids.namecoin;
+      description = "Namecoin daemon user";
+      home = dataDir;
+      createHome = true;
+    };
+
+    users.groups.namecoin = {
+      gid  = config.ids.gids.namecoin;
+    };
+
+    systemd.services.namecoind = {
+      description = "Namecoind daemon";
+      after    = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User  = "namecoin";
+        Group = "namecoin";
+        ExecStart  = "${pkgs.namecoind}/bin/namecoind -conf=${configFile} -datadir=${dataDir} -printtoconsole";
+        ExecStop   = "${pkgs.coreutils}/bin/kill -KILL $MAINPID";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Nice = "10";
+        PrivateTmp = true;
+        TimeoutStopSec     = "60s";
+        TimeoutStartSec    = "2s";
+        Restart            = "always";
+        StartLimitInterval = "120s";
+        StartLimitBurst    = "5";
+      };
+
+      preStart = optionalString (cfg.wallet != "${dataDir}/wallet.dat")  ''
+        # check wallet file permissions
+        if [ "$(stat --printf '%u' ${cfg.wallet})" != "${toString config.ids.uids.namecoin}" \
+           -o "$(stat --printf '%g' ${cfg.wallet})" != "${toString config.ids.gids.namecoin}" \
+           -o "$(stat --printf '%a' ${cfg.wallet})" != "640" ]; then
+           echo "ERROR: bad ownership or rights on ${cfg.wallet}" >&2
+           exit 1
+        fi
+      '';
+
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nat.nix b/nixpkgs/nixos/modules/services/networking/nat.nix
new file mode 100644
index 000000000000..21ae9eb8b6d4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nat.nix
@@ -0,0 +1,290 @@
+# This module enables Network Address Translation (NAT).
+# XXX: todo: support multiple upstream links
+# see http://yesican.chsoft.biz/lartc/MultihomedLinuxNetworking.html
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.networking.nat;
+
+  dest = if cfg.externalIP == null then "-j MASQUERADE" else "-j SNAT --to-source ${cfg.externalIP}";
+
+  helpers = import ./helpers.nix { inherit config lib; };
+
+  flushNat = ''
+    ${helpers}
+    ip46tables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
+    ip46tables -w -t nat -F nixos-nat-pre 2>/dev/null || true
+    ip46tables -w -t nat -X nixos-nat-pre 2>/dev/null || true
+    ip46tables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
+    ip46tables -w -t nat -F nixos-nat-post 2>/dev/null || true
+    ip46tables -w -t nat -X nixos-nat-post 2>/dev/null || true
+    ip46tables -w -t nat -D OUTPUT -j nixos-nat-out 2>/dev/null || true
+    ip46tables -w -t nat -F nixos-nat-out 2>/dev/null || true
+    ip46tables -w -t nat -X nixos-nat-out 2>/dev/null || true
+
+    ${cfg.extraStopCommands}
+  '';
+
+  setupNat = ''
+    ${helpers}
+    # Create subchain where we store rules
+    ip46tables -w -t nat -N nixos-nat-pre
+    ip46tables -w -t nat -N nixos-nat-post
+    ip46tables -w -t nat -N nixos-nat-out
+
+    # We can't match on incoming interface in POSTROUTING, so
+    # mark packets coming from the internal interfaces.
+    ${concatMapStrings (iface: ''
+      iptables -w -t nat -A nixos-nat-pre \
+        -i '${iface}' -j MARK --set-mark 1
+    '') cfg.internalInterfaces}
+
+    # NAT the marked packets.
+    ${optionalString (cfg.internalInterfaces != []) ''
+      iptables -w -t nat -A nixos-nat-post -m mark --mark 1 \
+        ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
+    ''}
+
+    # NAT packets coming from the internal IPs.
+    ${concatMapStrings (range: ''
+      iptables -w -t nat -A nixos-nat-post \
+        -s '${range}' ${optionalString (cfg.externalInterface != null) "-o ${cfg.externalInterface}"} ${dest}
+    '') cfg.internalIPs}
+
+    # NAT from external ports to internal ports.
+    ${concatMapStrings (fwd: ''
+      iptables -w -t nat -A nixos-nat-pre \
+        -i ${toString cfg.externalInterface} -p ${fwd.proto} \
+        --dport ${builtins.toString fwd.sourcePort} \
+        -j DNAT --to-destination ${fwd.destination}
+
+      ${concatMapStrings (loopbackip:
+        let
+          m                = builtins.match "([0-9.]+):([0-9-]+)" fwd.destination;
+          destinationIP    = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else elemAt m 0;
+          destinationPorts = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else builtins.replaceStrings ["-"] [":"] (elemAt m 1);
+        in ''
+          # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
+          iptables -w -t nat -A nixos-nat-out \
+            -d ${loopbackip} -p ${fwd.proto} \
+            --dport ${builtins.toString fwd.sourcePort} \
+            -j DNAT --to-destination ${fwd.destination}
+
+          # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from other hosts behind NAT
+          iptables -w -t nat -A nixos-nat-pre \
+            -d ${loopbackip} -p ${fwd.proto} \
+            --dport ${builtins.toString fwd.sourcePort} \
+            -j DNAT --to-destination ${fwd.destination}
+
+          iptables -w -t nat -A nixos-nat-post \
+            -d ${destinationIP} -p ${fwd.proto} \
+            --dport ${destinationPorts} \
+            -j SNAT --to-source ${loopbackip}
+        '') fwd.loopbackIPs}
+    '') cfg.forwardPorts}
+
+    ${optionalString (cfg.dmzHost != null) ''
+      iptables -w -t nat -A nixos-nat-pre \
+        -i ${toString cfg.externalInterface} -j DNAT \
+        --to-destination ${cfg.dmzHost}
+    ''}
+
+    ${cfg.extraCommands}
+
+    # Append our chains to the nat tables
+    ip46tables -w -t nat -A PREROUTING -j nixos-nat-pre
+    ip46tables -w -t nat -A POSTROUTING -j nixos-nat-post
+    ip46tables -w -t nat -A OUTPUT -j nixos-nat-out
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    networking.nat.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description =
+        ''
+          Whether to enable Network Address Translation (NAT).
+        '';
+    };
+
+    networking.nat.internalInterfaces = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "eth0" ];
+      description =
+        ''
+          The interfaces for which to perform NAT. Packets coming from
+          these interface and destined for the external interface will
+          be rewritten.
+        '';
+    };
+
+    networking.nat.internalIPs = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "192.168.1.0/24" ];
+      description =
+        ''
+          The IP address ranges for which to perform NAT.  Packets
+          coming from these addresses (on any interface) and destined
+          for the external interface will be rewritten.
+        '';
+    };
+
+    networking.nat.externalInterface = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "eth1";
+      description =
+        ''
+          The name of the external network interface.
+        '';
+    };
+
+    networking.nat.externalIP = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "203.0.113.123";
+      description =
+        ''
+          The public IP address to which packets from the local
+          network are to be rewritten.  If this is left empty, the
+          IP address associated with the external interface will be
+          used.
+        '';
+    };
+
+    networking.nat.forwardPorts = mkOption {
+      type = with types; listOf (submodule {
+        options = {
+          sourcePort = mkOption {
+            type = types.either types.int (types.strMatching "[[:digit:]]+:[[:digit:]]+");
+            example = 8080;
+            description = "Source port of the external interface; to specify a port range, use a string with a colon (e.g. \"60000:61000\")";
+          };
+
+          destination = mkOption {
+            type = types.str;
+            example = "10.0.0.1:80";
+            description = "Forward connection to destination ip:port; to specify a port range, use ip:start-end";
+          };
+
+          proto = mkOption {
+            type = types.str;
+            default = "tcp";
+            example = "udp";
+            description = "Protocol of forwarded connection";
+          };
+
+          loopbackIPs = mkOption {
+            type = types.listOf types.str;
+            default = [];
+            example = literalExample ''[ "55.1.2.3" ]'';
+            description = "Public IPs for NAT reflection; for connections to `loopbackip:sourcePort' from the host itself and from other hosts behind NAT";
+          };
+        };
+      });
+      default = [];
+      example = [ { sourcePort = 8080; destination = "10.0.0.1:80"; proto = "tcp"; } ];
+      description =
+        ''
+          List of forwarded ports from the external interface to
+          internal destinations by using DNAT.
+        '';
+    };
+
+    networking.nat.dmzHost = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "10.0.0.1";
+      description =
+        ''
+          The local IP address to which all traffic that does not match any
+          forwarding rule is forwarded.
+        '';
+    };
+
+    networking.nat.extraCommands = mkOption {
+      type = types.lines;
+      default = "";
+      example = "iptables -A INPUT -p icmp -j ACCEPT";
+      description =
+        ''
+          Additional shell commands executed as part of the nat
+          initialisation script.
+        '';
+    };
+
+    networking.nat.extraStopCommands = mkOption {
+      type = types.lines;
+      default = "";
+      example = "iptables -D INPUT -p icmp -j ACCEPT || true";
+      description =
+        ''
+          Additional shell commands executed as part of the nat
+          teardown script.
+        '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkMerge [
+    { networking.firewall.extraCommands = mkBefore flushNat; }
+    (mkIf config.networking.nat.enable {
+
+      assertions = [
+        { assertion = (cfg.dmzHost != null)    -> (cfg.externalInterface != null);
+          message = "networking.nat.dmzHost requires networking.nat.externalInterface";
+        }
+        { assertion = (cfg.forwardPorts != []) -> (cfg.externalInterface != null);
+          message = "networking.nat.forwardPorts requires networking.nat.externalInterface";
+        }
+      ];
+
+      environment.systemPackages = [ pkgs.iptables ];
+
+      boot = {
+        kernelModules = [ "nf_nat_ftp" ];
+        kernel.sysctl = {
+          "net.ipv4.conf.all.forwarding" = mkOverride 99 true;
+          "net.ipv4.conf.default.forwarding" = mkOverride 99 true;
+        };
+      };
+
+      networking.firewall = mkIf config.networking.firewall.enable {
+        extraCommands = setupNat;
+        extraStopCommands = flushNat;
+      };
+
+      systemd.services = mkIf (!config.networking.firewall.enable) { nat = {
+        description = "Network Address Translation";
+        wantedBy = [ "network.target" ];
+        after = [ "network-pre.target" "systemd-modules-load.service" ];
+        path = [ pkgs.iptables ];
+        unitConfig.ConditionCapability = "CAP_NET_ADMIN";
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        script = flushNat + setupNat;
+
+        postStop = flushNat;
+      }; };
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ndppd.nix b/nixpkgs/nixos/modules/services/networking/ndppd.nix
new file mode 100644
index 000000000000..77e979a8a424
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ndppd.nix
@@ -0,0 +1,189 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ndppd;
+
+  render = s: f: concatStringsSep "\n" (mapAttrsToList f s);
+  prefer = a: b: if a != null then a else b;
+
+  ndppdConf = prefer cfg.configFile (pkgs.writeText "ndppd.conf" ''
+    route-ttl ${toString cfg.routeTTL}
+    ${render cfg.proxies (proxyInterfaceName: proxy: ''
+    proxy ${prefer proxy.interface proxyInterfaceName} {
+      router ${boolToString proxy.router}
+      timeout ${toString proxy.timeout}
+      ttl ${toString proxy.ttl}
+      ${render proxy.rules (ruleNetworkName: rule: ''
+      rule ${prefer rule.network ruleNetworkName} {
+        ${rule.method}${if rule.method == "iface" then " ${rule.interface}" else ""}
+      }'')}
+    }'')}
+  '');
+
+  proxy = types.submodule {
+    options = {
+      interface = mkOption {
+        type = types.nullOr types.str;
+        description = ''
+          Listen for any Neighbor Solicitation messages on this interface,
+          and respond to them according to a set of rules.
+          Defaults to the name of the attrset.
+        '';
+        default = null;
+      };
+      router = mkOption {
+        type = types.bool;
+        description = ''
+          Turns on or off the router flag for Neighbor Advertisement Messages.
+        '';
+        default = true;
+      };
+      timeout = mkOption {
+        type = types.int;
+        description = ''
+          Controls how long to wait for a Neighbor Advertisment Message before
+          invalidating the entry, in milliseconds.
+        '';
+        default = 500;
+      };
+      ttl = mkOption {
+        type = types.int;
+        description = ''
+          Controls how long a valid or invalid entry remains in the cache, in
+          milliseconds.
+        '';
+        default = 30000;
+      };
+      rules = mkOption {
+        type = types.attrsOf rule;
+        description = ''
+          This is a rule that the target address is to match against. If no netmask
+          is provided, /128 is assumed. You may have several rule sections, and the
+          addresses may or may not overlap.
+        '';
+        default = {};
+      };
+    };
+  };
+
+  rule = types.submodule {
+    options = {
+      network = mkOption {
+        type = types.nullOr types.str;
+        description = ''
+          This is the target address is to match against. If no netmask
+          is provided, /128 is assumed. The addresses of serveral rules
+          may or may not overlap.
+          Defaults to the name of the attrset.
+        '';
+        default = null;
+      };
+      method = mkOption {
+        type = types.enum [ "static" "iface" "auto" ];
+        description = ''
+          static: Immediately answer any Neighbor Solicitation Messages
+            (if they match the IP rule).
+          iface: Forward the Neighbor Solicitation Message through the specified
+            interface and only respond if a matching Neighbor Advertisement
+            Message is received.
+          auto: Same as iface, but instead of manually specifying the outgoing
+            interface, check for a matching route in /proc/net/ipv6_route.
+        '';
+        default = "auto";
+      };
+      interface = mkOption {
+        type = types.nullOr types.str;
+        description = "Interface to use when method is iface.";
+        default = null;
+      };
+    };
+  };
+
+in {
+  options.services.ndppd = {
+    enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
+    interface = mkOption {
+      type = types.nullOr types.str;
+      description = ''
+        Interface which is on link-level with router.
+        (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+      '';
+      default = null;
+      example = "eth0";
+    };
+    network = mkOption {
+      type = types.nullOr types.str;
+      description = ''
+        Network that we proxy.
+        (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+      '';
+      default = null;
+      example = "1111::/64";
+    };
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      description = "Path to configuration file.";
+      default = null;
+    };
+    routeTTL = mkOption {
+      type = types.int;
+      description = ''
+        This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route,
+        in milliseconds.
+      '';
+      default = 30000;
+    };
+    proxies = mkOption {
+      type = types.attrsOf proxy;
+      description = ''
+        This sets up a listener, that will listen for any Neighbor Solicitation
+        messages, and respond to them according to a set of rules.
+      '';
+      default = {};
+      example = literalExample ''
+        {
+          eth0.rules."1111::/64" = {};
+        }
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    warnings = mkIf (cfg.interface != null && cfg.network != null) [ ''
+      The options services.ndppd.interface and services.ndppd.network will probably be removed soon,
+      please use services.ndppd.proxies.<interface>.rules.<network> instead.
+    '' ];
+
+    services.ndppd.proxies = mkIf (cfg.interface != null && cfg.network != null) {
+      ${cfg.interface}.rules.${cfg.network} = {};
+    };
+
+    systemd.services.ndppd = {
+      description = "NDP Proxy Daemon";
+      documentation = [ "man:ndppd(1)" "man:ndppd.conf(5)" ];
+      after = [ "network-pre.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}";
+
+        # Sandboxing
+        CapabilityBoundingSet = "CAP_NET_RAW CAP_NET_ADMIN";
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = "AF_INET6 AF_PACKET AF_NETLINK";
+        RestrictNamespaces = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/networkmanager.nix b/nixpkgs/nixos/modules/services/networking/networkmanager.nix
new file mode 100644
index 000000000000..cc789897b29f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/networkmanager.nix
@@ -0,0 +1,491 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.networking.networkmanager;
+
+  basePackages = with pkgs; [
+    crda
+    modemmanager
+    networkmanager
+    networkmanager-fortisslvpn
+    networkmanager-iodine
+    networkmanager-l2tp
+    networkmanager-openconnect
+    networkmanager-openvpn
+    networkmanager-vpnc
+   ] ++ optional (!delegateWireless && !enableIwd) wpa_supplicant;
+
+  delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [];
+
+  enableIwd = cfg.wifi.backend == "iwd";
+
+  configFile = pkgs.writeText "NetworkManager.conf" ''
+    [main]
+    plugins=keyfile
+    dhcp=${cfg.dhcp}
+    dns=${cfg.dns}
+    # If resolvconf is disabled that means that resolv.conf is managed by some other module.
+    rc-manager=${if config.networking.resolvconf.enable then "resolvconf" else "unmanaged"}
+
+    [keyfile]
+    ${optionalString (cfg.unmanaged != [])
+      ''unmanaged-devices=${lib.concatStringsSep ";" cfg.unmanaged}''}
+
+    [logging]
+    level=${cfg.logLevel}
+    audit=${lib.boolToString config.security.audit.enable}
+
+    [connection]
+    ipv6.ip6-privacy=2
+    ethernet.cloned-mac-address=${cfg.ethernet.macAddress}
+    wifi.cloned-mac-address=${cfg.wifi.macAddress}
+    ${optionalString (cfg.wifi.powersave != null)
+      ''wifi.powersave=${if cfg.wifi.powersave then "3" else "2"}''}
+
+    [device]
+    wifi.scan-rand-mac-address=${if cfg.wifi.scanRandMacAddress then "yes" else "no"}
+    wifi.backend=${cfg.wifi.backend}
+
+    ${cfg.extraConfig}
+  '';
+
+  /*
+    [network-manager]
+    Identity=unix-group:networkmanager
+    Action=org.freedesktop.NetworkManager.*
+    ResultAny=yes
+    ResultInactive=no
+    ResultActive=yes
+
+    [modem-manager]
+    Identity=unix-group:networkmanager
+    Action=org.freedesktop.ModemManager*
+    ResultAny=yes
+    ResultInactive=no
+    ResultActive=yes
+  */
+  polkitConf = ''
+    polkit.addRule(function(action, subject) {
+      if (
+        subject.isInGroup("networkmanager")
+        && (action.id.indexOf("org.freedesktop.NetworkManager.") == 0
+            || action.id.indexOf("org.freedesktop.ModemManager")  == 0
+        ))
+          { return polkit.Result.YES; }
+    });
+  '';
+
+  ns = xs: pkgs.writeText "nameservers" (
+    concatStrings (map (s: "nameserver ${s}\n") xs)
+  );
+
+  overrideNameserversScript = pkgs.writeScript "02overridedns" ''
+    #!/bin/sh
+    PATH=${with pkgs; makeBinPath [ gnused gnugrep coreutils ]}
+    tmp=$(mktemp)
+    sed '/nameserver /d' /etc/resolv.conf > $tmp
+    grep 'nameserver ' /etc/resolv.conf | \
+      grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns
+    cat $tmp ${ns cfg.insertNameservers} $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf
+    rm -f $tmp $tmp.ns
+  '';
+
+  dispatcherTypesSubdirMap = {
+    basic = "";
+    pre-up = "pre-up.d/";
+    pre-down = "pre-down.d/";
+  };
+
+  macAddressOpt = mkOption {
+    type = types.either types.str (types.enum ["permanent" "preserve" "random" "stable"]);
+    default = "preserve";
+    example = "00:11:22:33:44:55";
+    description = ''
+      Set the MAC address of the interface.
+      <variablelist>
+        <varlistentry>
+          <term>"XX:XX:XX:XX:XX:XX"</term>
+          <listitem><para>MAC address of the interface</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"permanent"</literal></term>
+          <listitem><para>Use the permanent MAC address of the device</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"preserve"</literal></term>
+          <listitem><para>Don’t change the MAC address of the device upon activation</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"random"</literal></term>
+          <listitem><para>Generate a randomized value upon each connect</para></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>"stable"</literal></term>
+          <listitem><para>Generate a stable, hashed MAC address</para></listitem>
+        </varlistentry>
+      </variablelist>
+    '';
+  };
+
+in {
+
+  meta = {
+    maintainers = teams.freedesktop.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    networking.networkmanager = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use NetworkManager to obtain an IP address and other
+          configuration for all network interfaces that are not manually
+          configured. If enabled, a group <literal>networkmanager</literal>
+          will be created. Add all users that should have permission
+          to change network settings to this group.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Configuration appended to the generated NetworkManager.conf.
+          Refer to
+          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
+            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
+          </link>
+          or
+          <citerefentry>
+            <refentrytitle>NetworkManager.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for more information.
+        '';
+      };
+
+      unmanaged = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          List of interfaces that will not be managed by NetworkManager.
+          Interface name can be specified here, but if you need more fidelity,
+          refer to
+          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec">
+            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html#device-spec
+          </link>
+          or the "Device List Format" Appendix of
+          <citerefentry>
+            <refentrytitle>NetworkManager.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>.
+        '';
+      };
+
+      packages = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        description = ''
+          Extra packages that provide NetworkManager plugins.
+        '';
+        apply = list: basePackages ++ list;
+      };
+
+      dhcp = mkOption {
+        type = types.enum [ "dhclient" "dhcpcd" "internal" ];
+        default = "internal";
+        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 = [];
+        description = ''
+          A list of name servers that should be appended
+          to the ones configured in NetworkManager or received by DHCP.
+        '';
+      };
+
+      insertNameservers = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          A list of name servers that should be inserted before
+          the ones configured in NetworkManager or received by DHCP.
+        '';
+      };
+
+      ethernet.macAddress = macAddressOpt;
+
+      wifi = {
+        macAddress = macAddressOpt;
+
+        backend = mkOption {
+          type = types.enum [ "wpa_supplicant" "iwd" ];
+          default = "wpa_supplicant";
+          description = ''
+            Specify the Wi-Fi backend used for the device.
+            Currently supported are <option>wpa_supplicant</option> or <option>iwd</option> (experimental).
+          '';
+        };
+
+        powersave = mkOption {
+          type = types.nullOr types.bool;
+          default = null;
+          description = ''
+            Whether to enable Wi-Fi power saving.
+          '';
+        };
+
+        scanRandMacAddress = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to enable MAC address randomization of a Wi-Fi device
+            during scanning.
+          '';
+        };
+      };
+
+      dns = mkOption {
+        type = types.enum [ "default" "dnsmasq" "unbound" "systemd-resolved" "none" ];
+        default = "default";
+        description = ''
+          Set the DNS (<literal>resolv.conf</literal>) processing mode.
+          </para>
+          <para>
+          A description of these modes can be found in the main section of
+          <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html">
+            https://developer.gnome.org/NetworkManager/stable/NetworkManager.conf.html
+          </link>
+          or in
+          <citerefentry>
+            <refentrytitle>NetworkManager.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>.
+        '';
+      };
+
+      dispatcherScripts = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            source = mkOption {
+              type = types.path;
+              description = ''
+                Path to the hook script.
+              '';
+            };
+
+            type = mkOption {
+              type = types.enum (attrNames dispatcherTypesSubdirMap);
+              default = "basic";
+              description = ''
+                Dispatcher hook type. Look up the hooks described at
+                <link xlink:href="https://developer.gnome.org/NetworkManager/stable/NetworkManager.html">https://developer.gnome.org/NetworkManager/stable/NetworkManager.html</link>
+                and choose the type depending on the output folder.
+                You should then filter the event type (e.g., "up"/"down") from within your script.
+              '';
+            };
+          };
+        });
+        default = [];
+        example = literalExample ''
+        [ {
+              source = pkgs.writeText "upHook" '''
+
+                if [ "$2" != "up" ]; then
+                    logger "exit: event $2 != up"
+                    exit
+                fi
+
+                # coreutils and iproute are in PATH too
+                logger "Device $DEVICE_IFACE coming up"
+            ''';
+            type = "basic";
+        } ]'';
+        description = ''
+          A list of scripts which will be executed in response to  network  events.
+        '';
+      };
+
+      enableStrongSwan = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the StrongSwan plugin.
+          </para><para>
+          If you enable this option the
+          <literal>networkmanager_strongswan</literal> plugin will be added to
+          the <option>networking.networkmanager.packages</option> option
+          so you don't need to to that yourself.
+        '';
+      };
+    };
+  };
+
+  imports = [
+    (mkRenamedOptionModule [ "networking" "networkmanager" "useDnsmasq" ] [ "networking" "networkmanager" "dns" ])
+    (mkRemovedOptionModule ["networking" "networkmanager" "dynamicHosts"] ''
+      This option was removed because allowing (multiple) regular users to
+      override host entries affecting the whole system opens up a huge attack
+      vector. There seem to be very rare cases where this might be useful.
+      Consider setting system-wide host entries using networking.hosts, provide
+      them via the DNS server in your network, or use environment.etc
+      to add a file into /etc/NetworkManager/dnsmasq.d reconfiguring hostsdir.
+    '')
+  ];
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [];
+        message = ''
+          You can not use networking.networkmanager with networking.wireless.
+          Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager.
+        '';
+      }
+    ];
+
+    environment.etc = with pkgs; {
+      "NetworkManager/NetworkManager.conf".source = configFile;
+
+      "NetworkManager/VPN/nm-openvpn-service.name".source =
+        "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name";
+
+      "NetworkManager/VPN/nm-vpnc-service.name".source =
+        "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name";
+
+      "NetworkManager/VPN/nm-openconnect-service.name".source =
+        "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name";
+
+      "NetworkManager/VPN/nm-fortisslvpn-service.name".source =
+        "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name";
+
+      "NetworkManager/VPN/nm-l2tp-service.name".source =
+        "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name";
+
+      "NetworkManager/VPN/nm-iodine-service.name".source =
+        "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name";
+      }
+      // optionalAttrs (cfg.appendNameservers != [] || cfg.insertNameservers != [])
+         {
+           "NetworkManager/dispatcher.d/02overridedns".source = overrideNameserversScript;
+         }
+      // optionalAttrs cfg.enableStrongSwan
+         {
+           "NetworkManager/VPN/nm-strongswan-service.name".source =
+             "${pkgs.networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
+         }
+      // listToAttrs (lib.imap1 (i: s:
+         {
+            name = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
+            value = { mode = "0544"; inherit (s) source; };
+         }) cfg.dispatcherScripts);
+
+    environment.systemPackages = cfg.packages;
+
+    users.groups = {
+      networkmanager.gid = config.ids.gids.networkmanager;
+      nm-openvpn.gid = config.ids.gids.nm-openvpn;
+    };
+
+    users.users = {
+      nm-openvpn = {
+        uid = config.ids.uids.nm-openvpn;
+        extraGroups = [ "networkmanager" ];
+      };
+      nm-iodine = {
+        isSystemUser = true;
+        group = "networkmanager";
+      };
+    };
+
+    systemd.packages = cfg.packages;
+
+    systemd.tmpfiles.rules = [
+      "d /etc/NetworkManager/system-connections 0700 root root -"
+      "d /etc/ipsec.d 0700 root root -"
+      "d /var/lib/NetworkManager-fortisslvpn 0700 root root -"
+
+      "d /var/lib/dhclient 0755 root root -"
+      "d /var/lib/misc 0755 root root -" # for dnsmasq.leases
+    ];
+
+    systemd.services.NetworkManager = {
+      wantedBy = [ "network.target" ];
+      restartTriggers = [ configFile ];
+
+      aliases = [ "dbus-org.freedesktop.NetworkManager.service" ];
+
+      serviceConfig = {
+        StateDirectory = "NetworkManager";
+        StateDirectoryMode = 755; # not sure if this really needs to be 755
+      };
+    };
+
+    systemd.services.NetworkManager-wait-online = {
+      wantedBy = [ "network-online.target" ];
+    };
+
+    systemd.services.ModemManager.aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
+
+    # override unit as recommended by upstream - see https://github.com/NixOS/nixpkgs/issues/88089
+    # TODO: keep an eye on modem-manager releases as this will eventually be added to the upstream unit
+    systemd.services.ModemManager.serviceConfig.ExecStart = [
+      ""
+      "${pkgs.modemmanager}/sbin/ModemManager --filter-policy=STRICT"
+    ];
+
+    systemd.services.NetworkManager-dispatcher = {
+      wantedBy = [ "network.target" ];
+      restartTriggers = [ configFile ];
+
+      # useful binaries for user-specified hooks
+      path = [ pkgs.iproute pkgs.utillinux pkgs.coreutils ];
+      aliases = [ "dbus-org.freedesktop.nm-dispatcher.service" ];
+    };
+
+    # Turn off NixOS' network management when networking is managed entirely by NetworkManager
+    networking = mkMerge [
+      (mkIf (!delegateWireless) {
+        useDHCP = false;
+      })
+
+      (mkIf cfg.enableStrongSwan {
+        networkmanager.packages = [ pkgs.networkmanager_strongswan ];
+      })
+
+      (mkIf enableIwd {
+        wireless.iwd.enable = true;
+      })
+    ];
+
+    security.polkit.extraConfig = polkitConf;
+
+    services.dbus.packages = cfg.packages
+      ++ optional cfg.enableStrongSwan pkgs.strongswanNM
+      ++ optional (cfg.dns == "dnsmasq") pkgs.dnsmasq;
+
+    services.udev.packages = cfg.packages;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nftables.nix b/nixpkgs/nixos/modules/services/networking/nftables.nix
new file mode 100644
index 000000000000..ec9d9753cfe2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nftables.nix
@@ -0,0 +1,136 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+  cfg = config.networking.nftables;
+in
+{
+  ###### interface
+
+  options = {
+    networking.nftables.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description =
+        ''
+          Whether to enable nftables.  nftables is a Linux-based packet
+          filtering framework intended to replace frameworks like iptables.
+
+          This conflicts with the standard networking firewall, so make sure to
+          disable it before using nftables.
+
+          Note that if you have Docker enabled you will not be able to use
+          nftables without intervention. Docker uses iptables internally to
+          setup NAT for containers. This module disables the ip_tables kernel
+          module, however Docker automatically loads the module. Please see [1]
+          for more information.
+
+          There are other programs that use iptables internally too, such as
+          libvirt.
+
+          [1]: https://github.com/NixOS/nixpkgs/issues/24318#issuecomment-289216273
+        '';
+    };
+    networking.nftables.ruleset = mkOption {
+      type = types.lines;
+      example = ''
+        # Check out https://wiki.nftables.org/ for better documentation.
+        # Table for both IPv4 and IPv6.
+        table inet filter {
+          # Block all incomming connections traffic except SSH and "ping".
+          chain input {
+            type filter hook input priority 0;
+
+            # accept any localhost traffic
+            iifname lo accept
+
+            # accept traffic originated from us
+            ct state {established, related} accept
+
+            # ICMP
+            # routers may also want: mld-listener-query, nd-router-solicit
+            ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
+            ip protocol icmp icmp type { destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept
+
+            # allow "ping"
+            ip6 nexthdr icmpv6 icmpv6 type echo-request accept
+            ip protocol icmp icmp type echo-request accept
+
+            # accept SSH connections (required for a server)
+            tcp dport 22 accept
+
+            # count and drop any other traffic
+            counter drop
+          }
+
+          # Allow all outgoing connections.
+          chain output {
+            type filter hook output priority 0;
+            accept
+          }
+
+          chain forward {
+            type filter hook forward priority 0;
+            accept
+          }
+        }
+      '';
+      description =
+        ''
+          The ruleset to be used with nftables.  Should be in a format that
+          can be loaded using "/bin/nft -f".  The ruleset is updated atomically.
+        '';
+    };
+    networking.nftables.rulesetFile = mkOption {
+      type = types.path;
+      default = pkgs.writeTextFile {
+        name = "nftables-rules";
+        text = cfg.ruleset;
+      };
+      description =
+        ''
+          The ruleset file to be used with nftables.  Should be in a format that
+          can be loaded using "nft -f".  The ruleset is updated atomically.
+        '';
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    assertions = [{
+      assertion = config.networking.firewall.enable == false;
+      message = "You can not use nftables with services.networking.firewall.";
+    }];
+    boot.blacklistedKernelModules = [ "ip_tables" ];
+    environment.systemPackages = [ pkgs.nftables ];
+    systemd.services.nftables = {
+      description = "nftables firewall";
+      before = [ "network-pre.target" ];
+      wants = [ "network-pre.target" ];
+      wantedBy = [ "multi-user.target" ];
+      reloadIfChanged = true;
+      serviceConfig = let
+        rulesScript = pkgs.writeScript "nftables-rules" ''
+          #! ${pkgs.nftables}/bin/nft -f
+          flush ruleset
+          include "${cfg.rulesetFile}"
+        '';
+        checkScript = pkgs.writeScript "nftables-check" ''
+          #! ${pkgs.runtimeShell} -e
+          if $(${pkgs.kmod}/bin/lsmod | grep -q ip_tables); then
+            echo "Unload ip_tables before using nftables!" 1>&2
+            exit 1
+          else
+            ${rulesScript}
+          fi
+        '';
+      in {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = checkScript;
+        ExecReload = checkScript;
+        ExecStop = "${pkgs.nftables}/bin/nft flush ruleset";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix
new file mode 100644
index 000000000000..6523f4b8b9e0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/backend-params-submodule.nix
@@ -0,0 +1,131 @@
+{ lib, ...}:
+{ options = {
+    proto = lib.mkOption {
+      type        = lib.types.enum [ "h2" "http/1.1" ];
+      default     = "http/1.1";
+      description = ''
+        This option configures the protocol the backend server expects
+        to use.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+        for more detail.
+      '';
+    };
+
+    tls = lib.mkOption {
+      type        = lib.types.bool;
+      default     = false;
+      description = ''
+        This option determines whether nghttpx will negotiate its
+        connection with a backend server using TLS or not. The burden
+        is on the backend server to provide the TLS certificate!
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+        for more detail.
+      '';
+    };
+
+    sni = lib.mkOption {
+      type        = lib.types.nullOr lib.types.str;
+      default     = null;
+      description = ''
+        Override the TLS SNI field value. This value (in nghttpx)
+        defaults to the host value of the backend configuration.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+        for more detail.
+      '';
+    };
+
+    fall = lib.mkOption {
+      type        = lib.types.int;
+      default     = 0;
+      description = ''
+        If nghttpx cannot connect to the backend N times in a row, the
+        backend is assumed to be offline and is excluded from load
+        balancing. If N is 0 the backend is never excluded from load
+        balancing.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+        for more detail.
+      '';
+    };
+
+    rise = lib.mkOption {
+      type        = lib.types.int;
+      default     = 0;
+      description = ''
+        If the backend is excluded from load balancing, nghttpx will
+        periodically attempt to make a connection to the backend. If
+        the connection is successful N times in a row the backend is
+        re-included in load balancing. If N is 0 a backend is never
+        reconsidered for load balancing once it falls.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+        for more detail.
+      '';
+    };
+
+    affinity = lib.mkOption {
+      type        = lib.types.enum [ "ip" "none" ];
+      default     = "none";
+      description = ''
+        If "ip" is given, client IP based session affinity is
+        enabled. If "none" is given, session affinity is disabled.
+
+        Session affinity is enabled (by nghttpx) per-backend
+        pattern. If at least one backend has a non-"none" affinity,
+        then session affinity is enabled for all backend servers
+        sharing the same pattern.
+
+        It is advised to set affinity on all backends explicitly if
+        session affinity is desired. The session affinity may break if
+        one of the backend gets unreachable, or backend settings are
+        reloaded or replaced by API.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+        for more detail.
+      '';
+    };
+
+    dns = lib.mkOption {
+      type        = lib.types.bool;
+      default     = false;
+      description = ''
+        Name resolution of a backends host name is done at start up,
+        or configuration reload. If "dns" is true, name resolution
+        takes place dynamically.
+
+        This is useful if a backends address changes frequently. If
+        "dns" is true, name resolution of a backend's host name at
+        start up, or configuration reload is skipped.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+        for more detail.
+      '';
+    };
+
+    redirect-if-not-tls = lib.mkOption {
+      type        = lib.types.bool;
+      default     = false;
+      description = ''
+        If true, a backend match requires the frontend connection be
+        TLS encrypted. If it is not, nghttpx responds to the request
+        with a 308 status code and https URI the client should use
+        instead in the Location header.
+
+        The port number in the redirect URI is 443 by default and can
+        be changed using 'services.nghttpx.redirect-https-port'
+        option.
+
+        If at least one backend has "redirect-if-not-tls" set to true,
+        this feature is enabled for all backend servers with the same
+        pattern. It is advised to set "redirect-if-no-tls" parameter
+        to all backends explicitly if this feature is desired.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+        for more detail.
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/backend-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/backend-submodule.nix
new file mode 100644
index 000000000000..eb559e926e76
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/backend-submodule.nix
@@ -0,0 +1,50 @@
+{ lib, ... }:
+{ options = {
+    server = lib.mkOption {
+      type =
+        lib.types.either
+          (lib.types.submodule (import ./server-options.nix))
+          (lib.types.path);
+      example = {
+        host = "127.0.0.1";
+        port = 8888;
+      };
+      default = {
+        host = "127.0.0.1";
+        port = 80;
+      };
+      description = ''
+        Backend server location specified as either a host:port pair
+        or a unix domain docket.
+      '';
+    };
+
+    patterns = lib.mkOption {
+      type    = lib.types.listOf lib.types.str;
+      example = [
+        "*.host.net/v1/"
+        "host.org/v2/mypath"
+        "/somepath"
+      ];
+      default     = [];
+      description = ''
+        List of nghttpx backend patterns.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-b
+        for more information on the pattern syntax and nghttpxs behavior.
+      '';
+    };
+
+    params = lib.mkOption {
+      type    = lib.types.nullOr (lib.types.submodule (import ./backend-params-submodule.nix));
+      example = {
+        proto = "h2";
+        tls   = true;
+      };
+      default     = null;
+      description = ''
+        Parameters to configure a backend.
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/default.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/default.nix
new file mode 100644
index 000000000000..881a2670f5db
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/default.nix
@@ -0,0 +1,118 @@
+{config, pkgs, lib, ...}:
+let
+  cfg = config.services.nghttpx;
+
+  # renderHost :: Either ServerOptions Path -> String
+  renderHost = server:
+    if builtins.isString server
+    then "unix://${server}"
+    else "${server.host},${builtins.toString server.port}";
+
+  # Filter out submodule parameters whose value is null or false or is
+  # the key _module.
+  #
+  # filterParams :: ParamsSubmodule -> ParamsSubmodule
+  filterParams = p:
+    lib.filterAttrs
+      (n: v: ("_module" != n) && (null != v) && (false != v))
+      (lib.optionalAttrs (null != p) p);
+
+  # renderBackend :: BackendSubmodule -> String
+  renderBackend = backend:
+    let
+      host = renderHost backend.server;
+      patterns = lib.concatStringsSep ":" backend.patterns;
+
+      # Render a set of backend parameters, this is somewhat
+      # complicated because nghttpx backend patterns can be entirely
+      # omitted and the params may be given as a mixed collection of
+      # 'key=val' pairs or atoms (e.g: 'proto=h2;tls')
+      params =
+        lib.mapAttrsToList
+          (n: v:
+            if builtins.isBool v
+            then n
+            else if builtins.isString v
+            then "${n}=${v}"
+            else "${n}=${builtins.toString v}")
+          (filterParams backend.params);
+
+      # NB: params are delimited by a ";" which is the same delimiter
+      # to separate the host;[pattern];[params] sections of a backend
+      sections =
+        builtins.filter (e: "" != e) ([
+          host
+          patterns
+        ]++params);
+      formattedSections = lib.concatStringsSep ";" sections;
+    in
+      "backend=${formattedSections}";
+
+  # renderFrontend :: FrontendSubmodule -> String
+  renderFrontend = frontend:
+    let
+      host   = renderHost frontend.server;
+      params0 =
+        lib.mapAttrsToList
+          (n: v: if builtins.isBool v then n else v)
+          (filterParams frontend.params);
+
+      # NB: nghttpx doesn't accept "tls", you must omit "no-tls" for
+      # the default behavior of turning on TLS.
+      params1 = lib.remove "tls" params0;
+          
+      sections          = [ host] ++ params1;
+      formattedSections = lib.concatStringsSep ";" sections;
+    in
+      "frontend=${formattedSections}";
+
+  configurationFile = pkgs.writeText "nghttpx.conf" ''
+    ${lib.optionalString (null != cfg.tls) ("private-key-file="+cfg.tls.key)}
+    ${lib.optionalString (null != cfg.tls) ("certificate-file="+cfg.tls.crt)}
+
+    user=nghttpx
+
+    ${lib.concatMapStringsSep "\n" renderFrontend cfg.frontends}
+    ${lib.concatMapStringsSep "\n" renderBackend  cfg.backends}
+
+    backlog=${builtins.toString cfg.backlog}
+    backend-address-family=${cfg.backend-address-family}
+
+    workers=${builtins.toString cfg.workers}
+    rlimit-nofile=${builtins.toString cfg.rlimit-nofile}
+
+    ${lib.optionalString cfg.single-thread "single-thread=yes"}
+    ${lib.optionalString cfg.single-process "single-process=yes"}
+
+    ${cfg.extraConfig}
+  '';
+in
+{ imports = [
+    ./nghttpx-options.nix
+  ];
+  
+  config = lib.mkIf cfg.enable {
+
+    users.groups.nghttpx = { };
+    users.users.nghttpx = {
+      group = config.users.groups.nghttpx.name;
+      isSystemUser = true;
+    };
+      
+
+    systemd.services = {
+      nghttpx = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network.target" ];
+        script   = ''
+          ${pkgs.nghttp2}/bin/nghttpx --conf=${configurationFile}
+        '';
+
+        serviceConfig = {
+          Restart    = "on-failure";
+          RestartSec = 60;
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix
new file mode 100644
index 000000000000..33c8572bd14f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-params-submodule.nix
@@ -0,0 +1,64 @@
+{ lib, ...}:
+{ options = {
+    tls = lib.mkOption {
+      type        = lib.types.enum [ "tls" "no-tls" ];
+      default     = "tls";
+      description = ''
+        Enable or disable TLS. If true (enabled) the key and
+        certificate must be configured for nghttpx.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+        for more detail.
+      '';
+    };
+
+    sni-fwd = lib.mkOption {
+      type    = lib.types.bool;
+      default = false;
+      description = ''
+        When performing a match to select a backend server, SNI host
+        name received from the client is used instead of the request
+        host. See --backend option about the pattern match.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+        for more detail.
+      '';
+    };
+
+    api = lib.mkOption {
+      type        = lib.types.bool;
+      default     = false;
+      description = ''
+        Enable API access for this frontend. This enables you to
+        dynamically modify nghttpx at run-time therefore this feature
+        is disabled by default and should be turned on with care.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+        for more detail.
+      '';
+    };
+
+    healthmon = lib.mkOption {
+      type        = lib.types.bool;
+      default     = false;
+      description = ''
+        Make this frontend a health monitor endpoint. Any request
+        received on this frontend is responded to with a 200 OK.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+        for more detail.
+      '';
+    };
+
+    proxyproto = lib.mkOption {
+      type        = lib.types.bool;
+      default     = false;
+      description = ''
+        Accept PROXY protocol version 1 on frontend connection.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-f
+        for more detail.
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-submodule.nix
new file mode 100644
index 000000000000..887ef4502131
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/frontend-submodule.nix
@@ -0,0 +1,36 @@
+{ lib, ... }:
+{ options = {
+    server = lib.mkOption {
+      type =
+        lib.types.either
+          (lib.types.submodule (import ./server-options.nix))
+          (lib.types.path);
+      example = {
+        host = "127.0.0.1";
+        port = 8888;
+      };
+      default = {
+        host = "127.0.0.1";
+        port = 80;
+      };
+      description = ''
+        Frontend server interface binding specification as either a
+        host:port pair or a unix domain docket.
+
+        NB: a host of "*" listens on all interfaces and includes IPv6
+        addresses.
+      '';
+    };
+
+    params = lib.mkOption {
+      type    = lib.types.nullOr (lib.types.submodule (import ./frontend-params-submodule.nix));
+      example = {
+        tls   = "tls";
+      };
+      default     = null;
+      description = ''
+        Parameters to configure a backend.
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
new file mode 100644
index 000000000000..51f1d081b971
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/nghttpx-options.nix
@@ -0,0 +1,142 @@
+{ lib, ... }:
+{ options.services.nghttpx = {
+    enable = lib.mkEnableOption "nghttpx";
+
+    frontends = lib.mkOption {
+      type        = lib.types.listOf (lib.types.submodule (import ./frontend-submodule.nix));
+      description = ''
+        A list of frontend listener specifications.
+      '';
+      example = [
+        { server = {
+            host = "*";
+            port = 80;
+          };
+
+          params = {
+            tls = "no-tls";
+          };
+        }
+      ];
+    };
+
+    backends  = lib.mkOption {
+      type = lib.types.listOf (lib.types.submodule (import ./backend-submodule.nix));
+      description = ''
+        A list of backend specifications.
+      '';
+      example = [
+        { server = {
+            host = "172.16.0.22";
+            port = 8443;
+          };
+          patterns = [ "/" ];
+          params   = {
+            proto               = "http/1.1";
+            redirect-if-not-tls = true;
+          };
+        }
+      ];
+    };
+
+    tls = lib.mkOption {
+      type        = lib.types.nullOr (lib.types.submodule (import ./tls-submodule.nix));
+      default     = null;
+      description = ''
+        TLS certificate and key paths. Note that this does not enable
+        TLS for a frontend listener, to do so, a frontend
+        specification must set <literal>params.tls</literal> to true.
+      '';
+      example = {
+        key = "/etc/ssl/keys/server.key";
+        crt = "/etc/ssl/certs/server.crt";
+      };
+    };
+
+    extraConfig = lib.mkOption {
+      type        = lib.types.lines;
+      default     = "";
+      description = ''
+        Extra configuration options to be appended to the generated
+        configuration file.
+      '';
+    };
+
+    single-process = lib.mkOption {
+      type        = lib.types.bool;
+      default     = false;
+      description = ''
+        Run this program in a single process mode for debugging
+        purpose. Without this option, nghttpx creates at least 2
+        processes: master and worker processes. If this option is
+        used, master and worker are unified into a single
+        process. nghttpx still spawns additional process if neverbleed
+        is used. In the single process mode, the signal handling
+        feature is disabled.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--single-process
+      '';
+    };
+
+    backlog = lib.mkOption {
+      type        = lib.types.int;
+      default     = 65536;
+      description = ''
+        Listen backlog size.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--backlog
+      '';
+    };
+
+    backend-address-family = lib.mkOption {
+      type = lib.types.enum [
+        "auto"
+        "IPv4"
+        "IPv6"
+      ];
+      default = "auto";
+      description = ''
+        Specify address family of backend connections. If "auto" is
+        given, both IPv4 and IPv6 are considered. If "IPv4" is given,
+        only IPv4 address is considered. If "IPv6" is given, only IPv6
+        address is considered.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--backend-address-family
+      '';
+    };
+
+    workers = lib.mkOption {
+      type        = lib.types.int;
+      default     = 1;
+      description = ''
+        Set the number of worker threads.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx-n
+      '';
+    };
+
+    single-thread = lib.mkOption {
+      type        = lib.types.bool;
+      default     = false;
+      description = ''
+        Run everything in one thread inside the worker process. This
+        feature is provided for better debugging experience, or for
+        the platforms which lack thread support. If threading is
+        disabled, this option is always enabled.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--single-thread
+      '';
+    };
+
+    rlimit-nofile = lib.mkOption {
+      type        = lib.types.int;
+      default     = 0;
+      description = ''
+        Set maximum number of open files (RLIMIT_NOFILE) to &lt;N&gt;. If 0
+        is given, nghttpx does not set the limit.
+
+        Please see https://nghttp2.org/documentation/nghttpx.1.html#cmdoption-nghttpx--rlimit-nofile
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/server-options.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/server-options.nix
new file mode 100644
index 000000000000..ef23bfd793c5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/server-options.nix
@@ -0,0 +1,18 @@
+{ lib, ... }:
+{ options = {
+    host = lib.mkOption {
+      type        = lib.types.str;
+      example     = "127.0.0.1";
+      description = ''
+        Server host address.
+      '';
+    };
+    port = lib.mkOption {
+      type        = lib.types.int;
+      example     = 5088;
+      description = ''
+        Server host port.
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nghttpx/tls-submodule.nix b/nixpkgs/nixos/modules/services/networking/nghttpx/tls-submodule.nix
new file mode 100644
index 000000000000..8f3cdaae2c81
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nghttpx/tls-submodule.nix
@@ -0,0 +1,21 @@
+{lib, ...}:
+{ options = {
+    key = lib.mkOption {
+      type        = lib.types.str;
+      example     = "/etc/ssl/keys/mykeyfile.key";
+      default     = "/etc/ssl/keys/server.key";
+      description = ''
+        Path to the TLS key file.
+      '';
+    };
+
+    crt = lib.mkOption {
+      type        = lib.types.str;
+      example     = "/etc/ssl/certs/mycert.crt";
+      default     = "/etc/ssl/certs/server.crt";
+      description = ''
+        Path to the TLS certificate file.
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ngircd.nix b/nixpkgs/nixos/modules/services/networking/ngircd.nix
new file mode 100644
index 000000000000..4b2fa7795922
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ngircd.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ngircd;
+
+  configFile = pkgs.stdenv.mkDerivation {
+    name = "ngircd.conf";
+
+    text = cfg.config;
+
+    preferLocalBuild = true;
+
+    buildCommand = ''
+      echo -n "$text" > $out
+      ${cfg.package}/sbin/ngircd --config $out --configtest
+    '';
+  };
+in {
+  options = {
+    services.ngircd = {
+      enable = mkEnableOption "the ngircd IRC server";
+
+      config = mkOption {
+        description = "The ngircd configuration (see ngircd.conf(5)).";
+
+        type = types.lines;
+      };
+
+      package = mkOption {
+        description = "The ngircd package.";
+
+        type = types.package;
+
+        default = pkgs.ngircd;
+        defaultText = "pkgs.ngircd";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    #!!! TODO: Use ExecReload (see https://github.com/NixOS/nixpkgs/issues/1988)
+    systemd.services.ngircd = {
+      description = "The ngircd IRC server";
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig.ExecStart = "${cfg.package}/sbin/ngircd --config ${configFile} --nodaemon";
+
+      serviceConfig.User = "ngircd";
+    };
+
+    users.users.ngircd = {
+      uid = config.ids.uids.ngircd;
+      description = "ngircd user.";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nix-serve.nix b/nixpkgs/nixos/modules/services/networking/nix-serve.nix
new file mode 100644
index 000000000000..347d87b3f385
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nix-serve.nix
@@ -0,0 +1,81 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nix-serve;
+in
+{
+  options = {
+    services.nix-serve = {
+      enable = mkEnableOption "nix-serve, the standalone Nix binary cache server";
+
+      port = mkOption {
+        type = types.int;
+        default = 5000;
+        description = ''
+          Port number where nix-serve will listen on.
+        '';
+      };
+
+      bindAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = ''
+          IP address where nix-serve will bind its listening socket.
+        '';
+      };
+
+      secretKeyFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The path to the file used for signing derivation data.
+          Generate with:
+
+          ```
+          nix-store --generate-binary-cache-key key-name secret-key-file public-key-file
+          ```
+
+          Make sure user `nix-serve` has read access to the private key file.
+
+          For more details see <citerefentry><refentrytitle>nix-store</refentrytitle><manvolnum>1</manvolnum></citerefentry>.
+        '';
+      };
+
+      extraParams = mkOption {
+        type = types.separatedString " ";
+        default = "";
+        description = ''
+          Extra command line parameters for nix-serve.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.nix-serve = {
+      description = "nix-serve binary cache server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ config.nix.package.out pkgs.bzip2.bin ];
+      environment.NIX_REMOTE = "daemon";
+      environment.NIX_SECRET_KEY_FILE = cfg.secretKeyFile;
+
+      serviceConfig = {
+        Restart = "always";
+        RestartSec = "5s";
+        ExecStart = "${pkgs.nix-serve}/bin/nix-serve " +
+          "--listen ${cfg.bindAddress}:${toString cfg.port} ${cfg.extraParams}";
+        User = "nix-serve";
+        Group = "nogroup";
+      };
+    };
+
+    users.users.nix-serve = {
+      description = "Nix-serve user";
+      uid = config.ids.uids.nix-serve;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nix-store-gcs-proxy.nix b/nixpkgs/nixos/modules/services/networking/nix-store-gcs-proxy.nix
new file mode 100644
index 000000000000..3f2ce5bca4da
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nix-store-gcs-proxy.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  opts = { name, config, ... }: {
+    options = {
+      enable = mkOption {
+        default = true;
+        type = types.bool;
+        example = true;
+        description = "Whether to enable proxy for this bucket";
+      };
+      bucketName = mkOption {
+        type = types.str;
+        default = name;
+        example = "my-bucket-name";
+        description = "Name of Google storage bucket";
+      };
+      address = mkOption {
+        type = types.str;
+        example = "localhost:3000";
+        description = "The address of the proxy.";
+      };
+    };
+  };
+  enabledProxies = lib.filterAttrs (n: v: v.enable) config.services.nix-store-gcs-proxy;
+  mapProxies = function: lib.mkMerge (lib.mapAttrsToList function enabledProxies);
+in
+{
+  options.services.nix-store-gcs-proxy = mkOption {
+    type = types.attrsOf (types.submodule opts);
+    default = {};
+    description = ''
+      An attribute set describing an HTTP to GCS proxy that allows us to use GCS
+      bucket via HTTP protocol.
+    '';
+  };
+
+  config.systemd.services = mapProxies (name: cfg: {
+    "nix-store-gcs-proxy-${name}" = {
+      description = "A HTTP nix store that proxies requests to Google Storage";
+      wantedBy = ["multi-user.target"];
+
+      serviceConfig = {
+        RestartSec = 5;
+        StartLimitInterval = 10;
+        ExecStart = ''
+          ${pkgs.nix-store-gcs-proxy}/bin/nix-store-gcs-proxy \
+            --bucket-name ${cfg.bucketName} \
+            --addr ${cfg.address}
+        '';
+
+        DynamicUser = true;
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        PrivateUsers = true;
+
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+
+        NoNewPrivileges = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+      };
+    };
+  });
+
+  meta.maintainers = [ maintainers.mrkkrp ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nixops-dns.nix b/nixpkgs/nixos/modules/services/networking/nixops-dns.nix
new file mode 100644
index 000000000000..2bb1263b7fa2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nixops-dns.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  pkg = pkgs.nixops-dns;
+  cfg = config.services.nixops-dns;
+in
+
+{
+  options = {
+    services.nixops-dns = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the nixops-dns resolution
+          of NixOps virtual machines via dnsmasq and fake domain name.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        description = ''
+          The user the nixops-dns daemon should run as.
+          This should be the user, which is also used for nixops and
+          have the .nixops directory in its home.
+        '';
+      };
+
+      domain = mkOption {
+        type = types.str;
+        description = ''
+          Fake domain name to resolve to NixOps virtual machines.
+
+          For example "ops" will resolve "vm.ops".
+        '';
+        example = "ops";
+        default = "ops";
+      };
+
+      dnsmasq = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enable dnsmasq forwarding to nixops-dns. This allows to use
+          nixops-dns for `services.nixops-dns.domain` resolution
+          while forwarding the rest of the queries to original resolvers.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.nixops-dns = {
+      description = "nixops-dns: DNS server for resolving NixOps machines";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "simple";
+        User = cfg.user;
+        ExecStart="${pkg}/bin/nixops-dns --domain=.${cfg.domain}";
+      };
+    };
+
+    services.dnsmasq = mkIf cfg.dnsmasq {
+      enable = true;
+      resolveLocalQueries = true;
+      servers = [
+        "/${cfg.domain}/127.0.0.1#5300"
+      ];
+      extraConfig = ''
+        bind-interfaces
+        listen-address=127.0.0.1
+      '';
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix b/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix
new file mode 100644
index 000000000000..cc061bf6e3b9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nntp-proxy.nix
@@ -0,0 +1,234 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) nntp-proxy;
+
+  proxyUser = "nntp-proxy";
+
+  cfg = config.services.nntp-proxy;
+
+  configBool = b: if b then "TRUE" else "FALSE";
+
+  confFile = pkgs.writeText "nntp-proxy.conf" ''
+    nntp_server:
+    {
+      # NNTP Server host and port address
+      server = "${cfg.upstreamServer}";
+      port = ${toString cfg.upstreamPort};
+      # NNTP username
+      username = "${cfg.upstreamUser}";
+      # NNTP password in clear text
+      password = "${cfg.upstreamPassword}";
+      # Maximum number of connections allowed by the NNTP
+      max_connections = ${toString cfg.upstreamMaxConnections};
+    };
+
+    proxy:
+    {
+      # Local address and port to bind to
+      bind_ip = "${cfg.listenAddress}";
+      bind_port = ${toString cfg.port};
+
+      # SSL key and cert file
+      ssl_key = "${cfg.sslKey}";
+      ssl_cert = "${cfg.sslCert}";
+
+      # prohibit users from posting
+      prohibit_posting = ${configBool cfg.prohibitPosting};
+      # Verbose levels: ERROR, WARNING, NOTICE, INFO, DEBUG
+      verbose = "${toUpper cfg.verbosity}";
+      # Password is made with: 'mkpasswd -m sha-512 <password>'
+      users = (${concatStringsSep ",\n" (mapAttrsToList (username: userConfig:
+        ''
+          {
+              username = "${username}";
+              password = "${userConfig.passwordHash}";
+              max_connections = ${toString userConfig.maxConnections};
+          }
+        '') cfg.users)});
+    };
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.nntp-proxy = {
+      enable = mkEnableOption "NNTP-Proxy";
+
+      upstreamServer = mkOption {
+        type = types.str;
+        default = "";
+        example = "ssl-eu.astraweb.com";
+        description = ''
+          Upstream server address
+        '';
+      };
+
+      upstreamPort = mkOption {
+        type = types.int;
+        default = 563;
+        description = ''
+          Upstream server port
+        '';
+      };
+
+      upstreamMaxConnections = mkOption {
+        type = types.int;
+        default = 20;
+        description = ''
+          Upstream server maximum allowed concurrent connections
+        '';
+      };
+
+      upstreamUser = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Upstream server username
+        '';
+      };
+
+      upstreamPassword = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Upstream server password
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        example = "[::]";
+        description = ''
+          Proxy listen address (IPv6 literal addresses need to be enclosed in "[" and "]" characters)
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 5555;
+        description = ''
+          Proxy listen port
+        '';
+      };
+
+      sslKey = mkOption {
+        type = types.str;
+        default = "key.pem";
+        example = "/path/to/your/key.file";
+        description = ''
+          Proxy ssl key path
+        '';
+      };
+
+      sslCert = mkOption {
+        type = types.str;
+        default = "cert.pem";
+        example = "/path/to/your/cert.file";
+        description = ''
+          Proxy ssl certificate path
+        '';
+      };
+
+      prohibitPosting = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to prohibit posting to the upstream server
+        '';
+      };
+
+      verbosity = mkOption {
+        type = types.enum [ "error" "warning" "notice" "info" "debug" ];
+        default = "info";
+        example = "error";
+        description = ''
+          Verbosity level
+        '';
+      };
+
+      users = mkOption {
+        type = types.attrsOf (types.submodule {
+          options = {
+            username = mkOption {
+              type = types.str;
+              default = null;
+              description = ''
+                Username
+              '';
+            };
+
+            passwordHash = mkOption {
+              type = types.str;
+              default = null;
+              example = "$6$GtzE7FrpE$wwuVgFYU.TZH4Rz.Snjxk9XGua89IeVwPQ/fEUD8eujr40q5Y021yhn0aNcsQ2Ifw.BLclyzvzgegopgKcneL0";
+              description = ''
+                SHA-512 password hash (can be generated by
+                <code>mkpasswd -m sha-512 &lt;password&gt;</code>)
+              '';
+            };
+
+            maxConnections = mkOption {
+              type = types.int;
+              default = 1;
+              description = ''
+                Maximum number of concurrent connections to the proxy for this user
+              '';
+            };
+          };
+        });
+        description = ''
+          NNTP-Proxy user configuration
+        '';
+
+        default = {};
+        example = literalExample ''
+          "user1" = {
+            passwordHash = "$6$1l0t5Kn2Dk$appzivc./9l/kjq57eg5UCsBKlcfyCr0zNWYNerKoPsI1d7eAwiT0SVsOVx/CTgaBNT/u4fi2vN.iGlPfv1ek0";
+            maxConnections = 5;
+          };
+          "anotheruser" = {
+            passwordHash = "$6$6lwEsWB.TmsS$W7m1riUx4QrA8pKJz8hvff0dnF1NwtZXgdjmGqA1Dx2MDPj07tI9GNcb0SWlMglE.2/hBgynDdAd/XqqtRqVQ0";
+            maxConnections = 7;
+          };
+        '';
+      };
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.${proxyUser} =
+      { uid = config.ids.uids.nntp-proxy;
+        description = "NNTP-Proxy daemon user";
+      };
+
+    systemd.services.nntp-proxy = {
+      description = "NNTP proxy";
+      after = [ "network.target" "nss-lookup.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = { User="${proxyUser}"; };
+      serviceConfig.ExecStart = "${nntp-proxy}/bin/nntp-proxy ${confFile}";
+      preStart = ''
+        if [ ! \( -f ${cfg.sslCert} -a -f ${cfg.sslKey} \) ]; then
+          ${pkgs.openssl.bin}/bin/openssl req -subj '/CN=AutoGeneratedCert/O=NixOS Service/C=US' \
+          -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout ${cfg.sslKey} -out ${cfg.sslCert};
+        fi
+      '';
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nsd.nix b/nixpkgs/nixos/modules/services/networking/nsd.nix
new file mode 100644
index 000000000000..6e3eed0c5570
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nsd.nix
@@ -0,0 +1,980 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nsd;
+
+  username = "nsd";
+  stateDir = "/var/lib/nsd";
+  pidFile = stateDir + "/var/nsd.pid";
+
+  # build nsd with the options needed for the given config
+  nsdPkg = pkgs.nsd.override {
+    configFile = "${configFile}/nsd.conf";
+
+    bind8Stats = cfg.bind8Stats;
+    ipv6 = cfg.ipv6;
+    ratelimit = cfg.ratelimit.enable;
+    rootServer = cfg.rootServer;
+    zoneStats = length (collect (x: (x.zoneStats or null) != null) cfg.zones) > 0;
+  };
+
+  mkZoneFileName = name: if name == "." then "root" else name;
+
+  nsdEnv = pkgs.buildEnv {
+    name = "nsd-env";
+
+    paths = [ configFile ]
+      ++ mapAttrsToList (name: zone: writeZoneData name zone.data) zoneConfigs;
+
+    postBuild = ''
+      echo "checking zone files"
+      cd $out/zones
+
+      for zoneFile in *; do
+        echo "|- checking zone '$out/zones/$zoneFile'"
+        ${nsdPkg}/sbin/nsd-checkzone "$zoneFile" "$zoneFile" || {
+          if grep -q \\\\\\$ "$zoneFile"; then
+            echo zone "$zoneFile" contains escaped dollar signes \\\$
+            echo Escaping them is not needed any more. Please make shure \
+                 to unescape them where they prefix a variable name
+          fi
+
+          exit 1
+        }
+      done
+
+      echo "checking configuration file"
+      ${nsdPkg}/sbin/nsd-checkconf $out/nsd.conf
+    '';
+  };
+
+  writeZoneData = name: text: pkgs.writeTextFile {
+    name = "nsd-zone-${mkZoneFileName name}";
+    inherit text;
+    destination = "/zones/${mkZoneFileName name}";
+  };
+
+
+  # options are ordered alphanumerically by the nixos option name
+  configFile = pkgs.writeTextDir "nsd.conf" ''
+    server:
+      chroot:   "${stateDir}"
+      username: ${username}
+
+      # The directory for zonefile: files. The daemon chdirs here.
+      zonesdir: "${stateDir}"
+
+      # the list of dynamically added zones.
+      database:     "${stateDir}/var/nsd.db"
+      pidfile:      "${pidFile}"
+      xfrdfile:     "${stateDir}/var/xfrd.state"
+      xfrdir:       "${stateDir}/tmp"
+      zonelistfile: "${stateDir}/var/zone.list"
+
+      # interfaces
+    ${forEach "  ip-address: " cfg.interfaces}
+
+      ip-freebind:         ${yesOrNo  cfg.ipFreebind}
+      hide-version:        ${yesOrNo  cfg.hideVersion}
+      identity:            "${cfg.identity}"
+      ip-transparent:      ${yesOrNo  cfg.ipTransparent}
+      do-ip4:              ${yesOrNo  cfg.ipv4}
+      ipv4-edns-size:      ${toString cfg.ipv4EDNSSize}
+      do-ip6:              ${yesOrNo  cfg.ipv6}
+      ipv6-edns-size:      ${toString cfg.ipv6EDNSSize}
+      log-time-ascii:      ${yesOrNo  cfg.logTimeAscii}
+      ${maybeString "nsid: " cfg.nsid}
+      port:                ${toString cfg.port}
+      reuseport:           ${yesOrNo  cfg.reuseport}
+      round-robin:         ${yesOrNo  cfg.roundRobin}
+      server-count:        ${toString cfg.serverCount}
+      ${maybeToString "statistics: " cfg.statistics}
+      tcp-count:           ${toString cfg.tcpCount}
+      tcp-query-count:     ${toString cfg.tcpQueryCount}
+      tcp-timeout:         ${toString cfg.tcpTimeout}
+      verbosity:           ${toString cfg.verbosity}
+      ${maybeString "version: " cfg.version}
+      xfrd-reload-timeout: ${toString cfg.xfrdReloadTimeout}
+      zonefiles-check:     ${yesOrNo  cfg.zonefilesCheck}
+
+      ${maybeString "rrl-ipv4-prefix-length: " cfg.ratelimit.ipv4PrefixLength}
+      ${maybeString "rrl-ipv6-prefix-length: " cfg.ratelimit.ipv6PrefixLength}
+      rrl-ratelimit:           ${toString cfg.ratelimit.ratelimit}
+      ${maybeString "rrl-slip: "               cfg.ratelimit.slip}
+      rrl-size:                ${toString cfg.ratelimit.size}
+      rrl-whitelist-ratelimit: ${toString cfg.ratelimit.whitelistRatelimit}
+
+    ${keyConfigFile}
+
+    remote-control:
+      control-enable:    ${yesOrNo  cfg.remoteControl.enable}
+      control-key-file:  "${cfg.remoteControl.controlKeyFile}"
+      control-cert-file: "${cfg.remoteControl.controlCertFile}"
+    ${forEach "  control-interface: " cfg.remoteControl.interfaces}
+      control-port:      ${toString cfg.remoteControl.port}
+      server-key-file:   "${cfg.remoteControl.serverKeyFile}"
+      server-cert-file:  "${cfg.remoteControl.serverCertFile}"
+
+    ${concatStrings (mapAttrsToList zoneConfigFile zoneConfigs)}
+
+    ${cfg.extraConfig}
+  '';
+
+  yesOrNo = b: if b then "yes" else "no";
+  maybeString = prefix: x: if x == null then "" else ''${prefix} "${x}"'';
+  maybeToString = prefix: x: if x == null then "" else ''${prefix} ${toString x}'';
+  forEach = pre: l: concatMapStrings (x: pre + x + "\n") l;
+
+
+  keyConfigFile = concatStrings (mapAttrsToList (keyName: keyOptions: ''
+    key:
+      name:      "${keyName}"
+      algorithm: "${keyOptions.algorithm}"
+      include:   "${stateDir}/private/${keyName}"
+  '') cfg.keys);
+
+  copyKeys = concatStrings (mapAttrsToList (keyName: keyOptions: ''
+    secret=$(cat "${keyOptions.keyFile}")
+    dest="${stateDir}/private/${keyName}"
+    echo "  secret: \"$secret\"" > "$dest"
+    chown ${username}:${username} "$dest"
+    chmod 0400 "$dest"
+  '') cfg.keys);
+
+
+  # options are ordered alphanumerically by the nixos option name
+  zoneConfigFile = name: zone: ''
+    zone:
+      name:         "${name}"
+      zonefile:     "${stateDir}/zones/${mkZoneFileName name}"
+      ${maybeString "outgoing-interface: " zone.outgoingInterface}
+    ${forEach     "  rrl-whitelist: "      zone.rrlWhitelist}
+      ${maybeString "zonestats: "          zone.zoneStats}
+
+      ${maybeToString "max-refresh-time: " zone.maxRefreshSecs}
+      ${maybeToString "min-refresh-time: " zone.minRefreshSecs}
+      ${maybeToString "max-retry-time:   " zone.maxRetrySecs}
+      ${maybeToString "min-retry-time:   " zone.minRetrySecs}
+
+      allow-axfr-fallback: ${yesOrNo       zone.allowAXFRFallback}
+    ${forEach     "  allow-notify: "       zone.allowNotify}
+    ${forEach     "  request-xfr: "        zone.requestXFR}
+
+    ${forEach     "  notify: "             zone.notify}
+      notify-retry:                        ${toString zone.notifyRetry}
+    ${forEach     "  provide-xfr: "        zone.provideXFR}
+  '';
+
+  zoneConfigs = zoneConfigs' {} "" { children = cfg.zones; };
+
+  zoneConfigs' = parent: name: zone:
+    if !(zone ? children) || zone.children == null || zone.children == { }
+      # leaf -> actual zone
+      then listToAttrs [ (nameValuePair name (parent // zone)) ]
+
+      # fork -> pattern
+      else zipAttrsWith (name: head) (
+        mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child)
+                       zone.children
+      );
+
+  # fighting infinite recursion
+  zoneOptions = zoneOptionsRaw // childConfig zoneOptions1 true;
+  zoneOptions1 = zoneOptionsRaw // childConfig zoneOptions2 false;
+  zoneOptions2 = zoneOptionsRaw // childConfig zoneOptions3 false;
+  zoneOptions3 = zoneOptionsRaw // childConfig zoneOptions4 false;
+  zoneOptions4 = zoneOptionsRaw // childConfig zoneOptions5 false;
+  zoneOptions5 = zoneOptionsRaw // childConfig zoneOptions6 false;
+  zoneOptions6 = zoneOptionsRaw // childConfig null         false;
+
+  childConfig = x: v: { options.children = { type = types.attrsOf x; visible = v; }; };
+
+  # options are ordered alphanumerically
+  zoneOptionsRaw = types.submodule {
+    options = {
+
+      allowAXFRFallback = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If NSD as secondary server should be allowed to AXFR if the primary
+          server does not allow IXFR.
+        '';
+      };
+
+      allowNotify = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name"
+                    "10.0.3.4&255.255.0.0 BLOCKED"
+                  ];
+        description = ''
+          Listed primary servers are allowed to notify this secondary server.
+          <screen><![CDATA[
+          Format: <ip> <key-name | NOKEY | BLOCKED>
+
+          <ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges:
+          * 10.0.0.0/24            # via subnet size
+          * 10.0.0.0&255.255.255.0 # via subnet mask
+          * 10.0.0.1-10.0.0.254    # via range
+
+          A optional port number could be added with a '@':
+          * 2001:1234::1@1234
+
+          <key-name | NOKEY | BLOCKED>
+          * <key-name> will use the specified TSIG key
+          * NOKEY      no TSIG signature is required
+          * BLOCKED    notifies from non-listed or blocked IPs will be ignored
+          * ]]></screen>
+        '';
+      };
+
+      children = mkOption {
+        default = {};
+        description = ''
+          Children zones inherit all options of their parents. Attributes
+          defined in a child will overwrite the ones of its parent. Only
+          leaf zones will be actually served. This way it's possible to
+          define maybe zones which share most attributes without
+          duplicating everything. This mechanism replaces nsd's patterns
+          in a save and functional way.
+        '';
+      };
+
+      data = mkOption {
+        type = types.lines;
+        default = "";
+        example = "";
+        description = ''
+          The actual zone data. This is the content of your zone file.
+          Use imports or pkgs.lib.readFile if you don't want this data in your config file.
+        '';
+      };
+
+      dnssec = mkEnableOption "DNSSEC";
+
+      dnssecPolicy = {
+        algorithm = mkOption {
+          type = types.str;
+          default = "RSASHA256";
+          description = "Which algorithm to use for DNSSEC";
+        };
+        keyttl = mkOption {
+          type = types.str;
+          default = "1h";
+          description = "TTL for dnssec records";
+        };
+        coverage = mkOption {
+          type = types.str;
+          default = "1y";
+          description = ''
+            The length of time to ensure that keys will be correct; no action will be taken to create new keys to be activated after this time.
+          '';
+        };
+        zsk = mkOption {
+          type = keyPolicy;
+          default = { keySize = 2048;
+                      prePublish = "1w";
+                      postPublish = "1w";
+                      rollPeriod = "1mo";
+                    };
+          description = "Key policy for zone signing keys";
+        };
+        ksk = mkOption {
+          type = keyPolicy;
+          default = { keySize = 4096;
+                      prePublish = "1mo";
+                      postPublish = "1mo";
+                      rollPeriod = "0";
+                    };
+          description = "Key policy for key signing keys";
+        };
+      };
+
+      maxRefreshSecs = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Limit refresh time for secondary zones. This is the timer which
+          checks to see if the zone has to be refetched when it expires.
+          Normally the value from the SOA record is used, but this  option
+          restricts that value.
+        '';
+      };
+
+      minRefreshSecs = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Limit refresh time for secondary zones.
+        '';
+      };
+
+      maxRetrySecs = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Limit retry time for secondary zones. This is the timeout after
+          a failed fetch attempt for the zone. Normally the value from
+          the SOA record is used, but this option restricts that value.
+        '';
+      };
+
+      minRetrySecs = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Limit retry time for secondary zones.
+        '';
+      };
+
+
+      notify = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ];
+        description = ''
+          This primary server will notify all given secondary servers about
+          zone changes.
+          <screen><![CDATA[
+          Format: <ip> <key-name | NOKEY>
+
+          <ip> a plain IPv4/IPv6 address with on optional port number (ip@port)
+
+          <key-name | NOKEY>
+          * <key-name> sign notifies with the specified key
+          * NOKEY      don't sign notifies
+          ]]></screen>
+        '';
+      };
+
+      notifyRetry = mkOption {
+        type = types.int;
+        default = 5;
+        description = ''
+          Specifies the number of retries for failed notifies. Set this along with notify.
+        '';
+      };
+
+      outgoingInterface = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "2000::1@1234";
+        description = ''
+          This address will be used for zone-transfere requests if configured
+          as a secondary server or notifications in case of a primary server.
+          Supply either a plain IPv4 or IPv6 address with an optional port
+          number (ip@port).
+        '';
+      };
+
+      provideXFR = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ];
+        description = ''
+          Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED
+          address range 192.0.2.0/24, 1.2.3.4&amp;255.255.0.0, 3.0.2.20-3.0.2.40
+        '';
+      };
+
+      requestXFR = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [];
+        description = ''
+          Format: <code>[AXFR|UDP] &lt;ip-address&gt; &lt;key-name | NOKEY&gt;</code>
+        '';
+      };
+
+      rrlWhitelist = mkOption {
+        type = with types; listOf (enum [ "nxdomain" "error" "referral" "any" "rrsig" "wildcard" "nodata" "dnskey" "positive" "all" ]);
+        default = [];
+        description = ''
+          Whitelists the given rrl-types.
+        '';
+      };
+
+      zoneStats = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "%s";
+        description = ''
+          When set to something distinct to null NSD is able to collect
+          statistics per zone. All statistics of this zone(s) will be added
+          to the group specified by this given name. Use "%s" to use the zones
+          name as the group. The groups are output from nsd-control stats
+          and stats_noreset.
+        '';
+      };
+    };
+  };
+
+  keyPolicy = types.submodule {
+    options = {
+      keySize = mkOption {
+        type = types.int;
+        description = "Key size in bits";
+      };
+      prePublish = mkOption {
+        type = types.str;
+        description = "How long in advance to publish new keys";
+      };
+      postPublish = mkOption {
+        type = types.str;
+        description = "How long after deactivation to keep a key in the zone";
+      };
+      rollPeriod = mkOption {
+        type = types.str;
+        description = "How frequently to change keys";
+      };
+    };
+  };
+
+  dnssecZones = (filterAttrs (n: v: if v ? dnssec then v.dnssec else false) zoneConfigs);
+
+  dnssec = dnssecZones != {};
+
+  dnssecTools = pkgs.bind.override { enablePython = true; };
+
+  signZones = optionalString dnssec ''
+    mkdir -p ${stateDir}/dnssec
+    chown ${username}:${username} ${stateDir}/dnssec
+    chmod 0600 ${stateDir}/dnssec
+
+    ${concatStrings (mapAttrsToList signZone dnssecZones)}
+  '';
+  signZone = name: zone: ''
+    ${dnssecTools}/bin/dnssec-keymgr -g ${dnssecTools}/bin/dnssec-keygen -s ${dnssecTools}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${policyFile name zone.dnssecPolicy} ${name}
+    ${dnssecTools}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name}
+    ${nsdPkg}/sbin/nsd-checkzone ${name} ${stateDir}/zones/${name}.signed && mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name}
+  '';
+  policyFile = name: policy: pkgs.writeText "${name}.policy" ''
+    zone ${name} {
+      algorithm ${policy.algorithm};
+      key-size zsk ${toString policy.zsk.keySize};
+      key-size ksk ${toString policy.ksk.keySize};
+      keyttl ${policy.keyttl};
+      pre-publish zsk ${policy.zsk.prePublish};
+      pre-publish ksk ${policy.ksk.prePublish};
+      post-publish zsk ${policy.zsk.postPublish};
+      post-publish ksk ${policy.ksk.postPublish};
+      roll-period zsk ${policy.zsk.rollPeriod};
+      roll-period ksk ${policy.ksk.rollPeriod};
+      coverage ${policy.coverage};
+    };
+  '';
+in
+{
+  # options are ordered alphanumerically
+  options.services.nsd = {
+
+    enable = mkEnableOption "NSD authoritative DNS server";
+
+    bind8Stats = mkEnableOption "BIND8 like statistics";
+
+    dnssecInterval = mkOption {
+      type = types.str;
+      default = "1h";
+      description = ''
+        How often to check whether dnssec key rollover is required
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Extra nsd config.
+      '';
+    };
+
+    hideVersion = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether NSD should answer VERSION.BIND and VERSION.SERVER CHAOS class queries.
+      '';
+    };
+
+    identity = mkOption {
+      type = types.str;
+      default = "unidentified server";
+      description = ''
+        Identify the server (CH TXT ID.SERVER entry).
+      '';
+    };
+
+    interfaces = mkOption {
+      type = types.listOf types.str;
+      default = [ "127.0.0.0" "::1" ];
+      description = ''
+        What addresses the server should listen to.
+      '';
+    };
+
+    ipFreebind = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to bind to nonlocal addresses and interfaces that are down.
+        Similar to ip-transparent.
+      '';
+    };
+
+    ipTransparent = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Allow binding to non local addresses.
+      '';
+    };
+
+    ipv4 = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to listen on IPv4 connections.
+      '';
+    };
+
+    ipv4EDNSSize = mkOption {
+      type = types.int;
+      default = 4096;
+      description = ''
+        Preferred EDNS buffer size for IPv4.
+      '';
+    };
+
+    ipv6 = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to listen on IPv6 connections.
+      '';
+    };
+
+    ipv6EDNSSize = mkOption {
+      type = types.int;
+      default = 4096;
+      description = ''
+        Preferred EDNS buffer size for IPv6.
+      '';
+    };
+
+    logTimeAscii = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Log time in ascii, if false then in unix epoch seconds.
+      '';
+    };
+
+    nsid = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        NSID identity (hex string, or "ascii_somestring").
+      '';
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 53;
+      description = ''
+        Port the service should bind do.
+      '';
+    };
+
+    reuseport = mkOption {
+      type = types.bool;
+      default = pkgs.stdenv.isLinux;
+      description = ''
+        Whether to enable SO_REUSEPORT on all used sockets. This lets multiple
+        processes bind to the same port. This speeds up operation especially
+        if the server count is greater than one and makes fast restarts less
+        prone to fail
+      '';
+    };
+
+    rootServer = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether this server will be a root server (a DNS root server, you
+        usually don't want that).
+      '';
+    };
+
+    roundRobin = mkEnableOption "round robin rotation of records";
+
+    serverCount = mkOption {
+      type = types.int;
+      default = 1;
+      description = ''
+        Number of NSD servers to fork. Put the number of CPUs to use here.
+      '';
+    };
+
+    statistics = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      description = ''
+        Statistics are produced every number of seconds. Prints to log.
+        If null no statistics are logged.
+      '';
+    };
+
+    tcpCount = mkOption {
+      type = types.int;
+      default = 100;
+      description = ''
+        Maximum number of concurrent TCP connections per server.
+      '';
+    };
+
+    tcpQueryCount = mkOption {
+      type = types.int;
+      default = 0;
+      description = ''
+        Maximum number of queries served on a single TCP connection.
+        0 means no maximum.
+      '';
+    };
+
+    tcpTimeout = mkOption {
+      type = types.int;
+      default = 120;
+      description = ''
+        TCP timeout in seconds.
+      '';
+    };
+
+    verbosity = mkOption {
+      type = types.int;
+      default = 0;
+      description = ''
+        Verbosity level.
+      '';
+    };
+
+    version = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The version string replied for CH TXT version.server and version.bind
+        queries. Will use the compiled package version on null.
+        See hideVersion for enabling/disabling this responses.
+      '';
+    };
+
+    xfrdReloadTimeout = mkOption {
+      type = types.int;
+      default = 1;
+      description = ''
+        Number of seconds between reloads triggered by xfrd.
+      '';
+    };
+
+    zonefilesCheck = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to check mtime of all zone files on start and sighup.
+      '';
+    };
+
+
+    keys = mkOption {
+      type = types.attrsOf (types.submodule {
+        options = {
+
+          algorithm = mkOption {
+            type = types.str;
+            default = "hmac-sha256";
+            description = ''
+              Authentication algorithm for this key.
+            '';
+          };
+
+          keyFile = mkOption {
+            type = types.path;
+            description = ''
+              Path to the file which contains the actual base64 encoded
+              key. The key will be copied into "${stateDir}/private" before
+              NSD starts. The copied file is only accessibly by the NSD
+              user.
+            '';
+          };
+
+        };
+      });
+      default = {};
+      example = literalExample ''
+        { "tsig.example.org" = {
+            algorithm = "hmac-md5";
+            keyFile = "/path/to/my/key";
+          };
+        }
+      '';
+      description = ''
+        Define your TSIG keys here.
+      '';
+    };
+
+
+    ratelimit = {
+
+      enable = mkEnableOption "ratelimit capabilities";
+
+      ipv4PrefixLength = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          IPv4 prefix length. Addresses are grouped by netblock.
+        '';
+      };
+
+      ipv6PrefixLength = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          IPv6 prefix length. Addresses are grouped by netblock.
+        '';
+      };
+
+      ratelimit = mkOption {
+        type = types.int;
+        default = 200;
+        description = ''
+          Max qps allowed from any query source.
+          0 means unlimited. With an verbosity of 2 blocked and
+          unblocked subnets will be logged.
+        '';
+      };
+
+      slip = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Number of packets that get discarded before replying a SLIP response.
+          0 disables SLIP responses. 1 will make every response a SLIP response.
+        '';
+      };
+
+      size = mkOption {
+        type = types.int;
+        default = 1000000;
+        description = ''
+          Size of the hashtable. More buckets use more memory but lower
+          the chance of hash hash collisions.
+        '';
+      };
+
+      whitelistRatelimit = mkOption {
+        type = types.int;
+        default = 2000;
+        description = ''
+          Max qps allowed from whitelisted sources.
+          0 means unlimited. Set the rrl-whitelist option for specific
+          queries to apply this limit instead of the default to them.
+        '';
+      };
+
+    };
+
+
+    remoteControl = {
+
+      enable = mkEnableOption "remote control via nsd-control";
+
+      controlCertFile = mkOption {
+        type = types.path;
+        default = "/etc/nsd/nsd_control.pem";
+        description = ''
+          Path to the client certificate signed with the server certificate.
+          This file is used by nsd-control and generated by nsd-control-setup.
+        '';
+      };
+
+      controlKeyFile = mkOption {
+        type = types.path;
+        default = "/etc/nsd/nsd_control.key";
+        description = ''
+          Path to the client private key, which is used by nsd-control
+          but not by the server. This file is generated by nsd-control-setup.
+        '';
+      };
+
+      interfaces = mkOption {
+        type = types.listOf types.str;
+        default = [ "127.0.0.1" "::1" ];
+        description = ''
+          Which interfaces NSD should bind to for remote control.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8952;
+        description = ''
+          Port number for remote control operations (uses TLS over TCP).
+        '';
+      };
+
+      serverCertFile = mkOption {
+        type = types.path;
+        default = "/etc/nsd/nsd_server.pem";
+        description = ''
+          Path to the server self signed certificate, which is used by the server
+          but and by nsd-control. This file is generated by nsd-control-setup.
+        '';
+      };
+
+      serverKeyFile = mkOption {
+        type = types.path;
+        default = "/etc/nsd/nsd_server.key";
+        description = ''
+          Path to the server private key, which is used by the server
+          but not by nsd-control. This file is generated by nsd-control-setup.
+        '';
+      };
+
+    };
+
+    zones = mkOption {
+      type = types.attrsOf zoneOptions;
+      default = {};
+      example = literalExample ''
+        { "serverGroup1" = {
+            provideXFR = [ "10.1.2.3 NOKEY" ];
+            children = {
+              "example.com." = {
+                data = '''
+                  $ORIGIN example.com.
+                  $TTL    86400
+                  @ IN SOA a.ns.example.com. admin.example.com. (
+                  ...
+                ''';
+              };
+              "example.org." = {
+                data = '''
+                  $ORIGIN example.org.
+                  $TTL    86400
+                  @ IN SOA a.ns.example.com. admin.example.com. (
+                  ...
+                ''';
+              };
+            };
+          };
+
+          "example.net." = {
+            provideXFR = [ "10.3.2.1 NOKEY" ];
+            data = '''
+              ...
+            ''';
+          };
+        }
+      '';
+      description = ''
+        Define your zones here. Zones can cascade other zones and therefore
+        inherit settings from parent zones. Look at the definition of
+        children to learn about inheritance and child zones.
+        The given example will define 3 zones (example.(com|org|net).). Both
+        example.com. and example.org. inherit their configuration from
+        serverGroup1.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = singleton {
+      assertion = zoneConfigs ? "." -> cfg.rootServer;
+      message = "You have a root zone configured. If this is really what you "
+              + "want, please enable 'services.nsd.rootServer'.";
+    };
+
+    environment.systemPackages = [ nsdPkg ];
+
+    users.groups.${username}.gid = config.ids.gids.nsd;
+
+    users.users.${username} = {
+      description = "NSD service user";
+      home = stateDir;
+      createHome  = true;
+      uid = config.ids.uids.nsd;
+      group = username;
+    };
+
+    systemd.services.nsd = {
+      description = "NSD authoritative only domain name service";
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${nsdPkg}/sbin/nsd -d -c ${nsdEnv}/nsd.conf";
+        StandardError = "null";
+        PIDFile = pidFile;
+        Restart = "always";
+        RestartSec = "4s";
+        StartLimitBurst = 4;
+        StartLimitInterval = "5min";
+      };
+
+      preStart = ''
+        rm -Rf "${stateDir}/private/"
+        rm -Rf "${stateDir}/tmp/"
+
+        mkdir -m 0700 -p "${stateDir}/private"
+        mkdir -m 0700 -p "${stateDir}/tmp"
+        mkdir -m 0700 -p "${stateDir}/var"
+
+        cat > "${stateDir}/don't touch anything in here" << EOF
+        Everything in this directory except NSD's state in var and dnssec
+        is automatically generated and will be purged and redeployed by
+        the nsd.service pre-start script.
+        EOF
+
+        chown ${username}:${username} -R "${stateDir}/private"
+        chown ${username}:${username} -R "${stateDir}/tmp"
+        chown ${username}:${username} -R "${stateDir}/var"
+
+        rm -rf "${stateDir}/zones"
+        cp -rL "${nsdEnv}/zones" "${stateDir}/zones"
+
+        ${copyKeys}
+      '';
+    };
+
+    systemd.timers.nsd-dnssec = mkIf dnssec {
+      description = "Automatic DNSSEC key rollover";
+
+      wantedBy = [ "nsd.service" ];
+
+      timerConfig = {
+        OnActiveSec = cfg.dnssecInterval;
+        OnUnitActiveSec = cfg.dnssecInterval;
+      };
+    };
+
+    systemd.services.nsd-dnssec = mkIf dnssec {
+      description = "DNSSEC key rollover";
+
+      wantedBy = [ "nsd.service" ];
+      before = [ "nsd.service" ];
+
+      script = signZones;
+
+      postStop = ''
+        /run/current-system/systemd/bin/systemctl kill -s SIGHUP nsd.service
+      '';
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ hrdinka ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ntopng.nix b/nixpkgs/nixos/modules/services/networking/ntopng.nix
new file mode 100644
index 000000000000..c15257117137
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ntopng.nix
@@ -0,0 +1,116 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.ntopng;
+  redisCfg = config.services.redis;
+
+  configFile = if cfg.configText != "" then
+    pkgs.writeText "ntopng.conf" ''
+      ${cfg.configText}
+    ''
+    else
+    pkgs.writeText "ntopng.conf" ''
+      ${concatStringsSep " " (map (e: "--interface=" + e) cfg.interfaces)}
+      --http-port=${toString cfg.http-port}
+      --redis=localhost:${toString redisCfg.port}
+      ${cfg.extraConfig}
+    '';
+
+in
+
+{
+
+  options = {
+
+    services.ntopng = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enable ntopng, a high-speed web-based traffic analysis and flow
+          collection tool.
+
+          With the default configuration, ntopng monitors all network
+          interfaces and displays its findings at http://localhost:${toString
+          cfg.http-port}. Default username and password is admin/admin.
+
+          See the ntopng(8) manual page and http://www.ntop.org/products/ntop/
+          for more info.
+
+          Note that enabling ntopng will also enable redis (key-value
+          database server) for persistent data storage.
+        '';
+      };
+
+      interfaces = mkOption {
+        default = [ "any" ];
+        example = [ "eth0" "wlan0" ];
+        type = types.listOf types.str;
+        description = ''
+          List of interfaces to monitor. Use "any" to monitor all interfaces.
+        '';
+      };
+
+      http-port = mkOption {
+        default = 3000;
+        type = types.int;
+        description = ''
+          Sets the HTTP port of the embedded web server.
+        '';
+      };
+
+      configText = mkOption {
+        default = "";
+        example = ''
+          --interface=any
+          --http-port=3000
+          --disable-login
+        '';
+        type = types.lines;
+        description = ''
+          Overridable configuration file contents to use for ntopng. By
+          default, use the contents automatically generated by NixOS.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Configuration lines that will be appended to the generated ntopng
+          configuration file. Note that this mechanism does not work when the
+          manual <option>configText</option> option is used.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    # ntopng uses redis for data storage
+    services.redis.enable = true;
+
+    # nice to have manual page and ntopng command in PATH
+    environment.systemPackages = [ pkgs.ntopng ];
+
+    systemd.services.ntopng = {
+      description = "Ntopng Network Monitor";
+      requires = [ "redis.service" ];
+      after = [ "network.target" "redis.service" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = "mkdir -p /var/lib/ntopng/";
+      serviceConfig.ExecStart = "${pkgs.ntopng}/bin/ntopng ${configFile}";
+      unitConfig.Documentation = "man:ntopng(8)";
+    };
+
+    # ntopng drops priveleges to user "nobody" and that user is already defined
+    # in users-groups.nix.
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix b/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
new file mode 100644
index 000000000000..b7e4c89a155c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ntp/chrony.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.chrony;
+
+  stateDir = "/var/lib/chrony";
+  keyFile = "${stateDir}/chrony.keys";
+
+  configFile = pkgs.writeText "chrony.conf" ''
+    ${concatMapStringsSep "\n" (server: "server " + server + " iburst") cfg.servers}
+
+    ${optionalString
+      (cfg.initstepslew.enabled && (cfg.servers != []))
+      "initstepslew ${toString cfg.initstepslew.threshold} ${concatStringsSep " " cfg.servers}"
+    }
+
+    driftfile ${stateDir}/chrony.drift
+    keyfile ${keyFile}
+
+    ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
+
+    ${cfg.extraConfig}
+  '';
+
+  chronyFlags = "-n -m -u chrony -f ${configFile} ${toString cfg.extraFlags}";
+in
+{
+  options = {
+    services.chrony = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to synchronise your machine's time using chrony.
+          Make sure you disable NTP if you enable this service.
+        '';
+      };
+
+      servers = mkOption {
+        default = config.networking.timeServers;
+        description = ''
+          The set of NTP servers from which to synchronise.
+        '';
+      };
+
+      initstepslew = mkOption {
+        default = {
+          enabled = true;
+          threshold = 1000; # by default, same threshold as 'ntpd -g' (1000s)
+        };
+        description = ''
+          Allow chronyd to make a rapid measurement of the system clock error at
+          boot time, and to correct the system clock by stepping before normal
+          operation begins.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration directives that should be added to
+          <literal>chrony.conf</literal>
+        '';
+      };
+
+      extraFlags = mkOption {
+        default = [];
+        example = [ "-s" ];
+        type = types.listOf types.str;
+        description = "Extra flags passed to the chronyd command.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+
+    environment.systemPackages = [ pkgs.chrony ];
+
+    users.groups.chrony.gid = config.ids.gids.chrony;
+
+    users.users.chrony =
+      { uid = config.ids.uids.chrony;
+        group = "chrony";
+        description = "chrony daemon user";
+        home = stateDir;
+      };
+
+    services.timesyncd.enable = mkForce false;
+
+    systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "chronyd.service"; };
+
+    systemd.tmpfiles.rules = [
+      "d ${stateDir} 0755 chrony chrony - -"
+      "f ${keyFile} 0640 chrony chrony -"
+    ];
+
+    systemd.services.chronyd =
+      { description = "chrony NTP daemon";
+
+        wantedBy = [ "multi-user.target" ];
+        wants    = [ "time-sync.target" ];
+        before   = [ "time-sync.target" ];
+        after    = [ "network.target" ];
+        conflicts = [ "ntpd.service" "systemd-timesyncd.service" ];
+
+        path = [ pkgs.chrony ];
+
+        unitConfig.ConditionCapability = "CAP_SYS_TIME";
+        serviceConfig =
+          { Type = "simple";
+            ExecStart = "${pkgs.chrony}/bin/chronyd ${chronyFlags}";
+
+            ProtectHome = "yes";
+            ProtectSystem = "full";
+            PrivateTmp = "yes";
+            StateDirectory = "chrony";
+          };
+
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix b/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
new file mode 100644
index 000000000000..51398851adc6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ntp/ntpd.nix
@@ -0,0 +1,148 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) ntp;
+
+  cfg = config.services.ntp;
+
+  stateDir = "/var/lib/ntp";
+
+  ntpUser = "ntp";
+
+  configFile = pkgs.writeText "ntp.conf" ''
+    driftfile ${stateDir}/ntp.drift
+
+    restrict default ${toString cfg.restrictDefault}
+    restrict -6 default ${toString cfg.restrictDefault}
+    restrict source ${toString cfg.restrictSource}
+
+    restrict 127.0.0.1
+    restrict -6 ::1
+
+    ${toString (map (server: "server " + server + " iburst\n") cfg.servers)}
+
+    ${cfg.extraConfig}
+  '';
+
+  ntpFlags = "-c ${configFile} -u ${ntpUser}:nogroup ${toString cfg.extraFlags}";
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.ntp = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to synchronise your machine's time using ntpd, as a peer in
+          the NTP network.
+          </para>
+          <para>
+          Disables <literal>systemd.timesyncd</literal> if enabled.
+        '';
+      };
+
+      restrictDefault = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          The restriction flags to be set by default.
+          </para>
+          <para>
+          The default flags prevent external hosts from using ntpd as a DDoS
+          reflector, setting system time, and querying OS/ntpd version. As
+          recommended in section 6.5.1.1.3, answer "No" of
+          http://support.ntp.org/bin/view/Support/AccessRestrictions
+        '';
+        default = [ "limited" "kod" "nomodify" "notrap" "noquery" "nopeer" ];
+      };
+
+      restrictSource = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          The restriction flags to be set on source.
+          </para>
+          <para>
+          The default flags allow peers to be added by ntpd from configured
+          pool(s), but not by other means.
+        '';
+        default = [ "limited" "kod" "nomodify" "notrap" "noquery" ];
+      };
+
+      servers = mkOption {
+        default = config.networking.timeServers;
+        description = ''
+          The set of NTP servers from which to synchronise.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          fudge 127.127.1.0 stratum 10
+        '';
+        description = ''
+          Additional text appended to <filename>ntp.conf</filename>.
+        '';
+      };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        description = "Extra flags passed to the ntpd command.";
+        example = literalExample ''[ "--interface=eth0" ]'';
+        default = [];
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.ntp.enable {
+    meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+
+    # Make tools such as ntpq available in the system path.
+    environment.systemPackages = [ pkgs.ntp ];
+    services.timesyncd.enable = mkForce false;
+
+    systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd.service"; };
+
+    users.users.${ntpUser} =
+      { uid = config.ids.uids.ntp;
+        description = "NTP daemon user";
+        home = stateDir;
+      };
+
+    systemd.services.ntpd =
+      { description = "NTP Daemon";
+
+        wantedBy = [ "multi-user.target" ];
+        wants = [ "time-sync.target" ];
+        before = [ "time-sync.target" ];
+
+        preStart =
+          ''
+            mkdir -m 0755 -p ${stateDir}
+            chown ${ntpUser} ${stateDir}
+          '';
+
+        serviceConfig = {
+          ExecStart = "@${ntp}/bin/ntpd ntpd -g ${ntpFlags}";
+          Type = "forking";
+        };
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix b/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
new file mode 100644
index 000000000000..67a04d48d308
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ntp/openntpd.nix
@@ -0,0 +1,82 @@
+{ pkgs, lib, config, options, ... }:
+
+with lib;
+
+let
+  cfg = config.services.openntpd;
+
+  package = pkgs.openntpd_nixos;
+
+  configFile = ''
+    ${concatStringsSep "\n" (map (s: "server ${s}") cfg.servers)}
+    ${cfg.extraConfig}
+  '';
+
+  pidFile = "/run/openntpd.pid";
+
+in
+{
+  ###### interface
+
+  options.services.openntpd = {
+    enable = mkEnableOption "OpenNTP time synchronization server";
+
+    servers = mkOption {
+      default = config.services.ntp.servers;
+      type = types.listOf types.str;
+      inherit (options.services.ntp.servers) description;
+    };
+
+    extraConfig = mkOption {
+      type = with types; lines;
+      default = "";
+      example = ''
+        listen on 127.0.0.1
+        listen on ::1
+      '';
+      description = ''
+        Additional text appended to <filename>openntpd.conf</filename>.
+      '';
+    };
+
+    extraOptions = mkOption {
+      type = with types; separatedString " ";
+      default = "";
+      example = "-s";
+      description = ''
+        Extra options used when launching openntpd.
+      '';
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+    services.timesyncd.enable = mkForce false;
+
+    # Add ntpctl to the environment for status checking
+    environment.systemPackages = [ package ];
+
+    environment.etc."ntpd.conf".text = configFile;
+
+    users.users.ntp = {
+      uid = config.ids.uids.ntp;
+      description = "OpenNTP daemon user";
+      home = "/var/empty";
+    };
+
+    systemd.services.openntpd = {
+      description = "OpenNTP Server";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" "time-sync.target" ];
+      before = [ "time-sync.target" ];
+      after = [ "dnsmasq.service" "bind.service" "network-online.target" ];
+      serviceConfig = {
+        ExecStart = "${package}/sbin/ntpd -p ${pidFile} ${cfg.extraOptions}";
+        Type = "forking";
+        PIDFile = pidFile;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix b/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix
new file mode 100644
index 000000000000..b0d338a27941
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nullidentdmod.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }: with lib; let
+  cfg = config.services.nullidentdmod;
+
+in {
+  options.services.nullidentdmod = with types; {
+    enable = mkEnableOption "the nullidentdmod identd daemon";
+
+    userid = mkOption {
+      type = nullOr str;
+      description = "User ID to return. Set to null to return a random string each time.";
+      default = null;
+      example = "alice";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.sockets.nullidentdmod = {
+      description = "Socket for identd (NullidentdMod)";
+      listenStreams = [ "113" ];
+      socketConfig.Accept = true;
+      wantedBy = [ "sockets.target" ];
+    };
+
+    systemd.services."nullidentdmod@" = {
+      description = "NullidentdMod service";
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.nullidentdmod}/bin/nullidentdmod${optionalString (cfg.userid != null) " ${cfg.userid}"}";
+        StandardInput = "socket";
+        StandardOutput = "socket";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/nylon.nix b/nixpkgs/nixos/modules/services/networking/nylon.nix
new file mode 100644
index 000000000000..7c171281a926
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/nylon.nix
@@ -0,0 +1,166 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.nylon;
+
+  homeDir = "/var/lib/nylon";
+
+  configFile = cfg: pkgs.writeText "nylon-${cfg.name}.conf" ''
+    [General]
+    No-Simultaneous-Conn=${toString cfg.nrConnections}
+    Log=${if cfg.logging then "1" else "0"}
+    Verbose=${if cfg.verbosity then "1" else "0"}
+
+    [Server]
+    Binding-Interface=${cfg.acceptInterface}
+    Connecting-Interface=${cfg.bindInterface}
+    Port=${toString cfg.port}
+    Allow-IP=${concatStringsSep " " cfg.allowedIPRanges}
+    Deny-IP=${concatStringsSep " " cfg.deniedIPRanges}
+  '';
+
+  nylonOpts = { name, ... }: {
+
+    options = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enables nylon as a running service upon activation.
+        '';
+      };
+
+      name = mkOption {
+        type = types.str;
+        default = "";
+        description = "The name of this nylon instance.";
+      };
+
+      nrConnections = mkOption {
+        type = types.int;
+        default = 10;
+        description = ''
+          The number of allowed simultaneous connections to the daemon, default 10.
+        '';
+      };
+
+      logging = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable logging, default is no logging.
+        '';
+      };
+
+      verbosity = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable verbose output, default is to not be verbose.
+        '';
+      };
+
+      acceptInterface = mkOption {
+        type = types.str;
+        default = "lo";
+        description = ''
+          Tell nylon which interface to listen for client requests on, default is "lo".
+        '';
+      };
+
+      bindInterface = mkOption {
+        type = types.str;
+        default = "enp3s0f0";
+        description = ''
+          Tell nylon which interface to use as an uplink, default is "enp3s0f0".
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 1080;
+        description = ''
+          What port to listen for client requests, default is 1080.
+        '';
+      };
+
+      allowedIPRanges = mkOption {
+        type = with types; listOf str;
+        default = [ "192.168.0.0/16" "127.0.0.1/8" "172.16.0.1/12" "10.0.0.0/8" ];
+        description = ''
+           Allowed client IP ranges are evaluated first, defaults to ARIN IPv4 private ranges:
+             [ "192.168.0.0/16" "127.0.0.0/8" "172.16.0.0/12" "10.0.0.0/8" ]
+        '';
+      };
+
+      deniedIPRanges = mkOption {
+        type = with types; listOf str;
+        default = [ "0.0.0.0/0" ];
+        description = ''
+          Denied client IP ranges, these gets evaluated after the allowed IP ranges, defaults to all IPv4 addresses:
+            [ "0.0.0.0/0" ]
+          To block all other access than the allowed.
+        '';
+      };
+    };
+    config = { name = mkDefault name; };
+  };
+
+  mkNamedNylon = cfg: {
+    "nylon-${cfg.name}" = {
+      description = "Nylon, a lightweight SOCKS proxy server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig =
+      {
+        User = "nylon";
+        Group = "nylon";
+        WorkingDirectory = homeDir;
+        ExecStart = "${pkgs.nylon}/bin/nylon -f -c ${configFile cfg}";
+      };
+    };
+  };
+
+  anyNylons = collect (p: p ? enable) cfg;
+  enabledNylons = filter (p: p.enable == true) anyNylons;
+  nylonUnits = map (nylon: mkNamedNylon nylon) enabledNylons;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.nylon = mkOption {
+      default = {};
+      description = "Collection of named nylon instances";
+      type = with types; loaOf (submodule nylonOpts);
+      internal = true;
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf (length(enabledNylons) > 0) {
+
+    users.users.nylon = {
+      group = "nylon";
+      description = "Nylon SOCKS Proxy";
+      home = homeDir;
+      createHome = true;
+      uid = config.ids.uids.nylon;
+    };
+
+    users.groups.nylon.gid = config.ids.gids.nylon;
+
+    systemd.services = fold (a: b: a // b) {} nylonUnits;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ocserv.nix b/nixpkgs/nixos/modules/services/networking/ocserv.nix
new file mode 100644
index 000000000000..dc26ffeafeef
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ocserv.nix
@@ -0,0 +1,99 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.ocserv;
+
+in
+
+{
+  options.services.ocserv = {
+    enable = mkEnableOption "ocserv";
+
+    config = mkOption {
+      type = types.lines;
+
+      description = ''
+        Configuration content to start an OCServ server.
+
+        For a full configuration reference,please refer to the online documentation
+        (https://ocserv.gitlab.io/www/manual.html), the openconnect
+        recipes (https://github.com/openconnect/recipes) or `man ocserv`.
+      '';
+
+      example = ''
+        # configuration examples from $out/doc without explanatory comments.
+        # for a full reference please look at the installed man pages.
+        auth = "plain[passwd=./sample.passwd]"
+        tcp-port = 443
+        udp-port = 443
+        run-as-user = nobody
+        run-as-group = nogroup
+        socket-file = /run/ocserv-socket
+        server-cert = certs/server-cert.pem
+        server-key = certs/server-key.pem
+        keepalive = 32400
+        dpd = 90
+        mobile-dpd = 1800
+        switch-to-tcp-timeout = 25
+        try-mtu-discovery = false
+        cert-user-oid = 0.9.2342.19200300.100.1.1
+        tls-priorities = "NORMAL:%SERVER_PRECEDENCE:%COMPAT:-VERS-SSL3.0"
+        auth-timeout = 240
+        min-reauth-time = 300
+        max-ban-score = 80
+        ban-reset-time = 1200
+        cookie-timeout = 300
+        deny-roaming = false
+        rekey-time = 172800
+        rekey-method = ssl
+        use-occtl = true
+        pid-file = /run/ocserv.pid
+        device = vpns
+        predictable-ips = true
+        default-domain = example.com
+        ipv4-network = 192.168.1.0
+        ipv4-netmask = 255.255.255.0
+        dns = 192.168.1.2
+        ping-leases = false
+        route = 10.10.10.0/255.255.255.0
+        route = 192.168.0.0/255.255.0.0
+        no-route = 192.168.5.0/255.255.255.0
+        cisco-client-compat = true
+        dtls-legacy = true
+
+        [vhost:www.example.com]
+        auth = "certificate"
+        ca-cert = certs/ca.pem
+        server-cert = certs/server-cert-secp521r1.pem
+        server-key = cersts/certs/server-key-secp521r1.pem
+        ipv4-network = 192.168.2.0
+        ipv4-netmask = 255.255.255.0
+        cert-user-oid = 0.9.2342.19200300.100.1.1
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.ocserv ];
+    environment.etc."ocserv/ocserv.conf".text = cfg.config;
+
+    security.pam.services.ocserv = {};
+
+    systemd.services.ocserv = {
+      description = "OpenConnect SSL VPN server";
+      documentation = [ "man:ocserv(8)" ];
+      after = [ "dbus.service" "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        PrivateTmp = true;
+        PIDFile = "/run/ocserv.pid";
+        ExecStart = "${pkgs.ocserv}/bin/ocserv --foreground --pid-file /run/ocesrv.pid --config /etc/ocserv/ocserv.conf";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ofono.nix b/nixpkgs/nixos/modules/services/networking/ofono.nix
new file mode 100644
index 000000000000..40ef9433de0f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ofono.nix
@@ -0,0 +1,44 @@
+# Ofono daemon.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.ofono;
+
+  plugin_path =
+    lib.concatMapStringsSep ":"
+      (plugin: "${plugin}/lib/ofono/plugins")
+      cfg.plugins
+    ;
+
+in
+
+{
+  ###### interface
+  options = {
+    services.ofono = {
+      enable = mkEnableOption "Ofono";
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = literalExample "[ pkgs.modem-manager-gui ]";
+        description = ''
+          The list of plugins to install.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.dbus.packages = [ pkgs.ofono ];
+
+    systemd.packages = [ pkgs.ofono ];
+
+    systemd.services.ofono.environment.OFONO_PLUGIN_PATH = mkIf (cfg.plugins != []) plugin_path;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/oidentd.nix b/nixpkgs/nixos/modules/services/networking/oidentd.nix
new file mode 100644
index 000000000000..feb84806ba99
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/oidentd.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.oidentd.enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable ‘oidentd’, an implementation of the Ident
+        protocol (RFC 1413).  It allows remote systems to identify the
+        name of the user associated with a TCP connection.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.oidentd.enable {
+    systemd.services.oidentd = {
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Type = "forking";
+      script = "${pkgs.oidentd}/sbin/oidentd -u oidentd -g nogroup";
+    };
+
+    users.users.oidentd = {
+      description = "Ident Protocol daemon user";
+      group = "oidentd";
+      uid = config.ids.uids.oidentd;
+    };
+
+    users.groups.oidentd.gid = config.ids.gids.oidentd;
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/openfire.nix b/nixpkgs/nixos/modules/services/networking/openfire.nix
new file mode 100644
index 000000000000..fe0499d52323
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/openfire.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  ###### interface
+
+  options = {
+
+    services.openfire = {
+
+      enable = mkEnableOption "OpenFire XMPP server";
+
+      usePostgreSQL = mkOption {
+        type = types.bool;
+        default = true;
+        description = "
+          Whether you use PostgreSQL service for your storage back-end.
+        ";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.openfire.enable {
+
+    assertions = singleton
+      { assertion = !(config.services.openfire.usePostgreSQL -> config.services.postgresql.enable);
+        message = "OpenFire configured to use PostgreSQL but services.postgresql.enable is not enabled.";
+      };
+
+    systemd.services.openfire = {
+      description = "OpenFire XMPP server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ] ++
+        optional config.services.openfire.usePostgreSQL "postgresql.service";
+      path = with pkgs; [ jre openfire coreutils which gnugrep gawk gnused ];
+      script = ''
+        export HOME=/tmp
+        mkdir /var/log/openfire || true
+        mkdir /etc/openfire || true
+        for i in ${pkgs.openfire}/conf.inst/*; do
+            if ! test -f /etc/openfire/$(basename $i); then
+                cp $i /etc/openfire/
+            fi
+        done
+        openfire start
+      ''; # */
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/openvpn.nix b/nixpkgs/nixos/modules/services/networking/openvpn.nix
new file mode 100644
index 000000000000..dcd7e9e5fa4c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/openvpn.nix
@@ -0,0 +1,219 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.openvpn;
+
+  inherit (pkgs) openvpn;
+
+  makeOpenVPNJob = cfg: name:
+    let
+
+      path = (getAttr "openvpn-${name}" config.systemd.services).path;
+
+      upScript = ''
+        #! /bin/sh
+        export PATH=${path}
+
+        # For convenience in client scripts, extract the remote domain
+        # name and name server.
+        for var in ''${!foreign_option_*}; do
+          x=(''${!var})
+          if [ "''${x[0]}" = dhcp-option ]; then
+            if [ "''${x[1]}" = DOMAIN ]; then domain="''${x[2]}"
+            elif [ "''${x[1]}" = DNS ]; then nameserver="''${x[2]}"
+            fi
+          fi
+        done
+
+        ${cfg.up}
+        ${optionalString cfg.updateResolvConf
+           "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
+      '';
+
+      downScript = ''
+        #! /bin/sh
+        export PATH=${path}
+        ${optionalString cfg.updateResolvConf
+           "${pkgs.update-resolv-conf}/libexec/openvpn/update-resolv-conf"}
+        ${cfg.down}
+      '';
+
+      configFile = pkgs.writeText "openvpn-config-${name}"
+        ''
+          errors-to-stderr
+          ${optionalString (cfg.up != "" || cfg.down != "" || cfg.updateResolvConf) "script-security 2"}
+          ${cfg.config}
+          ${optionalString (cfg.up != "" || cfg.updateResolvConf)
+              "up ${pkgs.writeScript "openvpn-${name}-up" upScript}"}
+          ${optionalString (cfg.down != "" || cfg.updateResolvConf)
+              "down ${pkgs.writeScript "openvpn-${name}-down" downScript}"}
+          ${optionalString (cfg.authUserPass != null)
+              "auth-user-pass ${pkgs.writeText "openvpn-credentials-${name}" ''
+                ${cfg.authUserPass.username}
+                ${cfg.authUserPass.password}
+              ''}"}
+        '';
+
+    in {
+      description = "OpenVPN instance ‘${name}’";
+
+      wantedBy = optional cfg.autoStart "multi-user.target";
+      after = [ "network.target" ];
+
+      path = [ pkgs.iptables pkgs.iproute pkgs.nettools ];
+
+      serviceConfig.ExecStart = "@${openvpn}/sbin/openvpn openvpn --suppress-timestamps --config ${configFile}";
+      serviceConfig.Restart = "always";
+      serviceConfig.Type = "notify";
+    };
+
+in
+
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "openvpn" "enable" ] "")
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.openvpn.servers = mkOption {
+      default = {};
+
+      example = literalExample ''
+        {
+          server = {
+            config = '''
+              # Simplest server configuration: https://community.openvpn.net/openvpn/wiki/StaticKeyMiniHowto
+              # server :
+              dev tun
+              ifconfig 10.8.0.1 10.8.0.2
+              secret /root/static.key
+            ''';
+            up = "ip route add ...";
+            down = "ip route del ...";
+          };
+
+          client = {
+            config = '''
+              client
+              remote vpn.example.org
+              dev tun
+              proto tcp-client
+              port 8080
+              ca /root/.vpn/ca.crt
+              cert /root/.vpn/alice.crt
+              key /root/.vpn/alice.key
+            ''';
+            up = "echo nameserver $nameserver | ''${pkgs.openresolv}/sbin/resolvconf -m 0 -a $dev";
+            down = "''${pkgs.openresolv}/sbin/resolvconf -d $dev";
+          };
+        }
+      '';
+
+      description = ''
+        Each attribute of this option defines a systemd service that
+        runs an OpenVPN instance.  These can be OpenVPN servers or
+        clients.  The name of each systemd service is
+        <literal>openvpn-<replaceable>name</replaceable>.service</literal>,
+        where <replaceable>name</replaceable> is the corresponding
+        attribute name.
+      '';
+
+      type = with types; attrsOf (submodule {
+
+        options = {
+
+          config = mkOption {
+            type = types.lines;
+            description = ''
+              Configuration of this OpenVPN instance.  See
+              <citerefentry><refentrytitle>openvpn</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+              for details.
+
+              To import an external config file, use the following definition:
+              <literal>config = "config /path/to/config.ovpn"</literal>
+            '';
+          };
+
+          up = mkOption {
+            default = "";
+            type = types.lines;
+            description = ''
+              Shell commands executed when the instance is starting.
+            '';
+          };
+
+          down = mkOption {
+            default = "";
+            type = types.lines;
+            description = ''
+              Shell commands executed when the instance is shutting down.
+            '';
+          };
+
+          autoStart = mkOption {
+            default = true;
+            type = types.bool;
+            description = "Whether this OpenVPN instance should be started automatically.";
+          };
+
+          updateResolvConf = mkOption {
+            default = false;
+            type = types.bool;
+            description = ''
+              Use the script from the update-resolv-conf package to automatically
+              update resolv.conf with the DNS information provided by openvpn. The
+              script will be run after the "up" commands and before the "down" commands.
+            '';
+          };
+
+          authUserPass = mkOption {
+            default = null;
+            description = ''
+              This option can be used to store the username / password credentials
+              with the "auth-user-pass" authentication method.
+
+              WARNING: Using this option will put the credentials WORLD-READABLE in the Nix store!
+            '';
+            type = types.nullOr (types.submodule {
+
+              options = {
+                username = mkOption {
+                  description = "The username to store inside the credentials file.";
+                  type = types.str;
+                };
+
+                password = mkOption {
+                  description = "The password to store inside the credentials file.";
+                  type = types.str;
+                };
+              };
+            });
+          };
+        };
+
+      });
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf (cfg.servers != {}) {
+
+    systemd.services = listToAttrs (mapAttrsFlatten (name: value: nameValuePair "openvpn-${name}" (makeOpenVPNJob value name)) cfg.servers);
+
+    environment.systemPackages = [ openvpn ];
+
+    boot.kernelModules = [ "tun" ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ostinato.nix b/nixpkgs/nixos/modules/services/networking/ostinato.nix
new file mode 100644
index 000000000000..5e8cce5b89aa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ostinato.nix
@@ -0,0 +1,104 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  pkg = pkgs.ostinato;
+  cfg = config.services.ostinato;
+  configFile = pkgs.writeText "drone.ini" ''
+    [General]
+    RateAccuracy=${cfg.rateAccuracy}
+
+    [RpcServer]
+    Address=${cfg.rpcServer.address}
+
+    [PortList]
+    Include=${concatStringsSep "," cfg.portList.include}
+    Exclude=${concatStringsSep "," cfg.portList.exclude}
+  '';
+
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.ostinato = {
+
+      enable = mkEnableOption "Ostinato agent-controller (Drone)";
+
+      port = mkOption {
+        type = types.int;
+        default = 7878;
+        description = ''
+          Port to listen on.
+        '';
+      };
+
+      rateAccuracy = mkOption {
+        type = types.enum [ "High" "Low" ];
+        default = "High";
+        description = ''
+          To ensure that the actual transmit rate is as close as possible to
+          the configured transmit rate, Drone runs a busy-wait loop.
+          While this provides the maximum accuracy possible, the CPU
+          utilization is 100% while the transmit is on. You can however,
+          sacrifice the accuracy to reduce the CPU load.
+        '';
+      };
+
+      rpcServer = {
+        address = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            By default, the Drone RPC server will listen on all interfaces and
+            local IPv4 adresses for incoming connections from clients.  Specify
+            a single IPv4 or IPv6 address if you want to restrict that.
+            To listen on any IPv6 address, use ::
+          '';
+        };
+      };
+
+      portList = {
+        include = mkOption {
+          type = types.listOf types.str;
+          default = [];
+          example = ''[ "eth*" "lo*" ]'';
+          description = ''
+            For a port to pass the filter and appear on the port list managed
+            by drone, it be allowed by this include list.
+          '';
+        };
+        exclude = mkOption {
+          type = types.listOf types.str;
+          default = [];
+          example = ''[ "usbmon*" "eth0" ]'';
+          description = ''
+            A list of ports does not appear on the port list managed by drone.
+          '';
+        };
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkg ];
+
+    systemd.services.drone = {
+      description = "Ostinato agent-controller";
+      wantedBy = [ "multi-user.target" ];
+      script = ''
+        ${pkg}/bin/drone ${toString cfg.port} ${configFile}
+      '';
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/owamp.nix b/nixpkgs/nixos/modules/services/networking/owamp.nix
new file mode 100644
index 000000000000..637ed618b893
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/owamp.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.owamp;
+in
+{
+
+  ###### interface
+
+  options = {
+    services.owamp.enable = mkEnableOption ''Enable OWAMP server'';
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    users.users.owamp = {
+      group = "owamp";
+      description = "Owamp daemon";
+      isSystemUser = true;
+    };
+
+    users.groups.owamp = { };
+
+    systemd.services.owamp = {
+      description = "Owamp server";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart="${pkgs.owamp}/bin/owampd -R /run/owamp -d /run/owamp -v -Z ";
+        PrivateTmp = true;
+        Restart = "always";
+        Type="simple";
+        User = "owamp";
+        Group = "owamp";
+        RuntimeDirectory = "owamp";
+        StateDirectory = "owamp";
+        AmbientCapabilities = "cap_net_bind_service";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix b/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix
new file mode 100644
index 000000000000..6ff181377fcc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/pdns-recursor.nix
@@ -0,0 +1,224 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  dataDir  = "/var/lib/pdns-recursor";
+  username = "pdns-recursor";
+
+  cfg = config.services.pdns-recursor;
+
+  oneOrMore  = type: with types; either type (listOf type);
+  valueType  = with types; oneOf [ int str bool path ];
+  configType = with types; attrsOf (nullOr (oneOrMore valueType));
+
+  toBool    = val: if val then "yes" else "no";
+  serialize = val: with types;
+         if str.check       val then val
+    else if int.check       val then toString val
+    else if path.check      val then toString val
+    else if bool.check      val then toBool val
+    else if builtins.isList val then (concatMapStringsSep "," serialize val)
+    else "";
+
+  configFile = pkgs.writeText "recursor.conf"
+    (concatStringsSep "\n"
+      (flip mapAttrsToList cfg.settings
+        (name: val: "${name}=${serialize val}")));
+
+  mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
+
+in {
+  options.services.pdns-recursor = {
+    enable = mkEnableOption "PowerDNS Recursor, a recursive DNS server";
+
+    dns.address = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      description = ''
+        IP address Recursor DNS server will bind to.
+      '';
+    };
+
+    dns.port = mkOption {
+      type = types.int;
+      default = 53;
+      description = ''
+        Port number Recursor DNS server will bind to.
+      '';
+    };
+
+    dns.allowFrom = mkOption {
+      type = types.listOf types.str;
+      default = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" ];
+      example = [ "0.0.0.0/0" ];
+      description = ''
+        IP address ranges of clients allowed to make DNS queries.
+      '';
+    };
+
+    api.address = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      description = ''
+        IP address Recursor REST API server will bind to.
+      '';
+    };
+
+    api.port = mkOption {
+      type = types.int;
+      default = 8082;
+      description = ''
+        Port number Recursor REST API server will bind to.
+      '';
+    };
+
+    api.allowFrom = mkOption {
+      type = types.listOf types.str;
+      default = [ "0.0.0.0/0" ];
+      description = ''
+        IP address ranges of clients allowed to make API requests.
+      '';
+    };
+
+    exportHosts = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+       Whether to export names and IP addresses defined in /etc/hosts.
+      '';
+    };
+
+    forwardZones = mkOption {
+      type = types.attrs;
+      default = {};
+      description = ''
+        DNS zones to be forwarded to other authoritative servers.
+      '';
+    };
+
+    forwardZonesRecurse = mkOption {
+      type = types.attrs;
+      example = { eth = "127.0.0.1:5353"; };
+      default = {};
+      description = ''
+        DNS zones to be forwarded to other recursive servers.
+      '';
+    };
+
+    dnssecValidation = mkOption {
+      type = types.enum ["off" "process-no-validate" "process" "log-fail" "validate"];
+      default = "validate";
+      description = ''
+        Controls the level of DNSSEC processing done by the PowerDNS Recursor.
+        See https://doc.powerdns.com/md/recursor/dnssec/ for a detailed explanation.
+      '';
+    };
+
+    serveRFC1918 = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to directly resolve the RFC1918 reverse-mapping domains:
+        <literal>10.in-addr.arpa</literal>,
+        <literal>168.192.in-addr.arpa</literal>,
+        <literal>16-31.172.in-addr.arpa</literal>
+        This saves load on the AS112 servers.
+      '';
+    };
+
+    settings = mkOption {
+      type = configType;
+      default = { };
+      example = literalExample ''
+        {
+          loglevel = 8;
+          log-common-errors = true;
+        }
+      '';
+      description = ''
+        PowerDNS Recursor settings. Use this option to configure Recursor
+        settings not exposed in a NixOS option or to bypass one.
+        See the full documentation at
+        <link xlink:href="https://doc.powerdns.com/recursor/settings.html"/>
+        for the available options.
+      '';
+    };
+
+    luaConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        The content Lua configuration file for PowerDNS Recursor. See
+        <link xlink:href="https://doc.powerdns.com/recursor/lua-config/index.html"/>.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.pdns-recursor.settings = mkDefaultAttrs {
+      local-address = cfg.dns.address;
+      local-port    = cfg.dns.port;
+      allow-from    = cfg.dns.allowFrom;
+
+      webserver-address    = cfg.api.address;
+      webserver-port       = cfg.api.port;
+      webserver-allow-from = cfg.api.allowFrom;
+
+      forward-zones         = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones;
+      forward-zones-recurse = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZonesRecurse;
+      export-etc-hosts = cfg.exportHosts;
+      dnssec           = cfg.dnssecValidation;
+      serve-rfc1918    = cfg.serveRFC1918;
+      lua-config-file  = pkgs.writeText "recursor.lua" cfg.luaConfig;
+
+      log-timestamp  = false;
+      disable-syslog = true;
+    };
+
+    users.users.${username} = {
+      home = dataDir;
+      createHome = true;
+      uid = config.ids.uids.pdns-recursor;
+      description = "PowerDNS Recursor daemon user";
+    };
+
+    systemd.services.pdns-recursor = {
+      unitConfig.Documentation = "man:pdns_recursor(1) man:rec_control(1)";
+      description = "PowerDNS recursive server";
+      wantedBy = [ "multi-user.target" ];
+      after    = [ "network.target" ];
+
+      serviceConfig = {
+        User = username;
+        Restart    ="on-failure";
+        RestartSec = "5";
+        PrivateTmp = true;
+        PrivateDevices = true;
+        AmbientCapabilities = "cap_net_bind_service";
+        ExecStart = ''${pkgs.pdns-recursor}/bin/pdns_recursor \
+          --config-dir=${dataDir} \
+          --socket-dir=${dataDir}
+        '';
+      };
+
+      preStart = ''
+        # Link configuration file into recursor home directory
+        configPath=${dataDir}/recursor.conf
+        if [ "$(realpath $configPath)" != "${configFile}" ]; then
+          rm -f $configPath
+          ln -s ${configFile} $configPath
+        fi
+      '';
+    };
+  };
+
+  imports = [
+   (mkRemovedOptionModule [ "services" "pdns-recursor" "extraConfig" ]
+     "To change extra Recursor settings use services.pdns-recursor.settings instead.")
+  ];
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/pdnsd.nix b/nixpkgs/nixos/modules/services/networking/pdnsd.nix
new file mode 100644
index 000000000000..24b5bbc5104e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/pdnsd.nix
@@ -0,0 +1,91 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pdnsd;
+  pdnsd = pkgs.pdnsd;
+  pdnsdUser = "pdnsd";
+  pdnsdGroup = "pdnsd";
+  pdnsdConf = pkgs.writeText "pdnsd.conf"
+    ''
+      global {
+        run_as=${pdnsdUser};
+        cache_dir="${cfg.cacheDir}";
+        ${cfg.globalConfig}
+      }
+
+      server {
+        ${cfg.serverConfig}
+      }
+      ${cfg.extraConfig}
+    '';
+in
+
+{ options =
+    { services.pdnsd =
+        { enable = mkEnableOption "pdnsd";
+
+          cacheDir = mkOption {
+            type = types.str;
+            default = "/var/cache/pdnsd";
+            description = "Directory holding the pdnsd cache";
+          };
+
+          globalConfig = mkOption {
+            type = types.lines;
+            default = "";
+            description = ''
+              Global configuration that should be added to the global directory
+              of <literal>pdnsd.conf</literal>.
+            '';
+          };
+
+          serverConfig = mkOption {
+            type = types.lines;
+            default = "";
+            description = ''
+              Server configuration that should be added to the server directory
+              of <literal>pdnsd.conf</literal>.
+            '';
+          };
+
+          extraConfig = mkOption {
+            type = types.lines;
+            default = "";
+            description = ''
+              Extra configuration directives that should be added to
+              <literal>pdnsd.conf</literal>.
+            '';
+          };
+        };
+    };
+
+  config = mkIf cfg.enable {
+    users.users.${pdnsdUser} = {
+      uid = config.ids.uids.pdnsd;
+      group = pdnsdGroup;
+      description = "pdnsd user";
+    };
+
+    users.groups.${pdnsdGroup} = {
+      gid = config.ids.gids.pdnsd;
+    };
+
+    systemd.services.pdnsd =
+      { wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        preStart =
+          ''
+            mkdir -p "${cfg.cacheDir}"
+            touch "${cfg.cacheDir}/pdnsd.cache"
+            chown -R ${pdnsdUser}:${pdnsdGroup} "${cfg.cacheDir}"
+          '';
+        description = "pdnsd";
+        serviceConfig =
+          {
+            ExecStart = "${pdnsd}/bin/pdnsd -c ${pdnsdConf}";
+          };
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/pixiecore.nix b/nixpkgs/nixos/modules/services/networking/pixiecore.nix
new file mode 100644
index 000000000000..85aa40784af8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/pixiecore.nix
@@ -0,0 +1,134 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pixiecore;
+in
+{
+  meta.maintainers = with maintainers; [ bbigras danderson ];
+
+  options = {
+    services.pixiecore = {
+      enable = mkEnableOption "Pixiecore";
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports (67, 69 UDP and 4011, 'port', 'statusPort' TCP) in the firewall for Pixiecore.
+        '';
+      };
+
+      mode = mkOption {
+        description = "Which mode to use";
+        default = "boot";
+        type = types.enum [ "api" "boot" ];
+      };
+
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Log more things that aren't directly related to booting a recognized client";
+      };
+
+      dhcpNoBind = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Handle DHCP traffic without binding to the DHCP server port";
+      };
+
+      kernel = mkOption {
+        type = types.str or types.path;
+        default = "";
+        description = "Kernel path. Ignored unless mode is set to 'boot'";
+      };
+
+      initrd = mkOption {
+        type = types.str or types.path;
+        default = "";
+        description = "Initrd path. Ignored unless mode is set to 'boot'";
+      };
+
+      cmdLine = mkOption {
+        type = types.str;
+        default = "";
+        description = "Kernel commandline arguments. Ignored unless mode is set to 'boot'";
+      };
+
+      listen = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = "IPv4 address to listen on";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 80;
+        description = "Port to listen on for HTTP";
+      };
+
+      statusPort = mkOption {
+        type = types.port;
+        default = 80;
+        description = "HTTP port for status information (can be the same as --port)";
+      };
+
+      apiServer = mkOption {
+        type = types.str;
+        example = "localhost:8080";
+        description = "host:port to connect to the API. Ignored unless mode is set to 'api'";
+      };
+
+      extraArguments = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Additional command line arguments to pass to Pixiecore";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups.pixiecore = {};
+    users.users.pixiecore = {
+      description = "Pixiecore daemon user";
+      group = "pixiecore";
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 4011 cfg.port cfg.statusPort ];
+      allowedUDPPorts = [ 67 69 ];
+    };
+
+    systemd.services.pixiecore = {
+      description = "Pixiecore server";
+      after = [ "network.target"];
+      wants = [ "network.target"];
+      wantedBy = [ "multi-user.target"];
+      serviceConfig = {
+        User = "pixiecore";
+        Restart = "always";
+        AmbientCapabilities = [ "cap_net_bind_service" ] ++ optional cfg.dhcpNoBind "cap_net_raw";
+        ExecStart =
+          let
+            argString =
+              if cfg.mode == "boot"
+              then [ "boot" cfg.kernel ]
+                   ++ optional (cfg.initrd != "") cfg.initrd
+                   ++ optionals (cfg.cmdLine != "") [ "--cmdline" cfg.cmdLine ]
+              else [ "api" cfg.apiServer ];
+          in
+            ''
+              ${pkgs.pixiecore}/bin/pixiecore \
+                ${lib.escapeShellArgs argString} \
+                ${optionalString cfg.debug "--debug"} \
+                ${optionalString cfg.dhcpNoBind "--dhcp-no-bind"} \
+                --listen-addr ${lib.escapeShellArg cfg.listen} \
+                --port ${toString cfg.port} \
+                --status-port ${toString cfg.statusPort} \
+                ${escapeShellArgs cfg.extraArguments}
+              '';
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/polipo.nix b/nixpkgs/nixos/modules/services/networking/polipo.nix
new file mode 100644
index 000000000000..1ff9388346b6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/polipo.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.polipo;
+
+  polipoConfig = pkgs.writeText "polipo.conf" ''
+    proxyAddress = ${cfg.proxyAddress}
+    proxyPort = ${toString cfg.proxyPort}
+    allowedClients = ${concatStringsSep ", " cfg.allowedClients}
+    ${optionalString (cfg.parentProxy != "") "parentProxy = ${cfg.parentProxy}" }
+    ${optionalString (cfg.socksParentProxy != "") "socksParentProxy = ${cfg.socksParentProxy}" }
+    ${config.services.polipo.extraConfig}
+  '';
+
+in
+
+{
+
+  options = {
+
+    services.polipo = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to run the polipo caching web proxy.";
+      };
+
+      proxyAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "IP address on which Polipo will listen.";
+      };
+
+      proxyPort = mkOption {
+        type = types.int;
+        default = 8123;
+        description = "TCP port on which Polipo will listen.";
+      };
+
+      allowedClients = mkOption {
+        type = types.listOf types.str;
+        default = [ "127.0.0.1" "::1" ];
+        example = [ "127.0.0.1" "::1" "134.157.168.0/24" "2001:660:116::/48" ];
+        description = ''
+          List of IP addresses or network addresses that may connect to Polipo.
+        '';
+      };
+
+      parentProxy = mkOption {
+        type = types.str;
+        default = "";
+        example = "localhost:8124";
+        description = ''
+          Hostname and port number of an HTTP parent proxy;
+          it should have the form ‘host:port’.
+        '';
+      };
+
+      socksParentProxy = mkOption {
+        type = types.str;
+        default = "";
+        example = "localhost:9050";
+        description = ''
+          Hostname and port number of an SOCKS parent proxy;
+          it should have the form ‘host:port’.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Polio configuration. Contents will be added
+          verbatim to the configuration file.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.polipo =
+      { uid = config.ids.uids.polipo;
+        description = "Polipo caching proxy user";
+        home = "/var/cache/polipo";
+        createHome = true;
+      };
+
+    users.groups.polipo =
+      { gid = config.ids.gids.polipo;
+        members = [ "polipo" ];
+      };
+
+    systemd.services.polipo = {
+      description = "caching web proxy";
+      after = [ "network.target" "nss-lookup.target" ];
+      wantedBy = [ "multi-user.target"];
+      serviceConfig = {
+        ExecStart  = "${pkgs.polipo}/bin/polipo -c ${polipoConfig}";
+        User = "polipo";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/powerdns.nix b/nixpkgs/nixos/modules/services/networking/powerdns.nix
new file mode 100644
index 000000000000..ba05e15389f6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/powerdns.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.powerdns;
+  configDir = pkgs.writeTextDir "pdns.conf" "${cfg.extraConfig}";
+in {
+  options = {
+    services.powerdns = {
+      enable = mkEnableOption "Powerdns domain name server";
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "launch=bind";
+        description = ''
+          Extra lines to be added verbatim to pdns.conf.
+          Powerdns will chroot to /var/lib/powerdns.
+          So any file, powerdns is supposed to be read,
+          should be in /var/lib/powerdns and needs to specified
+          relative to the chroot.
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.powerdns.enable {
+    systemd.services.pdns = {
+      unitConfig.Documentation = "man:pdns_server(1) man:pdns_control(1)";
+      description = "Powerdns name server";
+      wantedBy = [ "multi-user.target" ];
+      after = ["network.target" "mysql.service" "postgresql.service" "openldap.service"];
+
+      serviceConfig = {
+        Restart="on-failure";
+        RestartSec="1";
+        StartLimitInterval="0";
+        PrivateDevices=true;
+        CapabilityBoundingSet="CAP_CHOWN CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_CHROOT";
+        NoNewPrivileges=true;
+        ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p /var/lib/powerdns";
+        ExecStart = "${pkgs.powerdns}/bin/pdns_server --setuid=nobody --setgid=nogroup --chroot=/var/lib/powerdns --socket-dir=/ --daemon=no --guardian=no --disable-syslog --write-pid=no --config-dir=${configDir}";
+        ProtectSystem="full";
+        ProtectHome=true;
+        RestrictAddressFamilies="AF_UNIX AF_INET AF_INET6";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/pppd.nix b/nixpkgs/nixos/modules/services/networking/pppd.nix
new file mode 100644
index 000000000000..c1cbdb461765
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/pppd.nix
@@ -0,0 +1,136 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.pppd;
+in
+{
+  meta = {
+    maintainers = with maintainers; [ danderson ];
+  };
+
+  options = {
+    services.pppd = {
+      enable = mkEnableOption "pppd";
+
+      package = mkOption {
+        default = pkgs.ppp;
+        defaultText = "pkgs.ppp";
+        type = types.package;
+        description = "pppd package to use.";
+      };
+
+      peers = mkOption {
+        default = {};
+        description = "pppd peers.";
+        type = types.attrsOf (types.submodule (
+          { name, ... }:
+          {
+            options = {
+              name = mkOption {
+                type = types.str;
+                default = name;
+                example = "dialup";
+                description = "Name of the PPP peer.";
+              };
+
+              enable = mkOption {
+                type = types.bool;
+                default = true;
+                example = false;
+                description = "Whether to enable this PPP peer.";
+              };
+
+              autostart = mkOption {
+                type = types.bool;
+                default = true;
+                example = false;
+                description = "Whether the PPP session is automatically started at boot time.";
+              };
+
+              config = mkOption {
+                type = types.lines;
+                default = "";
+                description = "pppd configuration for this peer, see the pppd(8) man page.";
+              };
+            };
+          }));
+      };
+    };
+  };
+
+  config = let
+    enabledConfigs = filter (f: f.enable) (attrValues cfg.peers);
+
+    mkEtc = peerCfg: {
+      name = "ppp/peers/${peerCfg.name}";
+      value.text = peerCfg.config;
+    };
+
+    mkSystemd = peerCfg: {
+      name = "pppd-${peerCfg.name}";
+      value = {
+        restartTriggers = [ config.environment.etc."ppp/peers/${peerCfg.name}".source ];
+        before = [ "network.target" ];
+        wants = [ "network.target" ];
+        after = [ "network-pre.target" ];
+        environment = {
+          # pppd likes to write directly into /var/run. This is rude
+          # on a modern system, so we use libredirect to transparently
+          # move those files into /run/pppd.
+          LD_PRELOAD = "${pkgs.libredirect}/lib/libredirect.so";
+          NIX_REDIRECTS = "/var/run=/run/pppd";
+        };
+        serviceConfig = {
+          ExecStart = "${getBin cfg.package}/sbin/pppd call ${peerCfg.name} nodetach nolog";
+          Restart = "always";
+          RestartSec = 5;
+
+          AmbientCapabilities = "CAP_SYS_TTY_CONFIG CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_ADMIN";
+          CapabilityBoundingSet = "CAP_SYS_TTY_CONFIG CAP_NET_ADMIN CAP_NET_RAW CAP_SYS_ADMIN";
+          KeyringMode = "private";
+          LockPersonality = true;
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateMounts = true;
+          PrivateTmp = true;
+          ProtectControlGroups = true;
+          ProtectHome = true;
+          ProtectHostname = true;
+          ProtectKernelModules = true;
+          # pppd can be configured to tweak kernel settings.
+          ProtectKernelTunables = false;
+          ProtectSystem = "strict";
+          RemoveIPC = true;
+          RestrictAddressFamilies = "AF_PACKET AF_UNIX AF_PPPOX AF_ATMPVC AF_ATMSVC AF_INET AF_INET6 AF_IPX";
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          SecureBits = "no-setuid-fixup-locked noroot-locked";
+          SystemCallFilter = "@system-service";
+          SystemCallArchitectures = "native";
+
+          # All pppd instances on a system must share a runtime
+          # directory in order for PPP multilink to work correctly. So
+          # we give all instances the same /run/pppd directory to store
+          # things in.
+          #
+          # For the same reason, we can't set PrivateUsers=true, because
+          # all instances need to run as the same user to access the
+          # multilink database.
+          RuntimeDirectory = "pppd";
+          RuntimeDirectoryPreserve = true;
+        };
+        wantedBy = mkIf peerCfg.autostart [ "multi-user.target" ];
+      };
+    };
+
+    etcFiles = listToAttrs (map mkEtc enabledConfigs);
+    systemdConfigs = listToAttrs (map mkSystemd enabledConfigs);
+
+  in mkIf cfg.enable {
+    environment.etc = etcFiles;
+    systemd.services = systemdConfigs;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/pptpd.nix b/nixpkgs/nixos/modules/services/networking/pptpd.nix
new file mode 100644
index 000000000000..3e7753b9dd35
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/pptpd.nix
@@ -0,0 +1,124 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+  options = {
+    services.pptpd = {
+      enable = mkEnableOption "pptpd, the Point-to-Point Tunneling Protocol daemon";
+
+      serverIp = mkOption {
+        type        = types.str;
+        description = "The server-side IP address.";
+        default     = "10.124.124.1";
+      };
+
+      clientIpRange = mkOption {
+        type        = types.str;
+        description = "The range from which client IPs are drawn.";
+        default     = "10.124.124.2-11";
+      };
+
+      maxClients = mkOption {
+        type        = types.int;
+        description = "The maximum number of simultaneous connections.";
+        default     = 10;
+      };
+
+      extraPptpdOptions = mkOption {
+        type        = types.lines;
+        description = "Adds extra lines to the pptpd configuration file.";
+        default     = "";
+      };
+
+      extraPppdOptions = mkOption {
+        type        = types.lines;
+        description = "Adds extra lines to the pppd options file.";
+        default     = "";
+        example     = ''
+          ms-dns 8.8.8.8
+          ms-dns 8.8.4.4
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.pptpd.enable {
+    systemd.services.pptpd = let
+      cfg = config.services.pptpd;
+
+      pptpd-conf = pkgs.writeText "pptpd.conf" ''
+        # Inspired from pptpd-1.4.0/samples/pptpd.conf
+        ppp ${ppp-pptpd-wrapped}/bin/pppd
+        option ${pppd-options}
+        pidfile /run/pptpd.pid
+        localip ${cfg.serverIp}
+        remoteip ${cfg.clientIpRange}
+        connections ${toString cfg.maxClients} # (Will get harmless warning if inconsistent with IP range)
+
+        # Extra
+        ${cfg.extraPptpdOptions}
+      '';
+
+      pppd-options = pkgs.writeText "ppp-options-pptpd.conf" ''
+        # From: cat pptpd-1.4.0/samples/options.pptpd | grep -v ^# | grep -v ^$
+        name pptpd
+        refuse-pap
+        refuse-chap
+        refuse-mschap
+        require-mschap-v2
+        require-mppe-128
+        proxyarp
+        lock
+        nobsdcomp
+        novj
+        novjccomp
+        nologfd
+
+        # Extra:
+        ${cfg.extraPppdOptions}
+      '';
+
+      ppp-pptpd-wrapped = pkgs.stdenv.mkDerivation {
+        name         = "ppp-pptpd-wrapped";
+        phases       = [ "installPhase" ];
+        buildInputs  = with pkgs; [ makeWrapper ];
+        installPhase = ''
+          mkdir -p $out/bin
+          makeWrapper ${pkgs.ppp}/bin/pppd $out/bin/pppd \
+            --set LD_PRELOAD    "${pkgs.libredirect}/lib/libredirect.so" \
+            --set NIX_REDIRECTS "/etc/ppp=/etc/ppp-pptpd"
+        '';
+      };
+    in {
+      description = "pptpd server";
+
+      requires = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        mkdir -p -m 700 /etc/ppp-pptpd
+
+        secrets="/etc/ppp-pptpd/chap-secrets"
+
+        [ -f "$secrets" ] || cat > "$secrets" << EOF
+        # From: pptpd-1.4.0/samples/chap-secrets
+        # Secrets for authentication using CHAP
+        # client	server	secret		IP addresses
+        #username	pptpd	password	*
+        EOF
+
+        chown root.root "$secrets"
+        chmod 600 "$secrets"
+      '';
+
+      serviceConfig = {
+        ExecStart = "${pkgs.pptpd}/bin/pptpd --conf ${pptpd-conf}";
+        KillMode  = "process";
+        Restart   = "on-success";
+        Type      = "forking";
+        PIDFile   = "/run/pptpd.pid";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/prayer.nix b/nixpkgs/nixos/modules/services/networking/prayer.nix
new file mode 100644
index 000000000000..f04dac01d9b8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/prayer.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) prayer;
+
+  cfg = config.services.prayer;
+
+  stateDir = "/var/lib/prayer";
+
+  prayerUser = "prayer";
+  prayerGroup = "prayer";
+
+  prayerExtraCfg = pkgs.writeText "extraprayer.cf" ''
+    prefix = "${prayer}"
+    var_prefix = "${stateDir}"
+    prayer_user = "${prayerUser}"
+    prayer_group = "${prayerGroup}"
+    sendmail_path = "/run/wrappers/bin/sendmail"
+
+    use_http_port ${cfg.port}
+
+    ${cfg.extraConfig}
+  '';
+
+  prayerCfg = pkgs.runCommand "prayer.cf" { preferLocalBuild = true; } ''
+    # We have to remove the http_port 80, or it will start a server there
+    cat ${prayer}/etc/prayer.cf | grep -v http_port > $out
+    cat ${prayerExtraCfg} >> $out
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.prayer = {
+
+      enable = mkEnableOption "the prayer webmail http server";
+
+      port = mkOption {
+        default = "2080";
+        description = ''
+          Port the prayer http server is listening to.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "" ;
+        description = ''
+          Extra configuration. Contents will be added verbatim to the configuration file.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.prayer.enable {
+    environment.systemPackages = [ prayer ];
+
+    users.users.${prayerUser} =
+      { uid = config.ids.uids.prayer;
+        description = "Prayer daemon user";
+        home = stateDir;
+      };
+
+    users.groups.${prayerGroup} =
+      { gid = config.ids.gids.prayer; };
+
+    systemd.services.prayer = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Type = "forking";
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+        chown ${prayerUser}.${prayerGroup} ${stateDir}
+      '';
+      script = "${prayer}/sbin/prayer --config-file=${prayerCfg}";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/privoxy.nix b/nixpkgs/nixos/modules/services/networking/privoxy.nix
new file mode 100644
index 000000000000..1f41c720adf5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/privoxy.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) privoxy;
+
+  cfg = config.services.privoxy;
+
+  confFile = pkgs.writeText "privoxy.conf" ''
+    user-manual ${privoxy}/share/doc/privoxy/user-manual
+    confdir ${privoxy}/etc/
+    listen-address  ${cfg.listenAddress}
+    enable-edit-actions ${if (cfg.enableEditActions == true) then "1" else "0"}
+    ${concatMapStrings (f: "actionsfile ${f}\n") cfg.actionsFiles}
+    ${concatMapStrings (f: "filterfile ${f}\n") cfg.filterFiles}
+    ${cfg.extraConfig}
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.privoxy = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Privoxy non-caching filtering proxy.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8118";
+        description = ''
+          Address the proxy server is listening to.
+        '';
+      };
+
+      actionsFiles = mkOption {
+        type = types.listOf types.str;
+        example = [ "match-all.action" "default.action" "/etc/privoxy/user.action" ];
+        default = [ "match-all.action" "default.action" ];
+        description = ''
+          List of paths to Privoxy action files.
+          These paths may either be absolute or relative to the privoxy configuration directory.
+        '';
+      };
+
+      filterFiles = mkOption {
+        type = types.listOf types.str;
+        example = [ "default.filter" "/etc/privoxy/user.filter" ];
+        default = [ "default.filter" ];
+        description = ''
+          List of paths to Privoxy filter files.
+          These paths may either be absolute or relative to the privoxy configuration directory.
+        '';
+      };
+
+      enableEditActions = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not the web-based actions file editor may be used.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "" ;
+        description = ''
+          Extra configuration. Contents will be added verbatim to the configuration file.
+        '';
+      };
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.privoxy = {
+      isSystemUser = true;
+      home = "/var/empty";
+      group = "privoxy";
+    };
+
+    users.groups.privoxy = {};
+
+    systemd.services.privoxy = {
+      description = "Filtering web proxy";
+      after = [ "network.target" "nss-lookup.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${privoxy}/bin/privoxy --no-daemon --user privoxy ${confFile}";
+
+      serviceConfig.PrivateDevices = true;
+      serviceConfig.PrivateTmp = true;
+      serviceConfig.ProtectHome = true;
+      serviceConfig.ProtectSystem = "full";
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/prosody.nix b/nixpkgs/nixos/modules/services/networking/prosody.nix
new file mode 100644
index 000000000000..cdd341c9fb62
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/prosody.nix
@@ -0,0 +1,882 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.prosody;
+
+  sslOpts = { ... }: {
+
+    options = {
+
+      key = mkOption {
+        type = types.path;
+        description = "Path to the key file.";
+      };
+
+      # TODO: rename to certificate to match the prosody config
+      cert = mkOption {
+        type = types.path;
+        description = "Path to the certificate file.";
+      };
+
+      extraOptions = mkOption {
+        type = types.attrs;
+        default = {};
+        description = "Extra SSL configuration options.";
+      };
+
+    };
+  };
+
+  discoOpts = {
+    options = {
+      url = mkOption {
+        type = types.str;
+        description = "URL of the endpoint you want to make discoverable";
+      };
+      description = mkOption {
+        type = types.str;
+        description = "A short description of the endpoint you want to advertise";
+      };
+    };
+  };
+
+  moduleOpts = {
+    # Required for compliance with https://compliance.conversations.im/about/
+    roster = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Allow users to have a roster";
+    };
+
+    saslauth = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Authentication for clients and servers. Recommended if you want to log in.";
+    };
+
+    tls = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Add support for secure TLS on c2s/s2s connections";
+    };
+
+    dialback = mkOption {
+      type = types.bool;
+      default = true;
+      description = "s2s dialback support";
+    };
+
+    disco = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Service discovery";
+    };
+
+    # Not essential, but recommended
+    carbons = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Keep multiple clients in sync";
+    };
+
+    csi = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Implements the CSI protocol that allows clients to report their active/inactive state to the server";
+    };
+
+    cloud_notify = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Push notifications to inform users of new messages or other pertinent information even when they have no XMPP clients online";
+    };
+
+    pep = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Enables users to publish their mood, activity, playing music and more";
+    };
+
+    private = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Private XML storage (for room bookmarks, etc.)";
+    };
+
+    blocklist = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Allow users to block communications with other users";
+    };
+
+    vcard = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Allow users to set vCards";
+    };
+
+    vcard_legacy = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Converts users profiles and Avatars between old and new formats";
+    };
+
+    bookmarks = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Allows interop between older clients that use XEP-0048: Bookmarks in its 1.0 version and recent clients which use it in PEP";
+    };
+
+    # Nice to have
+    version = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Replies to server version requests";
+    };
+
+    uptime = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Report how long server has been running";
+    };
+
+    time = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Let others know the time here on this server";
+    };
+
+    ping = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Replies to XMPP pings with pongs";
+    };
+
+    register = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Allow users to register on this server using a client and change passwords";
+    };
+
+    mam = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Store messages in an archive and allow users to access it";
+    };
+
+    smacks = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Allow a client to resume a disconnected session, and prevent message loss";
+    };
+
+    # Admin interfaces
+    admin_adhoc = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Allows administration via an XMPP client that supports ad-hoc commands";
+    };
+
+    http_files = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Serve static files from a directory over HTTP";
+    };
+
+    proxy65 = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Enables a file transfer proxy service which clients behind NAT can use";
+    };
+
+    admin_telnet = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Opens telnet console interface on localhost port 5582";
+    };
+
+    # HTTP modules
+    bosh = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable BOSH clients, aka 'Jabber over HTTP'";
+    };
+
+    websocket = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable WebSocket support";
+    };
+
+    # Other specific functionality
+    limits = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable bandwidth limiting for XMPP connections";
+    };
+
+    groups = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Shared roster support";
+    };
+
+    server_contact_info = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Publish contact information for this service";
+    };
+
+    announce = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Send announcement to all online users";
+    };
+
+    welcome = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Welcome users who register accounts";
+    };
+
+    watchregistrations = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Alert admins of registrations";
+    };
+
+    motd = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Send a message to users when they log in";
+    };
+
+    legacyauth = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Legacy authentication. Only used by some old clients and bots";
+    };
+  };
+
+  toLua = x:
+    if builtins.isString x then ''"${x}"''
+    else if builtins.isBool x then (if x == true then "true" else "false")
+    else if builtins.isInt x then toString x
+    else if builtins.isList x then ''{ ${lib.concatStringsSep ", " (map (n: toLua n) x) } }''
+    else throw "Invalid Lua value";
+
+  createSSLOptsStr = o: ''
+    ssl = {
+      cafile = "/etc/ssl/certs/ca-bundle.crt";
+      key = "${o.key}";
+      certificate = "${o.cert}";
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name} = ${toLua value};") o.extraOptions)}
+    };
+  '';
+
+  mucOpts = { ... }: {
+    options = {
+      domain = mkOption {
+        type = types.str;
+        description = "Domain name of the MUC";
+      };
+      name = mkOption {
+        type = types.str;
+        description = "The name to return in service discovery responses for the MUC service itself";
+        default = "Prosody Chatrooms";
+      };
+      restrictRoomCreation = mkOption {
+        type = types.enum [ true false "admin" "local" ];
+        default = false;
+        description = "Restrict room creation to server admins";
+      };
+      maxHistoryMessages = mkOption {
+        type = types.int;
+        default = 20;
+        description = "Specifies a limit on what each room can be configured to keep";
+      };
+      roomLocking = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enables room locking, which means that a room must be
+          configured before it can be used. Locked rooms are invisible
+          and cannot be entered by anyone but the creator
+        '';
+      };
+      roomLockTimeout = mkOption {
+        type = types.int;
+        default = 300;
+        description = ''
+          Timout after which the room is destroyed or unlocked if not
+          configured, in seconds
+       '';
+      };
+      tombstones = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          When a room is destroyed, it leaves behind a tombstone which
+          prevents the room being entered or recreated. It also allows
+          anyone who was not in the room at the time it was destroyed
+          to learn about it, and to update their bookmarks. Tombstones
+          prevents the case where someone could recreate a previously
+          semi-anonymous room in order to learn the real JIDs of those
+          who often join there.
+        '';
+      };
+      tombstoneExpiry = mkOption {
+        type = types.int;
+        default = 2678400;
+        description = ''
+          This settings controls how long a tombstone is considered
+          valid. It defaults to 31 days. After this time, the room in
+          question can be created again.
+        '';
+      };
+
+      vcard_muc = mkOption {
+        type = types.bool;
+        default = true;
+      description = "Adds the ability to set vCard for Multi User Chat rooms";
+      };
+
+      # Extra parameters. Defaulting to prosody default values.
+      # Adding them explicitly to make them visible from the options
+      # documentation.
+      #
+      # See https://prosody.im/doc/modules/mod_muc for more details.
+      roomDefaultPublic = mkOption {
+        type = types.bool;
+        default = true;
+        description = "If set, the MUC rooms will be public by default.";
+      };
+      roomDefaultMembersOnly = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If set, the MUC rooms will only be accessible to the members by default.";
+      };
+      roomDefaultModerated = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If set, the MUC rooms will be moderated by default.";
+      };
+      roomDefaultPublicJids = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If set, the MUC rooms will display the public JIDs by default.";
+      };
+      roomDefaultChangeSubject = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If set, the rooms will display the public JIDs by default.";
+      };
+      roomDefaultHistoryLength = mkOption {
+        type = types.int;
+        default = 20;
+        description = "Number of history message sent to participants by default.";
+      };
+      roomDefaultLanguage = mkOption {
+        type = types.str;
+        default = "en";
+        description = "Default room language.";
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional MUC specific configuration";
+      };
+    };
+  };
+
+  uploadHttpOpts = { ... }: {
+    options = {
+      domain = mkOption {
+        type = types.nullOr types.str;
+        description = "Domain name for the http-upload service";
+      };
+      uploadFileSizeLimit = mkOption {
+        type = types.str;
+        default = "50 * 1024 * 1024";
+        description = "Maximum file size, in bytes. Defaults to 50MB.";
+      };
+      uploadExpireAfter = mkOption {
+        type = types.str;
+        default = "60 * 60 * 24 * 7";
+        description = "Max age of a file before it gets deleted, in seconds.";
+      };
+      userQuota = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 1234;
+        description = ''
+          Maximum size of all uploaded files per user, in bytes. There
+          will be no quota if this option is set to null.
+        '';
+      };
+      httpUploadPath = mkOption {
+        type = types.str;
+        description = ''
+          Directory where the uploaded files will be stored. By
+          default, uploaded files are put in a sub-directory of the
+          default Prosody storage path (usually /var/lib/prosody).
+        '';
+        default = "/var/lib/prosody";
+      };
+    };
+  };
+
+  vHostOpts = { ... }: {
+
+    options = {
+
+      # TODO: require attribute
+      domain = mkOption {
+        type = types.str;
+        description = "Domain name";
+      };
+
+      enabled = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the virtual host";
+      };
+
+      ssl = mkOption {
+        type = types.nullOr (types.submodule sslOpts);
+        default = null;
+        description = "Paths to SSL files";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional virtual host specific configuration";
+      };
+
+    };
+
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.prosody = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the prosody server";
+      };
+
+      xmppComplianceSuite = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          The XEP-0423 defines a set of recommended XEPs to implement
+          for a server. It's generally a good idea to implement this
+          set of extensions if you want to provide your users with a
+          good XMPP experience.
+
+          This NixOS module aims to provide a "advanced server"
+          experience as per defined in the XEP-0423[1] specification.
+
+          Setting this option to true will prevent you from building a
+          NixOS configuration which won't comply with this standard.
+          You can explicitely decide to ignore this standard if you
+          know what you are doing by setting this option to false.
+
+          [1] https://xmpp.org/extensions/xep-0423.html
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        description = "Prosody package to use";
+        default = pkgs.prosody;
+        defaultText = "pkgs.prosody";
+        example = literalExample ''
+          pkgs.prosody.override {
+            withExtraLibs = [ pkgs.luaPackages.lpty ];
+            withCommunityModules = [ "auth_external" ];
+          };
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        description = "Directory where Prosody stores its data";
+        default = "/var/lib/prosody";
+      };
+
+      disco_items = mkOption {
+        type = types.listOf (types.submodule discoOpts);
+        default = [];
+        description = "List of discoverable items you want to advertise.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "prosody";
+        description = "User account under which prosody runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "prosody";
+        description = "Group account under which prosody runs.";
+      };
+
+      allowRegistration = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Allow account creation";
+      };
+
+      # HTTP server-related options
+      httpPorts = mkOption {
+        type = types.listOf types.int;
+        description = "Listening HTTP ports list for this service.";
+        default = [ 5280 ];
+      };
+
+      httpInterfaces = mkOption {
+        type = types.listOf types.str;
+        default = [ "*" "::" ];
+        description = "Interfaces on which the HTTP server will listen on.";
+      };
+
+      httpsPorts = mkOption {
+        type = types.listOf types.int;
+        description = "Listening HTTPS ports list for this service.";
+        default = [ 5281 ];
+      };
+
+      httpsInterfaces = mkOption {
+        type = types.listOf types.str;
+        default = [ "*" "::" ];
+        description = "Interfaces on which the HTTPS server will listen on.";
+      };
+
+      c2sRequireEncryption = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Force clients to use encrypted connections? This option will
+          prevent clients from authenticating unless they are using encryption.
+        '';
+      };
+
+      s2sRequireEncryption = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Force servers to use encrypted connections? This option will
+          prevent servers from authenticating unless they are using encryption.
+          Note that this is different from authentication.
+        '';
+      };
+
+      s2sSecureAuth = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Force certificate authentication for server-to-server connections?
+          This provides ideal security, but requires servers you communicate
+          with to support encryption AND present valid, trusted certificates.
+          For more information see https://prosody.im/doc/s2s#security
+        '';
+      };
+
+      s2sInsecureDomains = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "insecure.example.com" ];
+        description = ''
+          Some servers have invalid or self-signed certificates. You can list
+          remote domains here that will not be required to authenticate using
+          certificates. They will be authenticated using DNS instead, even
+          when s2s_secure_auth is enabled.
+        '';
+      };
+
+      s2sSecureDomains = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "jabber.org" ];
+        description = ''
+          Even if you leave s2s_secure_auth disabled, you can still require valid
+          certificates for some domains by specifying a list here.
+        '';
+      };
+
+
+      modules = moduleOpts;
+
+      extraModules = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Enable custom modules";
+      };
+
+      extraPluginPaths = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = "Addtional path in which to look find plugins/modules";
+      };
+
+      uploadHttp = mkOption {
+        description = ''
+          Configures the Prosody builtin HTTP server to handle user uploads.
+        '';
+        type = types.nullOr (types.submodule uploadHttpOpts);
+        default = null;
+        example = {
+          domain = "uploads.my-xmpp-example-host.org";
+        };
+      };
+
+      muc = mkOption {
+        type = types.listOf (types.submodule mucOpts);
+        default = [ ];
+        example = [ {
+          domain = "conference.my-xmpp-example-host.org";
+        } ];
+        description = "Multi User Chat (MUC) configuration";
+      };
+
+      virtualHosts = mkOption {
+
+        description = "Define the virtual hosts";
+
+        type = with types; loaOf (submodule vHostOpts);
+
+        example = {
+          myhost = {
+            domain = "my-xmpp-example-host.org";
+            enabled = true;
+          };
+        };
+
+        default = {
+          localhost = {
+            domain = "localhost";
+            enabled = true;
+          };
+        };
+
+      };
+
+      ssl = mkOption {
+        type = types.nullOr (types.submodule sslOpts);
+        default = null;
+        description = "Paths to SSL files";
+      };
+
+      admins = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "admin1@example.com" "admin2@example.com" ];
+        description = "List of administrators of the current host";
+      };
+
+      authentication = mkOption {
+        type = types.enum [ "internal_plain" "internal_hashed" "cyrus" "anonymous" ];
+        default = "internal_hashed";
+        example = "internal_plain";
+        description = "Authentication mechanism used for logins.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional prosody configuration";
+      };
+
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = let
+      genericErrMsg = ''
+
+          Having a server not XEP-0423-compliant might make your XMPP
+          experience terrible. See the NixOS manual for further
+          informations.
+
+          If you know what you're doing, you can disable this warning by
+          setting config.services.prosody.xmppComplianceSuite to false.
+      '';
+      errors = [
+        { assertion = (builtins.length cfg.muc > 0) || !cfg.xmppComplianceSuite;
+          message = ''
+            You need to setup at least a MUC domain to comply with
+            XEP-0423.
+          '' + genericErrMsg;}
+        { assertion = cfg.uploadHttp != null || !cfg.xmppComplianceSuite;
+          message = ''
+            You need to setup the uploadHttp module through
+            config.services.prosody.uploadHttp to comply with
+            XEP-0423.
+          '' + genericErrMsg;}
+      ];
+    in errors;
+
+    environment.systemPackages = [ cfg.package ];
+
+    environment.etc."prosody/prosody.cfg.lua".text =
+      let
+        httpDiscoItems = if (cfg.uploadHttp != null)
+            then [{ url = cfg.uploadHttp.domain; description = "HTTP upload endpoint";}]
+            else [];
+        mucDiscoItems = builtins.foldl'
+            (acc: muc: [{ url = muc.domain; description = "${muc.domain} MUC endpoint";}] ++ acc)
+            []
+            cfg.muc;
+        discoItems = cfg.disco_items ++ httpDiscoItems ++ mucDiscoItems;
+      in ''
+
+      pidfile = "/run/prosody/prosody.pid"
+
+      log = "*syslog"
+
+      data_path = "${cfg.dataDir}"
+      plugin_paths = {
+        ${lib.concatStringsSep ", " (map (n: "\"${n}\"") cfg.extraPluginPaths) }
+      }
+
+      ${ optionalString  (cfg.ssl != null) (createSSLOptsStr cfg.ssl) }
+
+      admins = ${toLua cfg.admins}
+
+      -- we already build with libevent, so we can just enable it for a more performant server
+      use_libevent = true
+
+      modules_enabled = {
+
+        ${ lib.concatStringsSep "\n  " (lib.mapAttrsToList
+          (name: val: optionalString val "${toLua name};")
+        cfg.modules) }
+        ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.package.communityModules)}
+        ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.extraModules)}
+      };
+
+      disco_items = {
+      ${ lib.concatStringsSep "\n" (builtins.map (x: ''{ "${x.url}", "${x.description}"};'') discoItems)} 
+      };
+
+      allow_registration = ${toLua cfg.allowRegistration}
+
+      c2s_require_encryption = ${toLua cfg.c2sRequireEncryption}
+
+      s2s_require_encryption = ${toLua cfg.s2sRequireEncryption}
+
+      s2s_secure_auth = ${toLua cfg.s2sSecureAuth}
+
+      s2s_insecure_domains = ${toLua cfg.s2sInsecureDomains}
+
+      s2s_secure_domains = ${toLua cfg.s2sSecureDomains}
+
+      authentication = ${toLua cfg.authentication}
+
+      http_interfaces = ${toLua cfg.httpInterfaces}
+
+      https_interfaces = ${toLua cfg.httpsInterfaces}
+
+      http_ports = ${toLua cfg.httpPorts}
+
+      https_ports = ${toLua cfg.httpsPorts}
+
+      ${ cfg.extraConfig }
+
+      ${lib.concatMapStrings (muc: ''
+        Component ${toLua muc.domain} "muc"
+            modules_enabled = { "muc_mam"; ${optionalString muc.vcard_muc ''"vcard_muc";'' } }
+            name = ${toLua muc.name}
+            restrict_room_creation = ${toLua muc.restrictRoomCreation}
+            max_history_messages = ${toLua muc.maxHistoryMessages}
+            muc_room_locking = ${toLua muc.roomLocking}
+            muc_room_lock_timeout = ${toLua muc.roomLockTimeout}
+            muc_tombstones = ${toLua muc.tombstones}
+            muc_tombstone_expiry = ${toLua muc.tombstoneExpiry}
+            muc_room_default_public = ${toLua muc.roomDefaultPublic}
+            muc_room_default_members_only = ${toLua muc.roomDefaultMembersOnly}
+            muc_room_default_moderated = ${toLua muc.roomDefaultModerated}
+            muc_room_default_public_jids = ${toLua muc.roomDefaultPublicJids}
+            muc_room_default_change_subject = ${toLua muc.roomDefaultChangeSubject}
+            muc_room_default_history_length = ${toLua muc.roomDefaultHistoryLength}
+            muc_room_default_language = ${toLua muc.roomDefaultLanguage}
+            ${ muc.extraConfig }
+        '') cfg.muc}
+
+      ${ lib.optionalString (cfg.uploadHttp != null) ''
+        Component ${toLua cfg.uploadHttp.domain} "http_upload"
+            http_upload_file_size_limit = ${cfg.uploadHttp.uploadFileSizeLimit}
+            http_upload_expire_after = ${cfg.uploadHttp.uploadExpireAfter}
+            ${lib.optionalString (cfg.uploadHttp.userQuota != null) "http_upload_quota = ${toLua cfg.uploadHttp.userQuota}"}
+            http_upload_path = ${toLua cfg.uploadHttp.httpUploadPath}
+      ''}
+
+      ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: ''
+        VirtualHost "${v.domain}"
+          enabled = ${boolToString v.enabled};
+          ${ optionalString (v.ssl != null) (createSSLOptsStr v.ssl) }
+          ${ v.extraConfig }
+        '') cfg.virtualHosts) }
+    '';
+
+    users.users.prosody = mkIf (cfg.user == "prosody") {
+      uid = config.ids.uids.prosody;
+      description = "Prosody user";
+      createHome = true;
+      inherit (cfg) group;
+      home = "${cfg.dataDir}";
+    };
+
+    users.groups.prosody = mkIf (cfg.group == "prosody") {
+      gid = config.ids.gids.prosody;
+    };
+
+    systemd.services.prosody = {
+      description = "Prosody XMPP server";
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ config.environment.etc."prosody/prosody.cfg.lua".source ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        Type = "forking";
+        RuntimeDirectory = [ "prosody" ];
+        PIDFile = "/run/prosody/prosody.pid";
+        ExecStart = "${cfg.package}/bin/prosodyctl start";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+
+        MemoryDenyWriteExecute = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        PrivateTmp = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+      };
+    };
+
+  };
+  meta.doc = ./prosody.xml;
+}
diff --git a/nixpkgs/nixos/modules/services/networking/prosody.xml b/nixpkgs/nixos/modules/services/networking/prosody.xml
new file mode 100644
index 000000000000..7859cb1578b7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/prosody.xml
@@ -0,0 +1,88 @@
+<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-prosody">
+ <title>Prosody</title>
+ <para>
+  <link xlink:href="https://prosody.im/">Prosody</link> is an open-source, modern XMPP server.
+ </para>
+ <section xml:id="module-services-prosody-basic-usage">
+  <title>Basic usage</title>
+
+  <para>
+    A common struggle for most XMPP newcomers is to find the right set
+    of XMPP Extensions (XEPs) to setup. Forget to activate a few of
+    those and your XMPP experience might turn into a nightmare!
+  </para>
+
+  <para>
+    The XMPP community tackles this problem by creating a meta-XEP
+    listing a decent set of XEPs you should implement. This meta-XEP
+    is issued every year, the 2020 edition being
+    <link xlink:href="https://xmpp.org/extensions/xep-0423.html">XEP-0423</link>.
+  </para>
+  <para>
+    The NixOS Prosody module will implement most of these recommendend XEPs out of
+    the box. That being said, two components still require some
+    manual configuration: the
+    <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link>
+    and the <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link> ones.
+    You'll need to create a DNS subdomain for each of those. The current convention is to name your
+    MUC endpoint <literal>conference.example.org</literal> and your HTTP upload domain <literal>upload.example.org</literal>.
+  </para>
+  <para>
+    A good configuration to start with, including a
+    <link xlink:href="https://xmpp.org/extensions/xep-0045.html">Multi User Chat (MUC)</link>
+    endpoint as well as a <link xlink:href="https://xmpp.org/extensions/xep-0363.html">HTTP File Upload</link>
+    endpoint will look like this:
+    <programlisting>
+services.prosody = {
+  <link linkend="opt-services.prosody.enable">enable</link> = true;
+  <link linkend="opt-services.prosody.admins">admins</link> = [ "root@example.org" ];
+  <link linkend="opt-services.prosody.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem";
+  <link linkend="opt-services.prosody.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem";
+  <link linkend="opt-services.prosody.virtualHosts">virtualHosts</link>."example.org" = {
+      <link linkend="opt-services.prosody.virtualHosts._name__.enabled">enabled</link> = true;
+      <link linkend="opt-services.prosody.virtualHosts._name__.domain">domain</link> = "example.org";
+      <link linkend="opt-services.prosody.virtualHosts._name__.ssl.cert">ssl.cert</link> = "/var/lib/acme/example.org/fullchain.pem";
+      <link linkend="opt-services.prosody.virtualHosts._name__.ssl.key">ssl.key</link> = "/var/lib/acme/example.org/key.pem";
+  };
+  <link linkend="opt-services.prosody.muc">muc</link> = [ {
+      <link linkend="opt-services.prosody.muc">domain</link> = "conference.example.org";
+  } ];
+  <link linkend="opt-services.prosody.uploadHttp">uploadHttp</link> = {
+      <link linkend="opt-services.prosody.uploadHttp.domain">domain</link> = "upload.example.org";
+  };
+};</programlisting>
+  </para>
+ </section>
+ <section xml:id="module-services-prosody-letsencrypt">
+  <title>Let's Encrypt Configuration</title>
+ <para>
+   As you can see in the code snippet from the
+   <link linkend="module-services-prosody-basic-usage">previous section</link>,
+   you'll need a single TLS certificate covering your main endpoint,
+   the MUC one as well as the HTTP Upload one. We can generate such a
+   certificate by leveraging the ACME
+   <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains</link> module option.
+ </para>
+ <para>
+   Provided the setup detailed in the previous section, you'll need the following acme configuration to generate
+   a TLS certificate for the three endponits:
+    <programlisting>
+security.acme = {
+  <link linkend="opt-security.acme.email">email</link> = "root@example.org";
+  <link linkend="opt-security.acme.acceptTerms">acceptTerms</link> = true;
+  <link linkend="opt-security.acme.certs">certs</link> = {
+    "example.org" = {
+      <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/www/example.org";
+      <link linkend="opt-security.acme.certs._name_.email">email</link> = "root@example.org";
+      <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains."conference.example.org"</link> = null;
+      <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains."upload.example.org"</link> = null;
+    };
+  };
+};</programlisting>
+ </para>
+</section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/networking/quagga.nix b/nixpkgs/nixos/modules/services/networking/quagga.nix
new file mode 100644
index 000000000000..5acdd5af8f8f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/quagga.nix
@@ -0,0 +1,185 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.quagga;
+
+  services = [ "babel" "bgp" "isis" "ospf6" "ospf" "pim" "rip" "ripng" ];
+  allServices = services ++ [ "zebra" ];
+
+  isEnabled = service: cfg.${service}.enable;
+
+  daemonName = service: if service == "zebra" then service else "${service}d";
+
+  configFile = service:
+    let
+      scfg = cfg.${service};
+    in
+      if scfg.configFile != null then scfg.configFile
+      else pkgs.writeText "${daemonName service}.conf"
+        ''
+          ! Quagga ${daemonName service} configuration
+          !
+          hostname ${config.networking.hostName}
+          log syslog
+          service password-encryption
+          !
+          ${scfg.config}
+          !
+          end
+        '';
+
+  serviceOptions = service:
+    {
+      enable = mkEnableOption "the Quagga ${toUpper service} routing protocol";
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/etc/quagga/${daemonName service}.conf";
+        description = ''
+          Configuration file to use for Quagga ${daemonName service}.
+          By default the NixOS generated files are used.
+        '';
+      };
+
+      config = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          let
+            examples = {
+              rip = ''
+                router rip
+                  network 10.0.0.0/8
+              '';
+
+              ospf = ''
+                router ospf
+                  network 10.0.0.0/8 area 0
+              '';
+
+              bgp = ''
+                router bgp 65001
+                  neighbor 10.0.0.1 remote-as 65001
+              '';
+            };
+          in
+            examples.${service} or "";
+        description = ''
+          ${daemonName service} configuration statements.
+        '';
+      };
+
+      vtyListenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = ''
+          Address to bind to for the VTY interface.
+        '';
+      };
+
+      vtyListenPort = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          TCP Port to bind to for the VTY interface.
+        '';
+      };
+    };
+
+in
+
+{
+
+  ###### interface
+  imports = [
+    {
+      options.services.quagga = {
+        zebra = (serviceOptions "zebra") // {
+          enable = mkOption {
+            type = types.bool;
+            default = any isEnabled services;
+            description = ''
+              Whether to enable the Zebra routing manager.
+
+              The Zebra routing manager is automatically enabled
+              if any routing protocols are configured.
+            '';
+          };
+        };
+      };
+    }
+    { options.services.quagga = (genAttrs services serviceOptions); }
+  ];
+
+  ###### implementation
+
+  config = mkIf (any isEnabled allServices) {
+
+    environment.systemPackages = [
+      pkgs.quagga               # for the vtysh tool
+    ];
+
+    users.users.quagga = {
+      description = "Quagga daemon user";
+      isSystemUser = true;
+      group = "quagga";
+    };
+
+    users.groups = {
+      quagga = {};
+      # Members of the quaggavty group can use vtysh to inspect the Quagga daemons
+      quaggavty = { members = [ "quagga" ]; };
+    };
+
+    systemd.services =
+      let
+        quaggaService = service:
+          let
+            scfg = cfg.${service};
+            daemon = daemonName service;
+          in
+            nameValuePair daemon ({
+              wantedBy = [ "multi-user.target" ];
+              restartTriggers = [ (configFile service) ];
+
+              serviceConfig = {
+                Type = "forking";
+                PIDFile = "/run/quagga/${daemon}.pid";
+                ExecStart = "@${pkgs.quagga}/libexec/quagga/${daemon} ${daemon} -d -f ${configFile service}"
+                  + optionalString (scfg.vtyListenAddress != "") " -A ${scfg.vtyListenAddress}"
+                  + optionalString (scfg.vtyListenPort != null) " -P ${toString scfg.vtyListenPort}";
+                ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+                Restart = "on-abort";
+              };
+            } // (
+              if service == "zebra" then
+                {
+                  description = "Quagga Zebra routing manager";
+                  unitConfig.Documentation = "man:zebra(8)";
+                  after = [ "network.target" ];
+                  preStart = ''
+                    install -m 0755 -o quagga -g quagga -d /run/quagga
+
+                    ${pkgs.iproute}/bin/ip route flush proto zebra
+                  '';
+                }
+              else
+                {
+                  description = "Quagga ${toUpper service} routing daemon";
+                  unitConfig.Documentation = "man:${daemon}(8) man:zebra(8)";
+                  bindsTo = [ "zebra.service" ];
+                  after = [ "network.target" "zebra.service" ];
+                }
+            ));
+       in
+         listToAttrs (map quaggaService (filter isEnabled allServices));
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ tavyc ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/quassel.nix b/nixpkgs/nixos/modules/services/networking/quassel.nix
new file mode 100644
index 000000000000..da723ec86adf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/quassel.nix
@@ -0,0 +1,132 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.quassel;
+  quassel = cfg.package;
+  user = if cfg.user != null then cfg.user else "quassel";
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.quassel = {
+
+      enable = mkEnableOption "the Quassel IRC client daemon";
+
+      certificateFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Path to the certificate used for SSL connections with clients.
+        '';
+      };
+
+      requireSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Require SSL for connections from clients.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.quasselDaemon;
+        defaultText = "pkgs.quasselDaemon";
+        description = ''
+          The package of the quassel daemon.
+        '';
+        example = literalExample "pkgs.quasselDaemon";
+      };
+
+      interfaces = mkOption {
+        default = [ "127.0.0.1" ];
+        description = ''
+          The interfaces the Quassel daemon will be listening to.  If `[ 127.0.0.1 ]',
+          only clients on the local host can connect to it; if `[ 0.0.0.0 ]', clients
+          can access it from any network interface.
+        '';
+      };
+
+      portNumber = mkOption {
+        default = 4242;
+        description = ''
+          The port number the Quassel daemon will be listening to.
+        '';
+      };
+
+      dataDir = mkOption {
+        default = ''/home/${user}/.config/quassel-irc.org'';
+        description = ''
+          The directory holding configuration files, the SQlite database and the SSL Cert.
+        '';
+      };
+
+      user = mkOption {
+        default = null;
+        description = ''
+          The existing user the Quassel daemon should run as. If left empty, a default "quassel" user will be created.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.requireSSL -> cfg.certificateFile != null;
+        message = "Quassel needs a certificate file in order to require SSL";
+      }];
+
+    users.users = optionalAttrs (cfg.user == null) {
+      quassel = {
+        name = "quassel";
+        description = "Quassel IRC client daemon";
+        group = "quassel";
+        uid = config.ids.uids.quassel;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.user == null) {
+      quassel = {
+        name = "quassel";
+        gid = config.ids.gids.quassel;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${user} - - -"
+    ];
+
+    systemd.services.quassel =
+      { description = "Quassel IRC client daemon";
+
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ] ++ optional config.services.postgresql.enable "postgresql.service"
+                                     ++ optional config.services.mysql.enable "mysql.service";
+
+        serviceConfig =
+        {
+          ExecStart = concatStringsSep " " ([
+            "${quassel}/bin/quasselcore"
+            "--listen=${concatStringsSep "," cfg.interfaces}"
+            "--port=${toString cfg.portNumber}"
+            "--configdir=${cfg.dataDir}"
+          ] ++ optional cfg.requireSSL "--require-ssl"
+            ++ optional (cfg.certificateFile != null) "--ssl-cert=${cfg.certificateFile}");
+          User = user;
+        };
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/quicktun.nix b/nixpkgs/nixos/modules/services/networking/quicktun.nix
new file mode 100644
index 000000000000..fb783c836464
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/quicktun.nix
@@ -0,0 +1,118 @@
+{ config, pkgs, lib, ... }:
+
+let
+
+  cfg = config.services.quicktun;
+
+in
+
+with lib;
+
+{
+  options = {
+
+    services.quicktun = mkOption {
+      default = { };
+      description = "QuickTun tunnels";
+      type = types.attrsOf (types.submodule {
+        options = {
+          tunMode = mkOption {
+            type = types.int;
+            default = 0;
+            example = 1;
+            description = "";
+          };
+
+          remoteAddress = mkOption {
+            type = types.str;
+            example = "tunnel.example.com";
+            description = "";
+          };
+
+          localAddress = mkOption {
+            type = types.str;
+            example = "0.0.0.0";
+            description = "";
+          };
+
+          localPort = mkOption {
+            type = types.int;
+            default = 2998;
+            description = "";
+          };
+
+          remotePort = mkOption {
+            type = types.int;
+            default = 2998;
+            description = "";
+          };
+
+          remoteFloat = mkOption {
+            type = types.int;
+            default = 0;
+            description = "";
+          };
+
+          protocol = mkOption {
+            type = types.str;
+            default = "nacltai";
+            description = "";
+          };
+
+          privateKey = mkOption {
+            type = types.str;
+            description = "";
+          };
+
+          publicKey = mkOption {
+            type = types.str;
+            description = "";
+          };
+
+          timeWindow = mkOption {
+            type = types.int;
+            default = 5;
+            description = "";
+          };
+
+          upScript = mkOption {
+            type = types.lines;
+            default = "";
+            description = "";
+          };
+        };
+      });
+    };
+
+  };
+
+  config = mkIf (cfg != []) {
+    systemd.services = fold (a: b: a // b) {} (
+      mapAttrsToList (name: qtcfg: {
+        "quicktun-${name}" = {
+          wantedBy = [ "multi-user.target" ];
+          after = [ "network.target" ];
+          environment = {
+            INTERFACE = name;
+            TUN_MODE = toString qtcfg.tunMode;
+            REMOTE_ADDRESS = qtcfg.remoteAddress;
+            LOCAL_ADDRESS = qtcfg.localAddress;
+            LOCAL_PORT = toString qtcfg.localPort;
+            REMOTE_PORT = toString qtcfg.remotePort;
+            REMOTE_FLOAT = toString qtcfg.remoteFloat;
+            PRIVATE_KEY = qtcfg.privateKey;
+            PUBLIC_KEY = qtcfg.publicKey;
+            TIME_WINDOW = toString qtcfg.timeWindow;
+            TUN_UP_SCRIPT = pkgs.writeScript "quicktun-${name}-up.sh" qtcfg.upScript;
+            SUID = "nobody";
+          };
+          serviceConfig = {
+            Type = "simple";
+            ExecStart = "${pkgs.quicktun}/bin/quicktun.${qtcfg.protocol}";
+          };
+        };
+      }) cfg
+    );
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/quorum.nix b/nixpkgs/nixos/modules/services/networking/quorum.nix
new file mode 100644
index 000000000000..2f612c9db686
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/quorum.nix
@@ -0,0 +1,229 @@
+{ config, pkgs, lib, ... }:
+let
+
+  inherit (lib) mkEnableOption mkIf mkOption literalExample types optionalString;
+
+  cfg = config.services.quorum;
+  dataDir = "/var/lib/quorum";
+  genesisFile = pkgs.writeText "genesis.json" (builtins.toJSON cfg.genesis);
+  staticNodesFile = pkgs.writeText "static-nodes.json" (builtins.toJSON cfg.staticNodes);
+
+in {
+  options = {
+
+    services.quorum = {
+      enable = mkEnableOption "Quorum blockchain daemon";
+
+      user = mkOption {
+        type = types.str;
+        default = "quorum";
+        description = "The user as which to run quorum.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = cfg.user;
+        description = "The group as which to run quorum.";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 21000;
+        description = "Override the default port on which to listen for connections.";
+      };
+
+      nodekeyFile = mkOption {
+        type = types.path;
+        default = "${dataDir}/nodekey";
+        description = "Path to the nodekey.";
+      };
+
+      staticNodes = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "enode://dd333ec28f0a8910c92eb4d336461eea1c20803eed9cf2c056557f986e720f8e693605bba2f4e8f289b1162e5ac7c80c914c7178130711e393ca76abc1d92f57@0.0.0.0:30303?discport=0" ];
+        description = "List of validator nodes.";
+      };
+
+      privateconfig = mkOption {
+        type = types.str;
+        default = "ignore";
+        description = "Configuration of privacy transaction manager.";
+      };
+
+      syncmode = mkOption {
+        type = types.enum [ "fast" "full" "light" ];
+        default = "full";
+        description = "Blockchain sync mode.";
+      };
+
+      blockperiod = mkOption {
+        type = types.int;
+        default = 5;
+        description = "Default minimum difference between two consecutive block's timestamps in seconds.";
+      };
+
+      permissioned = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Allow only a defined list of nodes to connect.";
+      };
+
+      rpc = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Enable RPC interface.";
+        };
+
+        address = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = "Listening address for RPC connections.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 22004;
+          description = "Override the default port on which to listen for RPC connections.";
+        };
+
+        api = mkOption {
+          type = types.str;
+          default = "admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul";
+          description = "API's offered over the HTTP-RPC interface.";
+        };
+      };
+
+     ws = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Enable WS-RPC interface.";
+        };
+
+        address = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = "Listening address for WS-RPC connections.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 8546;
+          description = "Override the default port on which to listen for WS-RPC connections.";
+        };
+
+        api = mkOption {
+          type = types.str;
+          default = "admin,db,eth,debug,miner,net,shh,txpool,personal,web3,quorum,istanbul";
+          description = "API's offered over the WS-RPC interface.";
+        };
+
+       origins = mkOption {
+          type = types.str;
+          default = "*";
+          description = "Origins from which to accept websockets requests";
+       };
+     };
+
+      genesis = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        example = literalExample '' {
+          alloc = {
+            a47385db68718bdcbddc2d2bb7c54018066ec111 = {
+              balance = "1000000000000000000000000000";
+            };
+          };
+          coinbase = "0x0000000000000000000000000000000000000000";
+          config = {
+            byzantiumBlock = 4;
+            chainId = 494702925;
+            eip150Block = 2;
+            eip155Block = 3;
+            eip158Block = 3;
+            homesteadBlock = 1;
+            isQuorum = true;
+            istanbul = {
+              epoch = 30000;
+              policy = 0;
+            };
+          };
+          difficulty = "0x1";
+          extraData = "0x0000000000000000000000000000000000000000000000000000000000000000f85ad59438f0508111273d8e482f49410ca4078afc86a961b8410000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0";
+          gasLimit = "0x2FEFD800";
+          mixHash = "0x63746963616c2062797a616e74696e65201111756c7420746f6c6572616e6365";
+          nonce = "0x0";
+          parentHash = "0x0000000000000000000000000000000000000000000000000000000000000000";
+          timestamp = "0x00";
+          }'';
+        description = "Blockchain genesis settings.";
+      };
+     };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.quorum ];
+    systemd.tmpfiles.rules = [
+      "d '${dataDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
+    ];
+    systemd.services.quorum = {
+      description = "Quorum daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        PRIVATE_CONFIG = "${cfg.privateconfig}";
+      };
+      preStart = ''
+        if [ ! -d ${dataDir}/geth ]; then
+          if [ ! -d ${dataDir}/keystore ]; then
+            echo ERROR: You need to create a wallet before initializing your genesis file, run:
+            echo   # su -s /bin/sh - quorum
+            echo   $ geth --datadir ${dataDir} account new
+            echo and configure your genesis file accordingly.
+            exit 1;
+          fi
+          ln -s ${staticNodesFile} ${dataDir}/static-nodes.json
+          ${pkgs.quorum}/bin/geth --datadir ${dataDir} init ${genesisFile}
+        fi
+      '';
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = ''${pkgs.quorum}/bin/geth \
+            --nodiscover \
+            --verbosity 5 \
+            --nodekey ${cfg.nodekeyFile} \
+            --istanbul.blockperiod ${toString cfg.blockperiod} \
+            --syncmode ${cfg.syncmode} \
+            ${optionalString (cfg.permissioned)
+            "--permissioned"} \
+            --mine --minerthreads 1 \
+            ${optionalString (cfg.rpc.enable)
+            "--rpc --rpcaddr ${cfg.rpc.address} --rpcport ${toString cfg.rpc.port} --rpcapi ${cfg.rpc.api}"} \
+            ${optionalString (cfg.ws.enable)
+            "--ws --wsaddr ${cfg.ws.address} --wsport ${toString cfg.ws.port} --wsapi ${cfg.ws.api} --wsorigins ${cfg.ws.origins}"} \
+            --emitcheckpoints \
+            --datadir ${dataDir} \
+            --port ${toString cfg.port}'';
+        Restart = "on-failure";
+
+        # Hardening measures
+        PrivateTmp = "true";
+        ProtectSystem = "full";
+        NoNewPrivileges = "true";
+        PrivateDevices = "true";
+        MemoryDenyWriteExecute = "true";
+      };
+    };
+    users.users.${cfg.user} = {
+      name = cfg.user;
+      group = cfg.group;
+      description = "Quorum daemon user";
+      home = dataDir;
+      isSystemUser = true;
+    };
+    users.groups.${cfg.group} = {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/racoon.nix b/nixpkgs/nixos/modules/services/networking/racoon.nix
new file mode 100644
index 000000000000..328f4cb1497f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/racoon.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.racoon;
+in {
+  options.services.racoon = {
+    enable = mkEnableOption "racoon";
+
+    config = mkOption {
+      description = "Contents of racoon configuration file.";
+      default = "";
+      type = types.str;
+    };
+
+    configPath = mkOption {
+      description = "Location of racoon config if config is not provided.";
+      default = "/etc/racoon/racoon.conf";
+      type = types.path;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.racoon = {
+      description = "Racoon Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.ipsecTools}/bin/racoon -f ${
+          if (cfg.config != "") then pkgs.writeText "racoon.conf" cfg.config
+          else cfg.configPath
+        }";
+        ExecReload = "${pkgs.ipsecTools}/bin/racoonctl reload-config";
+        PIDFile = "/run/racoon.pid";
+        Type = "forking";
+        Restart = "always";
+      };
+      preStart = ''
+        rm /run/racoon.pid || true
+        mkdir -p /var/racoon
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/radicale.nix b/nixpkgs/nixos/modules/services/networking/radicale.nix
new file mode 100644
index 000000000000..30bf22586f86
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/radicale.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.radicale;
+
+  confFile = pkgs.writeText "radicale.conf" cfg.config;
+
+  # This enables us to default to version 2 while still not breaking configurations of people with version 1
+  defaultPackage = if versionAtLeast config.system.stateVersion "17.09" then {
+    pkg = pkgs.radicale2;
+    text = "pkgs.radicale2";
+  } else {
+    pkg = pkgs.radicale1;
+    text = "pkgs.radicale1";
+  };
+in
+
+{
+
+  options = {
+    services.radicale.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+          Enable Radicale CalDAV and CardDAV server.
+      '';
+    };
+
+    services.radicale.package = mkOption {
+      type = types.package;
+      default = defaultPackage.pkg;
+      defaultText = defaultPackage.text;
+      description = ''
+        Radicale package to use. This defaults to version 1.x if
+        <literal>system.stateVersion &lt; 17.09</literal> and version 2.x
+        otherwise.
+      '';
+    };
+
+    services.radicale.config = mkOption {
+      type = types.str;
+      default = "";
+      description = ''
+        Radicale configuration, this will set the service
+        configuration file.
+      '';
+    };
+
+    services.radicale.extraArgs = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = "Extra arguments passed to the Radicale daemon.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+
+    users.users.radicale =
+      { uid = config.ids.uids.radicale;
+        description = "radicale user";
+        home = "/var/lib/radicale";
+        createHome = true;
+      };
+
+    users.groups.radicale =
+      { gid = config.ids.gids.radicale; };
+
+    systemd.services.radicale = {
+      description = "A Simple Calendar and Contact Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = concatStringsSep " " ([
+          "${cfg.package}/bin/radicale" "-C" confFile
+        ] ++ (
+          map escapeShellArg cfg.extraArgs
+        ));
+        User = "radicale";
+        Group = "radicale";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ aneeshusa infinisil ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/radvd.nix b/nixpkgs/nixos/modules/services/networking/radvd.nix
new file mode 100644
index 000000000000..f4b00c9b356e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/radvd.nix
@@ -0,0 +1,73 @@
+# Module for the IPv6 Router Advertisement Daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.radvd;
+
+  confFile = pkgs.writeText "radvd.conf" cfg.config;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.radvd.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description =
+        ''
+          Whether to enable the Router Advertisement Daemon
+          (<command>radvd</command>), which provides link-local
+          advertisements of IPv6 router addresses and prefixes using
+          the Neighbor Discovery Protocol (NDP).  This enables
+          stateless address autoconfiguration in IPv6 clients on the
+          network.
+        '';
+    };
+
+    services.radvd.config = mkOption {
+      example =
+        ''
+          interface eth0 {
+            AdvSendAdvert on;
+            prefix 2001:db8:1234:5678::/64 { };
+          };
+        '';
+      description =
+        ''
+          The contents of the radvd configuration file.
+        '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.radvd =
+      { uid = config.ids.uids.radvd;
+        description = "Router Advertisement Daemon User";
+      };
+
+    systemd.services.radvd =
+      { description = "IPv6 Router Advertisement Daemon";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        serviceConfig =
+          { ExecStart = "@${pkgs.radvd}/bin/radvd radvd -n -u radvd -C ${confFile}";
+            Restart = "always";
+          };
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/rdnssd.nix b/nixpkgs/nixos/modules/services/networking/rdnssd.nix
new file mode 100644
index 000000000000..469504c43172
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/rdnssd.nix
@@ -0,0 +1,80 @@
+# Module for rdnssd, a daemon that configures DNS servers in
+# /etc/resolv/conf from IPv6 RDNSS advertisements.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  mergeHook = pkgs.writeScript "rdnssd-merge-hook" ''
+    #! ${pkgs.runtimeShell} -e
+    ${pkgs.openresolv}/bin/resolvconf -u
+  '';
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.rdnssd.enable = mkOption {
+      type = types.bool;
+      default = false;
+      #default = config.networking.enableIPv6;
+      description =
+        ''
+          Whether to enable the RDNSS daemon
+          (<command>rdnssd</command>), which configures DNS servers in
+          <filename>/etc/resolv.conf</filename> from RDNSS
+          advertisements sent by IPv6 routers.
+        '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.rdnssd.enable {
+
+    assertions = [{
+      assertion = config.networking.resolvconf.enable;
+      message = "rdnssd needs resolvconf to work (probably something sets up a static resolv.conf)";
+    }];
+
+    systemd.services.rdnssd = {
+      description = "RDNSS daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        # Create the proper run directory
+        mkdir -p /run/rdnssd
+        touch /run/rdnssd/resolv.conf
+        chown -R rdnssd /run/rdnssd
+
+        # Link the resolvconf interfaces to rdnssd
+        rm -f /run/resolvconf/interfaces/rdnssd
+        ln -s /run/rdnssd/resolv.conf /run/resolvconf/interfaces/rdnssd
+        ${mergeHook}
+      '';
+
+      postStop = ''
+        rm -f /run/resolvconf/interfaces/rdnssd
+        ${mergeHook}
+      '';
+
+      serviceConfig = {
+        ExecStart = "@${pkgs.ndisc6}/bin/rdnssd rdnssd -p /run/rdnssd/rdnssd.pid -r /run/rdnssd/resolv.conf -u rdnssd -H ${mergeHook}";
+        Type = "forking";
+        PIDFile = "/run/rdnssd/rdnssd.pid";
+      };
+    };
+
+    users.users.rdnssd = {
+      description = "RDNSSD Daemon User";
+      uid = config.ids.uids.rdnssd;
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/redsocks.nix b/nixpkgs/nixos/modules/services/networking/redsocks.nix
new file mode 100644
index 000000000000..8481f9debf39
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/redsocks.nix
@@ -0,0 +1,272 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.redsocks;
+in
+{
+  ##### interface
+  options = {
+    services.redsocks = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable redsocks.";
+      };
+
+      log_debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Log connection progress.";
+      };
+
+      log_info = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Log start and end of client sessions.";
+      };
+
+      log = mkOption {
+        type = types.str;
+        default = "stderr";
+        description =
+          ''
+            Where to send logs.
+
+            Possible values are:
+              - stderr
+              - file:/path/to/file
+              - syslog:FACILITY where FACILITY is any of "daemon", "local0",
+              etc.
+          '';
+      };
+
+      chroot = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description =
+          ''
+            Chroot under which to run redsocks. Log file is opened before
+            chroot, but if logging to syslog /etc/localtime may be required.
+          '';
+      };
+
+      redsocks = mkOption {
+        description =
+          ''
+            Local port to proxy associations to be performed.
+
+            The example shows how to configure a proxy to handle port 80 as HTTP
+            relay, and all other ports as HTTP connect.
+          '';
+        example = [
+          { port = 23456; proxy = "1.2.3.4:8080"; type = "http-relay";
+            redirectCondition = "--dport 80";
+            doNotRedirect = [ "-d 1.2.0.0/16" ];
+          }
+          { port = 23457; proxy = "1.2.3.4:8080"; type = "http-connect";
+            redirectCondition = true;
+            doNotRedirect = [ "-d 1.2.0.0/16" ];
+          }
+        ];
+        type = types.listOf (types.submodule { options = {
+          ip = mkOption {
+            type = types.str;
+            default = "127.0.0.1";
+            description =
+              ''
+                IP on which redsocks should listen. Defaults to 127.0.0.1 for
+                security reasons.
+              '';
+          };
+
+          port = mkOption {
+            type = types.int;
+            default = 12345;
+            description = "Port on which redsocks should listen.";
+          };
+
+          proxy = mkOption {
+            type = types.str;
+            description =
+              ''
+                Proxy through which redsocks should forward incoming traffic.
+                Example: "example.org:8080"
+              '';
+          };
+
+          type = mkOption {
+            type = types.enum [ "socks4" "socks5" "http-connect" "http-relay" ];
+            description = "Type of proxy.";
+          };
+
+          login = mkOption {
+            type = with types; nullOr str;
+            default = null;
+            description = "Login to send to proxy.";
+          };
+
+          password = mkOption {
+            type = with types; nullOr str;
+            default = null;
+            description =
+              ''
+                Password to send to proxy. WARNING, this will end up
+                world-readable in the store! Awaiting
+                https://github.com/NixOS/nix/issues/8 to be able to fix.
+              '';
+          };
+
+          disclose_src = mkOption {
+            type = types.enum [ "false" "X-Forwarded-For" "Forwarded_ip"
+                                "Forwarded_ipport" ];
+            default = "false";
+            description =
+              ''
+                Way to disclose client IP to the proxy.
+                  - "false": do not disclose
+                http-connect supports the following ways:
+                  - "X-Forwarded-For": add header "X-Forwarded-For: IP"
+                  - "Forwarded_ip": add header "Forwarded: for=IP" (see RFC7239)
+                  - "Forwarded_ipport": add header 'Forwarded: for="IP:port"'
+              '';
+          };
+
+          redirectInternetOnly = mkOption {
+            type = types.bool;
+            default = true;
+            description = "Exclude all non-globally-routable IPs from redsocks";
+          };
+
+          doNotRedirect = mkOption {
+            type = with types; listOf str;
+            default = [];
+            description =
+              ''
+                Iptables filters that if matched will get the packet off of
+                redsocks.
+              '';
+            example = [ "-d 1.2.3.4" ];
+          };
+
+          redirectCondition = mkOption {
+            type = with types; either bool str;
+            default = false;
+            description =
+              ''
+                Conditions to make outbound packets go through this redsocks
+                instance.
+
+                If set to false, no packet will be forwarded. If set to true,
+                all packets will be forwarded (except packets excluded by
+                redirectInternetOnly).
+
+                If set to a string, this is an iptables filter that will be
+                matched against packets before getting them into redsocks. For
+                example, setting it to "--dport 80" will only send
+                packets to port 80 to redsocks. Note "-p tcp" is always
+                implicitly added, as udp can only be proxied through redudp or
+                the like.
+              '';
+          };
+        };});
+      };
+
+      # TODO: Add support for redudp and dnstc
+    };
+  };
+
+  ##### implementation
+  config = let
+    redsocks_blocks = concatMapStrings (block:
+      let proxy = splitString ":" block.proxy; in
+      ''
+        redsocks {
+          local_ip = ${block.ip};
+          local_port = ${toString block.port};
+
+          ip = ${elemAt proxy 0};
+          port = ${elemAt proxy 1};
+          type = ${block.type};
+
+          ${optionalString (block.login != null) "login = \"${block.login}\";"}
+          ${optionalString (block.password != null) "password = \"${block.password}\";"}
+
+          disclose_src = ${block.disclose_src};
+        }
+      '') cfg.redsocks;
+    configfile = pkgs.writeText "redsocks.conf"
+      ''
+        base {
+          log_debug = ${if cfg.log_debug then "on" else "off" };
+          log_info = ${if cfg.log_info then "on" else "off" };
+          log = ${cfg.log};
+
+          daemon = off;
+          redirector = iptables;
+
+          user = redsocks;
+          group = redsocks;
+          ${optionalString (cfg.chroot != null) "chroot = ${cfg.chroot};"}
+        }
+
+        ${redsocks_blocks}
+      '';
+    internetOnly = [ # TODO: add ipv6-equivalent
+      "-d 0.0.0.0/8"
+      "-d 10.0.0.0/8"
+      "-d 127.0.0.0/8"
+      "-d 169.254.0.0/16"
+      "-d 172.16.0.0/12"
+      "-d 192.168.0.0/16"
+      "-d 224.168.0.0/4"
+      "-d 240.168.0.0/4"
+    ];
+    redCond = block:
+      optionalString (isString block.redirectCondition) block.redirectCondition;
+    iptables = concatImapStrings (idx: block:
+      let chain = "REDSOCKS${toString idx}"; doNotRedirect =
+        concatMapStringsSep "\n"
+          (f: "ip46tables -t nat -A ${chain} ${f} -j RETURN 2>/dev/null || true")
+          (block.doNotRedirect ++ (optionals block.redirectInternetOnly internetOnly));
+      in
+      optionalString (block.redirectCondition != false)
+        ''
+          ip46tables -t nat -F ${chain} 2>/dev/null || true
+          ip46tables -t nat -N ${chain} 2>/dev/null || true
+          ${doNotRedirect}
+          ip46tables -t nat -A ${chain} -p tcp -j REDIRECT --to-ports ${toString block.port}
+
+          # TODO: show errors, when it will be easily possible by a switch to
+          # iptables-restore
+          ip46tables -t nat -A OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true
+        ''
+    ) cfg.redsocks;
+  in
+    mkIf cfg.enable {
+      users.groups.redsocks = {};
+      users.users.redsocks = {
+        description = "Redsocks daemon";
+        group = "redsocks";
+        isSystemUser = true;
+      };
+
+      systemd.services.redsocks = {
+        description = "Redsocks";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        script = "${pkgs.redsocks}/bin/redsocks -c ${configfile}";
+      };
+
+      networking.firewall.extraCommands = iptables;
+
+      networking.firewall.extraStopCommands =
+        concatImapStringsSep "\n" (idx: block:
+          let chain = "REDSOCKS${toString idx}"; in
+          optionalString (block.redirectCondition != false)
+            "ip46tables -t nat -D OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true"
+        ) cfg.redsocks;
+    };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/resilio.nix b/nixpkgs/nixos/modules/services/networking/resilio.nix
new file mode 100644
index 000000000000..e74e03fc0b07
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/resilio.nix
@@ -0,0 +1,263 @@
+{ 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 = entry.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.
+        '';
+      };
+
+      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.users.rslsync = {
+      description     = "Resilio Sync Service user";
+      home            = cfg.storagePath;
+      createHome      = true;
+      uid             = config.ids.uids.rslsync;
+      group           = "rslsync";
+    };
+
+    users.groups.rslsync = {};
+
+    systemd.services.resilio = with pkgs; {
+      description = "Resilio Sync Service";
+      wantedBy    = [ "multi-user.target" ];
+      after       = [ "network.target" ];
+      serviceConfig = {
+        Restart   = "on-abort";
+        UMask     = "0002";
+        User      = "rslsync";
+        ExecStart = ''
+          ${resilioSync}/bin/rslsync --nodaemon --config ${configFile}
+        '';
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/rpcbind.nix b/nixpkgs/nixos/modules/services/networking/rpcbind.nix
new file mode 100644
index 000000000000..0a5df6987092
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/rpcbind.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.rpcbind = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable `rpcbind', an ONC RPC directory service
+          notably used by NFS and NIS, and which can be queried
+          using the rpcinfo(1) command. `rpcbind` is a replacement for
+          `portmap`.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.rpcbind.enable {
+    environment.systemPackages = [ pkgs.rpcbind ];
+
+    systemd.packages = [ pkgs.rpcbind ];
+
+    systemd.services.rpcbind = {
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    users.users.rpc = {
+      group = "nogroup";
+      uid = config.ids.uids.rpc;
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/rxe.nix b/nixpkgs/nixos/modules/services/networking/rxe.nix
new file mode 100644
index 000000000000..c7d174a00de2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/rxe.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.networking.rxe;
+
+in {
+  ###### interface
+
+  options = {
+    networking.rxe = {
+      enable = mkEnableOption "RDMA over converged ethernet";
+      interfaces = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "eth0" ];
+        description = ''
+          Enable RDMA on the listed interfaces. The corresponding virtual
+          RDMA interfaces will be named rxe_&lt;interface&gt;.
+          UDP port 4791 must be open on the respective ethernet interfaces.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.rxe = {
+      description = "RoCE interfaces";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "systemd-modules-load.service" "network-online.target" ];
+      wants = [ "network-pre.target" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+        ExecStart = map ( x:
+          "${pkgs.iproute}/bin/rdma link add rxe_${x} type rxe netdev ${x}"
+          ) cfg.interfaces;
+
+        ExecStop = map ( x:
+          "${pkgs.iproute}/bin/rdma link delete rxe_${x}"
+          ) cfg.interfaces;
+      };
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/sabnzbd.nix b/nixpkgs/nixos/modules/services/networking/sabnzbd.nix
new file mode 100644
index 000000000000..ff5aef7d1cb4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/sabnzbd.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.sabnzbd;
+  inherit (pkgs) sabnzbd;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.sabnzbd = {
+      enable = mkEnableOption "the sabnzbd server";
+
+      configFile = mkOption {
+        default = "/var/lib/sabnzbd/sabnzbd.ini";
+        description = "Path to config file.";
+      };
+
+      user = mkOption {
+        default = "sabnzbd";
+        description = "User to run the service as";
+      };
+
+      group = mkOption {
+        default = "sabnzbd";
+        description = "Group to run the service as";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.sabnzbd = {
+          uid = config.ids.uids.sabnzbd;
+          group = "sabnzbd";
+          description = "sabnzbd user";
+          home = "/var/lib/sabnzbd/";
+          createHome = true;
+    };
+
+    users.groups.sabnzbd = {
+      gid = config.ids.gids.sabnzbd;
+    };
+
+    systemd.services.sabnzbd = {
+        description = "sabnzbd server";
+        wantedBy    = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        serviceConfig = {
+          Type = "forking";
+          GuessMainPID = "no";
+          User = "${cfg.user}";
+          Group = "${cfg.group}";
+          ExecStart = "${sabnzbd}/bin/sabnzbd -d -f ${cfg.configFile}";
+        };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/searx.nix b/nixpkgs/nixos/modules/services/networking/searx.nix
new file mode 100644
index 000000000000..60fb3d5d6d44
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/searx.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.searx;
+
+  configFile = cfg.configFile;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.searx = {
+
+      enable = mkEnableOption
+        "the searx server. See https://github.com/asciimoo/searx";
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "
+          The path of the Searx server configuration file. If no file
+          is specified, a default file is used (default config file has
+          debug mode enabled).
+        ";
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.searx;
+        defaultText = "pkgs.searx";
+        description = "searx package to use.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.searx.enable {
+
+    users.users.searx =
+      { uid = config.ids.uids.searx;
+        description = "Searx user";
+        createHome = true;
+        home = "/var/lib/searx";
+      };
+
+    users.groups.searx =
+      { gid = config.ids.gids.searx;
+      };
+
+    systemd.services.searx =
+      {
+        description = "Searx server, the meta search engine.";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = "searx";
+          ExecStart = "${cfg.package}/bin/searx-run";
+        };
+      } // (optionalAttrs (configFile != null) {
+        environment.SEARX_SETTINGS_PATH = configFile;
+      });
+
+    environment.systemPackages = [ cfg.package ];
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/seeks.nix b/nixpkgs/nixos/modules/services/networking/seeks.nix
new file mode 100644
index 000000000000..40729225b6d0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/seeks.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.seeks;
+
+  confDir = cfg.confDir;
+
+  seeks = pkgs.seeks.override { seeks_confDir = confDir; };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.seeks = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Whether to enable the Seeks server.
+        ";
+      };
+
+      confDir = mkOption {
+        default = "";
+        type = types.str;
+        description = "
+          The Seeks server configuration. If it is not specified,
+          a default configuration is used.
+        ";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.seeks.enable {
+
+    users.users.seeks =
+      { uid = config.ids.uids.seeks;
+        description = "Seeks user";
+        createHome = true;
+        home = "/var/lib/seeks";
+      };
+
+    users.groups.seeks =
+      { gid = config.ids.gids.seeks;
+      };
+
+    systemd.services.seeks =
+      {
+        description = "Seeks server, the p2p search engine.";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = "seeks";
+          ExecStart = "${seeks}/bin/seeks";
+        };
+      };
+
+    environment.systemPackages = [ seeks ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/shadowsocks.nix b/nixpkgs/nixos/modules/services/networking/shadowsocks.nix
new file mode 100644
index 000000000000..af12db590f00
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/shadowsocks.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.shadowsocks;
+
+  opts = {
+    server = cfg.localAddress;
+    server_port = cfg.port;
+    method = cfg.encryptionMethod;
+    mode = cfg.mode;
+    user = "nobody";
+    fast_open = true;
+  } // optionalAttrs (cfg.password != null) { password = cfg.password; };
+
+  configFile = pkgs.writeText "shadowsocks.json" (builtins.toJSON opts);
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.shadowsocks = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run shadowsocks-libev shadowsocks server.
+        '';
+      };
+
+      localAddress = mkOption {
+        type = types.coercedTo types.str singleton (types.listOf types.str);
+        default = [ "[::0]" "0.0.0.0" ];
+        description = ''
+          Local addresses to which the server binds.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8388;
+        description = ''
+          Port which the server uses.
+        '';
+      };
+
+      password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Password for connecting clients.
+        '';
+      };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Password file with a password for connecting clients.
+        '';
+      };
+
+      mode = mkOption {
+        type = types.enum [ "tcp_only" "tcp_and_udp" "udp_only" ];
+        default = "tcp_and_udp";
+        description = ''
+          Relay protocols.
+        '';
+      };
+
+      encryptionMethod = mkOption {
+        type = types.str;
+        default = "chacha20-ietf-poly1305";
+        description = ''
+          Encryption method. See <link xlink:href="https://github.com/shadowsocks/shadowsocks-org/wiki/AEAD-Ciphers"/>.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    assertions = singleton
+      { assertion = cfg.password == null || cfg.passwordFile == null;
+        message = "Cannot use both password and passwordFile for shadowsocks-libev";
+      };
+
+    systemd.services.shadowsocks-libev = {
+      description = "shadowsocks-libev Daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.shadowsocks-libev ] ++ optional (cfg.passwordFile != null) pkgs.jq;
+      serviceConfig.PrivateTmp = true;
+      script = ''
+        ${optionalString (cfg.passwordFile != null) ''
+          cat ${configFile} | jq --arg password "$(cat "${cfg.passwordFile}")" '. + { password: $password }' > /tmp/shadowsocks.json
+        ''}
+        exec ss-server -c ${if cfg.passwordFile != null then "/tmp/shadowsocks.json" else configFile}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/shairport-sync.nix b/nixpkgs/nixos/modules/services/networking/shairport-sync.nix
new file mode 100644
index 000000000000..b4b86a2d55be
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/shairport-sync.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.shairport-sync;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.shairport-sync = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the shairport-sync daemon.
+
+          Running with a local system-wide or remote pulseaudio server
+          is recommended.
+        '';
+      };
+
+      arguments = mkOption {
+        default = "-v -o pa";
+        description = ''
+          Arguments to pass to the daemon. Defaults to a local pulseaudio
+          server.
+        '';
+      };
+
+      user = mkOption {
+        default = "shairport";
+        description = ''
+          User account name under which to run shairport-sync. The account
+          will be created.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.shairport-sync.enable {
+
+    services.avahi.enable = true;
+    services.avahi.publish.enable = true;
+    services.avahi.publish.userServices = true;
+
+    users.users.${cfg.user} =
+      { description = "Shairport user";
+        isSystemUser = true;
+        createHome = true;
+        home = "/var/lib/shairport-sync";
+        extraGroups = [ "audio" ] ++ optional config.hardware.pulseaudio.enable "pulse";
+      };
+
+    systemd.services.shairport-sync =
+      {
+        description = "shairport-sync";
+        after = [ "network.target" "avahi-daemon.service" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = cfg.user;
+          ExecStart = "${pkgs.shairport-sync}/bin/shairport-sync ${cfg.arguments}";
+          RuntimeDirectory = "shairport-sync";
+        };
+      };
+
+    environment.systemPackages = [ pkgs.shairport-sync ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/shorewall.nix b/nixpkgs/nixos/modules/services/networking/shorewall.nix
new file mode 100644
index 000000000000..16383be2530f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/shorewall.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, ... }:
+let
+  types = lib.types;
+  cfg = config.services.shorewall;
+in {
+  options = {
+    services.shorewall = {
+      enable = lib.mkOption {
+        type        = types.bool;
+        default     = false;
+        description = ''
+          Whether to enable Shorewall IPv4 Firewall.
+          <warning>
+            <para>
+            Enabling this service WILL disable the existing NixOS
+            firewall! Default firewall rules provided by packages are not
+            considered at the moment.
+            </para>
+          </warning>
+        '';
+      };
+      package = lib.mkOption {
+        type        = types.package;
+        default     = pkgs.shorewall;
+        defaultText = "pkgs.shorewall";
+        description = "The shorewall package to use.";
+      };
+      configs = lib.mkOption {
+        type        = types.attrsOf types.lines;
+        default     = {};
+        description = ''
+          This option defines the Shorewall configs.
+          The attribute name defines the name of the config,
+          and the attribute value defines the content of the config.
+        '';
+        apply = lib.mapAttrs (name: text: pkgs.writeText "${name}" text);
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.firewall.enable = false;
+    systemd.services.shorewall = {
+      description     = "Shorewall IPv4 Firewall";
+      after           = [ "ipset.target" ];
+      before          = [ "network-pre.target" ];
+      wants           = [ "network-pre.target" ];
+      wantedBy        = [ "multi-user.target" ];
+      reloadIfChanged = true;
+      restartTriggers = lib.attrValues cfg.configs;
+      serviceConfig = {
+        Type            = "oneshot";
+        RemainAfterExit = "yes";
+        ExecStart       = "${cfg.package}/bin/shorewall start";
+        ExecReload      = "${cfg.package}/bin/shorewall reload";
+        ExecStop        = "${cfg.package}/bin/shorewall stop";
+      };
+      preStart = ''
+        install -D -d -m 750 /var/lib/shorewall
+        install -D -d -m 755 /var/lock/subsys
+        touch                /var/log/shorewall.log
+        chown 750            /var/log/shorewall.log
+      '';
+    };
+    environment = {
+      etc = lib.mapAttrs' (name: conf: lib.nameValuePair "shorewall/${name}" {source=conf;}) cfg.configs;
+      systemPackages = [ cfg.package ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/shorewall6.nix b/nixpkgs/nixos/modules/services/networking/shorewall6.nix
new file mode 100644
index 000000000000..e081aedc6c34
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/shorewall6.nix
@@ -0,0 +1,70 @@
+{ config, lib, pkgs, ... }:
+let
+  types = lib.types;
+  cfg = config.services.shorewall6;
+in {
+  options = {
+    services.shorewall6 = {
+      enable = lib.mkOption {
+        type        = types.bool;
+        default     = false;
+        description = ''
+          Whether to enable Shorewall IPv6 Firewall.
+          <warning>
+            <para>
+            Enabling this service WILL disable the existing NixOS
+            firewall! Default firewall rules provided by packages are not
+            considered at the moment.
+            </para>
+          </warning>
+        '';
+      };
+      package = lib.mkOption {
+        type        = types.package;
+        default     = pkgs.shorewall;
+        defaultText = "pkgs.shorewall";
+        description = "The shorewall package to use.";
+      };
+      configs = lib.mkOption {
+        type        = types.attrsOf types.lines;
+        default     = {};
+        description = ''
+          This option defines the Shorewall configs.
+          The attribute name defines the name of the config,
+          and the attribute value defines the content of the config.
+        '';
+        apply = lib.mapAttrs (name: text: pkgs.writeText "${name}" text);
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.firewall.enable = false;
+    systemd.services.shorewall6 = {
+      description     = "Shorewall IPv6 Firewall";
+      after           = [ "ipset.target" ];
+      before          = [ "network-pre.target" ];
+      wants           = [ "network-pre.target" ];
+      wantedBy        = [ "multi-user.target" ];
+      reloadIfChanged = true;
+      restartTriggers = lib.attrValues cfg.configs;
+      serviceConfig = {
+        Type            = "oneshot";
+        RemainAfterExit = "yes";
+        ExecStart       = "${cfg.package}/bin/shorewall6 start";
+        ExecReload      = "${cfg.package}/bin/shorewall6 reload";
+        ExecStop        = "${cfg.package}/bin/shorewall6 stop";
+      };
+      preStart = ''
+        install -D -d -m 750 /var/lib/shorewall6
+        install -D -d -m 755 /var/lock/subsys
+        touch                /var/log/shorewall6.log
+        chown 750            /var/log/shorewall6.log
+      '';
+    };
+    environment = {
+      etc = lib.mapAttrs' (name: conf: lib.nameValuePair "shorewall6/${name}" {source=conf;}) cfg.configs;
+      systemPackages = [ cfg.package ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/shout.nix b/nixpkgs/nixos/modules/services/networking/shout.nix
new file mode 100644
index 000000000000..a808a7f39d05
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/shout.nix
@@ -0,0 +1,113 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.shout;
+  shoutHome = "/var/lib/shout";
+
+  defaultConfig = pkgs.runCommand "config.js" { preferLocalBuild = true; } ''
+    EDITOR=true ${pkgs.shout}/bin/shout config --home $PWD
+    mv config.js $out
+  '';
+
+  finalConfigFile = if (cfg.configFile != null) then cfg.configFile else ''
+    var _ = require('${pkgs.shout}/lib/node_modules/shout/node_modules/lodash')
+
+    module.exports = _.merge(
+      {},
+      require('${defaultConfig}'),
+      ${builtins.toJSON cfg.config}
+    )
+  '';
+
+in {
+  options.services.shout = {
+    enable = mkEnableOption "Shout web IRC client";
+
+    private = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Make your shout instance private. You will need to configure user
+        accounts by adding entries in <filename>${shoutHome}/users</filename>.
+      '';
+    };
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      description = "IP interface to listen on for http connections.";
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 9000;
+      description = "TCP port to listen on for http connections.";
+    };
+
+    configFile = mkOption {
+      type = types.nullOr types.lines;
+      default = null;
+      description = ''
+        Contents of Shout's <filename>config.js</filename> file.
+
+        Used for backward compatibility, recommended way is now to use
+        the <literal>config</literal> option.
+
+        Documentation: http://shout-irc.com/docs/server/configuration.html
+      '';
+    };
+
+    config = mkOption {
+      default = {};
+      type = types.attrs;
+      example = {
+        displayNetwork = false;
+        defaults = {
+          name = "Your Network";
+          host = "localhost";
+          port = 6697;
+        };
+      };
+      description = ''
+        Shout <filename>config.js</filename> contents as attribute set (will be
+        converted to JSON to generate the configuration file).
+
+        The options defined here will be merged to the default configuration file.
+
+        Documentation: http://shout-irc.com/docs/server/configuration.html
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.shout = {
+      uid = config.ids.uids.shout;
+      description = "Shout daemon user";
+      home = shoutHome;
+      createHome = true;
+    };
+
+    systemd.services.shout = {
+      description = "Shout web IRC client";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      preStart = "ln -sf ${pkgs.writeText "config.js" finalConfigFile} ${shoutHome}/config.js";
+      script = concatStringsSep " " [
+        "${pkgs.shout}/bin/shout"
+        (if cfg.private then "--private" else "--public")
+        "--port" (toString cfg.port)
+        "--host" (toString cfg.listenAddress)
+        "--home" shoutHome
+      ];
+      serviceConfig = {
+        User = "shout";
+        ProtectHome = "true";
+        ProtectSystem = "full";
+        PrivateTmp = "true";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/skydns.nix b/nixpkgs/nixos/modules/services/networking/skydns.nix
new file mode 100644
index 000000000000..e79d6de92644
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/skydns.nix
@@ -0,0 +1,92 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.skydns;
+
+in {
+  options.services.skydns = {
+    enable = mkEnableOption "skydns service";
+
+    etcd = {
+      machines = mkOption {
+        default = [ "http://127.0.0.1:2379" ];
+        type = types.listOf types.str;
+        description = "Skydns list of etcd endpoints to connect to.";
+      };
+
+      tlsKey = mkOption {
+        default = null;
+        type = types.nullOr types.path;
+        description = "Skydns path of TLS client certificate - private key.";
+      };
+
+      tlsPem = mkOption {
+        default = null;
+        type = types.nullOr types.path;
+        description = "Skydns path of TLS client certificate - public key.";
+      };
+
+      caCert = mkOption {
+        default = null;
+        type = types.nullOr types.path;
+        description = "Skydns path of TLS certificate authority public key.";
+      };
+    };
+
+    address = mkOption {
+      default = "0.0.0.0:53";
+      type = types.str;
+      description = "Skydns address to bind to.";
+    };
+
+    domain = mkOption {
+      default = "skydns.local.";
+      type = types.str;
+      description = "Skydns default domain if not specified by etcd config.";
+    };
+
+    nameservers = mkOption {
+      default = map (n: n + ":53") config.networking.nameservers;
+      type = types.listOf types.str;
+      description = "Skydns list of nameservers to forward DNS requests to when not authoritative for a domain.";
+      example = ["8.8.8.8:53" "8.8.4.4:53"];
+    };
+
+    package = mkOption {
+      default = pkgs.skydns;
+      defaultText = "pkgs.skydns";
+      type = types.package;
+      description = "Skydns package to use.";
+    };
+
+    extraConfig = mkOption {
+      default = {};
+      type = types.attrsOf types.str;
+      description = "Skydns attribute set of extra config options passed as environemnt variables.";
+    };
+  };
+
+  config = mkIf (cfg.enable) {
+    systemd.services.skydns = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "etcd.service" ];
+      description = "Skydns Service";
+      environment = {
+        ETCD_MACHINES = concatStringsSep "," cfg.etcd.machines;
+        ETCD_TLSKEY = cfg.etcd.tlsKey;
+        ETCD_TLSPEM = cfg.etcd.tlsPem;
+        ETCD_CACERT = cfg.etcd.caCert;
+        SKYDNS_ADDR = cfg.address;
+        SKYDNS_DOMAIN = cfg.domain;
+        SKYDNS_NAMESERVERS = concatStringsSep "," cfg.nameservers;
+      };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/skydns";
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/smartdns.nix b/nixpkgs/nixos/modules/services/networking/smartdns.nix
new file mode 100644
index 000000000000..f1888af70416
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/smartdns.nix
@@ -0,0 +1,61 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+  inherit (lib.types) attrsOf coercedTo listOf oneOf str int bool;
+  cfg = config.services.smartdns;
+
+  confFile = pkgs.writeText "smartdns.conf" (with generators;
+    toKeyValue {
+      mkKeyValue = mkKeyValueDefault {
+        mkValueString = v:
+          if isBool v then
+            if v then "yes" else "no"
+          else
+            mkValueStringDefault { } v;
+      } " ";
+      listsAsDuplicateKeys =
+        true; # Allowing duplications because we need to deal with multiple entries with the same key.
+    } cfg.settings);
+in {
+  options.services.smartdns = {
+    enable = mkEnableOption "SmartDNS DNS server";
+
+    bindPort = mkOption {
+      type = types.port;
+      default = 53;
+      description = "DNS listening port number.";
+    };
+
+    settings = mkOption {
+      type =
+      let atom = oneOf [ str int bool ];
+      in attrsOf (coercedTo atom toList (listOf atom));
+      example = literalExample ''
+        {
+          bind = ":5353 -no-rule -group example";
+          cache-size = 4096;
+          server-tls = [ "8.8.8.8:853" "1.1.1.1:853" ];
+          server-https = "https://cloudflare-dns.com/dns-query -exclude-default-group";
+          prefetch-domain = true;
+          speed-check-mode = "ping,tcp:80";
+        };
+      '';
+      description = ''
+        A set that will be generated into configuration file, see the <link xlink:href="https://github.com/pymumu/smartdns/blob/master/ReadMe_en.md#configuration-parameter">SmartDNS README</link> for details of configuration parameters.
+        You could override the options here like <option>services.smartdns.bindPort</option> by writing <literal>settings.bind = ":5353 -no-rule -group example";</literal>.
+      '';
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.smartdns.settings.bind = mkDefault ":${toString cfg.bindPort}";
+
+    systemd.packages = [ pkgs.smartdns ];
+    systemd.services.smartdns.wantedBy = [ "multi-user.target" ];
+    environment.etc."smartdns/smartdns.conf".source = confFile;
+    environment.etc."default/smartdns".source =
+      "${pkgs.smartdns}/etc/default/smartdns";
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/smokeping.nix b/nixpkgs/nixos/modules/services/networking/smokeping.nix
new file mode 100644
index 000000000000..37ee2a803890
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/smokeping.nix
@@ -0,0 +1,320 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+
+  cfg = config.services.smokeping;
+  smokepingHome = "/var/lib/smokeping";
+  smokepingPidDir = "/run";
+  configFile =
+    if cfg.config == null
+      then
+        ''
+          *** General ***
+          cgiurl   = ${cfg.cgiUrl}
+          contact = ${cfg.ownerEmail}
+          datadir  = ${smokepingHome}/data
+          imgcache = ${smokepingHome}/cache
+          imgurl   = ${cfg.imgUrl}
+          linkstyle = ${cfg.linkStyle}
+          ${lib.optionalString (cfg.mailHost != "") "mailhost = ${cfg.mailHost}"}
+          owner = ${cfg.owner}
+          pagedir = ${smokepingHome}/cache
+          piddir  = ${smokepingPidDir}
+          ${lib.optionalString (cfg.sendmail != null) "sendmail = ${cfg.sendmail}"}
+          smokemail = ${cfg.smokeMailTemplate}
+          *** Presentation ***
+          template = ${cfg.presentationTemplate}
+          ${cfg.presentationConfig}
+          *** Alerts ***
+          ${cfg.alertConfig}
+          *** Database ***
+          ${cfg.databaseConfig}
+          *** Probes ***
+          ${cfg.probeConfig}
+          *** Targets ***
+          ${cfg.targetConfig}
+          ${cfg.extraConfig}
+        ''
+      else
+        cfg.config;
+
+  configPath = pkgs.writeText "smokeping.conf" configFile;
+  cgiHome = pkgs.writeScript "smokeping.fcgi" ''
+    #!${pkgs.bash}/bin/bash
+    ${cfg.package}/bin/smokeping_cgi ${configPath}
+  '';
+in
+
+{
+  options = {
+    services.smokeping = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable the smokeping service";
+      };
+      alertConfig = mkOption {
+        type = types.lines;
+        default = ''
+          to = root@localhost
+          from = smokeping@localhost
+        '';
+        example = literalExample ''
+          to = alertee@address.somewhere
+          from = smokealert@company.xy
+
+          +someloss
+          type = loss
+          # in percent
+          pattern = >0%,*12*,>0%,*12*,>0%
+          comment = loss 3 times  in a row;
+        '';
+        description = "Configuration for alerts.";
+      };
+      cgiUrl = mkOption {
+        type = types.str;
+        default = "http://${cfg.hostName}:${toString cfg.port}/smokeping.cgi";
+        defaultText = "http://\${hostName}:\${toString port}/smokeping.cgi";
+        example = "https://somewhere.example.com/smokeping.cgi";
+        description = "URL to the smokeping cgi.";
+      };
+      config = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = "Full smokeping config supplied by the user. Overrides " +
+          "and replaces any other configuration supplied.";
+      };
+      databaseConfig = mkOption {
+        type = types.lines;
+        default = ''
+          step     = 300
+          pings    = 20
+          # consfn mrhb steps total
+          AVERAGE  0.5   1  1008
+          AVERAGE  0.5  12  4320
+              MIN  0.5  12  4320
+              MAX  0.5  12  4320
+          AVERAGE  0.5 144   720
+              MAX  0.5 144   720
+              MIN  0.5 144   720
+
+        '';
+        example = literalExample ''
+          # near constant pings.
+          step     = 30
+          pings    = 20
+          # consfn mrhb steps total
+          AVERAGE  0.5   1  10080
+          AVERAGE  0.5  12  43200
+              MIN  0.5  12  43200
+              MAX  0.5  12  43200
+          AVERAGE  0.5 144   7200
+              MAX  0.5 144   7200
+              MIN  0.5 144   7200
+        '';
+        description = ''Configure the ping frequency and retention of the rrd files.
+          Once set, changing the interval will require deletion or migration of all
+          the collected data.'';
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Any additional customization not already included.";
+      };
+      hostName = mkOption {
+        type = types.str;
+        default = config.networking.hostName;
+        example = "somewhere.example.com";
+        description = "DNS name for the urls generated in the cgi.";
+      };
+      imgUrl = mkOption {
+        type = types.str;
+        default = "http://${cfg.hostName}:${toString cfg.port}/cache";
+        defaultText = "http://\${hostName}:\${toString port}/cache";
+        example = "https://somewhere.example.com/cache";
+        description = "Base url for images generated in the cgi.";
+      };
+      linkStyle = mkOption {
+        type = types.enum ["original" "absolute" "relative"];
+        default = "relative";
+        example = "absolute";
+        description = "DNS name for the urls generated in the cgi.";
+      };
+      mailHost = mkOption {
+        type = types.str;
+        default = "";
+        example = "localhost";
+        description = "Use this SMTP server to send alerts";
+      };
+      owner = mkOption {
+        type = types.str;
+        default = "nobody";
+        example = "Joe Admin";
+        description = "Real name of the owner of the instance";
+      };
+      ownerEmail = mkOption {
+        type = types.str;
+        default = "no-reply@${cfg.hostName}";
+        example = "no-reply@yourdomain.com";
+        description = "Email contact for owner";
+      };
+      package = mkOption {
+        type = types.package;
+        default = pkgs.smokeping;
+        defaultText = "pkgs.smokeping";
+        description = "Specify a custom smokeping package";
+      };
+      port = mkOption {
+        type = types.int;
+        default = 8081;
+        example = 8081;
+        description = "TCP port to use for the web server.";
+      };
+      presentationConfig = mkOption {
+        type = types.lines;
+        default = ''
+          + charts
+          menu = Charts
+          title = The most interesting destinations
+          ++ stddev
+          sorter = StdDev(entries=>4)
+          title = Top Standard Deviation
+          menu = Std Deviation
+          format = Standard Deviation %f
+          ++ max
+          sorter = Max(entries=>5)
+          title = Top Max Roundtrip Time
+          menu = by Max
+          format = Max Roundtrip Time %f seconds
+          ++ loss
+          sorter = Loss(entries=>5)
+          title = Top Packet Loss
+          menu = Loss
+          format = Packets Lost %f
+          ++ median
+          sorter = Median(entries=>5)
+          title = Top Median Roundtrip Time
+          menu = by Median
+          format = Median RTT %f seconds
+          + overview
+          width = 600
+          height = 50
+          range = 10h
+          + detail
+          width = 600
+          height = 200
+          unison_tolerance = 2
+          "Last 3 Hours"    3h
+          "Last 30 Hours"   30h
+          "Last 10 Days"    10d
+          "Last 360 Days"   360d
+        '';
+        description = "presentation graph style";
+      };
+      presentationTemplate = mkOption {
+        type = types.str;
+        default = "${pkgs.smokeping}/etc/basepage.html.dist";
+        description = "Default page layout for the web UI.";
+      };
+      probeConfig = mkOption {
+        type = types.lines;
+        default = ''
+          + FPing
+          binary = ${config.security.wrapperDir}/fping
+        '';
+        description = "Probe configuration";
+      };
+      sendmail = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/wrappers/bin/sendmail";
+        description = "Use this sendmail compatible script to deliver alerts";
+      };
+      smokeMailTemplate = mkOption {
+        type = types.str;
+        default = "${cfg.package}/etc/smokemail.dist";
+        description = "Specify the smokemail template for alerts.";
+      };
+      targetConfig = mkOption {
+        type = types.lines;
+        default = ''
+					probe = FPing
+					menu = Top
+					title = Network Latency Grapher
+					remark = Welcome to the SmokePing website of xxx Company. \
+									 Here you will learn all about the latency of our network.
+					+ Local
+					menu = Local
+					title = Local Network
+					++ LocalMachine
+					menu = Local Machine
+					title = This host
+					host = localhost
+        '';
+        description = "Target configuration";
+      };
+      user = mkOption {
+        type = types.str;
+        default = "smokeping";
+        description = "User that runs smokeping and (optionally) thttpd";
+      };
+      webService = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Enable a smokeping web interface";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = !(cfg.sendmail != null && cfg.mailHost != "");
+        message = "services.smokeping: sendmail and Mailhost cannot both be enabled.";
+      }
+    ];
+    security.wrappers = {
+      fping.source = "${pkgs.fping}/bin/fping";
+      fping6.source = "${pkgs.fping}/bin/fping6";
+    };
+    environment.systemPackages = [ pkgs.fping ];
+    users.users.${cfg.user} = {
+      isNormalUser = false;
+      isSystemUser = true;
+      uid = config.ids.uids.smokeping;
+      description = "smokeping daemon user";
+      home = smokepingHome;
+      createHome = true;
+    };
+    systemd.services.smokeping = {
+      wantedBy = [ "multi-user.target"];
+      serviceConfig = {
+        User = cfg.user;
+        Restart = "on-failure";
+      };
+      preStart = ''
+        mkdir -m 0755 -p ${smokepingHome}/cache ${smokepingHome}/data
+        rm -f ${smokepingHome}/cropper
+        ln -s ${cfg.package}/htdocs/cropper ${smokepingHome}/cropper
+        rm -f ${smokepingHome}/smokeping.fcgi
+        ln -s ${cgiHome} ${smokepingHome}/smokeping.fcgi
+        ${cfg.package}/bin/smokeping --check --config=${configPath}
+        ${cfg.package}/bin/smokeping --static --config=${configPath}
+      '';
+      script = ''${cfg.package}/bin/smokeping --config=${configPath} --nodaemon'';
+    };
+    systemd.services.thttpd = mkIf cfg.webService {
+      wantedBy = [ "multi-user.target"];
+      requires = [ "smokeping.service"];
+      partOf = [ "smokeping.service"];
+      path = with pkgs; [ bash rrdtool smokeping thttpd ];
+      script = ''thttpd -u ${cfg.user} -c "**.fcgi" -d ${smokepingHome} -p ${builtins.toString cfg.port} -D -nos'';
+      serviceConfig.Restart = "always";
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ erictapen ];
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/sniproxy.nix b/nixpkgs/nixos/modules/services/networking/sniproxy.nix
new file mode 100644
index 000000000000..0345c12d3afe
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/sniproxy.nix
@@ -0,0 +1,99 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.sniproxy;
+
+  configFile = pkgs.writeText "sniproxy.conf" ''
+    user ${cfg.user}
+    pidfile /run/sniproxy.pid
+    ${cfg.config}
+  '';
+
+in
+{
+  options = {
+    services.sniproxy = {
+      enable = mkEnableOption "sniproxy server";
+
+      user = mkOption {
+        type = types.str;
+        default = "sniproxy";
+        description = "User account under which sniproxy runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "sniproxy";
+        description = "Group under which sniproxy runs.";
+      };
+
+      config = mkOption {
+        type = types.lines;
+        default = "";
+        description = "sniproxy.conf configuration excluding the daemon username and pid file.";
+        example = literalExample ''
+          error_log {
+            filename /var/log/sniproxy/error.log
+          }
+          access_log {
+            filename /var/log/sniproxy/access.log
+          }
+          listen 443 {
+            proto tls
+          }
+          table {
+            example.com 192.0.2.10
+            example.net 192.0.2.20
+        }
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.str;
+        default = "/var/log/sniproxy/";
+        description = "Location of the log directory for sniproxy.";
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.sniproxy = {
+      description = "sniproxy server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        test -d ${cfg.logDir} || {
+          echo "Creating initial log directory for sniproxy in ${cfg.logDir}"
+          mkdir -p ${cfg.logDir}
+          chmod 640 ${cfg.logDir}
+          }
+        chown -R ${cfg.user}:${cfg.group} ${cfg.logDir}
+      '';
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.sniproxy}/bin/sniproxy -c ${configFile}";
+        Restart = "always";
+      };
+    };
+
+    users.users = mkIf (cfg.user == "sniproxy") {
+      sniproxy = {
+        group = cfg.group;
+        uid = config.ids.uids.sniproxy;
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "sniproxy") {
+      sniproxy = {
+        gid = config.ids.gids.sniproxy;
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/softether.nix b/nixpkgs/nixos/modules/services/networking/softether.nix
new file mode 100644
index 000000000000..2dc73d81b258
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/softether.nix
@@ -0,0 +1,163 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.softether;
+
+  package = cfg.package.override { dataDir = cfg.dataDir; };
+
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.softether = {
+
+      enable = mkEnableOption "SoftEther VPN services";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.softether;
+        defaultText = "pkgs.softether";
+        description = ''
+          softether derivation to use.
+        '';
+      };
+
+      vpnserver.enable = mkEnableOption "SoftEther VPN Server";
+
+      vpnbridge.enable = mkEnableOption "SoftEther VPN Bridge";
+
+      vpnclient = {
+        enable = mkEnableOption "SoftEther VPN Client";
+        up = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Shell commands executed when the Virtual Network Adapter(s) is/are starting.
+          '';
+        };
+        down = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Shell commands executed when the Virtual Network Adapter(s) is/are shutting down.
+          '';
+        };
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/softether";
+        description = ''
+          Data directory for SoftEther VPN.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable (
+
+    mkMerge [{
+      environment.systemPackages = [ package ];
+
+      systemd.services.softether-init = {
+        description = "SoftEther VPN services initial task";
+        wantedBy = [ "network.target" ];
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = false;
+        };
+        script = ''
+            for d in vpnserver vpnbridge vpnclient vpncmd; do
+                if ! test -e ${cfg.dataDir}/$d; then
+                    ${pkgs.coreutils}/bin/mkdir -m0700 -p ${cfg.dataDir}/$d
+                    install -m0600 ${package}${cfg.dataDir}/$d/hamcore.se2 ${cfg.dataDir}/$d/hamcore.se2
+                fi
+            done
+            rm -rf ${cfg.dataDir}/vpncmd/vpncmd
+            ln -s ${package}${cfg.dataDir}/vpncmd/vpncmd ${cfg.dataDir}/vpncmd/vpncmd
+        '';
+      };
+    }
+
+    (mkIf (cfg.vpnserver.enable) {
+      systemd.services.vpnserver = {
+        description = "SoftEther VPN Server";
+        after = [ "softether-init.service" ];
+        requires = [ "softether-init.service" ];
+        wantedBy = [ "network.target" ];
+        serviceConfig = {
+          Type = "forking";
+          ExecStart = "${package}/bin/vpnserver start";
+          ExecStop = "${package}/bin/vpnserver stop";
+        };
+        preStart = ''
+            rm -rf ${cfg.dataDir}/vpnserver/vpnserver
+            ln -s ${package}${cfg.dataDir}/vpnserver/vpnserver ${cfg.dataDir}/vpnserver/vpnserver
+        '';
+        postStop = ''
+            rm -rf ${cfg.dataDir}/vpnserver/vpnserver
+        '';
+      };
+    })
+
+    (mkIf (cfg.vpnbridge.enable) {
+      systemd.services.vpnbridge = {
+        description = "SoftEther VPN Bridge";
+        after = [ "softether-init.service" ];
+        requires = [ "softether-init.service" ];
+        wantedBy = [ "network.target" ];
+        serviceConfig = {
+          Type = "forking";
+          ExecStart = "${package}/bin/vpnbridge start";
+          ExecStop = "${package}/bin/vpnbridge stop";
+        };
+        preStart = ''
+            rm -rf ${cfg.dataDir}/vpnbridge/vpnbridge
+            ln -s ${package}${cfg.dataDir}/vpnbridge/vpnbridge ${cfg.dataDir}/vpnbridge/vpnbridge
+        '';
+        postStop = ''
+            rm -rf ${cfg.dataDir}/vpnbridge/vpnbridge
+        '';
+      };
+    })
+
+    (mkIf (cfg.vpnclient.enable) {
+      systemd.services.vpnclient = {
+        description = "SoftEther VPN Client";
+        after = [ "softether-init.service" ];
+        requires = [ "softether-init.service" ];
+        wantedBy = [ "network.target" ];
+        serviceConfig = {
+          Type = "forking";
+          ExecStart = "${package}/bin/vpnclient start";
+          ExecStop = "${package}/bin/vpnclient stop";
+        };
+        preStart = ''
+            rm -rf ${cfg.dataDir}/vpnclient/vpnclient
+            ln -s ${package}${cfg.dataDir}/vpnclient/vpnclient ${cfg.dataDir}/vpnclient/vpnclient
+        '';
+        postStart = ''
+            sleep 1
+            ${cfg.vpnclient.up}
+        '';
+        postStop = ''
+            rm -rf ${cfg.dataDir}/vpnclient/vpnclient
+            sleep 1
+            ${cfg.vpnclient.down}
+        '';
+      };
+      boot.kernelModules = [ "tun" ];
+    })
+
+  ]);
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/spacecookie.nix b/nixpkgs/nixos/modules/services/networking/spacecookie.nix
new file mode 100644
index 000000000000..c4d06df6ad4a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/spacecookie.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.spacecookie;
+  configFile = pkgs.writeText "spacecookie.json" (lib.generators.toJSON {} {
+    inherit (cfg) hostname port root;
+  });
+in {
+
+  options = {
+
+    services.spacecookie = {
+
+      enable = mkEnableOption "spacecookie";
+
+      hostname = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "The hostname the service is reachable via. Clients will use this hostname for further requests after loading the initial gopher menu.";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 70;
+        description = "Port the gopher service should be exposed on.";
+      };
+
+      root = mkOption {
+        type = types.path;
+        default = "/srv/gopher";
+        description = "The root directory spacecookie serves via gopher.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.sockets.spacecookie = {
+      description = "Socket for the Spacecookie Gopher Server";
+      wantedBy = [ "sockets.target" ];
+      listenStreams = [ "[::]:${toString cfg.port}" ];
+      socketConfig = {
+        BindIPv6Only = "both";
+      };
+    };
+
+    systemd.services.spacecookie = {
+      description = "Spacecookie Gopher Server";
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "spacecookie.socket" ];
+
+      serviceConfig = {
+        Type = "notify";
+        ExecStart = "${pkgs.haskellPackages.spacecookie}/bin/spacecookie ${configFile}";
+        FileDescriptorStoreMax = 1;
+
+        DynamicUser = true;
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        PrivateUsers = true;
+
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+        LockPersonality = true;
+        RestrictRealtime = true;
+
+        # AF_UNIX for communication with systemd
+        # AF_INET replaced by BindIPv6Only=both
+        RestrictAddressFamilies = "AF_UNIX AF_INET6";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/spiped.nix b/nixpkgs/nixos/modules/services/networking/spiped.nix
new file mode 100644
index 000000000000..e60d9abf42a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/spiped.nix
@@ -0,0 +1,220 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.spiped;
+in
+{
+  options = {
+    services.spiped = {
+      enable = mkOption {
+        type        = types.bool;
+        default     = false;
+        description = "Enable the spiped service module.";
+      };
+
+      config = mkOption {
+        type = types.attrsOf (types.submodule (
+          {
+            options = {
+              encrypt = mkOption {
+                type    = types.bool;
+                default = false;
+                description = ''
+                  Take unencrypted connections from the
+                  <literal>source</literal> socket and send encrypted
+                  connections to the <literal>target</literal> socket.
+                '';
+              };
+
+              decrypt = mkOption {
+                type    = types.bool;
+                default = false;
+                description = ''
+                  Take encrypted connections from the
+                  <literal>source</literal> socket and send unencrypted
+                  connections to the <literal>target</literal> socket.
+                '';
+              };
+
+              source = mkOption {
+                type    = types.str;
+                description = ''
+                  Address on which spiped should listen for incoming
+                  connections.  Must be in one of the following formats:
+                  <literal>/absolute/path/to/unix/socket</literal>,
+                  <literal>host.name:port</literal>,
+                  <literal>[ip.v4.ad.dr]:port</literal> or
+                  <literal>[ipv6::addr]:port</literal> - note that
+                  hostnames are resolved when spiped is launched and are
+                  not re-resolved later; thus if DNS entries change
+                  spiped will continue to connect to the expired
+                  address.
+                '';
+              };
+
+              target = mkOption {
+                type    = types.str;
+                description = "Address to which spiped should connect.";
+              };
+
+              keyfile = mkOption {
+                type    = types.path;
+                description = ''
+                  Name of a file containing the spiped key. As the
+                  daemon runs as the <literal>spiped</literal> user, the
+                  key file must be somewhere owned by that user. By
+                  default, we recommend putting the keys for any spipe
+                  services in <literal>/var/lib/spiped</literal>.
+                '';
+              };
+
+              timeout = mkOption {
+                type = types.int;
+                default = 5;
+                description = ''
+                  Timeout, in seconds, after which an attempt to connect to
+                  the target or a protocol handshake will be aborted (and the
+                  connection dropped) if not completed
+                '';
+              };
+
+              maxConns = mkOption {
+                type = types.int;
+                default = 100;
+                description = ''
+                  Limit on the number of simultaneous connections allowed.
+                '';
+              };
+
+              waitForDNS = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  Wait for DNS. Normally when <literal>spiped</literal> is
+                  launched it resolves addresses and binds to its source
+                  socket before the parent process returns; with this option
+                  it will daemonize first and retry failed DNS lookups until
+                  they succeed. This allows <literal>spiped</literal> to
+                  launch even if DNS isn't set up yet, but at the expense of
+                  losing the guarantee that once <literal>spiped</literal> has
+                  finished launching it will be ready to create pipes.
+                '';
+              };
+
+              disableKeepalives = mkOption {
+                type = types.bool;
+                default = false;
+                description = "Disable transport layer keep-alives.";
+              };
+
+              weakHandshake = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  Use fast/weak handshaking: This reduces the CPU time spent
+                  in the initial connection setup, at the expense of losing
+                  perfect forward secrecy.
+                '';
+              };
+
+              resolveRefresh = mkOption {
+                type = types.int;
+                default = 60;
+                description = ''
+                  Resolution refresh time for the target socket, in seconds.
+                '';
+              };
+
+              disableReresolution = mkOption {
+                type = types.bool;
+                default = false;
+                description = "Disable target address re-resolution.";
+              };
+            };
+          }
+        ));
+
+        default = {};
+
+        example = literalExample ''
+          {
+            pipe1 =
+              { keyfile = "/var/lib/spiped/pipe1.key";
+                encrypt = true;
+                source  = "localhost:6000";
+                target  = "endpoint.example.com:7000";
+              };
+            pipe2 =
+              { keyfile = "/var/lib/spiped/pipe2.key";
+                decrypt = true;
+                source  = "0.0.0.0:7000";
+                target  = "localhost:3000";
+              };
+          }
+        '';
+
+        description = ''
+          Configuration for a secure pipe daemon. The daemon can be
+          started, stopped, or examined using
+          <literal>systemctl</literal>, under the name
+          <literal>spiped@foo</literal>.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = mapAttrsToList (name: c: {
+      assertion = (c.encrypt -> !c.decrypt) || (c.decrypt -> c.encrypt);
+      message   = "A pipe must either encrypt or decrypt";
+    }) cfg.config;
+
+    users.groups.spiped.gid = config.ids.gids.spiped;
+    users.users.spiped = {
+      description = "Secure Pipe Service user";
+      group       = "spiped";
+      uid         = config.ids.uids.spiped;
+    };
+
+    systemd.services."spiped@" = {
+      description = "Secure pipe '%i'";
+      after       = [ "network.target" ];
+
+      serviceConfig = {
+        Restart   = "always";
+        User      = "spiped";
+        PermissionsStartOnly = true;
+      };
+
+      preStart  = ''
+        cd /var/lib/spiped
+        chmod -R 0660 *
+        chown -R spiped:spiped *
+      '';
+      scriptArgs = "%i";
+      script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`";
+    };
+
+    system.activationScripts.spiped = optionalString (cfg.config != {})
+      "mkdir -p /var/lib/spiped";
+
+    # Setup spiped config files
+    environment.etc = mapAttrs' (name: cfg: nameValuePair "spiped/${name}.spec"
+      { text = concatStringsSep " "
+          [ (if cfg.encrypt then "-e" else "-d")        # Mode
+            "-s ${cfg.source}"                          # Source
+            "-t ${cfg.target}"                          # Target
+            "-k ${cfg.keyfile}"                         # Keyfile
+            "-n ${toString cfg.maxConns}"               # Max number of conns
+            "-o ${toString cfg.timeout}"                # Timeout
+            (optionalString cfg.waitForDNS "-D")        # Wait for DNS
+            (optionalString cfg.weakHandshake "-f")     # No PFS
+            (optionalString cfg.disableKeepalives "-j") # Keepalives
+            (if cfg.disableReresolution then "-R"
+              else "-r ${toString cfg.resolveRefresh}")
+          ];
+      }) cfg.config;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/squid.nix b/nixpkgs/nixos/modules/services/networking/squid.nix
new file mode 100644
index 000000000000..9d063b92aa1e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/squid.nix
@@ -0,0 +1,168 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.squid;
+
+
+  squidConfig = pkgs.writeText "squid.conf"
+    (if cfg.configText != null then cfg.configText else
+    ''
+    #
+    # Recommended minimum configuration (3.5):
+    #
+
+    # Example rule allowing access from your local networks.
+    # Adapt to list your (internal) IP networks from where browsing
+    # should be allowed
+    acl localnet src 10.0.0.0/8     # RFC 1918 possible internal network
+    acl localnet src 172.16.0.0/12  # RFC 1918 possible internal network
+    acl localnet src 192.168.0.0/16 # RFC 1918 possible internal network
+    acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines
+    acl localnet src fc00::/7       # RFC 4193 local private network range
+    acl localnet src fe80::/10      # RFC 4291 link-local (directly plugged) machines
+
+    acl SSL_ports port 443          # https
+    acl Safe_ports port 80          # http
+    acl Safe_ports port 21          # ftp
+    acl Safe_ports port 443         # https
+    acl Safe_ports port 70          # gopher
+    acl Safe_ports port 210         # wais
+    acl Safe_ports port 1025-65535  # unregistered ports
+    acl Safe_ports port 280         # http-mgmt
+    acl Safe_ports port 488         # gss-http
+    acl Safe_ports port 591         # filemaker
+    acl Safe_ports port 777         # multiling http
+    acl CONNECT method CONNECT
+
+    #
+    # Recommended minimum Access Permission configuration:
+    #
+    # Deny requests to certain unsafe ports
+    http_access deny !Safe_ports
+
+    # Deny CONNECT to other than secure SSL ports
+    http_access deny CONNECT !SSL_ports
+
+    # Only allow cachemgr access from localhost
+    http_access allow localhost manager
+    http_access deny manager
+
+    # We strongly recommend the following be uncommented to protect innocent
+    # web applications running on the proxy server who think the only
+    # one who can access services on "localhost" is a local user
+    http_access deny to_localhost
+
+    # Application logs to syslog, access and store logs have specific files
+    cache_log       syslog
+    access_log      stdio:/var/log/squid/access.log
+    cache_store_log stdio:/var/log/squid/store.log
+
+    # Required by systemd service
+    pid_filename    /run/squid.pid
+
+    # Run as user and group squid
+    cache_effective_user squid squid
+
+    #
+    # INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
+    #
+    ${cfg.extraConfig}
+
+    # Example rule allowing access from your local networks.
+    # Adapt localnet in the ACL section to list your (internal) IP networks
+    # from where browsing should be allowed
+    http_access allow localnet
+    http_access allow localhost
+
+    # And finally deny all other access to this proxy
+    http_access deny all
+
+    # Squid normally listens to port 3128
+    http_port ${toString cfg.proxyPort}
+
+    # Leave coredumps in the first cache dir
+    coredump_dir /var/cache/squid
+
+    #
+    # Add any of your own refresh_pattern entries above these.
+    #
+    refresh_pattern ^ftp:           1440    20%     10080
+    refresh_pattern ^gopher:        1440    0%      1440
+    refresh_pattern -i (/cgi-bin/|\?) 0     0%      0
+    refresh_pattern .               0       20%     4320
+  '');
+
+in
+
+{
+
+  options = {
+
+    services.squid = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to run squid web proxy.";
+      };
+
+      proxyPort = mkOption {
+        type = types.int;
+        default = 3128;
+        description = "TCP port on which squid will listen.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Squid configuration. Contents will be added
+          verbatim to the configuration file.
+        '';
+      };
+
+      configText = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = ''
+          Verbatim contents of squid.conf. If null (default), use the
+          autogenerated file from NixOS instead.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.squid = {
+      isSystemUser = true;
+      group = "squid";
+      home = "/var/cache/squid";
+      createHome = true;
+    };
+
+    users.groups.squid = {};
+
+    systemd.services.squid = {
+      description = "Squid caching web proxy";
+      after = [ "network.target" "nss-lookup.target" ];
+      wantedBy = [ "multi-user.target"];
+      preStart = ''
+        mkdir -p "/var/log/squid"
+        chown squid:squid "/var/log/squid"
+      '';
+      serviceConfig = {
+        Type="forking";
+        PIDFile="/run/squid.pid";
+        ExecStart  = "${pkgs.squid}/bin/squid -YCs -f ${squidConfig}";
+      };
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix b/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix
new file mode 100644
index 000000000000..41d0584080e4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ssh/lshd.nix
@@ -0,0 +1,183 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) lsh;
+
+  cfg = config.services.lshd;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.lshd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the GNU lshd SSH2 daemon, which allows
+          secure remote login.
+        '';
+      };
+
+      portNumber = mkOption {
+        default = 22;
+        description = ''
+          The port on which to listen for connections.
+        '';
+      };
+
+      interfaces = mkOption {
+        default = [];
+        description = ''
+          List of network interfaces where listening for connections.
+          When providing the empty list, `[]', lshd listens on all
+          network interfaces.
+        '';
+        example = [ "localhost" "1.2.3.4:443" ];
+      };
+
+      hostKey = mkOption {
+        default = "/etc/lsh/host-key";
+        description = ''
+          Path to the server's private key.  Note that this key must
+          have been created, e.g., using "lsh-keygen --server |
+          lsh-writekey --server", so that you can run lshd.
+        '';
+      };
+
+      syslog = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''Whether to enable syslog output.'';
+      };
+
+      passwordAuthentication = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''Whether to enable password authentication.'';
+      };
+
+      publicKeyAuthentication = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''Whether to enable public key authentication.'';
+      };
+
+      rootLogin = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''Whether to enable remote root login.'';
+      };
+
+      loginShell = mkOption {
+        default = null;
+        description = ''
+          If non-null, override the default login shell with the
+          specified value.
+        '';
+        example = "/nix/store/xyz-bash-10.0/bin/bash10";
+      };
+
+      srpKeyExchange = mkOption {
+        default = false;
+        description = ''
+          Whether to enable SRP key exchange and user authentication.
+        '';
+      };
+
+      tcpForwarding = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''Whether to enable TCP/IP forwarding.'';
+      };
+
+      x11Forwarding = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''Whether to enable X11 forwarding.'';
+      };
+
+      subsystems = mkOption {
+        description = ''
+          List of subsystem-path pairs, where the head of the pair
+          denotes the subsystem name, and the tail denotes the path to
+          an executable implementing it.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.lshd.subsystems = [ ["sftp" "${pkgs.lsh}/sbin/sftp-server"] ];
+
+    systemd.services.lshd = {
+      description = "GNU lshd SSH2 daemon";
+
+      after = [ "network.target" ];
+
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        LD_LIBRARY_PATH = config.system.nssModules.path;
+      };
+
+      preStart = ''
+        test -d /etc/lsh || mkdir -m 0755 -p /etc/lsh
+        test -d /var/spool/lsh || mkdir -m 0755 -p /var/spool/lsh
+
+        if ! test -f /var/spool/lsh/yarrow-seed-file
+        then
+            # XXX: It would be nice to provide feedback to the
+            # user when this fails, so that they can retry it
+            # manually.
+            ${lsh}/bin/lsh-make-seed --sloppy \
+               -o /var/spool/lsh/yarrow-seed-file
+        fi
+
+        if ! test -f "${cfg.hostKey}"
+        then
+            ${lsh}/bin/lsh-keygen --server | \
+            ${lsh}/bin/lsh-writekey --server -o "${cfg.hostKey}"
+        fi
+      '';
+
+      script = with cfg; ''
+        ${lsh}/sbin/lshd --daemonic \
+          --password-helper="${lsh}/sbin/lsh-pam-checkpw" \
+          -p ${toString portNumber} \
+          ${if interfaces == [] then ""
+            else (concatStrings (map (i: "--interface=\"${i}\"")
+                                     interfaces))} \
+          -h "${hostKey}" \
+          ${if !syslog then "--no-syslog" else ""} \
+          ${if passwordAuthentication then "--password" else "--no-password" } \
+          ${if publicKeyAuthentication then "--publickey" else "--no-publickey" } \
+          ${if rootLogin then "--root-login" else "--no-root-login" } \
+          ${if loginShell != null then "--login-shell=\"${loginShell}\"" else "" } \
+          ${if srpKeyExchange then "--srp-keyexchange" else "--no-srp-keyexchange" } \
+          ${if !tcpForwarding then "--no-tcpip-forward" else "--tcpip-forward"} \
+          ${if x11Forwarding then "--x11-forward" else "--no-x11-forward" } \
+          --subsystems=${concatStringsSep ","
+                                          (map (pair: (head pair) + "=" +
+                                                      (head (tail pair)))
+                                               subsystems)}
+      '';
+    };
+
+    security.pam.services.lshd = {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix
new file mode 100644
index 000000000000..a294bbfba0aa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/ssh/sshd.nix
@@ -0,0 +1,557 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  # The splicing information needed for nativeBuildInputs isn't available
+  # on the derivations likely to be used as `cfgc.package`.
+  # This middle-ground solution ensures *an* sshd can do their basic validation
+  # on the configuration.
+  validationPackage = if pkgs.stdenv.buildPlatform == pkgs.stdenv.hostPlatform
+    then cfgc.package
+    else pkgs.buildPackages.openssh;
+
+  sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ validationPackage ]; } ''
+    cat >$out <<EOL
+    ${cfg.extraConfig}
+    EOL
+
+    ssh-keygen -q -f mock-hostkey -N ""
+    sshd -t -f $out -h mock-hostkey
+  '';
+
+  cfg  = config.services.openssh;
+  cfgc = config.programs.ssh;
+
+  nssModulesPath = config.system.nssModules.path;
+
+  userOptions = {
+
+    options.openssh.authorizedKeys = {
+      keys = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          A list of verbatim OpenSSH public keys that should be added to the
+          user's authorized keys. The keys are added to a file that the SSH
+          daemon reads in addition to the the user's authorized_keys file.
+          You can combine the <literal>keys</literal> and
+          <literal>keyFiles</literal> options.
+          Warning: If you are using <literal>NixOps</literal> then don't use this
+          option since it will replace the key required for deployment via ssh.
+        '';
+      };
+
+      keyFiles = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          A list of files each containing one OpenSSH public key that should be
+          added to the user's authorized keys. The contents of the files are
+          read at build time and added to a file that the SSH daemon reads in
+          addition to the the user's authorized_keys file. You can combine the
+          <literal>keyFiles</literal> and <literal>keys</literal> options.
+        '';
+      };
+    };
+
+  };
+
+  authKeysFiles = let
+    mkAuthKeyFile = u: nameValuePair "ssh/authorized_keys.d/${u.name}" {
+      mode = "0444";
+      source = pkgs.writeText "${u.name}-authorized_keys" ''
+        ${concatStringsSep "\n" u.openssh.authorizedKeys.keys}
+        ${concatMapStrings (f: readFile f + "\n") u.openssh.authorizedKeys.keyFiles}
+      '';
+    };
+    usersWithKeys = attrValues (flip filterAttrs config.users.users (n: u:
+      length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0
+    ));
+  in listToAttrs (map mkAuthKeyFile usersWithKeys);
+
+in
+
+{
+  imports = [
+    (mkAliasOptionModule [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ])
+    (mkAliasOptionModule [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ])
+  ];
+
+  ###### interface
+
+  options = {
+
+    services.openssh = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the OpenSSH secure shell daemon, which
+          allows secure remote logins.
+        '';
+      };
+
+      startWhenNeeded = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          If set, <command>sshd</command> is socket-activated; that
+          is, instead of having it permanently running as a daemon,
+          systemd will start an instance for each incoming connection.
+        '';
+      };
+
+      forwardX11 = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow X11 connections to be forwarded.
+        '';
+      };
+
+      allowSFTP = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable the SFTP subsystem in the SSH daemon.  This
+          enables the use of commands such as <command>sftp</command> and
+          <command>sshfs</command>.
+        '';
+      };
+
+      sftpFlags = mkOption {
+        type = with types; listOf str;
+        default = [];
+        example = [ "-f AUTHPRIV" "-l INFO" ];
+        description = ''
+          Commandline flags to add to sftp-server.
+        '';
+      };
+
+      permitRootLogin = mkOption {
+        default = "prohibit-password";
+        type = types.enum ["yes" "without-password" "prohibit-password" "forced-commands-only" "no"];
+        description = ''
+          Whether the root user can login using ssh.
+        '';
+      };
+
+      gatewayPorts = mkOption {
+        type = types.str;
+        default = "no";
+        description = ''
+          Specifies whether remote hosts are allowed to connect to
+          ports forwarded for the client.  See
+          <citerefentry><refentrytitle>sshd_config</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry>.
+        '';
+      };
+
+      ports = mkOption {
+        type = types.listOf types.port;
+        default = [22];
+        description = ''
+          Specifies on which ports the SSH daemon listens.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to automatically open the specified ports in the firewall.
+        '';
+      };
+
+      listenAddresses = mkOption {
+        type = with types; listOf (submodule {
+          options = {
+            addr = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                Host, IPv4 or IPv6 address to listen to.
+              '';
+            };
+            port = mkOption {
+              type = types.nullOr types.int;
+              default = null;
+              description = ''
+                Port to listen to.
+              '';
+            };
+          };
+        });
+        default = [];
+        example = [ { addr = "192.168.3.1"; port = 22; } { addr = "0.0.0.0"; port = 64022; } ];
+        description = ''
+          List of addresses and ports to listen on (ListenAddress directive
+          in config). If port is not specified for address sshd will listen
+          on all ports specified by <literal>ports</literal> option.
+          NOTE: this will override default listening on all local addresses and port 22.
+          NOTE: setting this option won't automatically enable given ports
+          in firewall configuration.
+        '';
+      };
+
+      passwordAuthentication = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Specifies whether password authentication is allowed.
+        '';
+      };
+
+      challengeResponseAuthentication = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Specifies whether challenge/response authentication is allowed.
+        '';
+      };
+
+      hostKeys = mkOption {
+        type = types.listOf types.attrs;
+        default =
+          [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; }
+            { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
+          ];
+        example =
+          [ { type = "rsa"; bits = 4096; path = "/etc/ssh/ssh_host_rsa_key"; rounds = 100; openSSHFormat = true; }
+            { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; rounds = 100; comment = "key comment"; }
+          ];
+        description = ''
+          NixOS can automatically generate SSH host keys.  This option
+          specifies the path, type and size of each key.  See
+          <citerefentry><refentrytitle>ssh-keygen</refentrytitle>
+          <manvolnum>1</manvolnum></citerefentry> for supported types
+          and sizes.
+        '';
+      };
+
+      authorizedKeysFiles = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Files from which authorized keys are read.";
+      };
+
+      authorizedKeysCommand = mkOption {
+        type = types.str;
+        default = "none";
+        description = ''
+          Specifies a program to be used to look up the user's public
+          keys. The program must be owned by root, not writable by group
+          or others and specified by an absolute path.
+        '';
+      };
+
+      authorizedKeysCommandUser = mkOption {
+        type = types.str;
+        default = "nobody";
+        description = ''
+          Specifies the user under whose account the AuthorizedKeysCommand
+          is run. It is recommended to use a dedicated user that has no
+          other role on the host than running authorized keys commands.
+        '';
+      };
+
+      kexAlgorithms = mkOption {
+        type = types.listOf types.str;
+        default = [
+          "curve25519-sha256@libssh.org"
+          "diffie-hellman-group-exchange-sha256"
+        ];
+        description = ''
+          Allowed key exchange algorithms
+          </para>
+          <para>
+          Defaults to recommended settings from both
+          <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" />
+          and
+          <link xlink:href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29" />
+        '';
+      };
+
+      ciphers = mkOption {
+        type = types.listOf types.str;
+        default = [
+          "chacha20-poly1305@openssh.com"
+          "aes256-gcm@openssh.com"
+          "aes128-gcm@openssh.com"
+          "aes256-ctr"
+          "aes192-ctr"
+          "aes128-ctr"
+        ];
+        description = ''
+          Allowed ciphers
+          </para>
+          <para>
+          Defaults to recommended settings from both
+          <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" />
+          and
+          <link xlink:href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29" />
+        '';
+      };
+
+      macs = mkOption {
+        type = types.listOf types.str;
+        default = [
+          "hmac-sha2-512-etm@openssh.com"
+          "hmac-sha2-256-etm@openssh.com"
+          "umac-128-etm@openssh.com"
+          "hmac-sha2-512"
+          "hmac-sha2-256"
+          "umac-128@openssh.com"
+        ];
+        description = ''
+          Allowed MACs
+          </para>
+          <para>
+          Defaults to recommended settings from both
+          <link xlink:href="https://stribika.github.io/2015/01/04/secure-secure-shell.html" />
+          and
+          <link xlink:href="https://wiki.mozilla.org/Security/Guidelines/OpenSSH#Modern_.28OpenSSH_6.7.2B.29" />
+        '';
+      };
+
+      logLevel = mkOption {
+        type = types.enum [ "QUIET" "FATAL" "ERROR" "INFO" "VERBOSE" "DEBUG" "DEBUG1" "DEBUG2" "DEBUG3" ];
+        default = "VERBOSE";
+        description = ''
+          Gives the verbosity level that is used when logging messages from sshd(8). The possible values are:
+          QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, and DEBUG3. The default is VERBOSE. DEBUG and DEBUG1
+          are equivalent. DEBUG2 and DEBUG3 each specify higher levels of debugging output. Logging with a DEBUG level
+          violates the privacy of users and is not recommended.
+
+          LogLevel VERBOSE logs user's key fingerprint on login.
+          Needed to have a clear audit track of which key was used to log in.
+        '';
+      };
+
+      useDns = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Specifies whether sshd(8) should look up the remote host name, and to check that the resolved host name for
+          the remote IP address maps back to the very same IP address.
+          If this option is set to no (the default) then only addresses and not host names may be used in
+          ~/.ssh/authorized_keys from and sshd_config Match Host directives.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Verbatim contents of <filename>sshd_config</filename>.";
+      };
+
+      moduliFile = mkOption {
+        example = "/etc/my-local-ssh-moduli;";
+        type = types.path;
+        description = ''
+          Path to <literal>moduli</literal> file to install in
+          <literal>/etc/ssh/moduli</literal>. If this option is unset, then
+          the <literal>moduli</literal> file shipped with OpenSSH will be used.
+        '';
+      };
+
+      strictModes = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether sshd should check file modes and ownership of directories
+        '';
+      };
+    };
+
+    users.users = mkOption {
+      type = with types; loaOf (submodule userOptions);
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = {
+      sshd = {
+        isSystemUser = true;
+        description = "SSH privilege separation user";
+      };
+    } // (optionalAttrs (cfg.authorizedKeysCommand != null) {
+      ${cfg.authorizedKeysCommandUser} = {};
+    });
+
+    services.openssh.moduliFile = mkDefault "${cfgc.package}/etc/ssh/moduli";
+
+    environment.etc = authKeysFiles //
+      { "ssh/moduli".source = cfg.moduliFile;
+        "ssh/sshd_config".source = sshconf;
+      };
+
+    systemd =
+      let
+        service =
+          { description = "SSH Daemon";
+            wantedBy = optional (!cfg.startWhenNeeded) "multi-user.target";
+            after = [ "network.target" ];
+            stopIfChanged = false;
+            path = [ cfgc.package pkgs.gawk ];
+            environment.LD_LIBRARY_PATH = nssModulesPath;
+
+            restartTriggers = optionals (!cfg.startWhenNeeded) [
+              config.environment.etc."ssh/sshd_config".source
+            ];
+
+            preStart =
+              ''
+                # Make sure we don't write to stdout, since in case of
+                # socket activation, it goes to the remote side (#19589).
+                exec >&2
+
+                mkdir -m 0755 -p /etc/ssh
+
+                ${flip concatMapStrings cfg.hostKeys (k: ''
+                  if ! [ -f "${k.path}" ]; then
+                      ssh-keygen \
+                        -t "${k.type}" \
+                        ${if k ? bits then "-b ${toString k.bits}" else ""} \
+                        ${if k ? rounds then "-a ${toString k.rounds}" else ""} \
+                        ${if k ? comment then "-C '${k.comment}'" else ""} \
+                        ${if k ? openSSHFormat && k.openSSHFormat then "-o" else ""} \
+                        -f "${k.path}" \
+                        -N ""
+                  fi
+                '')}
+              '';
+
+            serviceConfig =
+              { ExecStart =
+                  (optionalString cfg.startWhenNeeded "-") +
+                  "${cfgc.package}/bin/sshd " + (optionalString cfg.startWhenNeeded "-i ") +
+                  "-f /etc/ssh/sshd_config";
+                KillMode = "process";
+              } // (if cfg.startWhenNeeded then {
+                StandardInput = "socket";
+                StandardError = "journal";
+              } else {
+                Restart = "always";
+                Type = "simple";
+              });
+
+          };
+      in
+
+      if cfg.startWhenNeeded then {
+
+        sockets.sshd =
+          { description = "SSH Socket";
+            wantedBy = [ "sockets.target" ];
+            socketConfig.ListenStream = if cfg.listenAddresses != [] then
+              map (l: "${l.addr}:${toString (if l.port != null then l.port else 22)}") cfg.listenAddresses
+            else
+              cfg.ports;
+            socketConfig.Accept = true;
+          };
+
+        services."sshd@" = service;
+
+      } else {
+
+        services.sshd = service;
+
+      };
+
+    networking.firewall.allowedTCPPorts = if cfg.openFirewall then cfg.ports else [];
+
+    security.pam.services.sshd =
+      { startSession = true;
+        showMotd = true;
+        unixAuth = cfg.passwordAuthentication;
+      };
+
+    # These values are merged with the ones defined externally, see:
+    # https://github.com/NixOS/nixpkgs/pull/10155
+    # https://github.com/NixOS/nixpkgs/pull/41745
+    services.openssh.authorizedKeysFiles =
+      [ ".ssh/authorized_keys" ".ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ];
+
+    services.openssh.extraConfig = mkOrder 0
+      ''
+        UsePAM yes
+
+        AddressFamily ${if config.networking.enableIPv6 then "any" else "inet"}
+        ${concatMapStrings (port: ''
+          Port ${toString port}
+        '') cfg.ports}
+
+        ${concatMapStrings ({ port, addr, ... }: ''
+          ListenAddress ${addr}${if port != null then ":" + toString port else ""}
+        '') cfg.listenAddresses}
+
+        ${optionalString cfgc.setXAuthLocation ''
+            XAuthLocation ${pkgs.xorg.xauth}/bin/xauth
+        ''}
+
+        ${if cfg.forwardX11 then ''
+          X11Forwarding yes
+        '' else ''
+          X11Forwarding no
+        ''}
+
+        ${optionalString cfg.allowSFTP ''
+          Subsystem sftp ${cfgc.package}/libexec/sftp-server ${concatStringsSep " " cfg.sftpFlags}
+        ''}
+
+        PermitRootLogin ${cfg.permitRootLogin}
+        GatewayPorts ${cfg.gatewayPorts}
+        PasswordAuthentication ${if cfg.passwordAuthentication then "yes" else "no"}
+        ChallengeResponseAuthentication ${if cfg.challengeResponseAuthentication then "yes" else "no"}
+
+        PrintMotd no # handled by pam_motd
+
+        ${optionalString (cfg.authorizedKeysCommand != null) ''
+          AuthorizedKeysCommand ${cfg.authorizedKeysCommand}
+          AuthorizedKeysCommandUser ${cfg.authorizedKeysCommandUser}
+        ''}
+        AuthorizedKeysFile ${toString cfg.authorizedKeysFiles}
+        ${optionalString (cfg.authorizedKeysCommand != "none") ''
+          AuthorizedKeysCommand ${cfg.authorizedKeysCommand}
+          AuthorizedKeysCommandUser ${cfg.authorizedKeysCommandUser}
+        ''}
+
+        ${flip concatMapStrings cfg.hostKeys (k: ''
+          HostKey ${k.path}
+        '')}
+
+        KexAlgorithms ${concatStringsSep "," cfg.kexAlgorithms}
+        Ciphers ${concatStringsSep "," cfg.ciphers}
+        MACs ${concatStringsSep "," cfg.macs}
+
+        LogLevel ${cfg.logLevel}
+
+        ${if cfg.useDns then ''
+          UseDNS yes
+        '' else ''
+          UseDNS no
+        ''}
+
+        StrictModes ${if cfg.strictModes then "yes" else "no"}
+
+      '';
+
+    assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
+                    message = "cannot enable X11 forwarding without setting xauth location";}]
+      ++ forEach cfg.listenAddresses ({ addr, ... }: {
+        assertion = addr != null;
+        message = "addr must be specified in each listenAddresses entry";
+      });
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/sslh.nix b/nixpkgs/nixos/modules/services/networking/sslh.nix
new file mode 100644
index 000000000000..c4fa370a5fef
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/sslh.nix
@@ -0,0 +1,160 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sslh;
+  user = "sslh";
+  configFile = pkgs.writeText "sslh.conf" ''
+    verbose: ${boolToString cfg.verbose};
+    foreground: true;
+    inetd: false;
+    numeric: false;
+    transparent: ${boolToString cfg.transparent};
+    timeout: "${toString cfg.timeout}";
+
+    listen:
+    (
+      { host: "${cfg.listenAddress}"; port: "${toString cfg.port}"; }
+    );
+
+    ${cfg.appendConfig}
+  '';
+  defaultAppendConfig = ''
+    protocols:
+    (
+      { name: "ssh"; service: "ssh"; host: "localhost"; port: "22"; probe: "builtin"; },
+      { name: "openvpn"; host: "localhost"; port: "1194"; probe: "builtin"; },
+      { name: "xmpp"; host: "localhost"; port: "5222"; probe: "builtin"; },
+      { name: "http"; host: "localhost"; port: "80"; probe: "builtin"; },
+      { name: "ssl"; host: "localhost"; port: "443"; probe: "builtin"; },
+      { name: "anyprot"; host: "localhost"; port: "443"; probe: "builtin"; }
+    );
+  '';
+in
+{
+  options = {
+    services.sslh = {
+      enable = mkEnableOption "sslh";
+
+      verbose = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Verbose logs.";
+      };
+
+      timeout = mkOption {
+        type = types.int;
+        default = 2;
+        description = "Timeout in seconds.";
+      };
+
+      transparent = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Will the services behind sslh (Apache, sshd and so on) see the external IP and ports as if the external world connected directly to them";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "0.0.0.0";
+        description = "Listening address or hostname.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 443;
+        description = "Listening port.";
+      };
+
+      appendConfig = mkOption {
+        type = types.str;
+        default = defaultAppendConfig;
+        description = "Verbatim configuration file.";
+      };
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+      systemd.services.sslh = {
+        description = "Applicative Protocol Multiplexer (e.g. share SSH and HTTPS on the same port)";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = {
+          DynamicUser          = true;
+          User                 = "sslh";
+          PermissionsStartOnly = true;
+          Restart              = "always";
+          RestartSec           = "1s";
+          ExecStart            = "${pkgs.sslh}/bin/sslh -F${configFile}";
+          KillMode             = "process";
+          AmbientCapabilities  = "CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID";
+          PrivateTmp           = true;
+          PrivateDevices       = true;
+          ProtectSystem        = "full";
+          ProtectHome          = true;
+        };
+      };
+    })
+
+    # code from https://github.com/yrutschle/sslh#transparent-proxy-support
+    # the only difference is using iptables mark 0x2 instead of 0x1 to avoid conflicts with nixos/nat module
+    (mkIf (cfg.enable && cfg.transparent) {
+      # Set route_localnet = 1 on all interfaces so that ssl can use "localhost" as destination
+      boot.kernel.sysctl."net.ipv4.conf.default.route_localnet" = 1;
+      boot.kernel.sysctl."net.ipv4.conf.all.route_localnet"     = 1;
+
+      systemd.services.sslh = let
+        iptablesCommands = [
+          # DROP martian packets as they would have been if route_localnet was zero
+          # Note: packets not leaving the server aren't affected by this, thus sslh will still work
+          { table = "raw";    command = "PREROUTING  ! -i lo -d 127.0.0.0/8 -j DROP"; }
+          { table = "mangle"; command = "POSTROUTING ! -o lo -s 127.0.0.0/8 -j DROP"; }
+          # Mark all connections made by ssl for special treatment (here sslh is run as user ${user})
+          { table = "nat";    command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; }
+          # Outgoing packets that should go to sslh instead have to be rerouted, so mark them accordingly (copying over the connection mark)
+          { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; }
+        ];
+        ip6tablesCommands = [
+          { table = "raw";    command = "PREROUTING  ! -i lo -d ::1/128     -j DROP"; }
+          { table = "mangle"; command = "POSTROUTING ! -o lo -s ::1/128     -j DROP"; }
+          { table = "nat";    command = "OUTPUT -m owner --uid-owner ${user} -p tcp --tcp-flags FIN,SYN,RST,ACK SYN -j CONNMARK --set-xmark 0x02/0x0f"; }
+          { table = "mangle"; command = "OUTPUT ! -o lo -p tcp -m connmark --mark 0x02/0x0f -j CONNMARK --restore-mark --mask 0x0f"; }
+        ];
+      in {
+        path = [ pkgs.iptables pkgs.iproute pkgs.procps ];
+
+        preStart = ''
+          # Cleanup old iptables entries which might be still there
+          ${concatMapStringsSep "\n" ({table, command}: "while iptables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") iptablesCommands}
+          ${concatMapStringsSep "\n" ({table, command}:       "iptables -w -t ${table} -A ${command}"                           ) iptablesCommands}
+
+          # Configure routing for those marked packets
+          ip rule  add fwmark 0x2 lookup 100
+          ip route add local 0.0.0.0/0 dev lo table 100
+
+        '' + optionalString config.networking.enableIPv6 ''
+          ${concatMapStringsSep "\n" ({table, command}: "while ip6tables -w -t ${table} -D ${command} 2>/dev/null; do echo; done") ip6tablesCommands}
+          ${concatMapStringsSep "\n" ({table, command}:       "ip6tables -w -t ${table} -A ${command}"                           ) ip6tablesCommands}
+
+          ip -6 rule  add fwmark 0x2 lookup 100
+          ip -6 route add local ::/0 dev lo table 100
+        '';
+
+        postStop = ''
+          ${concatMapStringsSep "\n" ({table, command}: "iptables -w -t ${table} -D ${command}") iptablesCommands}
+
+          ip rule  del fwmark 0x2 lookup 100
+          ip route del local 0.0.0.0/0 dev lo table 100
+        '' + optionalString config.networking.enableIPv6 ''
+          ${concatMapStringsSep "\n" ({table, command}: "ip6tables -w -t ${table} -D ${command}") ip6tablesCommands}
+
+          ip -6 rule  del fwmark 0x2 lookup 100
+          ip -6 route del local ::/0 dev lo table 100
+        '';
+      };
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix
new file mode 100644
index 000000000000..0fec3ef00ad9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/module.nix
@@ -0,0 +1,84 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with (import ./param-lib.nix lib);
+
+let
+  cfg = config.services.strongswan-swanctl;
+  swanctlParams = import ./swanctl-params.nix lib;
+in  {
+  options.services.strongswan-swanctl = {
+    enable = mkEnableOption "strongswan-swanctl service";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.strongswan;
+      defaultText = "pkgs.strongswan";
+      description = ''
+        The strongswan derivation to use.
+      '';
+    };
+
+    strongswan.extraConfig = mkOption {
+      type = types.str;
+      default = "";
+      description = ''
+        Contents of the <literal>strongswan.conf</literal> file.
+      '';
+    };
+
+    swanctl = paramsToOptions swanctlParams;
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = !config.services.strongswan.enable;
+        message = "cannot enable both services.strongswan and services.strongswan-swanctl. Choose either one.";
+      }
+    ];
+
+    environment.etc."swanctl/swanctl.conf".text =
+      paramsToConf cfg.swanctl swanctlParams;
+
+    # The swanctl command complains when the following directories don't exist:
+    # See: https://wiki.strongswan.org/projects/strongswan/wiki/Swanctldirectory
+    system.activationScripts.strongswan-swanctl-etc = stringAfter ["etc"] ''
+      mkdir -p '/etc/swanctl/x509'     # Trusted X.509 end entity certificates
+      mkdir -p '/etc/swanctl/x509ca'   # Trusted X.509 Certificate Authority certificates
+      mkdir -p '/etc/swanctl/x509ocsp'
+      mkdir -p '/etc/swanctl/x509aa'   # Trusted X.509 Attribute Authority certificates
+      mkdir -p '/etc/swanctl/x509ac'   # Attribute Certificates
+      mkdir -p '/etc/swanctl/x509crl'  # Certificate Revocation Lists
+      mkdir -p '/etc/swanctl/pubkey'   # Raw public keys
+      mkdir -p '/etc/swanctl/private'  # Private keys in any format
+      mkdir -p '/etc/swanctl/rsa'      # PKCS#1 encoded RSA private keys
+      mkdir -p '/etc/swanctl/ecdsa'    # Plain ECDSA private keys
+      mkdir -p '/etc/swanctl/bliss'
+      mkdir -p '/etc/swanctl/pkcs8'    # PKCS#8 encoded private keys of any type
+      mkdir -p '/etc/swanctl/pkcs12'   # PKCS#12 containers
+    '';
+
+    systemd.services.strongswan-swanctl = {
+      description = "strongSwan IPsec IKEv1/IKEv2 daemon using swanctl";
+      wantedBy = [ "multi-user.target" ];
+      after    = [ "network-online.target" ];
+      path     = with pkgs; [ kmod iproute iptables utillinux ];
+      environment = {
+        STRONGSWAN_CONF = pkgs.writeTextFile {
+          name = "strongswan.conf";
+          text = cfg.strongswan.extraConfig;
+        };
+        SWANCTL_DIR = "/etc/swanctl";
+      };
+      restartTriggers = [ config.environment.etc."swanctl/swanctl.conf".source ];
+      serviceConfig = {
+        ExecStart     = "${cfg.package}/sbin/charon-systemd";
+        Type          = "notify";
+        ExecStartPost = "${cfg.package}/sbin/swanctl --load-all --noprompt";
+        ExecReload    = "${cfg.package}/sbin/swanctl --reload";
+        Restart       = "on-abnormal";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
new file mode 100644
index 000000000000..dfdfc50d8ae2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
@@ -0,0 +1,162 @@
+# In the following context a parameter is an attribute set that
+# contains a NixOS option and a render function. It also contains the
+# attribute: '_type = "param"' so we can distinguish it from other
+# sets.
+#
+# The render function is used to convert the value of the option to a
+# snippet of strongswan.conf. Most parameters simply render their
+# value to a string. For example, take the following parameter:
+#
+#   threads = mkIntParam 10 "Threads to use for request handling.";
+#
+# When a users defines the corresponding option as for example:
+#
+#   services.strongswan-swanctl.strongswan.threads = 32;
+#
+# It will get rendered to the following snippet in strongswan.conf:
+#
+#   threads = 32
+#
+# Some parameters however need to be able to change the attribute
+# name. For example, take the following parameter:
+#
+#   id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") "...";
+#
+# A user can define the corresponding option as for example:
+#
+#   id = {
+#     "foo" = "bar";
+#     "baz" = "qux";
+#   };
+#
+# This will get rendered to the following snippet:
+#
+#   foo-id = bar
+#   baz-id = qux
+#
+# For this reason the render function is not simply a function from
+# value -> string but a function from a value to an attribute set:
+# { "${name}" = string }. This allows parameters to change the attribute
+# name like in the previous example.
+
+lib :
+
+with lib;
+with (import ./param-lib.nix lib);
+
+rec {
+  mkParamOfType = type : strongswanDefault : description : {
+    _type = "param";
+    option = mkOption {
+      type = types.nullOr type;
+      default = null;
+      description = documentDefault description strongswanDefault;
+    };
+    render = single toString;
+  };
+
+  documentDefault = description : strongswanDefault :
+    if strongswanDefault == null
+    then description
+    else description + ''
+      </para><para>
+      StrongSwan default: <literal><![CDATA[${builtins.toJSON strongswanDefault}]]></literal>
+    '';
+
+  single = f: name: value: { ${name} = f value; };
+
+  mkStrParam         = mkParamOfType types.str;
+  mkOptionalStrParam = mkStrParam null;
+
+  mkEnumParam = values : mkParamOfType (types.enum values);
+
+  mkIntParam         = mkParamOfType types.int;
+  mkOptionalIntParam = mkIntParam null;
+
+  # We should have floats in Nix...
+  mkFloatParam = mkStrParam;
+
+  # TODO: Check for hex format:
+  mkHexParam         = mkStrParam;
+  mkOptionalHexParam = mkOptionalStrParam;
+
+  # TODO: Check for duration format:
+  mkDurationParam         = mkStrParam;
+  mkOptionalDurationParam = mkOptionalStrParam;
+
+  mkYesNoParam = strongswanDefault : description : {
+    _type = "param";
+    option = mkOption {
+      type = types.nullOr types.bool;
+      default = null;
+      description = documentDefault description strongswanDefault;
+    };
+    render = single (b: if b then "yes" else "no");
+  };
+  yes = true;
+  no  = false;
+
+  mkSpaceSepListParam = mkSepListParam " ";
+  mkCommaSepListParam = mkSepListParam ",";
+
+  mkSepListParam = sep : strongswanDefault : description : {
+    _type = "param";
+    option = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = documentDefault description strongswanDefault;
+    };
+    render = single (value: concatStringsSep sep value);
+  };
+
+  mkAttrsOfParams = params :
+    mkAttrsOf params (types.submodule {options = paramsToOptions params;});
+
+  mkAttrsOfParam = param :
+    mkAttrsOf param param.option.type;
+
+  mkAttrsOf = param : option : description : {
+    _type = "param";
+    option = mkOption {
+      type = types.attrsOf option;
+      default = {};
+      inherit description;
+    };
+    render = single (attrs:
+      (paramsToRenderedStrings attrs
+        (mapAttrs (_n: _v: param) attrs)));
+  };
+
+  mkPrefixedAttrsOfParams = params :
+    mkPrefixedAttrsOf params (types.submodule {options = paramsToOptions params;});
+
+  mkPrefixedAttrsOfParam = param :
+    mkPrefixedAttrsOf param param.option.type;
+
+  mkPrefixedAttrsOf = p : option : description : {
+    _type = "param";
+    option = mkOption {
+      type = types.attrsOf option;
+      default = {};
+      inherit description;
+    };
+    render = prefix: attrs:
+      let prefixedAttrs = mapAttrs' (name: nameValuePair "${prefix}-${name}") attrs;
+      in paramsToRenderedStrings prefixedAttrs
+           (mapAttrs (_n: _v: p) prefixedAttrs);
+  };
+
+  mkPostfixedAttrsOfParams = params : description : {
+    _type = "param";
+    option = mkOption {
+      type = types.attrsOf (types.submodule {options = paramsToOptions params;});
+      default = {};
+      inherit description;
+    };
+    render = postfix: attrs:
+      let postfixedAttrs = mapAttrs' (name: nameValuePair "${name}-${postfix}") attrs;
+      in paramsToRenderedStrings postfixedAttrs
+           (mapAttrs (_n: _v: params) postfixedAttrs);
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
new file mode 100644
index 000000000000..2bbb39a76049
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
@@ -0,0 +1,82 @@
+lib :
+
+with lib;
+
+rec {
+  paramsToConf = cfg : ps : mkConf 0 (paramsToRenderedStrings cfg ps);
+
+  # mkConf takes an indentation level (which usually starts at 0) and a nested
+  # attribute set of strings and will render that set to a strongswan.conf style
+  # configuration format. For example:
+  #
+  #   mkConf 0 {a = "1"; b = { c = { "foo" = "2"; "bar" = "3"; }; d = "4";};}   =>   ''
+  #   a = 1
+  #   b {
+  #     c {
+  #       foo = 2
+  #       bar = 3
+  #     }
+  #     d = 4
+  #   }''
+  mkConf = indent : ps :
+    concatMapStringsSep "\n"
+      (name:
+        let value = ps.${name};
+            indentation = replicate indent " ";
+        in
+        indentation + (
+          if isAttrs value
+          then "${name} {\n" +
+                 mkConf (indent + 2) value + "\n" +
+               indentation + "}"
+          else "${name} = ${value}"
+        )
+      )
+      (attrNames ps);
+
+  replicate = n : c : concatStrings (builtins.genList (_x : c) n);
+
+  # `paramsToRenderedStrings cfg ps` converts the NixOS configuration `cfg`
+  # (typically the "config" argument of a NixOS module) and the set of
+  # parameters `ps` (an attribute set where the values are constructed using the
+  # parameter constructors in ./param-constructors.nix) to a nested attribute
+  # set of strings (rendered parameters).
+  paramsToRenderedStrings = cfg : ps :
+    filterEmptySets (
+      (mapParamsRecursive (path: name: param:
+        let value = attrByPath path null cfg;
+        in optionalAttrs (value != null) (param.render name value)
+      ) ps));
+
+  filterEmptySets = set : filterAttrs (n: v: (v != null)) (mapAttrs (name: value:
+    if isAttrs value
+    then let value' = filterEmptySets value;
+         in if value' == {}
+            then null
+            else value'
+    else value
+  ) set);
+
+  # Recursively map over every parameter in the given attribute set.
+  mapParamsRecursive = mapAttrsRecursiveCond' (as: (!(as ? _type && as._type == "param")));
+
+  mapAttrsRecursiveCond' = cond: f: set:
+    let
+      recurse = path: set:
+        let
+          g =
+            name: value:
+            if isAttrs value && cond value
+              then { ${name} = recurse (path ++ [name]) value; }
+              else f (path ++ [name]) name value;
+        in mapAttrs'' g set;
+    in recurse [] set;
+
+  mapAttrs'' = f: set:
+    foldl' (a: b: a // b) {} (map (attr: f attr set.${attr}) (attrNames set));
+
+  # Extract the options from the given set of parameters.
+  paramsToOptions = ps :
+    mapParamsRecursive (_path: name: param: { ${name} = param.option; }) ps;
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
new file mode 100644
index 000000000000..808cb863a9cf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -0,0 +1,1300 @@
+# See: https://wiki.strongswan.org/projects/strongswan/wiki/Swanctlconf
+#
+# When strongSwan is upgraded please update the parameters in this file. You can
+# see which parameters should be deleted, changed or added by diffing
+# swanctl.opt:
+#
+#   git clone https://github.com/strongswan/strongswan.git
+#   cd strongswan
+#   git diff 5.7.2..5.8.0 src/swanctl/swanctl.opt
+
+lib: with (import ./param-constructors.nix lib);
+
+let
+  certParams = {
+    file = mkOptionalStrParam ''
+      Absolute path to the certificate to load. Passed as-is to the daemon, so
+      it must be readable by it.
+      </para><para>
+      Configure either this or <option>handle</option>, but not both, in one section.
+    '';
+
+    handle = mkOptionalHexParam ''
+      Hex-encoded CKA_ID or handle of the certificate on a token or TPM,
+      respectively.
+      </para><para>
+      Configure either this or <option>file</option>, but not both, in one section.
+    '';
+
+    slot = mkOptionalIntParam ''
+      Optional slot number of the token that stores the certificate.
+    '';
+
+    module = mkOptionalStrParam ''
+      Optional PKCS#11 module name.
+    '';
+  };
+in {
+  authorities = mkAttrsOfParams ({
+
+    cacert = mkOptionalStrParam ''
+      The certificates may use a relative path from the swanctl
+      <literal>x509ca</literal> directory or an absolute path.
+      </para><para>
+      Configure one of <option>cacert</option>,
+      <option>file</option>, or
+      <option>handle</option> per section.
+    '';
+
+    cert_uri_base = mkOptionalStrParam ''
+      Defines the base URI for the Hash and URL feature supported by
+      IKEv2. Instead of exchanging complete certificates, IKEv2 allows one to
+      send an URI that resolves to the DER encoded certificate. The certificate
+      URIs are built by appending the SHA1 hash of the DER encoded certificates
+      to this base URI.
+    '';
+
+    crl_uris = mkCommaSepListParam [] ''
+      List of CRL distribution points (ldap, http, or file URI).
+    '';
+
+    ocsp_uris = mkCommaSepListParam [] ''
+      List of OCSP URIs.
+    '';
+
+  } // certParams) ''
+    Section defining complementary attributes of certification authorities, each
+    in its own subsection with an arbitrary yet unique name
+  '';
+
+  connections = mkAttrsOfParams {
+
+    version = mkIntParam 0 ''
+      IKE major version to use for connection.
+      <itemizedlist>
+      <listitem><para>1 uses IKEv1 aka ISAKMP,</para></listitem>
+      <listitem><para>2 uses IKEv2.</para></listitem>
+      <listitem><para>A connection using the default of 0 accepts both IKEv1 and IKEv2 as
+      responder, and initiates the connection actively with IKEv2.</para></listitem>
+      </itemizedlist>
+    '';
+
+    local_addrs	= mkCommaSepListParam [] ''
+      Local address(es) to use for IKE communication. Takes
+      single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges.
+      </para><para>
+      As initiator, the first non-range/non-subnet is used to initiate the
+      connection from. As responder, the local destination address must match at
+      least to one of the specified addresses, subnets or ranges.
+      </para><para>
+      If FQDNs are assigned they are resolved every time a configuration lookup
+      is done. If DNS resolution times out, the lookup is delayed for that time.
+    '';
+
+    remote_addrs = mkCommaSepListParam [] ''
+      Remote address(es) to use for IKE communication. Takes
+      single IPv4/IPv6 addresses, DNS names, CIDR subnets or IP address ranges.
+      </para><para>
+      As initiator, the first non-range/non-subnet is used to initiate the
+      connection to. As responder, the initiator source address must match at
+      least to one of the specified addresses, subnets or ranges.
+      </para><para>
+      If FQDNs are assigned they are resolved every time a configuration lookup
+      is done. If DNS resolution times out, the lookup is delayed for that time.
+      To initiate a connection, at least one specific address or DNS name must
+      be specified.
+    '';
+
+    local_port = mkIntParam 500 ''
+      Local UDP port for IKE communication. By default the port of the socket
+      backend is used, which is usually <literal>500</literal>. If port
+      <literal>500</literal> is used, automatic IKE port floating to port
+      <literal>4500</literal> is used to work around NAT issues.
+      </para><para>
+      Using a non-default local IKE port requires support from the socket
+      backend in use (socket-dynamic).
+    '';
+
+    remote_port = mkIntParam 500 ''
+      Remote UDP port for IKE communication. If the default of port
+      <literal>500</literal> is used, automatic IKE port floating to port
+      <literal>4500</literal> is used to work around NAT issues.
+    '';
+
+    proposals = mkCommaSepListParam ["default"] ''
+      A proposal is a set of algorithms. For non-AEAD algorithms, this includes
+      for IKE an encryption algorithm, an integrity algorithm, a pseudo random
+      function and a Diffie-Hellman group. For AEAD algorithms, instead of
+      encryption and integrity algorithms, a combined algorithm is used.
+      </para><para>
+      In IKEv2, multiple algorithms of the same kind can be specified in a
+      single proposal, from which one gets selected. In IKEv1, only one
+      algorithm per kind is allowed per proposal, more algorithms get implicitly
+      stripped. Use multiple proposals to offer different algorithms
+      combinations in IKEv1.
+      </para><para>
+      Algorithm keywords get separated using dashes. Multiple proposals may be
+      specified in a list. The special value <literal>default</literal> forms a
+      default proposal of supported algorithms considered safe, and is usually a
+      good choice for interoperability.
+    '';
+
+    vips = mkCommaSepListParam [] ''
+      List of virtual IPs to request in IKEv2 configuration payloads or IKEv1
+      Mode Config. The wildcard addresses <literal>0.0.0.0</literal> and
+      <literal>::</literal> request an arbitrary address, specific addresses may
+      be defined. The responder may return a different address, though, or none
+      at all.
+    '';
+
+    aggressive = mkYesNoParam no ''
+      Enables Aggressive Mode instead of Main Mode with Identity
+      Protection. Aggressive Mode is considered less secure, because the ID and
+      HASH payloads are exchanged unprotected. This allows a passive attacker to
+      snoop peer identities, and even worse, start dictionary attacks on the
+      Preshared Key.
+    '';
+
+    pull = mkYesNoParam yes ''
+      If the default of yes is used, Mode Config works in pull mode, where the
+      initiator actively requests a virtual IP. With no, push mode is used,
+      where the responder pushes down a virtual IP to the initiating peer.
+      </para><para>
+      Push mode is currently supported for IKEv1, but not in IKEv2. It is used
+      by a few implementations only, pull mode is recommended.
+    '';
+
+    dscp = mkStrParam "000000" ''
+      Differentiated Services Field Codepoint to set on outgoing IKE packets for
+      this connection. The value is a six digit binary encoded string specifying
+      the Codepoint to set, as defined in RFC 2474.
+    '';
+
+    encap = mkYesNoParam no ''
+      To enforce UDP encapsulation of ESP packets, the IKE daemon can fake the
+      NAT detection payloads. This makes the peer believe that NAT takes place
+      on the path, forcing it to encapsulate ESP packets in UDP.
+      </para><para>
+      Usually this is not required, but it can help to work around connectivity
+      issues with too restrictive intermediary firewalls.
+    '';
+
+    mobike = mkYesNoParam yes ''
+      Enables MOBIKE on IKEv2 connections. MOBIKE is enabled by default on IKEv2
+      connections, and allows mobility of clients and multi-homing on servers by
+      migrating active IPsec tunnels.
+      </para><para>
+      Usually keeping MOBIKE enabled is unproblematic, as it is not used if the
+      peer does not indicate support for it. However, due to the design of
+      MOBIKE, IKEv2 always floats to port 4500 starting from the second
+      exchange. Some implementations don't like this behavior, hence it can be
+      disabled.
+    '';
+
+    dpd_delay = mkDurationParam "0s" ''
+      Interval to check the liveness of a peer actively using IKEv2
+      INFORMATIONAL exchanges or IKEv1 R_U_THERE messages. Active DPD checking
+      is only enforced if no IKE or ESP/AH packet has been received for the
+      configured DPD delay.
+    '';
+
+    dpd_timeout = mkDurationParam "0s" ''
+      Charon by default uses the normal retransmission mechanism and timeouts to
+      check the liveness of a peer, as all messages are used for liveness
+      checking. For compatibility reasons, with IKEv1 a custom interval may be
+      specified; this option has no effect on connections using IKEv2.
+    '';
+
+    fragmentation = mkEnumParam ["yes" "accept" "force" "no"] "yes" ''
+      Use IKE fragmentation (proprietary IKEv1 extension or RFC 7383 IKEv2
+      fragmentation). Acceptable values are <literal>yes</literal> (the default
+      since 5.5.1), <literal>accept</literal> (since versions:5.5.3),
+      <literal>force</literal> and <literal>no</literal>.
+      <itemizedlist>
+      <listitem><para>If set to <literal>yes</literal>, and the peer
+      supports it, oversized IKE messages will be sent in fragments.</para></listitem>
+      <listitem><para>If set to
+      <literal>accept</literal>, support for fragmentation is announced to the peer but the daemon
+      does not send its own messages in fragments.</para></listitem>
+      <listitem><para>If set to <literal>force</literal> (only
+      supported for IKEv1) the initial IKE message will already be fragmented if
+      required.</para></listitem>
+      <listitem><para>Finally, setting the option to <literal>no</literal> will disable announcing
+      support for this feature.</para></listitem>
+      </itemizedlist>
+      </para><para>
+      Note that fragmented IKE messages sent by a peer are always processed
+      irrespective of the value of this option (even when set to no).
+    '';
+
+    childless = mkEnumParam [ "allow" "force" "never" ] "allow" ''
+      Use childless IKE_SA initiation (RFC 6023) for IKEv2.  Acceptable values
+      are <literal>allow</literal> (the default), <literal>force</literal> and
+      <literal>never</literal>. If set to <literal>allow</literal>, responders
+      will accept childless IKE_SAs (as indicated via notify in the IKE_SA_INIT
+      response) while initiators continue to create regular IKE_SAs with the
+      first CHILD_SA created during IKE_AUTH, unless the IKE_SA is initiated
+      explicitly without any children (which will fail if the responder does not
+      support or has disabled this extension).  If set to
+      <literal>force</literal>, only childless initiation is accepted and the
+      first CHILD_SA is created with a separate CREATE_CHILD_SA exchange
+      (e.g. to use an independent DH exchange for all CHILD_SAs). Finally,
+      setting the option to <literal>never</literal> disables support for
+      childless IKE_SAs as responder.
+    '';
+
+    send_certreq = mkYesNoParam yes ''
+      Send certificate request payloads to offer trusted root CA certificates to
+      the peer. Certificate requests help the peer to choose an appropriate
+      certificate/private key for authentication and are enabled by default.
+      Disabling certificate requests can be useful if too many trusted root CA
+      certificates are installed, as each certificate request increases the size
+      of the initial IKE packets.
+   '';
+
+    send_cert = mkEnumParam ["always" "never" "ifasked" ] "ifasked" ''
+      Send certificate payloads when using certificate authentication.
+      <itemizedlist>
+      <listitem><para>With the default of <literal>ifasked</literal> the daemon sends
+      certificate payloads only if certificate requests have been received.</para></listitem>
+      <listitem><para><literal>never</literal> disables sending of certificate payloads
+      altogether,</para></listitem>
+      <listitem><para><literal>always</literal> causes certificate payloads to be sent
+      unconditionally whenever certificate authentication is used.</para></listitem>
+      </itemizedlist>
+    '';
+
+    ppk_id = mkOptionalStrParam ''
+       String identifying the Postquantum Preshared Key (PPK) to be used.
+    '';
+
+    ppk_required = mkYesNoParam no ''
+       Whether a Postquantum Preshared Key (PPK) is required for this connection.
+    '';
+
+    keyingtries = mkIntParam 1 ''
+      Number of retransmission sequences to perform during initial
+      connect. Instead of giving up initiation after the first retransmission
+      sequence with the default value of <literal>1</literal>, additional
+      sequences may be started according to the configured value. A value of
+      <literal>0</literal> initiates a new sequence until the connection
+      establishes or fails with a permanent error.
+    '';
+
+    unique = mkEnumParam ["no" "never" "keep" "replace"] "no" ''
+      Connection uniqueness policy to enforce. To avoid multiple connections
+      from the same user, a uniqueness policy can be enforced.
+      </para><para>
+      <itemizedlist>
+      <listitem><para>
+      The value <literal>never</literal> does never enforce such a policy, even
+      if a peer included INITIAL_CONTACT notification messages,
+      </para></listitem>
+      <listitem><para>
+      whereas <literal>no</literal> replaces existing connections for the same
+      identity if a new one has the INITIAL_CONTACT notify.
+      </para></listitem>
+      <listitem><para>
+      <literal>keep</literal> rejects new connection attempts if the same user
+      already has an active connection,
+      </para></listitem>
+      <listitem><para>
+      <literal>replace</literal> deletes any existing connection if a new one
+      for the same user gets established.
+      </para></listitem>
+      </itemizedlist>
+      To compare connections for uniqueness, the remote IKE identity is used. If
+      EAP or XAuth authentication is involved, the EAP-Identity or XAuth
+      username is used to enforce the uniqueness policy instead.
+      </para><para>
+      On initiators this setting specifies whether an INITIAL_CONTACT notify is
+      sent during IKE_AUTH if no existing connection is found with the remote
+      peer (determined by the identities of the first authentication
+      round). Unless set to <literal>never</literal> the client will send a notify.
+    '';
+
+    reauth_time	= mkDurationParam "0s" ''
+      Time to schedule IKE reauthentication. IKE reauthentication recreates the
+      IKE/ISAKMP SA from scratch and re-evaluates the credentials. In asymmetric
+      configurations (with EAP or configuration payloads) it might not be
+      possible to actively reauthenticate as responder. The IKEv2
+      reauthentication lifetime negotiation can instruct the client to perform
+      reauthentication.
+      </para><para>
+      Reauthentication is disabled by default. Enabling it usually may lead to
+      small connection interruptions, as strongSwan uses a break-before-make
+      policy with IKEv2 to avoid any conflicts with associated tunnel resources.
+    '';
+
+    rekey_time = mkDurationParam "4h" ''
+      IKE rekeying refreshes key material using a Diffie-Hellman exchange, but
+      does not re-check associated credentials. It is supported in IKEv2 only,
+      IKEv1 performs a reauthentication procedure instead.
+      </para><para>
+      With the default value IKE rekeying is scheduled every 4 hours, minus the
+      configured rand_time. If a reauth_time is configured, rekey_time defaults
+      to zero, disabling rekeying; explicitly set both to enforce rekeying and
+      reauthentication.
+    '';
+
+    over_time = mkOptionalDurationParam ''
+      Hard IKE_SA lifetime if rekey/reauth does not complete, as time. To avoid
+      having an IKE/ISAKMP kept alive if IKE reauthentication or rekeying fails
+      perpetually, a maximum hard lifetime may be specified. If the IKE_SA fails
+      to rekey or reauthenticate within the specified time, the IKE_SA gets
+      closed.
+      </para><para>
+      In contrast to CHILD_SA rekeying, over_time is relative in time to the
+      rekey_time and reauth_time values, as it applies to both.
+      </para><para>
+      The default is 10% of the longer of <option>rekey_time</option> and
+      <option>reauth_time</option>.
+    '';
+
+    rand_time = mkOptionalDurationParam ''
+      Time range from which to choose a random value to subtract from
+      rekey/reauth times. To avoid having both peers initiating the rekey/reauth
+      procedure simultaneously, a random time gets subtracted from the
+      rekey/reauth times.
+      </para><para>
+      The default is equal to the configured <option>over_time</option>.
+    '';
+
+    pools = mkCommaSepListParam [] ''
+      List of named IP pools to allocate virtual IP addresses
+      and other configuration attributes from. Each name references a pool by
+      name from either the pools section or an external pool.
+    '';
+
+    if_id_in = mkStrParam "0" ''
+      XFRM interface ID set on inbound policies/SA, can be overridden by child
+      config, see there for details.
+    '';
+
+    if_id_out = mkStrParam "0" ''
+      XFRM interface ID set on outbound policies/SA, can be overridden by child
+      config, see there for details.
+    '';
+
+    mediation = mkYesNoParam no ''
+      Whether this connection is a mediation connection, that is, whether this
+      connection is used to mediate other connections using the IKEv2 Mediation
+      Extension. Mediation connections create no CHILD_SA.
+    '';
+
+    mediated_by = mkOptionalStrParam ''
+      The name of the connection to mediate this connection through. If given,
+      the connection will be mediated through the named mediation
+      connection. The mediation connection must have mediation enabled.
+    '';
+
+    mediation_peer = mkOptionalStrParam ''
+      Identity under which the peer is registered at the mediation server, that
+      is, the IKE identity the other end of this connection uses as its local
+      identity on its connection to the mediation server. This is the identity
+      we request the mediation server to mediate us with. Only relevant on
+      connections that set mediated_by. If it is not given, the remote IKE
+      identity of the first authentication round of this connection will be
+      used.
+    '';
+
+    local = mkPrefixedAttrsOfParams {
+
+      round = mkIntParam 0 ''
+        Optional numeric identifier by which authentication rounds are
+        sorted. If not specified rounds are ordered by their position in the
+        config file/vici message.
+      '';
+
+      certs = mkCommaSepListParam [] ''
+        List of certificate candidates to use for
+        authentication. The certificates may use a relative path from the
+        swanctl <literal>x509</literal> directory or an absolute path.
+        </para><para>
+        The certificate used for authentication is selected based on the
+        received certificate request payloads. If no appropriate CA can be
+        located, the first certificate is used.
+      '';
+
+      cert = mkPostfixedAttrsOfParams certParams ''
+        Section for a certificate candidate to use for
+        authentication. Certificates in certs are transmitted as binary blobs,
+        these sections offer more flexibility.
+      '';
+
+      pubkeys = mkCommaSepListParam [] ''
+        List of raw public key candidates to use for
+        authentication. The public keys may use a relative path from the swanctl
+        <literal>pubkey</literal> directory or an absolute path.
+        </para><para>
+        Even though multiple local public keys could be defined in principle,
+        only the first public key in the list is used for authentication.
+      '';
+
+      auth = mkStrParam "pubkey" ''
+        Authentication to perform locally.
+        <itemizedlist>
+        <listitem><para>
+        The default <literal>pubkey</literal> uses public key authentication
+        using a private key associated to a usable certificate.
+        </para></listitem>
+        <listitem><para>
+        <literal>psk</literal> uses pre-shared key authentication.
+        </para></listitem>
+        <listitem><para>
+        The IKEv1 specific <literal>xauth</literal> is used for XAuth or Hybrid
+        authentication,
+        </para></listitem>
+        <listitem><para>
+        while the IKEv2 specific <literal>eap</literal> keyword defines EAP
+        authentication.
+        </para></listitem>
+        <listitem><para>
+        For <literal>xauth</literal>, a specific backend name may be appended,
+        separated by a dash. The appropriate <literal>xauth</literal> backend is
+        selected to perform the XAuth exchange. For traditional XAuth, the
+        <literal>xauth</literal> method is usually defined in the second
+        authentication round following an initial <literal>pubkey</literal> (or
+        <literal>psk</literal>) round. Using <literal>xauth</literal> in the
+        first round performs Hybrid Mode client authentication.
+        </para></listitem>
+        <listitem><para>
+        For <literal>eap</literal>, a specific EAP method name may be appended, separated by a
+        dash. An EAP module implementing the appropriate method is selected to
+        perform the EAP conversation.
+        </para></listitem>
+        <listitem><para>
+        Since 5.4.0, if both peers support RFC 7427 ("Signature Authentication
+        in IKEv2") specific hash algorithms to be used during IKEv2
+        authentication may be configured. To do so use <literal>ike:</literal>
+        followed by a trust chain signature scheme constraint (see description
+        of the <option>remote</option> section's <option>auth</option>
+        keyword). For example, with <literal>ike:pubkey-sha384-sha256</literal>
+        a public key signature scheme with either SHA-384 or SHA-256 would get
+        used for authentication, in that order and depending on the hash
+        algorithms supported by the peer. If no specific hash algorithms are
+        configured, the default is to prefer an algorithm that matches or
+        exceeds the strength of the signature key. If no constraints with
+        <literal>ike:</literal> prefix are configured any signature scheme
+        constraint (without <literal>ike:</literal> prefix) will also apply to
+        IKEv2 authentication, unless this is disabled in
+        <literal>strongswan.conf</literal>. To use RSASSA-PSS signatures use
+        <literal>rsa/pss</literal> instead of <literal>pubkey</literal> or
+        <literal>rsa</literal> as in e.g.
+        <literal>ike:rsa/pss-sha256</literal>. If <literal>pubkey</literal> or
+        <literal>rsa</literal> constraints are configured RSASSA-PSS signatures
+        will only be used if enabled in <literal>strongswan.conf</literal>(5).
+        </para></listitem>
+        </itemizedlist>
+      '';
+
+      id = mkOptionalStrParam ''
+        IKE identity to use for authentication round. When using certificate
+        authentication, the IKE identity must be contained in the certificate,
+        either as subject or as subjectAltName.
+      '';
+
+      eap_id = mkOptionalStrParam ''
+        Client EAP-Identity to use in EAP-Identity exchange and the EAP method.
+      '';
+
+      aaa_id = mkOptionalStrParam ''
+        Server side EAP-Identity to expect in the EAP method. Some EAP methods,
+        such as EAP-TLS, use an identity for the server to perform mutual
+        authentication. This identity may differ from the IKE identity,
+        especially when EAP authentication is delegated from the IKE responder
+        to an AAA backend.
+        </para><para>
+        For EAP-(T)TLS, this defines the identity for which the server must
+        provide a certificate in the TLS exchange.
+      '';
+
+      xauth_id = mkOptionalStrParam ''
+        Client XAuth username used in the XAuth exchange.
+      '';
+
+    } ''
+      Section for a local authentication round. A local authentication round
+      defines the rules how authentication is performed for the local
+      peer. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple
+      Authentication or IKEv1 XAuth.
+      </para><para>
+      Each round is defined in a section having <literal>local</literal> as
+      prefix, and an optional unique suffix. To define a single authentication
+      round, the suffix may be omitted.
+    '';
+
+    remote = mkPrefixedAttrsOfParams {
+
+      round = mkIntParam 0 ''
+        Optional numeric identifier by which authentication rounds are
+        sorted. If not specified rounds are ordered by their position in the
+        config file/vici message.
+      '';
+
+      id = mkStrParam "%any" ''
+        IKE identity to expect for authentication round. When using certificate
+        authentication, the IKE identity must be contained in the certificate,
+        either as subject or as subjectAltName.
+      '';
+
+      eap_id = mkOptionalStrParam ''
+        Identity to use as peer identity during EAP authentication. If set to
+        <literal>%any</literal> the EAP-Identity method will be used to ask the
+        client for an EAP identity.
+      '';
+
+      groups = mkCommaSepListParam [] ''
+        Authorization group memberships to require. The peer
+        must prove membership to at least one of the specified groups. Group
+        membership can be certified by different means, for example by
+        appropriate Attribute Certificates or by an AAA backend involved in the
+        authentication.
+      '';
+
+      cert_policy = mkCommaSepListParam [] ''
+        List of certificate policy OIDs the peer's certificate
+        must have. OIDs are specified using the numerical dotted representation.
+      '';
+
+      certs = mkCommaSepListParam [] ''
+        List of certificates to accept for authentication. The certificates may
+        use a relative path from the swanctl <literal>x509</literal> directory
+        or an absolute path.
+      '';
+
+      cert = mkPostfixedAttrsOfParams certParams ''
+        Section for a certificate candidate to use for
+        authentication. Certificates in certs are transmitted as binary blobs,
+        these sections offer more flexibility.
+      '';
+
+      cacerts = mkCommaSepListParam [] ''
+        List of CA certificates to accept for
+        authentication. The certificates may use a relative path from the
+        swanctl <literal>x509ca</literal> directory or an absolute path.
+      '';
+
+      cacert = mkPostfixedAttrsOfParams certParams ''
+        Section for a CA certificate to accept for authentication. Certificates
+        in cacerts are transmitted as binary blobs, these sections offer more
+        flexibility.
+      '';
+
+      pubkeys = mkCommaSepListParam [] ''
+        List of raw public keys to accept for
+        authentication. The public keys may use a relative path from the swanctl
+        <literal>pubkey</literal> directory or an absolute path.
+      '';
+
+      revocation = mkEnumParam ["strict" "ifuri" "relaxed"] "relaxed" ''
+        Certificate revocation policy for CRL or OCSP revocation.
+        <itemizedlist>
+        <listitem><para>
+        A <literal>strict</literal> revocation policy fails if no revocation information is
+        available, i.e. the certificate is not known to be unrevoked.
+        </para></listitem>
+        <listitem><para>
+        <literal>ifuri</literal> fails only if a CRL/OCSP URI is available, but certificate
+        revocation checking fails, i.e. there should be revocation information
+        available, but it could not be obtained.
+        </para></listitem>
+        <listitem><para>
+        The default revocation policy <literal>relaxed</literal> fails only if a certificate is
+        revoked, i.e. it is explicitly known that it is bad.
+        </para></listitem>
+        </itemizedlist>
+      '';
+
+      auth = mkStrParam "pubkey" ''
+        Authentication to expect from remote. See the <option>local</option>
+        section's <option>auth</option> keyword description about the details of
+        supported mechanisms.
+        </para><para>
+        Since 5.4.0, to require a trustchain public key strength for the remote
+        side, specify the key type followed by the minimum strength in bits (for
+        example <literal>ecdsa-384</literal> or
+        <literal>rsa-2048-ecdsa-256</literal>). To limit the acceptable set of
+        hashing algorithms for trustchain validation, append hash algorithms to
+        pubkey or a key strength definition (for example
+        <literal>pubkey-sha256-sha512</literal>,
+        <literal>rsa-2048-sha256-sha384-sha512</literal> or
+        <literal>rsa-2048-sha256-ecdsa-256-sha256-sha384</literal>).
+        Unless disabled in <literal>strongswan.conf</literal>, or explicit IKEv2
+        signature constraints are configured (refer to the description of the
+        <option>local</option> section's <option>auth</option> keyword for
+        details), such key types and hash algorithms are also applied as
+        constraints against IKEv2 signature authentication schemes used by the
+        remote side. To require RSASSA-PSS signatures use
+        <literal>rsa/pss</literal> instead of <literal>pubkey</literal> or
+        <literal>rsa</literal> as in e.g. <literal>rsa/pss-sha256</literal>. If
+        <literal>pubkey</literal> or <literal>rsa</literal> constraints are
+        configured RSASSA-PSS signatures will only be accepted if enabled in
+        <literal>strongswan.conf</literal>(5).
+        </para><para>
+        To specify trust chain constraints for EAP-(T)TLS, append a colon to the
+        EAP method, followed by the key type/size and hash algorithm as
+        discussed above (e.g. <literal>eap-tls:ecdsa-384-sha384</literal>).
+      '';
+
+    } ''
+      Section for a remote authentication round. A remote authentication round
+      defines the constraints how the peers must authenticate to use this
+      connection. Multiple rounds may be defined to use IKEv2 RFC 4739 Multiple
+      Authentication or IKEv1 XAuth.
+      </para><para>
+      Each round is defined in a section having <literal>remote</literal> as
+      prefix, and an optional unique suffix. To define a single authentication
+      round, the suffix may be omitted.
+    '';
+
+    children = mkAttrsOfParams {
+      ah_proposals = mkCommaSepListParam [] ''
+        AH proposals to offer for the CHILD_SA. A proposal is a set of
+        algorithms. For AH, this includes an integrity algorithm and an optional
+        Diffie-Hellman group. If a DH group is specified, CHILD_SA/Quick Mode
+        rekeying and initial negotiation uses a separate Diffie-Hellman exchange
+        using the specified group (refer to esp_proposals for details).
+        </para><para>
+        In IKEv2, multiple algorithms of the same kind can be specified in a
+        single proposal, from which one gets selected. In IKEv1, only one
+        algorithm per kind is allowed per proposal, more algorithms get
+        implicitly stripped. Use multiple proposals to offer different algorithms
+        combinations in IKEv1.
+        </para><para>
+        Algorithm keywords get separated using dashes. Multiple proposals may be
+        specified in a list. The special value <literal>default</literal> forms
+        a default proposal of supported algorithms considered safe, and is
+        usually a good choice for interoperability. By default no AH proposals
+        are included, instead ESP is proposed.
+     '';
+
+      esp_proposals = mkCommaSepListParam ["default"] ''
+        ESP proposals to offer for the CHILD_SA. A proposal is a set of
+        algorithms. For ESP non-AEAD proposals, this includes an integrity
+        algorithm, an encryption algorithm, an optional Diffie-Hellman group and
+        an optional Extended Sequence Number Mode indicator. For AEAD proposals,
+        a combined mode algorithm is used instead of the separate
+        encryption/integrity algorithms.
+        </para><para>
+        If a DH group is specified, CHILD_SA/Quick Mode rekeying and initial
+        negotiation use a separate Diffie-Hellman exchange using the specified
+        group. However, for IKEv2, the keys of the CHILD_SA created implicitly
+        with the IKE_SA will always be derived from the IKE_SA's key material. So
+        any DH group specified here will only apply when the CHILD_SA is later
+        rekeyed or is created with a separate CREATE_CHILD_SA exchange. A
+        proposal mismatch might, therefore, not immediately be noticed when the
+        SA is established, but may later cause rekeying to fail.
+        </para><para>
+        Extended Sequence Number support may be indicated with the
+        <literal>esn</literal> and <literal>noesn</literal> values, both may be
+        included to indicate support for both modes. If omitted,
+        <literal>noesn</literal> is assumed.
+        </para><para>
+        In IKEv2, multiple algorithms of the same kind can be specified in a
+        single proposal, from which one gets selected. In IKEv1, only one
+        algorithm per kind is allowed per proposal, more algorithms get
+        implicitly stripped. Use multiple proposals to offer different algorithms
+        combinations in IKEv1.
+        </para><para>
+        Algorithm keywords get separated using dashes. Multiple proposals may be
+        specified as a list. The special value <literal>default</literal> forms
+        a default proposal of supported algorithms considered safe, and is
+        usually a good choice for interoperability. If no algorithms are
+        specified for AH nor ESP, the default set of algorithms for ESP is
+        included.
+      '';
+
+      sha256_96 = mkYesNoParam no ''
+        HMAC-SHA-256 is used with 128-bit truncation with IPsec. For
+        compatibility with implementations that incorrectly use 96-bit truncation
+        this option may be enabled to configure the shorter truncation length in
+        the kernel. This is not negotiated, so this only works with peers that
+        use the incorrect truncation length (or have this option enabled).
+      '';
+
+      local_ts = mkCommaSepListParam ["dynamic"] ''
+        List of local traffic selectors to include in CHILD_SA. Each selector is
+        a CIDR subnet definition, followed by an optional proto/port
+        selector. The special value <literal>dynamic</literal> may be used
+        instead of a subnet definition, which gets replaced by the tunnel outer
+        address or the virtual IP, if negotiated. This is the default.
+        </para><para>
+        A protocol/port selector is surrounded by opening and closing square
+        brackets. Between these brackets, a numeric or getservent(3) protocol
+        name may be specified. After the optional protocol restriction, an
+        optional port restriction may be specified, separated by a slash. The
+        port restriction may be numeric, a getservent(3) service name, or the
+        special value <literal>opaque</literal> for RFC 4301 OPAQUE
+        selectors. Port ranges may be specified as well, none of the kernel
+        backends currently support port ranges, though.
+        </para><para>
+        When IKEv1 is used only the first selector is interpreted, except if the
+        Cisco Unity extension plugin is used. This is due to a limitation of the
+        IKEv1 protocol, which only allows a single pair of selectors per
+        CHILD_SA. So to tunnel traffic matched by several pairs of selectors when
+        using IKEv1 several children (CHILD_SAs) have to be defined that cover
+        the selectors.  The IKE daemon uses traffic selector narrowing for IKEv1,
+        the same way it is standardized and implemented for IKEv2. However, this
+        may lead to problems with other implementations. To avoid that, configure
+        identical selectors in such scenarios.
+      '';
+
+      remote_ts = mkCommaSepListParam ["dynamic"] ''
+        List of remote selectors to include in CHILD_SA. See
+        <option>local_ts</option> for a description of the selector syntax.
+      '';
+
+      rekey_time = mkDurationParam "1h" ''
+        Time to schedule CHILD_SA rekeying. CHILD_SA rekeying refreshes key
+        material, optionally using a Diffie-Hellman exchange if a group is
+        specified in the proposal.  To avoid rekey collisions initiated by both
+        ends simultaneously, a value in the range of <option>rand_time</option>
+        gets subtracted to form the effective soft lifetime.
+        </para><para>
+        By default CHILD_SA rekeying is scheduled every hour, minus
+        <option>rand_time</option>.
+      '';
+
+      life_time = mkOptionalDurationParam ''
+        Maximum lifetime before CHILD_SA gets closed. Usually this hard lifetime
+        is never reached, because the CHILD_SA gets rekeyed before. If that fails
+        for whatever reason, this limit closes the CHILD_SA.  The default is 10%
+        more than the <option>rekey_time</option>.
+      '';
+
+      rand_time = mkOptionalDurationParam ''
+        Time range from which to choose a random value to subtract from
+        <option>rekey_time</option>. The default is the difference between
+        <option>life_time</option> and <option>rekey_time</option>.
+      '';
+
+      rekey_bytes = mkIntParam 0 ''
+        Number of bytes processed before initiating CHILD_SA rekeying. CHILD_SA
+        rekeying refreshes key material, optionally using a Diffie-Hellman
+        exchange if a group is specified in the proposal.
+        </para><para>
+        To avoid rekey collisions initiated by both ends simultaneously, a value
+        in the range of <option>rand_bytes</option> gets subtracted to form the
+        effective soft volume limit.
+        </para><para>
+        Volume based CHILD_SA rekeying is disabled by default.
+      '';
+
+      life_bytes = mkOptionalIntParam ''
+        Maximum bytes processed before CHILD_SA gets closed. Usually this hard
+        volume limit is never reached, because the CHILD_SA gets rekeyed
+        before. If that fails for whatever reason, this limit closes the
+        CHILD_SA.  The default is 10% more than <option>rekey_bytes</option>.
+      '';
+
+      rand_bytes = mkOptionalIntParam ''
+        Byte range from which to choose a random value to subtract from
+        <option>rekey_bytes</option>. The default is the difference between
+        <option>life_bytes</option> and <option>rekey_bytes</option>.
+      '';
+
+      rekey_packets = mkIntParam 0 ''
+        Number of packets processed before initiating CHILD_SA rekeying. CHILD_SA
+        rekeying refreshes key material, optionally using a Diffie-Hellman
+        exchange if a group is specified in the proposal.
+        </para><para>
+        To avoid rekey collisions initiated by both ends simultaneously, a value
+        in the range of <option>rand_packets</option> gets subtracted to form
+        the effective soft packet count limit.
+        </para><para>
+        Packet count based CHILD_SA rekeying is disabled by default.
+      '';
+
+      life_packets = mkOptionalIntParam ''
+        Maximum number of packets processed before CHILD_SA gets closed. Usually
+        this hard packets limit is never reached, because the CHILD_SA gets
+        rekeyed before. If that fails for whatever reason, this limit closes the
+        CHILD_SA.
+        </para><para>
+        The default is 10% more than <option>rekey_bytes</option>.
+      '';
+
+      rand_packets = mkOptionalIntParam ''
+        Packet range from which to choose a random value to subtract from
+        <option>rekey_packets</option>. The default is the difference between
+        <option>life_packets</option> and <option>rekey_packets</option>.
+      '';
+
+      updown = mkOptionalStrParam ''
+        Updown script to invoke on CHILD_SA up and down events.
+      '';
+
+      hostaccess = mkYesNoParam no ''
+        Hostaccess variable to pass to <literal>updown</literal> script.
+      '';
+
+      mode = mkEnumParam [ "tunnel"
+                           "transport"
+                           "transport_proxy"
+                           "beet"
+                           "pass"
+                           "drop"
+                         ] "tunnel" ''
+        IPsec Mode to establish CHILD_SA with.
+        <itemizedlist>
+        <listitem><para>
+        <literal>tunnel</literal> negotiates the CHILD_SA in IPsec Tunnel Mode,
+        </para></listitem>
+        <listitem><para>
+        whereas <literal>transport</literal> uses IPsec Transport Mode.
+        </para></listitem>
+        <listitem><para>
+        <literal>transport_proxy</literal> signifying the special Mobile IPv6
+        Transport Proxy Mode.
+        </para></listitem>
+        <listitem><para>
+        <literal>beet</literal> is the Bound End to End Tunnel mixture mode,
+        working with fixed inner addresses without the need to include them in
+        each packet.
+        </para></listitem>
+        <listitem><para>
+        Both <literal>transport</literal> and <literal>beet</literal> modes are
+        subject to mode negotiation; <literal>tunnel</literal> mode is
+        negotiated if the preferred mode is not available.
+        </para></listitem>
+        <listitem><para>
+        <literal>pass</literal> and <literal>drop</literal> are used to install
+        shunt policies which explicitly bypass the defined traffic from IPsec
+        processing or drop it, respectively.
+        </para></listitem>
+        </itemizedlist>
+      '';
+
+      policies = mkYesNoParam yes ''
+        Whether to install IPsec policies or not. Disabling this can be useful in
+        some scenarios e.g. MIPv6, where policies are not managed by the IKE
+        daemon. Since 5.3.3.
+      '';
+
+      policies_fwd_out = mkYesNoParam no ''
+        Whether to install outbound FWD IPsec policies or not. Enabling this is
+        required in case there is a drop policy that would match and block
+        forwarded traffic for this CHILD_SA. Since 5.5.1.
+      '';
+
+      dpd_action = mkEnumParam ["clear" "trap" "restart"] "clear" ''
+        Action to perform for this CHILD_SA on DPD timeout. The default clear
+        closes the CHILD_SA and does not take further action. trap installs a
+        trap policy, which will catch matching traffic and tries to re-negotiate
+        the tunnel on-demand. restart immediately tries to re-negotiate the
+        CHILD_SA under a fresh IKE_SA.
+      '';
+
+      ipcomp = mkYesNoParam no ''
+        Enable IPComp compression before encryption. If enabled, IKE tries to
+        negotiate IPComp compression to compress ESP payload data prior to
+        encryption.
+      '';
+
+      inactivity = mkDurationParam "0s" ''
+        Timeout before closing CHILD_SA after inactivity. If no traffic has been
+        processed in either direction for the configured timeout, the CHILD_SA
+        gets closed due to inactivity. The default value of 0 disables inactivity
+        checks.
+      '';
+
+      reqid = mkIntParam 0 ''
+        Fixed reqid to use for this CHILD_SA. This might be helpful in some
+        scenarios, but works only if each CHILD_SA configuration is instantiated
+        not more than once. The default of 0 uses dynamic reqids, allocated
+        incrementally.
+      '';
+
+      priority = mkIntParam 0 ''
+        Optional fixed priority for IPsec policies. This could be useful to
+        install high-priority drop policies. The default of 0 uses dynamically
+        calculated priorities based on the size of the traffic selectors.
+      '';
+
+      interface = mkOptionalStrParam ''
+        Optional interface name to restrict outbound IPsec policies.
+      '';
+
+      mark_in = mkStrParam "0/0x00000000" ''
+        Netfilter mark and mask for input traffic. On Linux, Netfilter may
+        require marks on each packet to match an SA/policy having that option
+        set. This allows installing duplicate policies and enables Netfilter
+        rules to select specific SAs/policies for incoming traffic. Note that
+        inbound marks are only set on policies, by default, unless
+        <option>mark_in_sa</option> is enabled. The special value
+        <literal>%unique</literal> sets a unique mark on each CHILD_SA instance,
+        beyond that the value <literal>%unique-dir</literal> assigns a different
+        unique mark for each
+        </para><para>
+        An additional mask may be appended to the mark, separated by
+        <literal>/</literal>. The default mask if omitted is
+        <literal>0xffffffff</literal>.
+      '';
+
+      mark_in_sa = mkYesNoParam no ''
+        Whether to set <option>mark_in</option> on the inbound SA. By default,
+        the inbound mark is only set on the inbound policy. The tuple destination
+        address, protocol and SPI is unique and the mark is not required to find
+        the correct SA, allowing to mark traffic after decryption instead (where
+        more specific selectors may be used) to match different policies. Marking
+        packets before decryption is still possible, even if no mark is set on
+        the SA.
+      '';
+
+      mark_out = mkStrParam "0/0x00000000" ''
+        Netfilter mark and mask for output traffic. On Linux, Netfilter may
+        require marks on each packet to match a policy/SA having that option
+        set. This allows installing duplicate policies and enables Netfilter
+        rules to select specific policies/SAs for outgoing traffic. The special
+        value <literal>%unique</literal> sets a unique mark on each CHILD_SA
+        instance, beyond that the value <literal>%unique-dir</literal> assigns a
+        different unique mark for each CHILD_SA direction (in/out).
+        </para><para>
+        An additional mask may be appended to the mark, separated by
+        <literal>/</literal>. The default mask if omitted is
+        <literal>0xffffffff</literal>.
+      '';
+
+      set_mark_in = mkStrParam "0/0x00000000" ''
+        Netfilter mark applied to packets after the inbound IPsec SA processed
+        them. This way it's not necessary to mark packets via Netfilter before
+        decryption or right afterwards to match policies or process them
+        differently (e.g. via policy routing).
+
+        An additional mask may be appended to the mark, separated by
+        <literal>/</literal>. The default mask if omitted is 0xffffffff. The
+        special value <literal>%same</literal> uses the value (but not the mask)
+        from <option>mark_in</option> as mark value, which can be fixed,
+        <literal>%unique</literal> or <literal>%unique-dir</literal>.
+
+        Setting marks in XFRM input requires Linux 4.19 or higher.
+      '';
+
+      set_mark_out = mkStrParam "0/0x00000000" ''
+        Netfilter mark applied to packets after the outbound IPsec SA processed
+        them. This allows processing ESP packets differently than the original
+        traffic (e.g. via policy routing).
+
+        An additional mask may be appended to the mark, separated by
+        <literal>/</literal>. The default mask if omitted is 0xffffffff. The
+        special value <literal>%same</literal> uses the value (but not the mask)
+        from <option>mark_out</option> as mark value, which can be fixed,
+        <literal>%unique_</literal> or <literal>%unique-dir</literal>.
+
+        Setting marks in XFRM output is supported since Linux 4.14. Setting a
+        mask requires at least Linux 4.19.
+      '';
+
+      if_id_in = mkStrParam "0" ''
+        XFRM interface ID set on inbound policies/SA. This allows installing
+        duplicate policies/SAs and associates them with an interface with the
+        same ID. The special value <literal>%unique</literal> sets a unique
+        interface ID on each CHILD_SA instance, beyond that the value
+        <literal>%unique-dir</literal> assigns a different unique interface ID
+        for each CHILD_SA direction (in/out).
+      '';
+
+      if_id_out = mkStrParam "0" ''
+        XFRM interface ID set on outbound policies/SA. This allows installing
+        duplicate policies/SAs and associates them with an interface with the
+        same ID. The special value <literal>%unique</literal> sets a unique
+        interface ID on each CHILD_SA instance, beyond that the value
+        <literal>%unique-dir</literal> assigns a different unique interface ID
+        for each CHILD_SA direction (in/out).
+
+        The daemon will not install routes for CHILD_SAs that have this option set.
+     '';
+
+      tfc_padding = mkParamOfType (with lib.types; either int (enum ["mtu"])) 0 ''
+        Pads ESP packets with additional data to have a consistent ESP packet
+        size for improved Traffic Flow Confidentiality. The padding defines the
+        minimum size of all ESP packets sent.  The default value of
+        <literal>0</literal> disables TFC padding, the special value
+        <literal>mtu</literal> adds TFC padding to create a packet size equal to
+        the Path Maximum Transfer Unit.
+      '';
+
+      replay_window = mkIntParam 32 ''
+        IPsec replay window to configure for this CHILD_SA. Larger values than
+        the default of <literal>32</literal> are supported using the Netlink
+        backend only, a value of <literal>0</literal> disables IPsec replay
+        protection.
+      '';
+
+      hw_offload = mkEnumParam ["yes" "no" "auto"] "no" ''
+        Enable hardware offload for this CHILD_SA, if supported by the IPsec
+        implementation. The value <literal>yes</literal> enforces offloading
+        and the installation will fail if it's not supported by either kernel or
+        device. The value <literal>auto</literal> enables offloading, if it's
+        supported, but the installation does not fail otherwise.
+      '';
+
+      copy_df = mkYesNoParam yes ''
+        Whether to copy the DF bit to the outer IPv4 header in tunnel mode. This
+        effectively disables Path MTU discovery (PMTUD). Controlling this
+        behavior is not supported by all kernel interfaces.
+      '';
+
+      copy_ecn = mkYesNoParam yes ''
+        Whether to copy the ECN (Explicit Congestion Notification) header field
+        to/from the outer IP header in tunnel mode. Controlling this behavior is
+        not supported by all kernel interfaces.
+      '';
+
+      copy_dscp = mkEnumParam [ "out" "in" "yes" "no" ] "out" ''
+        Whether to copy the DSCP (Differentiated Services Field Codepoint)
+        header field to/from the outer IP header in tunnel mode. The value
+        <literal>out</literal> only copies the field from the inner to the outer
+        header, the value <literal>in</literal> does the opposite and only
+        copies the field from the outer to the inner header when decapsulating,
+        the value <literal>yes</literal> copies the field in both directions,
+        and the value <literal>no</literal> disables copying the field
+        altogether. Setting this to <literal>yes</literal> or
+        <literal>in</literal> could allow an attacker to adversely affect other
+        traffic at the receiver, which is why the default is
+        <literal>out</literal>. Controlling this behavior is not supported by
+        all kernel interfaces.
+      '';
+
+      start_action = mkEnumParam ["none" "trap" "start"] "none" ''
+        Action to perform after loading the configuration.
+        <itemizedlist>
+        <listitem><para>
+        The default of <literal>none</literal> loads the connection only, which
+        then can be manually initiated or used as a responder configuration.
+        </para></listitem>
+        <listitem><para>
+        The value <literal>trap</literal> installs a trap policy, which triggers
+        the tunnel as soon as matching traffic has been detected.
+        </para></listitem>
+        <listitem><para>
+        The value <literal>start</literal> initiates the connection actively.
+        </para></listitem>
+        </itemizedlist>
+        When unloading or replacing a CHILD_SA configuration having a
+        <option>start_action</option> different from <literal>none</literal>,
+        the inverse action is performed. Configurations with
+        <literal>start</literal> get closed, while such with
+        <literal>trap</literal> get uninstalled.
+      '';
+
+      close_action = mkEnumParam ["none" "trap" "start"] "none" ''
+        Action to perform after a CHILD_SA gets closed by the peer.
+        <itemizedlist>
+        <listitem><para>
+        The default of <literal>none</literal> does not take any action,
+        </para></listitem>
+        <listitem><para>
+        <literal>trap</literal> installs a trap policy for the CHILD_SA.
+        </para></listitem>
+        <listitem><para>
+        <literal>start</literal> tries to re-create the CHILD_SA.
+        </para></listitem>
+        </itemizedlist>
+        </para><para>
+        <option>close_action</option> does not provide any guarantee that the
+        CHILD_SA is kept alive. It acts on explicit close messages only, but not
+        on negotiation failures. Use trap policies to reliably re-create failed
+        CHILD_SAs.
+      '';
+
+    } ''
+      CHILD_SA configuration sub-section. Each connection definition may have
+      one or more sections in its <option>children</option> subsection. The
+      section name defines the name of the CHILD_SA configuration, which must be
+      unique within the connection (denoted &#60;child&#62; below).
+    '';
+  } ''
+    Section defining IKE connection configurations, each in its own subsection
+    with an arbitrary yet unique name
+  '';
+
+  secrets = let
+    mkEapXauthParams = mkPrefixedAttrsOfParams {
+      secret = mkOptionalStrParam ''
+        Value of the EAP/XAuth secret. It may either be an ASCII string, a hex
+        encoded string if it has a 0x prefix or a Base64 encoded string if it
+        has a 0s prefix in its value.
+      '';
+
+      id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+        Identity the EAP/XAuth secret belongs to. Multiple unique identities may
+        be specified, each having an <literal>id</literal> prefix, if a secret
+        is shared between multiple users.
+      '';
+
+    } ''
+      EAP secret section for a specific secret. Each EAP secret is defined in a
+      unique section having the <literal>eap</literal> prefix. EAP secrets are
+      used for XAuth authentication as well.
+    '';
+
+  in {
+
+    eap   = mkEapXauthParams;
+    xauth = mkEapXauthParams;
+
+    ntlm = mkPrefixedAttrsOfParams {
+      secret = mkOptionalStrParam ''
+        Value of the NTLM secret, which is the NT Hash of the actual secret,
+        that is, MD4(UTF-16LE(secret)). The resulting 16-byte value may either
+        be given as a hex encoded string with a 0x prefix or as a Base64 encoded
+        string with a 0s prefix.
+      '';
+
+      id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+        Identity the NTLM secret belongs to. Multiple unique identities may be
+        specified, each having an id prefix, if a secret is shared between
+        multiple users.
+      '';
+    } ''
+      NTLM secret section for a specific secret. Each NTLM secret is defined in
+      a unique section having the <literal>ntlm</literal> prefix. NTLM secrets
+      may only be used for EAP-MSCHAPv2 authentication.
+    '';
+
+    ike = mkPrefixedAttrsOfParams {
+      secret = mkOptionalStrParam ''
+        Value of the IKE preshared secret. It may either be an ASCII string, a
+        hex encoded string if it has a 0x prefix or a Base64 encoded string if
+        it has a 0s prefix in its value.
+      '';
+
+      id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+        IKE identity the IKE preshared secret belongs to. Multiple unique
+        identities may be specified, each having an <literal>id</literal>
+        prefix, if a secret is shared between multiple peers.
+      '';
+    } ''
+      IKE preshared secret section for a specific secret. Each IKE PSK is
+      defined in a unique section having the <literal>ike</literal> prefix.
+    '';
+
+    ppk = mkPrefixedAttrsOfParams {
+      secret = mkOptionalStrParam ''
+	      Value of the PPK. It may either be an ASCII string, a hex encoded string
+	      if it has a <literal>0x</literal> prefix or a Base64 encoded string if
+	      it has a <literal>0s</literal> prefix in its value. Should have at least
+	      256 bits of entropy for 128-bit security.
+      '';
+
+      id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+	      PPK identity the PPK belongs to. Multiple unique identities may be
+	      specified, each having an <literal>id</literal> prefix, if a secret is
+	      shared between multiple peers.
+      '';
+    } ''
+	    Postquantum Preshared Key (PPK) section for a specific secret. Each PPK is
+	    defined in a unique section having the <literal>ppk</literal> prefix.
+    '';
+
+    private = mkPrefixedAttrsOfParams {
+      file = mkOptionalStrParam ''
+        File name in the private folder for which this passphrase should be used.
+      '';
+
+      secret = mkOptionalStrParam ''
+        Value of decryption passphrase for private key.
+      '';
+    } ''
+      Private key decryption passphrase for a key in the
+      <literal>private</literal> folder.
+    '';
+
+    rsa = mkPrefixedAttrsOfParams {
+      file = mkOptionalStrParam ''
+        File name in the <literal>rsa</literal> folder for which this passphrase
+        should be used.
+      '';
+      secret = mkOptionalStrParam ''
+        Value of decryption passphrase for RSA key.
+      '';
+    } ''
+      Private key decryption passphrase for a key in the <literal>rsa</literal>
+      folder.
+    '';
+
+    ecdsa = mkPrefixedAttrsOfParams {
+      file = mkOptionalStrParam ''
+        File name in the <literal>ecdsa</literal> folder for which this
+        passphrase should be used.
+      '';
+      secret = mkOptionalStrParam ''
+        Value of decryption passphrase for ECDSA key.
+      '';
+    } ''
+      Private key decryption passphrase for a key in the
+      <literal>ecdsa</literal> folder.
+    '';
+
+    pkcs8 = mkPrefixedAttrsOfParams {
+      file = mkOptionalStrParam ''
+        File name in the <literal>pkcs8</literal> folder for which this
+        passphrase should be used.
+      '';
+      secret = mkOptionalStrParam ''
+        Value of decryption passphrase for PKCS#8 key.
+      '';
+    } ''
+      Private key decryption passphrase for a key in the
+      <literal>pkcs8</literal> folder.
+    '';
+
+    pkcs12 = mkPrefixedAttrsOfParams {
+      file = mkOptionalStrParam ''
+        File name in the <literal>pkcs12</literal> folder for which this
+        passphrase should be used.
+      '';
+      secret = mkOptionalStrParam ''
+        Value of decryption passphrase for PKCS#12 container.
+      '';
+    } ''
+      PKCS#12 decryption passphrase for a container in the
+      <literal>pkcs12</literal> folder.
+    '';
+
+    token = mkPrefixedAttrsOfParams {
+      handle = mkOptionalHexParam ''
+        Hex-encoded CKA_ID or handle of the private key on the token or TPM,
+        respectively.
+      '';
+
+      slot = mkOptionalIntParam ''
+        Optional slot number to access the token.
+      '';
+
+      module = mkOptionalStrParam ''
+        Optional PKCS#11 module name to access the token.
+      '';
+
+      pin = mkOptionalStrParam ''
+        Optional PIN required to access the key on the token. If none is
+        provided the user is prompted during an interactive
+        <literal>--load-creds</literal> call.
+      '';
+    } ''Definition for a private key that's stored on a token/smartcard/TPM.'';
+
+  };
+
+  pools = mkAttrsOfParams {
+    addrs = mkOptionalStrParam ''
+      Subnet or range defining addresses allocated in pool. Accepts a single
+      CIDR subnet defining the pool to allocate addresses from or an address
+      range (&#60;from&#62;-&#60;to&#62;). Pools must be unique and non-overlapping.
+    '';
+
+    dns           = mkCommaSepListParam [] "Address or CIDR subnets";
+    nbns          = mkCommaSepListParam [] "Address or CIDR subnets";
+    dhcp          = mkCommaSepListParam [] "Address or CIDR subnets";
+    netmask       = mkCommaSepListParam [] "Address or CIDR subnets";
+    server        = mkCommaSepListParam [] "Address or CIDR subnets";
+    subnet        = mkCommaSepListParam [] "Address or CIDR subnets";
+    split_include = mkCommaSepListParam [] "Address or CIDR subnets";
+    split_exclude = mkCommaSepListParam [] "Address or CIDR subnets";
+  } ''
+    Section defining named pools. Named pools may be referenced by connections
+    with the pools option to assign virtual IPs and other configuration
+    attributes. Each pool must have a unique name (denoted &#60;name&#62; below).
+  '';
+}
diff --git a/nixpkgs/nixos/modules/services/networking/strongswan.nix b/nixpkgs/nixos/modules/services/networking/strongswan.nix
new file mode 100644
index 000000000000..13a1a897c5ed
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/strongswan.nix
@@ -0,0 +1,170 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (builtins) toFile;
+  inherit (lib) concatMapStringsSep concatStringsSep mapAttrsToList
+                mkIf mkEnableOption mkOption types literalExample;
+
+  cfg = config.services.strongswan;
+
+  ipsecSecrets = secrets: toFile "ipsec.secrets" (
+    concatMapStringsSep "\n" (f: "include ${f}") secrets
+  );
+
+  ipsecConf = {setup, connections, ca}:
+    let
+      # https://wiki.strongswan.org/projects/strongswan/wiki/IpsecConf
+      makeSections = type: sections: concatStringsSep "\n\n" (
+        mapAttrsToList (sec: attrs:
+          "${type} ${sec}\n" +
+            (concatStringsSep "\n" ( mapAttrsToList (k: v: "  ${k}=${v}") attrs ))
+        ) sections
+      );
+      setupConf       = makeSections "config" { inherit setup; };
+      connectionsConf = makeSections "conn" connections;
+      caConf          = makeSections "ca" ca;
+
+    in
+    builtins.toFile "ipsec.conf" ''
+      ${setupConf}
+      ${connectionsConf}
+      ${caConf}
+    '';
+
+  strongswanConf = {setup, connections, ca, secretsFile, managePlugins, enabledPlugins}: toFile "strongswan.conf" ''
+    charon {
+      ${if managePlugins then "load_modular = no" else ""}
+      ${if managePlugins then ("load = " + (concatStringsSep " " enabledPlugins)) else ""}
+      plugins {
+        stroke {
+          secrets_file = ${secretsFile}
+        }
+      }
+    }
+
+    starter {
+      config_file = ${ipsecConf { inherit setup connections ca; }}
+    }
+  '';
+
+in
+{
+  options.services.strongswan = {
+    enable = mkEnableOption "strongSwan";
+
+    secrets = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "/run/keys/ipsec-foo.secret" ];
+      description = ''
+        A list of paths to IPSec secret files. These
+        files will be included into the main ipsec.secrets file with
+        the <literal>include</literal> directive. It is safer if these
+        paths are absolute.
+      '';
+    };
+
+    setup = mkOption {
+      type = types.attrsOf types.str;
+      default = {};
+      example = { cachecrls = "yes"; strictcrlpolicy = "yes"; };
+      description = ''
+        A set of options for the ‘config setup’ section of the
+        <filename>ipsec.conf</filename> file. Defines general
+        configuration parameters.
+      '';
+    };
+
+    connections = mkOption {
+      type = types.attrsOf (types.attrsOf types.str);
+      default = {};
+      example = literalExample ''
+        {
+          "%default" = {
+            keyexchange = "ikev2";
+            keyingtries = "1";
+          };
+          roadwarrior = {
+            auto       = "add";
+            leftcert   = "/run/keys/moonCert.pem";
+            leftid     = "@moon.strongswan.org";
+            leftsubnet = "10.1.0.0/16";
+            right      = "%any";
+          };
+        }
+      '';
+      description = ''
+        A set of connections and their options for the ‘conn xxx’
+        sections of the <filename>ipsec.conf</filename> file.
+      '';
+    };
+
+    ca = mkOption {
+      type = types.attrsOf (types.attrsOf types.str);
+      default = {};
+      example = {
+        strongswan = {
+          auto   = "add";
+          cacert = "/run/keys/strongswanCert.pem";
+          crluri = "http://crl2.strongswan.org/strongswan.crl";
+        };
+      };
+      description = ''
+        A set of CAs (certification authorities) and their options for
+        the ‘ca xxx’ sections of the <filename>ipsec.conf</filename>
+        file.
+      '';
+    };
+
+    managePlugins = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If set to true, this option will disable automatic plugin loading and
+        then tell strongSwan to enable the plugins specified in the
+        <option>enabledPlugins</option> option.
+      '';
+    };
+
+    enabledPlugins = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        A list of additional plugins to enable if
+        <option>managePlugins</option> is true.
+      '';
+    };
+  };
+
+
+  config = with cfg;
+  let
+    secretsFile = ipsecSecrets cfg.secrets;
+  in
+  mkIf enable
+    {
+
+    # here we should use the default strongswan ipsec.secrets and
+    # append to it (default one is empty so not a pb for now)
+    environment.etc."ipsec.secrets".source = secretsFile;
+
+    systemd.services.strongswan = {
+      description = "strongSwan IPSec Service";
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [ kmod iproute iptables utillinux ]; # XXX Linux
+      after = [ "network-online.target" ];
+      environment = {
+        STRONGSWAN_CONF = strongswanConf { inherit setup connections ca secretsFile managePlugins enabledPlugins; };
+      };
+      serviceConfig = {
+        ExecStart  = "${pkgs.strongswan}/sbin/ipsec start --nofork";
+      };
+      preStart = ''
+        # with 'nopeerdns' setting, ppp writes into this folder
+        mkdir -m 700 -p /etc/ppp
+      '';
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/stubby.nix b/nixpkgs/nixos/modules/services/networking/stubby.nix
new file mode 100644
index 000000000000..c5e0f929a126
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/stubby.nix
@@ -0,0 +1,217 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfg = config.services.stubby;
+
+  fallbacks = concatMapStringsSep "\n  " (x: "- ${x}") cfg.fallbackProtocols;
+  listeners = concatMapStringsSep "\n  " (x: "- ${x}") cfg.listenAddresses;
+
+  # By default, the recursive resolvers maintained by the getdns
+  # project itself are enabled. More information about both getdns's servers,
+  # as well as third party options for upstream resolvers, can be found here:
+  # https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Test+Servers
+  #
+  # You can override these values by supplying a yaml-formatted array of your
+  # preferred upstream resolvers in the following format:
+  #
+  # 106 # - address_data: IPv4 or IPv6 address of the upstream
+  #   port: Port for UDP/TCP (default is 53)
+  #   tls_auth_name: Authentication domain name checked against the server
+  #                  certificate
+  #   tls_pubkey_pinset: An SPKI pinset verified against the keys in the server
+  #                      certificate
+  #     - digest: Only "sha256" is currently supported
+  #       value: Base64 encoded value of the sha256 fingerprint of the public
+  #              key
+  #   tls_port: Port for TLS (default is 853)
+
+  defaultUpstream = ''
+    - address_data: 145.100.185.15
+      tls_auth_name: "dnsovertls.sinodun.com"
+      tls_pubkey_pinset:
+        - digest: "sha256"
+          value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4=
+    - address_data: 145.100.185.16
+      tls_auth_name: "dnsovertls1.sinodun.com"
+      tls_pubkey_pinset:
+        - digest: "sha256"
+          value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA=
+    - address_data: 185.49.141.37
+      tls_auth_name: "getdnsapi.net"
+      tls_pubkey_pinset:
+        - digest: "sha256"
+          value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q=
+    - address_data: 2001:610:1:40ba:145:100:185:15
+      tls_auth_name: "dnsovertls.sinodun.com"
+      tls_pubkey_pinset:
+        - digest: "sha256"
+          value: 62lKu9HsDVbyiPenApnc4sfmSYTHOVfFgL3pyB+cBL4=
+    - address_data: 2001:610:1:40ba:145:100:185:16
+      tls_auth_name: "dnsovertls1.sinodun.com"
+      tls_pubkey_pinset:
+        - digest: "sha256"
+          value: cE2ecALeE5B+urJhDrJlVFmf38cJLAvqekONvjvpqUA=
+    - address_data: 2a04:b900:0:100::38
+      tls_auth_name: "getdnsapi.net"
+      tls_pubkey_pinset:
+        - digest: "sha256"
+          value: foxZRnIh9gZpWnl+zEiKa0EJ2rdCGroMWm02gaxSc9Q=
+  '';
+
+  # Resolution type is not changeable here because it is required per the
+  # stubby documentation:
+  #
+  # "resolution_type: Work in stub mode only (not recursive mode) - required for Stubby
+  # operation."
+  #
+  # https://dnsprivacy.org/wiki/display/DP/Configuring+Stubby
+
+  confFile = pkgs.writeText "stubby.yml" ''
+    resolution_type: GETDNS_RESOLUTION_STUB
+    dns_transport_list:
+      ${fallbacks}
+    appdata_dir: "/var/cache/stubby"
+    tls_authentication: ${cfg.authenticationMode}
+    tls_query_padding_blocksize: ${toString cfg.queryPaddingBlocksize}
+    edns_client_subnet_private: ${if cfg.subnetPrivate then "1" else "0"}
+    idle_timeout: ${toString cfg.idleTimeout}
+    listen_addresses:
+      ${listeners}
+    round_robin_upstreams: ${if cfg.roundRobinUpstreams then "1" else "0"}
+    ${cfg.extraConfig}
+    upstream_recursive_servers:
+    ${cfg.upstreamServers}
+  '';
+in
+
+{
+  options = {
+    services.stubby = {
+
+      enable = mkEnableOption "Stubby DNS resolver";
+
+      fallbackProtocols = mkOption {
+        default = [ "GETDNS_TRANSPORT_TLS" ];
+        type = with types; listOf (enum [
+          "GETDNS_TRANSPORT_TLS"
+          "GETDNS_TRANSPORT_TCP"
+          "GETDNS_TRANSPORT_UDP"
+        ]);
+        description = ''
+          Ordered list composed of one or more transport protocols.
+          Strict mode should only use <literal>GETDNS_TRANSPORT_TLS</literal>.
+          Other options are <literal>GETDNS_TRANSPORT_UDP</literal> and
+          <literal>GETDNS_TRANSPORT_TCP</literal>.
+        '';
+      };
+
+      authenticationMode = mkOption {
+        default = "GETDNS_AUTHENTICATION_REQUIRED";
+        type = types.enum [
+          "GETDNS_AUTHENTICATION_REQUIRED"
+          "GETDNS_AUTHENTICATION_NONE"
+        ];
+        description = ''
+          Selects the Strict or Opportunistic usage profile.
+          For strict, set to <literal>GETDNS_AUTHENTICATION_REQUIRED</literal>.
+          for opportunistic, use <literal>GETDNS_AUTHENTICATION_NONE</literal>.
+        '';
+      };
+
+      queryPaddingBlocksize = mkOption {
+        default = 128;
+        type = types.int;
+        description = ''
+          EDNS0 option to pad the size of the DNS query to the given blocksize.
+        '';
+      };
+
+      subnetPrivate = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          EDNS0 option for ECS client privacy. Default is
+          <literal>true</literal>. If set, this option prevents the client
+          subnet from being sent to authoritative nameservers.
+        '';
+      };
+
+      idleTimeout = mkOption {
+        default = 10000;
+        type = types.int;
+        description = "EDNS0 option for keepalive idle timeout expressed in
+        milliseconds.";
+      };
+
+      listenAddresses = mkOption {
+        default = [ "127.0.0.1" "0::1" ];
+        type = with types; listOf str;
+        description = ''
+          Sets the listen address for the stubby daemon.
+          Uses port 53 by default.
+          Ise IP@port to specify a different port.
+        '';
+      };
+
+      roundRobinUpstreams = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Instructs stubby to distribute queries across all available name
+          servers. Default is <literal>true</literal>. Set to
+          <literal>false</literal> in order to use the first available.
+        '';
+      };
+
+      upstreamServers = mkOption {
+        default = defaultUpstream;
+        type = types.lines;
+        description = ''
+          Replace default upstreams. See <citerefentry><refentrytitle>stubby
+          </refentrytitle><manvolnum>1</manvolnum></citerefentry> for an
+          example of the entry formatting. In Strict mode, at least one of the
+          following settings must be supplied for each nameserver:
+          <literal>tls_auth_name</literal> or
+          <literal>tls_pubkey_pinset</literal>.
+        '';
+      };
+
+      debugLogging = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Enable or disable debug level logging.";
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Add additional configuration options. see <citerefentry>
+          <refentrytitle>stubby</refentrytitle><manvolnum>1</manvolnum>
+          </citerefentry>for more options.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.stubby ];
+    systemd.services.stubby = {
+      description = "Stubby local DNS resolver";
+      after = [ "network.target" ];
+      before = [ "nss-lookup.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Type = "notify";
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
+        ExecStart = "${pkgs.stubby}/bin/stubby -C ${confFile} ${optionalString cfg.debugLogging "-l"}";
+        DynamicUser = true;
+        CacheDirectory = "stubby";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/stunnel.nix b/nixpkgs/nixos/modules/services/networking/stunnel.nix
new file mode 100644
index 000000000000..ab51bba2f6ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/stunnel.nix
@@ -0,0 +1,234 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.stunnel;
+  yesNo = val: if val then "yes" else "no";
+
+  verifyChainPathAssert = n: c: {
+    assertion = c.verifyHostname == null || (c.verifyChain || c.verifyPeer);
+    message =  "stunnel: \"${n}\" client configuration - hostname verification " +
+      "is not possible without either verifyChain or verifyPeer enabled";
+  };
+
+  serverConfig = {
+    options = {
+      accept = mkOption {
+        type = types.int;
+        description = "On which port stunnel should listen for incoming TLS connections.";
+      };
+
+      connect = mkOption {
+        type = types.int;
+        description = "To which port the decrypted connection should be forwarded.";
+      };
+
+      cert = mkOption {
+        type = types.path;
+        description = "File containing both the private and public keys.";
+      };
+    };
+  };
+
+  clientConfig = {
+    options = {
+      accept = mkOption {
+        type = types.str;
+        description = "IP:Port on which connections should be accepted.";
+      };
+
+      connect = mkOption {
+        type = types.str;
+        description = "IP:Port destination to connect to.";
+      };
+
+      verifyChain = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Check if the provided certificate has a valid certificate chain (against CAPath).";
+      };
+
+      verifyPeer = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Check if the provided certificate is contained in CAPath.";
+      };
+
+      CAPath = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "Path to a directory containing certificates to validate against.";
+      };
+
+      CAFile = mkOption {
+        type = types.nullOr types.path;
+        default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+        description = "Path to a file containing certificates to validate against.";
+      };
+
+      verifyHostname = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        description = "If set, stunnel checks if the provided certificate is valid for the given hostname.";
+      };
+    };
+  };
+
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.stunnel = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the stunnel TLS tunneling service.";
+      };
+
+      user = mkOption {
+        type = with types; nullOr str;
+        default = "nobody";
+        description = "The user under which stunnel runs.";
+      };
+
+      group = mkOption {
+        type = with types; nullOr str;
+        default = "nogroup";
+        description = "The group under which stunnel runs.";
+      };
+
+      logLevel = mkOption {
+        type = types.enum [ "emerg" "alert" "crit" "err" "warning" "notice" "info" "debug" ];
+        default = "info";
+        description = "Verbosity of stunnel output.";
+      };
+
+      fipsMode = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable FIPS 140-2 mode required for compliance.";
+      };
+
+      enableInsecureSSLv3 = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable support for the insecure SSLv3 protocol.";
+      };
+
+
+      servers = mkOption {
+        description = "Define the server configuations.";
+        type = with types; attrsOf (submodule serverConfig);
+        example = {
+          fancyWebserver = {
+            enable = true;
+            accept = 443;
+            connect = 8080;
+            cert = "/path/to/pem/file";
+          };
+        };
+        default = { };
+      };
+
+      clients = mkOption {
+        description = "Define the client configurations.";
+        type = with types; attrsOf (submodule clientConfig);
+        example = {
+          foobar = {
+            accept = "0.0.0.0:8080";
+            connect = "nixos.org:443";
+            verifyChain = false;
+          };
+        };
+        default = { };
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = concatLists [
+      (singleton {
+        assertion = (length (attrValues cfg.servers) != 0) || ((length (attrValues cfg.clients)) != 0);
+        message = "stunnel: At least one server- or client-configuration has to be present.";
+      })
+
+      (mapAttrsToList verifyChainPathAssert cfg.clients)
+    ];
+
+    environment.systemPackages = [ pkgs.stunnel ];
+
+    environment.etc."stunnel.cfg".text = ''
+      ${ if cfg.user != null then "setuid = ${cfg.user}" else "" }
+      ${ if cfg.group != null then "setgid = ${cfg.group}" else "" }
+
+      debug = ${cfg.logLevel}
+
+      ${ optionalString cfg.fipsMode "fips = yes" }
+      ${ optionalString cfg.enableInsecureSSLv3 "options = -NO_SSLv3" }
+
+      ; ----- SERVER CONFIGURATIONS -----
+      ${ lib.concatStringsSep "\n"
+           (lib.mapAttrsToList
+             (n: v: ''
+               [${n}]
+               accept = ${toString v.accept}
+               connect = ${toString v.connect}
+               cert = ${v.cert}
+
+             '')
+           cfg.servers)
+      }
+
+      ; ----- CLIENT CONFIGURATIONS -----
+      ${ lib.concatStringsSep "\n"
+           (lib.mapAttrsToList
+             (n: v: ''
+               [${n}]
+               client = yes
+               accept = ${v.accept}
+               connect = ${v.connect}
+               verifyChain = ${yesNo v.verifyChain}
+               verifyPeer = ${yesNo v.verifyPeer}
+               ${optionalString (v.CAPath != null) "CApath = ${v.CAPath}"}
+               ${optionalString (v.CAFile != null) "CAFile = ${v.CAFile}"}
+               ${optionalString (v.verifyHostname != null) "checkHost = ${v.verifyHostname}"}
+               OCSPaia = yes
+
+             '')
+           cfg.clients)
+      }
+    '';
+
+    systemd.services.stunnel = {
+      description = "stunnel TLS tunneling service";
+      after = [ "network.target" ];
+      wants = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ config.environment.etc."stunnel.cfg".source ];
+      serviceConfig = {
+        ExecStart = "${pkgs.stunnel}/bin/stunnel ${config.environment.etc."stunnel.cfg".source}";
+        Type = "forking";
+      };
+    };
+
+    meta.maintainers = with maintainers; [
+      # Server side
+      lschuermann
+      # Client side
+      das_j
+    ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/supplicant.nix b/nixpkgs/nixos/modules/services/networking/supplicant.nix
new file mode 100644
index 000000000000..b5b9989ce186
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/supplicant.nix
@@ -0,0 +1,249 @@
+{ config, lib, utils, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.networking.supplicant;
+
+  # We must escape interfaces due to the systemd interpretation
+  subsystemDevice = interface:
+    "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device";
+
+  serviceName = iface: "supplicant-${if (iface=="WLAN") then "wlan@" else (
+                                     if (iface=="LAN") then "lan@" else (
+                                     if (iface=="DBUS") then "dbus"
+                                     else (replaceChars [" "] ["-"] iface)))}";
+
+  # TODO: Use proper privilege separation for wpa_supplicant
+  supplicantService = iface: suppl:
+    let
+      deps = (if (iface=="WLAN"||iface=="LAN") then ["sys-subsystem-net-devices-%i.device"] else (
+             if (iface=="DBUS") then ["dbus.service"]
+             else (map subsystemDevice (splitString " " iface))))
+             ++ optional (suppl.bridge!="") (subsystemDevice suppl.bridge);
+
+      ifaceArg = concatStringsSep " -N " (map (i: "-i${i}") (splitString " " iface));
+      driverArg = optionalString (suppl.driver != null) "-D${suppl.driver}";
+      bridgeArg = optionalString (suppl.bridge!="") "-b${suppl.bridge}";
+      confFileArg = optionalString (suppl.configFile.path!=null) "-c${suppl.configFile.path}";
+      extraConfFile = pkgs.writeText "supplicant-extra-conf-${replaceChars [" "] ["-"] iface}" ''
+        ${optionalString suppl.userControlled.enable "ctrl_interface=DIR=${suppl.userControlled.socketDir} GROUP=${suppl.userControlled.group}"}
+        ${optionalString suppl.configFile.writable "update_config=1"}
+        ${suppl.extraConf}
+      '';
+    in
+      { description = "Supplicant ${iface}${optionalString (iface=="WLAN"||iface=="LAN") " %I"}";
+        wantedBy = [ "multi-user.target" ] ++ deps;
+        wants = [ "network.target" ];
+        bindsTo = deps;
+        after = deps;
+        before = [ "network.target" ];
+
+        path = [ pkgs.coreutils ];
+
+        preStart = ''
+          ${optionalString (suppl.configFile.path!=null) ''
+            touch -a ${suppl.configFile.path}
+            chmod 600 ${suppl.configFile.path}
+          ''}
+          ${optionalString suppl.userControlled.enable ''
+            if ! test -e ${suppl.userControlled.socketDir}; then
+                mkdir -m 0770 -p ${suppl.userControlled.socketDir}
+                chgrp ${suppl.userControlled.group} ${suppl.userControlled.socketDir}
+            fi
+
+            if test "$(stat --printf '%G' ${suppl.userControlled.socketDir})" != "${suppl.userControlled.group}"; then
+                echo "ERROR: bad ownership on ${suppl.userControlled.socketDir}" >&2
+                exit 1
+            fi
+          ''}
+        '';
+
+        serviceConfig.ExecStart = "${pkgs.wpa_supplicant}/bin/wpa_supplicant -s ${driverArg} ${confFileArg} -I${extraConfFile} ${bridgeArg} ${suppl.extraCmdArgs} ${if (iface=="WLAN"||iface=="LAN") then "-i%I" else (if (iface=="DBUS") then "-u" else ifaceArg)}";
+
+      };
+
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    networking.supplicant = mkOption {
+      type = with types; attrsOf (submodule {
+        options = {
+  
+          configFile = {
+  
+            path = mkOption {
+              type = types.nullOr types.path;
+              default = null;
+              example = literalExample "/etc/wpa_supplicant.conf";
+              description = ''
+                External <literal>wpa_supplicant.conf</literal> configuration file.
+                The configuration options defined declaratively within <literal>networking.supplicant</literal> have
+                precedence over options defined in <literal>configFile</literal>.
+              '';
+            };
+  
+            writable = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Whether the configuration file at <literal>configFile.path</literal> should be written to by
+                <literal>wpa_supplicant</literal>.
+              '';
+            };
+  
+          };
+  
+          extraConf = mkOption {
+            type = types.lines;
+            default = "";
+            example = ''
+              ap_scan=1
+              device_name=My-NixOS-Device
+              device_type=1-0050F204-1
+              driver_param=use_p2p_group_interface=1
+              disable_scan_offload=1
+              p2p_listen_reg_class=81
+              p2p_listen_channel=1
+              p2p_oper_reg_class=81
+              p2p_oper_channel=1
+              manufacturer=NixOS
+              model_name=NixOS_Unstable
+              model_number=2015
+            '';
+            description = ''
+              Configuration options for <literal>wpa_supplicant.conf</literal>.
+              Options defined here have precedence over options in <literal>configFile</literal>.
+              NOTE: Do not write sensitive data into <literal>extraConf</literal> as it will
+              be world-readable in the <literal>nix-store</literal>. For sensitive information
+              use the <literal>configFile</literal> instead.
+            '';
+          };
+  
+          extraCmdArgs = mkOption {
+            type = types.str;
+            default = "";
+            example = "-e/run/wpa_supplicant/entropy.bin";
+            description =
+              "Command line arguments to add when executing <literal>wpa_supplicant</literal>.";
+          };
+  
+          driver = mkOption {
+            type = types.nullOr types.str;
+            default = "nl80211,wext";
+            description = "Force a specific wpa_supplicant driver.";
+          };
+  
+          bridge = mkOption {
+            type = types.str;
+            default = "";
+            description = "Name of the bridge interface that wpa_supplicant should listen at.";
+          };
+  
+          userControlled = {
+  
+            enable = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
+                This is useful for laptop users that switch networks a lot and don't want
+                to depend on a large package such as NetworkManager just to pick nearby
+                access points.
+              '';
+            };
+  
+            socketDir = mkOption {
+              type = types.str;
+              default = "/run/wpa_supplicant";
+              description = "Directory of sockets for controlling wpa_supplicant.";
+            };
+  
+            group = mkOption {
+              type = types.str;
+              default = "wheel";
+              example = "network";
+              description = "Members of this group can control wpa_supplicant.";
+            };
+  
+          };
+        };
+      });
+
+      default = { };
+
+      example = literalExample ''
+        { "wlan0 wlan1" = {
+            configFile.path = "/etc/wpa_supplicant.conf";
+            userControlled.group = "network";
+            extraConf = '''
+              ap_scan=1
+              p2p_disabled=1
+            ''';
+            extraCmdArgs = "-u -W";
+            bridge = "br0";
+          };
+        }
+      '';
+
+      description = ''
+        Interfaces for which to start <command>wpa_supplicant</command>.
+        The supplicant is used to scan for and associate with wireless networks,
+        or to authenticate with 802.1x capable network switches.
+
+        The value of this option is an attribute set. Each attribute configures a
+        <command>wpa_supplicant</command> service, where the attribute name specifies
+        the name of the interface that <command>wpa_supplicant</command> operates on.
+        The attribute name can be a space separated list of interfaces.
+        The attribute names <literal>WLAN</literal>, <literal>LAN</literal> and <literal>DBUS</literal>
+        have a special meaning. <literal>WLAN</literal> and <literal>LAN</literal> are
+        configurations for universal <command>wpa_supplicant</command> service that is
+        started for each WLAN interface or for each LAN interface, respectively.
+        <literal>DBUS</literal> defines a device-unrelated <command>wpa_supplicant</command>
+        service that can be accessed through <literal>D-Bus</literal>.
+      '';
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf (cfg != {}) {
+
+    environment.systemPackages =  [ pkgs.wpa_supplicant ];
+
+    services.dbus.packages = [ pkgs.wpa_supplicant ];
+
+    systemd.services = mapAttrs' (n: v: nameValuePair (serviceName n) (supplicantService n v)) cfg;
+
+    services.udev.packages = [
+      (pkgs.writeTextFile {
+        name = "99-zzz-60-supplicant.rules";
+        destination = "/etc/udev/rules.d/99-zzz-60-supplicant.rules";
+        text = ''
+          ${flip (concatMapStringsSep "\n") (filter (n: n!="WLAN" && n!="LAN" && n!="DBUS") (attrNames cfg)) (iface:
+            flip (concatMapStringsSep "\n") (splitString " " iface) (i: ''
+              ACTION=="add", SUBSYSTEM=="net", ENV{INTERFACE}=="${i}", TAG+="systemd", ENV{SYSTEMD_WANTS}+="supplicant-${replaceChars [" "] ["-"] iface}.service", TAG+="SUPPLICANT_ASSIGNED"''))}
+
+          ${optionalString (hasAttr "WLAN" cfg) ''
+            ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", TAG!="SUPPLICANT_ASSIGNED", TAG+="systemd", PROGRAM="${pkgs.systemd}/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-wlan@$result.service"
+          ''}
+          ${optionalString (hasAttr "LAN" cfg) ''
+            ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="lan", TAG!="SUPPLICANT_ASSIGNED", TAG+="systemd", PROGRAM="${pkgs.systemd}/bin/systemd-escape -p %E{INTERFACE}", ENV{SYSTEMD_WANTS}+="supplicant-lan@$result.service"
+          ''}
+        '';
+      })];
+
+  };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/supybot.nix b/nixpkgs/nixos/modules/services/networking/supybot.nix
new file mode 100644
index 000000000000..dc9fb31ffd0b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/supybot.nix
@@ -0,0 +1,161 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg  = config.services.supybot;
+  isStateDirHome = hasPrefix "/home/" cfg.stateDir;
+  isStateDirVar = cfg.stateDir == "/var/lib/supybot";
+  pyEnv = pkgs.python3.withPackages (p: [ p.limnoria ] ++ (cfg.extraPackages p));
+in
+{
+  options = {
+
+    services.supybot = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable Supybot, an IRC bot (also known as Limnoria).";
+      };
+
+      stateDir = mkOption {
+        type = types.path;
+        default = if versionAtLeast config.system.stateVersion "20.09"
+          then "/var/lib/supybot"
+          else "/home/supybot";
+        defaultText = "/var/lib/supybot";
+        description = "The root directory, logs and plugins are stored here";
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        description = ''
+          Path to initial supybot config file. This can be generated by
+          running supybot-wizard.
+
+          Note: all paths should include the full path to the stateDir
+          directory (backup conf data logs logs/plugins plugins tmp web).
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        description = ''
+          Attribute set of additional plugins that will be symlinked to the
+          <filename>plugin</filename> subdirectory.
+
+          Please note that you still need to add the plugins to the config
+          file (or with <literal>!load</literal>) using their attribute name.
+        '';
+        example = literalExample ''
+          let
+            plugins = pkgs.fetchzip {
+              url = "https://github.com/ProgVal/Supybot-plugins/archive/57c2450c.zip";
+              sha256 = "077snf84ibnva3sbpzdfpfma6hcdw7dflwnhg6pw7mgnf0nd84qd";
+            };
+          in
+          {
+            Wikipedia = "''${plugins}/Wikipedia";
+            Decide = ./supy-decide;
+          }
+        '';
+      };
+
+      extraPackages = mkOption {
+        default = p: [];
+        description = ''
+          Extra Python packages available to supybot plugins. The
+          value must be a function which receives the attrset defined
+          in <varname>python3Packages</varname> as the sole argument.
+        '';
+        example = literalExample ''p: [ p.lxml p.requests ]'';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.python3Packages.limnoria ];
+
+    users.users.supybot = {
+      uid = config.ids.uids.supybot;
+      group = "supybot";
+      description = "Supybot IRC bot user";
+      home = cfg.stateDir;
+      isSystemUser = true;
+    };
+
+    users.groups.supybot = {
+      gid = config.ids.gids.supybot;
+    };
+
+    systemd.services.supybot = {
+      description = "Supybot, an IRC bot";
+      documentation = [ "https://limnoria.readthedocs.io/" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        # This needs to be created afresh every time
+        rm -f '${cfg.stateDir}/supybot.cfg.bak'
+      '';
+
+      serviceConfig = {
+        ExecStart = "${pyEnv}/bin/supybot ${cfg.stateDir}/supybot.cfg";
+        PIDFile = "/run/supybot.pid";
+        User = "supybot";
+        Group = "supybot";
+        UMask = "0007";
+        Restart = "on-abort";
+        StartLimitInterval = "5m";
+        StartLimitBurst = "1";
+
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateMounts = true;
+        PrivateTmp = true;
+        ProtectControlGroups = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+        RestrictSUIDSGID = true;
+        SystemCallArchitectures = "native";
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RemoveIPC = true;
+        ProtectHostname = true;
+        CapabilityBoundingSet = "";
+        ProtectSystem = "full";
+      }
+      // optionalAttrs isStateDirVar {
+        StateDirectory = "supybot";
+        ProtectSystem = "strict";
+      }
+      // optionalAttrs (!isStateDirHome) {
+        ProtectHome = true;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}'              0700 supybot supybot - -"
+      "d '${cfg.stateDir}/backup'       0750 supybot supybot - -"
+      "d '${cfg.stateDir}/conf'         0750 supybot supybot - -"
+      "d '${cfg.stateDir}/data'         0750 supybot supybot - -"
+      "d '${cfg.stateDir}/plugins'      0750 supybot supybot - -"
+      "d '${cfg.stateDir}/logs'         0750 supybot supybot - -"
+      "d '${cfg.stateDir}/logs/plugins' 0750 supybot supybot - -"
+      "d '${cfg.stateDir}/tmp'          0750 supybot supybot - -"
+      "d '${cfg.stateDir}/web'          0750 supybot supybot - -"
+      "L '${cfg.stateDir}/supybot.cfg'  -    -       -       - ${cfg.configFile}"
+    ]
+    ++ (flip mapAttrsToList cfg.plugins (name: dest:
+      "L+ '${cfg.stateDir}/plugins/${name}' - - - - ${dest}"
+    ));
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/syncplay.nix b/nixpkgs/nixos/modules/services/networking/syncplay.nix
new file mode 100644
index 000000000000..e3147c10502c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/syncplay.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.syncplay;
+
+  cmdArgs =
+    [ "--port" cfg.port ]
+    ++ optionals (cfg.salt != null) [ "--salt" cfg.salt ]
+    ++ optionals (cfg.certDir != null) [ "--tls" cfg.certDir ];
+
+in
+{
+  options = {
+    services.syncplay = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "If enabled, start the Syncplay server.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8999;
+        description = ''
+          TCP port to bind to.
+        '';
+      };
+
+      salt = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Salt to allow room operator passwords generated by this server
+          instance to still work when the server is restarted.
+        '';
+      };
+
+      certDir = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          TLS certificates directory to use for encryption. See
+          <link xlink:href="https://github.com/Syncplay/syncplay/wiki/TLS-support"/>.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "nobody";
+        description = ''
+          User to use when running Syncplay.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nogroup";
+        description = ''
+          Group to use when running Syncplay.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.syncplay = {
+      description = "Syncplay Service";
+      wantedBy    = [ "multi-user.target" ];
+      after       = [ "network-online.target "];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.syncplay}/bin/syncplay-server ${escapeShellArgs cmdArgs}";
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix b/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix
new file mode 100644
index 000000000000..f5ca63e78930
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/syncthing-relay.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.syncthing.relay;
+
+  dataDirectory = "/var/lib/syncthing-relay";
+
+  relayOptions =
+    [
+      "--keys=${dataDirectory}"
+      "--listen=${cfg.listenAddress}:${toString cfg.port}"
+      "--status-srv=${cfg.statusListenAddress}:${toString cfg.statusPort}"
+      "--provided-by=${escapeShellArg cfg.providedBy}"
+    ]
+    ++ optional (cfg.pools != null) "--pools=${escapeShellArg (concatStringsSep "," cfg.pools)}"
+    ++ optional (cfg.globalRateBps != null) "--global-rate=${toString cfg.globalRateBps}"
+    ++ optional (cfg.perSessionRateBps != null) "--per-session-rate=${toString cfg.perSessionRateBps}"
+    ++ cfg.extraOptions;
+in {
+  ###### interface
+
+  options.services.syncthing.relay = {
+    enable = mkEnableOption "Syncthing relay service";
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "";
+      example = "1.2.3.4";
+      description = ''
+        Address to listen on for relay traffic.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 22067;
+      description = ''
+        Port to listen on for relay traffic. This port should be added to
+        <literal>networking.firewall.allowedTCPPorts</literal>.
+      '';
+    };
+
+    statusListenAddress = mkOption {
+      type = types.str;
+      default = "";
+      example = "1.2.3.4";
+      description = ''
+        Address to listen on for serving the relay status API.
+      '';
+    };
+
+    statusPort = mkOption {
+      type = types.port;
+      default = 22070;
+      description = ''
+        Port to listen on for serving the relay status API. This port should be
+        added to <literal>networking.firewall.allowedTCPPorts</literal>.
+      '';
+    };
+
+    pools = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = ''
+        Relay pools to join. If null, uses the default global pool.
+      '';
+    };
+
+    providedBy = mkOption {
+      type = types.str;
+      default = "";
+      description = ''
+        Human-readable description of the provider of the relay (you).
+      '';
+    };
+
+    globalRateBps = mkOption {
+      type = types.nullOr types.ints.positive;
+      default = null;
+      description = ''
+        Global bandwidth rate limit in bytes per second.
+      '';
+    };
+
+    perSessionRateBps = mkOption {
+      type = types.nullOr types.ints.positive;
+      default = null;
+      description = ''
+        Per session bandwidth rate limit in bytes per second.
+      '';
+    };
+
+    extraOptions = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra command line arguments to pass to strelaysrv.
+      '';
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.syncthing-relay = {
+      description = "Syncthing relay service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = baseNameOf dataDirectory;
+
+        Restart = "on-failure";
+        ExecStart = "${pkgs.syncthing-relay}/bin/strelaysrv ${concatStringsSep " " relayOptions}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/syncthing.nix b/nixpkgs/nixos/modules/services/networking/syncthing.nix
new file mode 100644
index 000000000000..e717d78feed5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/syncthing.nix
@@ -0,0 +1,528 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.syncthing;
+  defaultUser = "syncthing";
+
+  devices = mapAttrsToList (name: device: {
+    deviceID = device.id;
+    inherit (device) name addresses introducer;
+  }) cfg.declarative.devices;
+
+  folders = mapAttrsToList ( _: folder: {
+    inherit (folder) path id label type;
+    devices = map (device: { deviceId = cfg.declarative.devices.${device}.id; }) folder.devices;
+    rescanIntervalS = folder.rescanInterval;
+    fsWatcherEnabled = folder.watch;
+    fsWatcherDelayS = folder.watchDelay;
+    ignorePerms = folder.ignorePerms;
+    versioning = folder.versioning;
+  }) (filterAttrs (
+    _: folder:
+    folder.enable
+  ) cfg.declarative.folders);
+
+  # get the api key by parsing the config.xml
+  getApiKey = pkgs.writers.writeDash "getAPIKey" ''
+    ${pkgs.libxml2}/bin/xmllint \
+      --xpath 'string(configuration/gui/apikey)'\
+      ${cfg.configDir}/config.xml
+  '';
+
+  updateConfig = pkgs.writers.writeDash "merge-syncthing-config" ''
+    set -efu
+    # wait for syncthing port to open
+    until ${pkgs.curl}/bin/curl -Ss ${cfg.guiAddress} -o /dev/null; do
+      sleep 1
+    done
+
+    API_KEY=$(${getApiKey})
+    OLD_CFG=$(${pkgs.curl}/bin/curl -Ss \
+      -H "X-API-Key: $API_KEY" \
+      ${cfg.guiAddress}/rest/system/config)
+
+    # generate the new config by merging with the nixos config options
+    NEW_CFG=$(echo "$OLD_CFG" | ${pkgs.jq}/bin/jq -s '.[] as $in | $in * {
+      "devices": (${builtins.toJSON devices}${optionalString (! cfg.declarative.overrideDevices) " + $in.devices"}),
+      "folders": (${builtins.toJSON folders}${optionalString (! cfg.declarative.overrideFolders) " + $in.folders"})
+    }')
+
+    # POST the new config to syncthing
+    echo "$NEW_CFG" | ${pkgs.curl}/bin/curl -Ss \
+      -H "X-API-Key: $API_KEY" \
+      ${cfg.guiAddress}/rest/system/config -d @-
+
+    # restart syncthing after sending the new config
+    ${pkgs.curl}/bin/curl -Ss \
+      -H "X-API-Key: $API_KEY" \
+      -X POST \
+      ${cfg.guiAddress}/rest/system/restart
+  '';
+in {
+  ###### interface
+  options = {
+    services.syncthing = {
+
+      enable = mkEnableOption ''
+        Syncthing - the self-hosted open-source alternative
+        to Dropbox and Bittorrent Sync. Initial interface will be
+        available on http://127.0.0.1:8384/.
+      '';
+
+      declarative = {
+        cert = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Path to users cert.pem file, will be copied into the syncthing's
+            <literal>configDir</literal>
+          '';
+        };
+
+        key = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Path to users key.pem file, will be copied into the syncthing's
+            <literal>configDir</literal>
+          '';
+        };
+
+        overrideDevices = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to delete the devices which are not configured via the
+            <literal>declarative.devices</literal> option.
+            If set to false, devices added via the webinterface will
+            persist but will have to be deleted manually.
+          '';
+        };
+
+        devices = mkOption {
+          default = {};
+          description = ''
+            Peers/devices which syncthing should communicate with.
+          '';
+          example = {
+            bigbox = {
+              id = "7CFNTQM-IMTJBHJ-3UWRDIU-ZGQJFR6-VCXZ3NB-XUH3KZO-N52ITXR-LAIYUAU";
+              addresses = [ "tcp://192.168.0.10:51820" ];
+            };
+          };
+          type = types.attrsOf (types.submodule ({ name, ... }: {
+            options = {
+
+              name = mkOption {
+                type = types.str;
+                default = name;
+                description = ''
+                  Name of the device
+                '';
+              };
+
+              addresses = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = ''
+                  The addresses used to connect to the device.
+                  If this is let empty, dynamic configuration is attempted
+                '';
+              };
+
+              id = mkOption {
+                type = types.str;
+                description = ''
+                  The id of the other peer, this is mandatory. It's documented at
+                  https://docs.syncthing.net/dev/device-ids.html
+                '';
+              };
+
+              introducer = mkOption {
+                type = types.bool;
+                default = false;
+                description = ''
+                  If the device should act as an introducer and be allowed
+                  to add folders on this computer.
+                '';
+              };
+
+            };
+          }));
+        };
+
+        overrideFolders = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to delete the folders which are not configured via the
+            <literal>declarative.folders</literal> option.
+            If set to false, folders added via the webinterface will persist
+            but will have to be deleted manually.
+          '';
+        };
+
+        folders = mkOption {
+          default = {};
+          description = ''
+            folders which should be shared by syncthing.
+          '';
+          example = literalExample ''
+            {
+              "/home/user/sync" = {
+                id = "syncme";
+                devices = [ "bigbox" ];
+              };
+            }
+          '';
+          type = types.attrsOf (types.submodule ({ name, ... }: {
+            options = {
+
+              enable = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  share this folder.
+                  This option is useful when you want to define all folders
+                  in one place, but not every machine should share all folders.
+                '';
+              };
+
+              path = mkOption {
+                type = types.str;
+                default = name;
+                description = ''
+                  The path to the folder which should be shared.
+                '';
+              };
+
+              id = mkOption {
+                type = types.str;
+                default = name;
+                description = ''
+                  The id of the folder. Must be the same on all devices.
+                '';
+              };
+
+              label = mkOption {
+                type = types.str;
+                default = name;
+                description = ''
+                  The label of the folder.
+                '';
+              };
+
+              devices = mkOption {
+                type = types.listOf types.str;
+                default = [];
+                description = ''
+                  The devices this folder should be shared with. Must be defined
+                  in the <literal>declarative.devices</literal> attribute.
+                '';
+              };
+
+              versioning = mkOption {
+                default = null;
+                description = ''
+                  How to keep changed/deleted files with syncthing.
+                  There are 4 different types of versioning with different parameters.
+                  See https://docs.syncthing.net/users/versioning.html
+                '';
+                example = [
+                  {
+                    versioning = {
+                      type = "simple";
+                      params.keep = "10";
+                    };
+                  }
+                  {
+                    versioning = {
+                      type = "trashcan";
+                      params.cleanoutDays = "1000";
+                    };
+                  }
+                  {
+                    versioning = {
+                      type = "staggered";
+                      params = {
+                        cleanInterval = "3600";
+                        maxAge = "31536000";
+                        versionsPath = "/syncthing/backup";
+                      };
+                    };
+                  }
+                  {
+                    versioning = {
+                      type = "external";
+                      params.versionsPath = pkgs.writers.writeBash "backup" ''
+                        folderpath="$1"
+                        filepath="$2"
+                        rm -rf "$folderpath/$filepath"
+                      '';
+                    };
+                  }
+                ];
+                type = with types; nullOr (submodule {
+                  options = {
+                    type = mkOption {
+                      type = enum [ "external" "simple" "staggered" "trashcan" ];
+                      description = ''
+                        Type of versioning.
+                        See https://docs.syncthing.net/users/versioning.html
+                      '';
+                    };
+                    params = mkOption {
+                      type = attrsOf (either str path);
+                      description = ''
+                        Parameters for versioning. Structure depends on versioning.type.
+                        See https://docs.syncthing.net/users/versioning.html
+                      '';
+                    };
+                  };
+                });
+              };
+
+
+
+              rescanInterval = mkOption {
+                type = types.int;
+                default = 3600;
+                description = ''
+                  How often the folders should be rescaned for changes.
+                '';
+              };
+
+              type = mkOption {
+                type = types.enum [ "sendreceive" "sendonly" "receiveonly" ];
+                default = "sendreceive";
+                description = ''
+                  Whether to send only changes from this folder, only receive them
+                  or propagate both.
+                '';
+              };
+
+              watch = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  Whether the folder should be watched for changes by inotify.
+                '';
+              };
+
+              watchDelay = mkOption {
+                type = types.int;
+                default = 10;
+                description = ''
+                  The delay after an inotify event is triggered.
+                '';
+              };
+
+              ignorePerms = mkOption {
+                type = types.bool;
+                default = true;
+                description = ''
+                  Whether to propagate permission changes.
+                '';
+              };
+
+            };
+          }));
+        };
+      };
+
+      guiAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8384";
+        description = ''
+          Address to serve the GUI.
+        '';
+      };
+
+      systemService = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Auto launch Syncthing as a system service.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = defaultUser;
+        description = ''
+          Syncthing will be run under this user (user will be created if it doesn't exist.
+          This can be your user name).
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = defaultUser;
+        description = ''
+          Syncthing will be run under this group (group will not be created if it doesn't exist.
+          This can be your user name).
+        '';
+      };
+
+      all_proxy = mkOption {
+        type = with types; nullOr str;
+        default = null;
+        example = "socks5://address.com:1234";
+        description = ''
+          Overwrites all_proxy environment variable for the syncthing process to
+          the given value. This is normaly used to let relay client connect
+          through SOCKS5 proxy server.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/syncthing";
+        description = ''
+          Path where synced directories will exist.
+        '';
+      };
+
+      configDir = mkOption {
+        type = types.path;
+        description = ''
+          Path where the settings and keys will exist.
+        '';
+        default =
+          let
+            nixos = config.system.stateVersion;
+            cond  = versionAtLeast nixos "19.03";
+          in cfg.dataDir + (optionalString cond "/.config/syncthing");
+      };
+
+      openDefaultPorts = mkOption {
+        type = types.bool;
+        default = false;
+        example = literalExample "true";
+        description = ''
+          Open the default ports in the firewall:
+            - TCP 22000 for transfers
+            - UDP 21027 for discovery
+          If multiple users are running syncthing on this machine, you will need to manually open a set of ports for each instance and leave this disabled.
+          Alternatively, if are running only a single instance on this machine using the default ports, enable this.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.syncthing;
+        defaultText = "pkgs.syncthing";
+        example = literalExample "pkgs.syncthing";
+        description = ''
+          Syncthing package to use.
+        '';
+      };
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule ["services" "syncthing" "useInotify"] ''
+      This option was removed because syncthing now has the inotify functionality included under the name "fswatcher".
+      It can be enabled on a per-folder basis through the webinterface.
+    '')
+  ];
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    networking.firewall = mkIf cfg.openDefaultPorts {
+      allowedTCPPorts = [ 22000 ];
+      allowedUDPPorts = [ 21027 ];
+    };
+
+    systemd.packages = [ pkgs.syncthing ];
+
+    users.users = mkIf (cfg.systemService && cfg.user == defaultUser) {
+      ${defaultUser} =
+        { group = cfg.group;
+          home  = cfg.dataDir;
+          createHome = true;
+          uid = config.ids.uids.syncthing;
+          description = "Syncthing daemon user";
+        };
+    };
+
+    users.groups = mkIf (cfg.systemService && cfg.group == defaultUser) {
+      ${defaultUser}.gid =
+        config.ids.gids.syncthing;
+    };
+
+    systemd.services = {
+      syncthing = mkIf cfg.systemService {
+        description = "Syncthing service";
+        after = [ "network.target" ];
+        environment = {
+          STNORESTART = "yes";
+          STNOUPGRADE = "yes";
+          inherit (cfg) all_proxy;
+        } // config.networking.proxy.envVars;
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          Restart = "on-failure";
+          SuccessExitStatus = "2 3 4";
+          RestartForceExitStatus="3 4";
+          User = cfg.user;
+          Group = cfg.group;
+          ExecStartPre = mkIf (cfg.declarative.cert != null || cfg.declarative.key != null)
+            "+${pkgs.writers.writeBash "syncthing-copy-keys" ''
+              install -dm700 -o ${cfg.user} -g ${cfg.group} ${cfg.configDir}
+              ${optionalString (cfg.declarative.cert != null) ''
+                install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem
+              ''}
+              ${optionalString (cfg.declarative.key != null) ''
+                install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.key} ${cfg.configDir}/key.pem
+              ''}
+            ''}"
+          ;
+          ExecStart = ''
+            ${cfg.package}/bin/syncthing \
+              -no-browser \
+              -gui-address=${cfg.guiAddress} \
+              -home=${cfg.configDir}
+          '';
+          MemoryDenyWriteExecute = true;
+          NoNewPrivileges = true;
+          PrivateDevices = true;
+          PrivateMounts = true;
+          PrivateTmp = true;
+          PrivateUsers = true;
+          ProtectControlGroups = true;
+          ProtectHostname = true;
+          ProtectKernelModules = true;
+          ProtectKernelTunables = true;
+          RestrictNamespaces = true;
+          RestrictRealtime = true;
+          RestrictSUIDSGID = true;
+          CapabilityBoundingSet = [
+            "~CAP_SYS_PTRACE" "~CAP_SYS_ADMIN"
+            "~CAP_SETGID" "~CAP_SETUID" "~CAP_SETPCAP"
+            "~CAP_SYS_TIME" "~CAP_KILL"
+          ];
+        };
+      };
+      syncthing-init = mkIf (
+        cfg.declarative.devices != {} || cfg.declarative.folders != {}
+      ) {
+        after = [ "syncthing.service" ];
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig = {
+          User = cfg.user;
+          RemainAfterExit = true;
+          Type = "oneshot";
+          ExecStart = updateConfig;
+        };
+      };
+
+      syncthing-resume = {
+        wantedBy = [ "suspend.target" ];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tailscale.nix b/nixpkgs/nixos/modules/services/networking/tailscale.nix
new file mode 100644
index 000000000000..4d6aeb75ebd1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tailscale.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.tailscale;
+in {
+  meta.maintainers = with maintainers; [ danderson mbaillie ];
+
+  options.services.tailscale = {
+    enable = mkEnableOption "Tailscale client daemon";
+
+    port = mkOption {
+      type = types.port;
+      default = 41641;
+      description = "The port to listen on for tunnel traffic (0=autoselect).";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.tailscale = {
+      description = "Tailscale client daemon";
+
+      after = [ "network-pre.target" ];
+      wants = [ "network-pre.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      unitConfig = {
+        StartLimitIntervalSec = 0;
+        StartLimitBurst = 0;
+      };
+
+      serviceConfig = {
+        ExecStart =
+          "${pkgs.tailscale}/bin/tailscaled --port ${toString cfg.port}";
+
+        RuntimeDirectory = "tailscale";
+        RuntimeDirectoryMode = 755;
+
+        StateDirectory = "tailscale";
+        StateDirectoryMode = 750;
+
+        CacheDirectory = "tailscale";
+        CacheDirectoryMode = 750;
+
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tcpcrypt.nix b/nixpkgs/nixos/modules/services/networking/tcpcrypt.nix
new file mode 100644
index 000000000000..5a91054e1668
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tcpcrypt.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.networking.tcpcrypt;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    networking.tcpcrypt.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable opportunistic TCP encryption. If the other end
+        speaks Tcpcrypt, then your traffic will be encrypted; otherwise
+        it will be sent in clear text. Thus, Tcpcrypt alone provides no
+        guarantees -- it is best effort. If, however, a Tcpcrypt
+        connection is successful and any attackers that exist are
+        passive, then Tcpcrypt guarantees privacy.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.tcpcryptd = {
+      uid = config.ids.uids.tcpcryptd;
+      description = "tcpcrypt daemon user";
+    };
+
+    systemd.services.tcpcrypt = {
+      description = "tcpcrypt";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = [ pkgs.iptables pkgs.tcpcrypt pkgs.procps ];
+
+      preStart = ''
+        mkdir -p /run/tcpcryptd
+        chown tcpcryptd /run/tcpcryptd
+        sysctl -n net.ipv4.tcp_ecn > /run/tcpcryptd/pre-tcpcrypt-ecn-state
+        sysctl -w net.ipv4.tcp_ecn=0
+
+        iptables -t raw -N nixos-tcpcrypt
+        iptables -t raw -A nixos-tcpcrypt -p tcp -m mark --mark 0x0/0x10 -j NFQUEUE --queue-num 666
+        iptables -t raw -I PREROUTING -j nixos-tcpcrypt
+
+        iptables -t mangle -N nixos-tcpcrypt
+        iptables -t mangle -A nixos-tcpcrypt -p tcp -m mark --mark 0x0/0x10 -j NFQUEUE --queue-num 666
+        iptables -t mangle -I POSTROUTING -j nixos-tcpcrypt
+      '';
+
+      script = "tcpcryptd -x 0x10";
+
+      postStop = ''
+        if [ -f /run/tcpcryptd/pre-tcpcrypt-ecn-state ]; then
+          sysctl -w net.ipv4.tcp_ecn=$(cat /run/tcpcryptd/pre-tcpcrypt-ecn-state)
+        fi
+
+        iptables -t mangle -D POSTROUTING -j nixos-tcpcrypt || true
+        iptables -t raw -D PREROUTING -j nixos-tcpcrypt || true
+
+        iptables -t raw -F nixos-tcpcrypt || true
+        iptables -t raw -X nixos-tcpcrypt || true
+
+        iptables -t mangle -F nixos-tcpcrypt || true
+        iptables -t mangle -X nixos-tcpcrypt || true
+      '';
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/teamspeak3.nix b/nixpkgs/nixos/modules/services/networking/teamspeak3.nix
new file mode 100644
index 000000000000..fadb32dcd777
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/teamspeak3.nix
@@ -0,0 +1,142 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  ts3 = pkgs.teamspeak_server;
+  cfg = config.services.teamspeak3;
+  user = "teamspeak";
+  group = "teamspeak";
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.teamspeak3 = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run the Teamspeak3 voice communication server daemon.
+        '';
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/teamspeak3-server";
+        description = ''
+          Directory to store TS3 database and other state/data files.
+        '';
+      };
+
+      logPath = mkOption {
+        type = types.path;
+        default = "/var/log/teamspeak3-server/";
+        description = ''
+          Directory to store log files in.
+        '';
+      };
+
+      voiceIP = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "0.0.0.0";
+        description = ''
+          IP on which the server instance will listen for incoming voice connections. Defaults to any IP.
+        '';
+      };
+
+      defaultVoicePort = mkOption {
+        type = types.int;
+        default = 9987;
+        description = ''
+          Default UDP port for clients to connect to virtual servers - used for first virtual server, subsequent ones will open on incrementing port numbers by default.
+        '';
+      };
+
+      fileTransferIP = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "0.0.0.0";
+        description = ''
+          IP on which the server instance will listen for incoming file transfer connections. Defaults to any IP.
+        '';
+      };
+
+      fileTransferPort = mkOption {
+        type = types.int;
+        default = 30033;
+        description = ''
+          TCP port opened for file transfers.
+        '';
+      };
+
+      queryIP = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "0.0.0.0";
+        description = ''
+          IP on which the server instance will listen for incoming ServerQuery connections. Defaults to any IP.
+        '';
+      };
+
+      queryPort = mkOption {
+        type = types.int;
+        default = 10011;
+        description = ''
+          TCP port opened for ServerQuery connections.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    users.users.teamspeak = {
+      description = "Teamspeak3 voice communication server daemon";
+      group = group;
+      uid = config.ids.uids.teamspeak;
+      home = cfg.dataDir;
+      createHome = true;
+    };
+
+    users.groups.teamspeak = {
+      gid = config.ids.gids.teamspeak;
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logPath}' - ${user} ${group} - -"
+    ];
+
+    systemd.services.teamspeak3-server = {
+      description = "Teamspeak3 voice communication server daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = ''
+          ${ts3}/bin/ts3server \
+            dbsqlpath=${ts3}/lib/teamspeak/sql/ logpath=${cfg.logPath} \
+            ${optionalString (cfg.voiceIP != null) "voice_ip=${cfg.voiceIP}"} \
+            default_voice_port=${toString cfg.defaultVoicePort} \
+            ${optionalString (cfg.fileTransferIP != null) "filetransfer_ip=${cfg.fileTransferIP}"} \
+            filetransfer_port=${toString cfg.fileTransferPort} \
+            ${optionalString (cfg.queryIP != null) "query_ip=${cfg.queryIP}"} \
+            query_port=${toString cfg.queryPort} license_accepted=1
+        '';
+        WorkingDirectory = cfg.dataDir;
+        User = user;
+        Group = group;
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ arobyn ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tedicross.nix b/nixpkgs/nixos/modules/services/networking/tedicross.nix
new file mode 100644
index 000000000000..0716975f594a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tedicross.nix
@@ -0,0 +1,100 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  dataDir = "/var/lib/tedicross";
+  cfg = config.services.tedicross;
+  configJSON = pkgs.writeText "tedicross-settings.json" (builtins.toJSON cfg.config);
+  configYAML = pkgs.runCommand "tedicross-settings.yaml" { preferLocalBuild = true; } ''
+    ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+  '';
+
+in {
+  options = {
+    services.tedicross = {
+      enable = mkEnableOption "the TediCross Telegram-Discord bridge service";
+
+      config = mkOption {
+        type = types.attrs;
+        # from https://github.com/TediCross/TediCross/blob/master/example.settings.yaml
+        example = literalExample ''
+          {
+            telegram = {
+              useFirstNameInsteadOfUsername = false;
+              colonAfterSenderName = false;
+              skipOldMessages = true;
+              sendEmojiWithStickers = true;
+            };
+            discord = {
+              useNickname = false;
+              skipOldMessages = true;
+              displayTelegramReplies = "embed";
+              replyLength = 100;
+            };
+            bridges = [
+              {
+                name = "Default bridge";
+                direction = "both";
+                telegram = {
+                  chatId = -123456789;
+                  relayJoinMessages = true;
+                  relayLeaveMessages = true;
+                  sendUsernames = true;
+                  ignoreCommands = true;
+                };
+                discord = {
+                  serverId = "DISCORD_SERVER_ID";
+                  channelId = "DISCORD_CHANNEL_ID";
+                  relayJoinMessages = true;
+                  relayLeaveMessages = true;
+                  sendUsernames = true;
+                  crossDeleteOnTelegram = true;
+                };
+              }
+            ];
+
+            debug = false;
+          }
+        '';
+        description = ''
+          <filename>settings.yaml</filename> configuration as a Nix attribute set.
+          Secret tokens should be specified using <option>environmentFile</option>
+          instead of this world-readable file.
+        '';
+      };
+
+      environmentFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          File containing environment variables to be passed to the TediCross service,
+          in which secret tokens can be specified securely using the
+          <literal>TELEGRAM_BOT_TOKEN</literal> and <literal>DISCORD_BOT_TOKEN</literal>
+          keys.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # from https://github.com/TediCross/TediCross/blob/master/guides/autostart/Linux.md
+    systemd.services.tedicross = {
+      description = "TediCross Telegram-Discord bridge service";
+      wantedBy = [ "multi-user.target" ];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${pkgs.nodePackages.tedicross}/bin/tedicross --config='${configYAML}' --data-dir='${dataDir}'";
+        Restart = "always";
+        DynamicUser = true;
+        StateDirectory = baseNameOf dataDir;
+        EnvironmentFile = cfg.environmentFile;
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ pacien ];
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/tftpd.nix b/nixpkgs/nixos/modules/services/networking/tftpd.nix
new file mode 100644
index 000000000000..c9c0a2b321d5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tftpd.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.tftpd.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable tftpd, a Trivial File Transfer Protocol server.
+        The server will be run as an xinetd service.
+      '';
+    };
+
+    services.tftpd.path = mkOption {
+      type = types.path;
+      default = "/srv/tftp";
+      description = ''
+        Where the tftp server files are stored.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.tftpd.enable {
+
+    services.xinetd.enable = true;
+
+    services.xinetd.services = singleton
+      { name = "tftp";
+        protocol = "udp";
+        server = "${pkgs.netkittftp}/sbin/in.tftpd";
+        serverArgs = "${config.services.tftpd.path}";
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/thelounge.nix b/nixpkgs/nixos/modules/services/networking/thelounge.nix
new file mode 100644
index 000000000000..a1b06703484b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/thelounge.nix
@@ -0,0 +1,75 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.thelounge;
+  dataDir = "/var/lib/thelounge";
+  configJsData = "module.exports = " + builtins.toJSON (
+    { private = cfg.private; port = cfg.port; } // cfg.extraConfig
+  );
+in {
+  options.services.thelounge = {
+    enable = mkEnableOption "The Lounge web IRC client";
+
+    private = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Make your The Lounge instance private. You will need to configure user
+        accounts by using the (<command>thelounge</command>) command or by adding
+        entries in <filename>${dataDir}/users</filename>. You might need to restart
+        The Lounge after making changes to the state directory.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 9000;
+      description = "TCP port to listen on for http connections.";
+    };
+
+    extraConfig = mkOption {
+      default = {};
+      type = types.attrs;
+      example = literalExample ''{
+        reverseProxy = true;
+        defaults = {
+          name = "Your Network";
+          host = "localhost";
+          port = 6697;
+        };
+      }'';
+      description = ''
+        The Lounge's <filename>config.js</filename> contents as attribute set (will be
+        converted to JSON to generate the configuration file).
+
+        The options defined here will be merged to the default configuration file.
+        Note: In case of duplicate configuration, options from <option>extraConfig</option> have priority.
+
+        Documentation: <link xlink:href="https://thelounge.chat/docs/server/configuration" />
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.thelounge = {
+      description = "thelounge service user";
+      group = "thelounge";
+      isSystemUser = true;
+    };
+    users.groups.thelounge = {};
+    systemd.services.thelounge = {
+      description = "The Lounge web IRC client";
+      wantedBy = [ "multi-user.target" ];
+      preStart = "ln -sf ${pkgs.writeText "config.js" configJsData} ${dataDir}/config.js";
+      serviceConfig = {
+        User = "thelounge";
+        StateDirectory = baseNameOf dataDir;
+        ExecStart = "${pkgs.thelounge}/bin/thelounge start";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.thelounge ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tinc.nix b/nixpkgs/nixos/modules/services/networking/tinc.nix
new file mode 100644
index 000000000000..e98aafc20937
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tinc.nix
@@ -0,0 +1,212 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.tinc;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.tinc = {
+
+      networks = mkOption {
+        default = { };
+        type = with types; attrsOf (submodule {
+          options = {
+
+            extraConfig = mkOption {
+              default = "";
+              type = types.lines;
+              description = ''
+                Extra lines to add to the tinc service configuration file.
+              '';
+            };
+
+            name = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                The name of the node which is used as an identifier when communicating
+                with the remote nodes in the mesh. If null then the hostname of the system
+                is used to derive a name (note that tinc may replace non-alphanumeric characters in
+                hostnames by underscores).
+              '';
+            };
+
+            ed25519PrivateKeyFile = mkOption {
+              default = null;
+              type = types.nullOr types.path;
+              description = ''
+                Path of the private ed25519 keyfile.
+              '';
+            };
+
+            debugLevel = mkOption {
+              default = 0;
+              type = types.addCheck types.int (l: l >= 0 && l <= 5);
+              description = ''
+                The amount of debugging information to add to the log. 0 means little
+                logging while 5 is the most logging. <command>man tincd</command> for
+                more details.
+              '';
+            };
+
+            hosts = mkOption {
+              default = { };
+              type = types.attrsOf types.lines;
+              description = ''
+                The name of the host in the network as well as the configuration for that host.
+                This name should only contain alphanumerics and underscores.
+              '';
+            };
+
+            interfaceType = mkOption {
+              default = "tun";
+              type = types.enum [ "tun" "tap" ];
+              description = ''
+                The type of virtual interface used for the network connection
+              '';
+            };
+
+            listenAddress = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                The ip address to listen on for incoming connections.
+              '';
+            };
+
+            bindToAddress = mkOption {
+              default = null;
+              type = types.nullOr types.str;
+              description = ''
+                The ip address to bind to (both listen on and send packets from).
+              '';
+            };
+
+            package = mkOption {
+              type = types.package;
+              default = pkgs.tinc_pre;
+              defaultText = "pkgs.tinc_pre";
+              description = ''
+                The package to use for the tinc daemon's binary.
+              '';
+            };
+
+            chroot = mkOption {
+              default = true;
+              type = types.bool;
+              description = ''
+                Change process root directory to the directory where the config file is located (/etc/tinc/netname/), for added security.
+                The chroot is performed after all the initialization is done, after writing pid files and opening network sockets.
+
+                Note that tinc can't run scripts anymore (such as tinc-down or host-up), unless it is setup to be runnable inside chroot environment.
+              '';
+            };
+          };
+        });
+
+        description = ''
+          Defines the tinc networks which will be started.
+          Each network invokes a different daemon.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf (cfg.networks != { }) {
+
+    environment.etc = fold (a: b: a // b) { }
+      (flip mapAttrsToList cfg.networks (network: data:
+        flip mapAttrs' data.hosts (host: text: nameValuePair
+          ("tinc/${network}/hosts/${host}")
+          ({ mode = "0644"; user = "tinc.${network}"; inherit text; })
+        ) // {
+          "tinc/${network}/tinc.conf" = {
+            mode = "0444";
+            text = ''
+              Name = ${if data.name == null then "$HOST" else data.name}
+              DeviceType = ${data.interfaceType}
+              ${optionalString (data.ed25519PrivateKeyFile != null) "Ed25519PrivateKeyFile = ${data.ed25519PrivateKeyFile}"}
+              ${optionalString (data.listenAddress != null) "ListenAddress = ${data.listenAddress}"}
+              ${optionalString (data.bindToAddress != null) "BindToAddress = ${data.bindToAddress}"}
+              Interface = tinc.${network}
+              ${data.extraConfig}
+            '';
+          };
+        }
+      ));
+
+    systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
+      ("tinc.${network}")
+      ({
+        description = "Tinc Daemon - ${network}";
+        wantedBy = [ "multi-user.target" ];
+        path = [ data.package ];
+        restartTriggers = [ config.environment.etc."tinc/${network}/tinc.conf".source ];
+        serviceConfig = {
+          Type = "simple";
+          Restart = "always";
+          RestartSec = "3";
+          ExecStart = "${data.package}/bin/tincd -D -U tinc.${network} -n ${network} ${optionalString (data.chroot) "-R"} --pidfile /run/tinc.${network}.pid -d ${toString data.debugLevel}";
+        };
+        preStart = ''
+          mkdir -p /etc/tinc/${network}/hosts
+          chown tinc.${network} /etc/tinc/${network}/hosts
+          mkdir -p /etc/tinc/${network}/invitations
+          chown tinc.${network} /etc/tinc/${network}/invitations
+
+          # Determine how we should generate our keys
+          if type tinc >/dev/null 2>&1; then
+            # Tinc 1.1+ uses the tinc helper application for key generation
+          ${if data.ed25519PrivateKeyFile != null then "  # Keyfile managed by nix" else ''
+            # Prefer ED25519 keys (only in 1.1+)
+            [ -f "/etc/tinc/${network}/ed25519_key.priv" ] || tinc -n ${network} generate-ed25519-keys
+          ''}
+            # Otherwise use RSA keys
+            [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tinc -n ${network} generate-rsa-keys 4096
+          else
+            # Tinc 1.0 uses the tincd application
+            [ -f "/etc/tinc/${network}/rsa_key.priv" ] || tincd -n ${network} -K 4096
+          fi
+        '';
+      })
+    );
+
+    environment.systemPackages = let
+      cli-wrappers = pkgs.stdenv.mkDerivation {
+        name = "tinc-cli-wrappers";
+        buildInputs = [ pkgs.makeWrapper ];
+        buildCommand = ''
+          mkdir -p $out/bin
+          ${concatStringsSep "\n" (mapAttrsToList (network: data:
+            optionalString (versionAtLeast data.package.version "1.1pre") ''
+              makeWrapper ${data.package}/bin/tinc "$out/bin/tinc.${network}" \
+                --add-flags "--pidfile=/run/tinc.${network}.pid" \
+                --add-flags "--config=/etc/tinc/${network}"
+            '') cfg.networks)}
+        '';
+      };
+    in [ cli-wrappers ];
+
+    users.users = flip mapAttrs' cfg.networks (network: _:
+      nameValuePair ("tinc.${network}") ({
+        description = "Tinc daemon user for ${network}";
+        isSystemUser = true;
+      })
+    );
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tinydns.nix b/nixpkgs/nixos/modules/services/networking/tinydns.nix
new file mode 100644
index 000000000000..79507b2ebcdd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tinydns.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  ###### interface
+
+  options = {
+    services.tinydns = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to run the tinydns dns server";
+      };
+
+      data = mkOption {
+        type = types.lines;
+        default = "";
+        description = "The DNS data to serve, in the format described by tinydns-data(8)";
+      };
+
+      ip = mkOption {
+        default = "0.0.0.0";
+        type = types.str;
+        description = "IP address on which to listen for connections";
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf config.services.tinydns.enable {
+    environment.systemPackages = [ pkgs.djbdns ];
+
+    users.users.tinydns.isSystemUser = true;
+
+    systemd.services.tinydns = {
+      description = "djbdns tinydns server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = with pkgs; [ daemontools djbdns ];
+      preStart = ''
+        rm -rf /var/lib/tinydns
+        tinydns-conf tinydns tinydns /var/lib/tinydns ${config.services.tinydns.ip}
+        cd /var/lib/tinydns/root/
+        ln -sf ${pkgs.writeText "tinydns-data" config.services.tinydns.data} data
+        tinydns-data
+      '';
+      script = ''
+        cd /var/lib/tinydns
+        exec ./run
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix b/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix
new file mode 100644
index 000000000000..f88e34827d00
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tox-bootstrapd.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  home = "/var/lib/tox-bootstrapd";
+  PIDFile = "${home}/pid";
+
+  pkg = pkgs.libtoxcore;
+  cfg = config.services.toxBootstrapd;
+  cfgFile = builtins.toFile "tox-bootstrapd.conf"
+    ''
+      port = ${toString cfg.port}
+      keys_file_path = "${home}/keys"
+      pid_file_path = "${PIDFile}"
+      ${cfg.extraConfig}
+    '';
+in
+{
+  options =
+    { services.toxBootstrapd =
+        { enable = mkOption {
+            type = types.bool;
+            default = false;
+            description =
+              ''
+                Whether to enable the Tox DHT bootstrap daemon.
+              '';
+          };
+
+          port = mkOption {
+            type = types.int;
+            default = 33445;
+            description = "Listening port (UDP).";
+          };
+
+          keysFile = mkOption {
+            type = types.str;
+            default = "${home}/keys";
+            description = "Node key file.";
+          };
+
+          extraConfig = mkOption {
+            type = types.lines;
+            default = "";
+            description =
+              ''
+                Configuration for bootstrap daemon.
+                See <link xlink:href="https://github.com/irungentoo/toxcore/blob/master/other/bootstrap_daemon/tox-bootstrapd.conf"/>
+                and <link xlink:href="http://wiki.tox.im/Nodes"/>.
+             '';
+          };
+      };
+
+    };
+
+  config = mkIf config.services.toxBootstrapd.enable {
+
+    users.users.tox-bootstrapd =
+      { uid = config.ids.uids.tox-bootstrapd;
+        description = "Tox bootstrap daemon user";
+        inherit home;
+        createHome = true;
+      };
+
+    systemd.services.tox-bootstrapd = {
+      description = "Tox DHT bootstrap daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig =
+        { ExecStart = "${pkg}/bin/tox-bootstrapd --config=${cfgFile}";
+          Type = "forking";
+          inherit PIDFile;
+          User = "tox-bootstrapd";
+        };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/tox-node.nix b/nixpkgs/nixos/modules/services/networking/tox-node.nix
new file mode 100644
index 000000000000..c24e7fd12850
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tox-node.nix
@@ -0,0 +1,95 @@
+{ lib, pkgs, config, ... }:
+
+with lib;
+
+let
+  pkg = pkgs.tox-node;
+  cfg = config.services.tox-node;
+  homeDir = "/var/lib/tox-node";
+
+  configFile = let
+    # fetchurl should be switched to getting this file from tox-node.src once
+    # the dpkg directory is in a release
+    src = pkgs.fetchurl {
+      url = "https://raw.githubusercontent.com/tox-rs/tox-node/master/dpkg/config.yml";
+      sha256 = "1431wzpzm786mcvyzk1rp7ar418n45dr75hdggxvlm7pkpam31xa";
+    };
+    confJSON = pkgs.writeText "config.json" (
+      builtins.toJSON {
+        log-type = cfg.logType;
+        keys-file = cfg.keysFile;
+        udp-address = cfg.udpAddress;
+        tcp-addresses = cfg.tcpAddresses;
+        tcp-connections-limit = cfg.tcpConnectionLimit;
+        lan-discovery = cfg.lanDiscovery;
+        threads = cfg.threads;
+        motd = cfg.motd;
+      }
+    );
+  in with pkgs; runCommand "config.yml" {} ''
+    ${remarshal}/bin/remarshal -if yaml -of json ${src} -o src.json
+    ${jq}/bin/jq -s '(.[0] | with_entries( select(.key == "bootstrap-nodes"))) * .[1]' src.json ${confJSON} > $out
+  '';
+
+in {
+  options.services.tox-node = {
+    enable = mkEnableOption "Tox Node service";
+
+    logType = mkOption {
+      type = types.enum [ "Stderr" "Stdout" "Syslog" "None" ];
+      default = "Stderr";
+      description = "Logging implementation.";
+    };
+    keysFile = mkOption {
+      type = types.str;
+      default = "${homeDir}/keys";
+      description = "Path to the file where DHT keys are stored.";
+    };
+    udpAddress = mkOption {
+      type = types.str;
+      default = "0.0.0.0:33445";
+      description = "UDP address to run DHT node.";
+    };
+    tcpAddresses = mkOption {
+      type = types.listOf types.str;
+      default = [ "0.0.0.0:33445" ];
+      description = "TCP addresses to run TCP relay.";
+    };
+    tcpConnectionLimit = mkOption {
+      type = types.int;
+      default = 8192;
+      description = "Maximum number of active TCP connections relay can hold";
+    };
+    lanDiscovery = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Enable local network discovery.";
+    };
+    threads = mkOption {
+      type = types.int;
+      default = 1;
+      description = "Number of threads for execution";
+    };
+    motd = mkOption {
+      type = types.str;
+      default = "Hi from tox-rs! I'm up {{uptime}}. TCP: incoming {{tcp_packets_in}}, outgoing {{tcp_packets_out}}, UDP: incoming {{udp_packets_in}}, outgoing {{udp_packets_out}}";
+      description = "Message of the day";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+    systemd.services.tox-node = {
+      description = "Tox Node";
+
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkg}/bin/tox-node config ${configFile}";
+        StateDirectory = "tox-node";
+        DynamicUser = true;
+        Restart = "always";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/toxvpn.nix b/nixpkgs/nixos/modules/services/networking/toxvpn.nix
new file mode 100644
index 000000000000..9e97faeebc1e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/toxvpn.nix
@@ -0,0 +1,68 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+  options = {
+    services.toxvpn = {
+      enable = mkEnableOption "toxvpn running on startup";
+
+      localip = mkOption {
+        type        = types.str;
+        default     = "10.123.123.1";
+        description = "your ip on the vpn";
+      };
+
+      port = mkOption {
+        type        = types.int;
+        default     = 33445;
+        description = "udp port for toxcore, port-forward to help with connectivity if you run many nodes behind one NAT";
+      };
+
+      auto_add_peers = mkOption {
+        type        = types.listOf types.str;
+        default     = [];
+        example     = ''[ "toxid1" "toxid2" ]'';
+        description = "peers to automatically connect to on startup";
+      };
+    };
+  };
+
+  config = mkIf config.services.toxvpn.enable {
+    systemd.services.toxvpn = {
+      description = "toxvpn daemon";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        mkdir -p /run/toxvpn || true
+        chown toxvpn /run/toxvpn
+      '';
+
+      path = [ pkgs.toxvpn ];
+
+      script = ''
+        exec toxvpn -i ${config.services.toxvpn.localip} -l /run/toxvpn/control -u toxvpn -p ${toString config.services.toxvpn.port} ${lib.concatMapStringsSep " " (x: "-a ${x}") config.services.toxvpn.auto_add_peers}
+      '';
+
+      serviceConfig = {
+        KillMode  = "process";
+        Restart   = "on-success";
+        Type      = "notify";
+      };
+
+      restartIfChanged = false; # Likely to be used for remote admin
+    };
+
+    environment.systemPackages = [ pkgs.toxvpn ];
+
+    users.users = {
+      toxvpn = {
+        uid        = config.ids.uids.toxvpn;
+        home       = "/var/lib/toxvpn";
+        createHome = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/trickster.nix b/nixpkgs/nixos/modules/services/networking/trickster.nix
new file mode 100644
index 000000000000..8760dd5a9382
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/trickster.nix
@@ -0,0 +1,112 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.trickster;
+in
+{
+
+  options = {
+    services.trickster = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable Trickster.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.trickster;
+        defaultText = "pkgs.trickster";
+        description = ''
+          Package that should be used for trickster.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Path to configuration file.
+        '';
+      };
+
+      instance-id = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Instance ID for when running multiple processes (default null).
+        '';
+      };
+
+      log-level = mkOption {
+        type = types.str;
+        default = "info";
+        description = ''
+          Level of Logging to use (debug, info, warn, error) (default "info").
+        '';
+      };
+
+      metrics-port = mkOption {
+        type = types.port;
+        default = 8082;
+        description = ''
+          Port that the /metrics endpoint will listen on.
+        '';
+      };
+
+      origin = mkOption {
+        type = types.str;
+        default = "http://prometheus:9090";
+        description = ''
+          URL to the Prometheus Origin. Enter it like you would in grafana, e.g., http://prometheus:9090 (default http://prometheus:9090).
+        '';
+      };
+
+      profiler-port = mkOption {
+        type = types.nullOr types.port;
+        default = null;
+        description = ''
+          Port that the /debug/pprof endpoint will listen on.
+        '';
+      };
+
+      proxy-port = mkOption {
+        type = types.port;
+        default = 9090;
+        description = ''
+          Port that the Proxy server will listen on.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.trickster = {
+      description = "Dashboard Accelerator for Prometheus";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = ''
+          ${cfg.package}/bin/trickster \
+          -log-level ${cfg.log-level} \
+          -metrics-port ${toString cfg.metrics-port} \
+          -origin ${cfg.origin} \
+          -proxy-port ${toString cfg.proxy-port} \
+          ${optionalString (cfg.configFile != null) "-config ${cfg.configFile}"} \
+          ${optionalString (cfg.profiler-port != null) "-profiler-port ${cfg.profiler-port}"} \
+          ${optionalString (cfg.instance-id != null) "-instance-id ${cfg.instance-id}"}
+        '';
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+      };
+    };
+
+  };  
+}
+
diff --git a/nixpkgs/nixos/modules/services/networking/tvheadend.nix b/nixpkgs/nixos/modules/services/networking/tvheadend.nix
new file mode 100644
index 000000000000..ccf879996631
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/tvheadend.nix
@@ -0,0 +1,61 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg     = config.services.tvheadend;
+    pidFile = "${config.users.users.tvheadend.home}/tvheadend.pid";
+in
+
+{
+  options = {
+    services.tvheadend = {
+      enable = mkEnableOption "Tvheadend";
+      httpPort = mkOption {
+        type        = types.int;
+        default     = 9981;
+        description = "Port to bind HTTP to.";
+      };
+
+      htspPort = mkOption {
+        type        = types.int;
+        default     = 9982;
+        description = "Port to bind HTSP to.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.tvheadend = {
+      description = "Tvheadend Service user";
+      home        = "/var/lib/tvheadend";
+      createHome  = true;
+      uid         = config.ids.uids.tvheadend;
+    };
+
+    systemd.services.tvheadend = {
+      description = "Tvheadend TV streaming server";
+      wantedBy    = [ "multi-user.target" ];
+      after       = [ "network.target" ];
+
+      serviceConfig = {
+        Type         = "forking";
+        PIDFile      = pidFile;
+        Restart      = "always";
+        RestartSec   = 5;
+        User         = "tvheadend";
+        Group        = "video";
+        ExecStart    = ''
+                       ${pkgs.tvheadend}/bin/tvheadend \
+                       --http_port ${toString cfg.httpPort} \
+                       --htsp_port ${toString cfg.htspPort} \
+                       -f \
+                       -C \
+                       -p ${pidFile} \
+                       -u tvheadend \
+                       -g video
+                       '';
+        ExecStop     = "${pkgs.coreutils}/bin/rm ${pidFile}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/unbound.nix b/nixpkgs/nixos/modules/services/networking/unbound.nix
new file mode 100644
index 000000000000..baed83591e1e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/unbound.nix
@@ -0,0 +1,148 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.unbound;
+
+  stateDir = "/var/lib/unbound";
+
+  access = concatMapStringsSep "\n  " (x: "access-control: ${x} allow") cfg.allowedAccess;
+
+  interfaces = concatMapStringsSep "\n  " (x: "interface: ${x}") cfg.interfaces;
+
+  isLocalAddress = x: substring 0 3 x == "::1" || substring 0 9 x == "127.0.0.1";
+
+  forward =
+    optionalString (any isLocalAddress cfg.forwardAddresses) ''
+      do-not-query-localhost: no
+    '' +
+    optionalString (cfg.forwardAddresses != []) ''
+      forward-zone:
+        name: .
+    '' +
+    concatMapStringsSep "\n" (x: "    forward-addr: ${x}") cfg.forwardAddresses;
+
+  rootTrustAnchorFile = "${stateDir}/root.key";
+
+  trustAnchor = optionalString cfg.enableRootTrustAnchor
+    "auto-trust-anchor-file: ${rootTrustAnchorFile}";
+
+  confFile = pkgs.writeText "unbound.conf" ''
+    server:
+      directory: "${stateDir}"
+      username: unbound
+      chroot: "${stateDir}"
+      pidfile: ""
+      ${interfaces}
+      ${access}
+      ${trustAnchor}
+    ${cfg.extraConfig}
+    ${forward}
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.unbound = {
+
+      enable = mkEnableOption "Unbound domain name server";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.unbound;
+        defaultText = "pkgs.unbound";
+        description = "The unbound package to use";
+      };
+
+      allowedAccess = mkOption {
+        default = [ "127.0.0.0/24" ];
+        type = types.listOf types.str;
+        description = "What networks are allowed to use unbound as a resolver.";
+      };
+
+      interfaces = mkOption {
+        default = [ "127.0.0.1" ] ++ optional config.networking.enableIPv6 "::1";
+        type = types.listOf types.str;
+        description = "What addresses the server should listen on.";
+      };
+
+      forwardAddresses = mkOption {
+        default = [ ];
+        type = types.listOf types.str;
+        description = "What servers to forward queries to.";
+      };
+
+      enableRootTrustAnchor = mkOption {
+        default = true;
+        type = types.bool;
+        description = "Use and update root trust anchor for DNSSEC validation.";
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Extra unbound config. See
+          <citerefentry><refentrytitle>unbound.conf</refentrytitle><manvolnum>8
+          </manvolnum></citerefentry>.
+        '';
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    users.users.unbound = {
+      description = "unbound daemon user";
+      isSystemUser = true;
+    };
+
+    networking.resolvconf.useLocalResolver = mkDefault true;
+
+    systemd.services.unbound = {
+      description = "Unbound recursive Domain Name Server";
+      after = [ "network.target" ];
+      before = [ "nss-lookup.target" ];
+      wants = [ "nss-lookup.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}/dev/
+        cp ${confFile} ${stateDir}/unbound.conf
+        ${optionalString cfg.enableRootTrustAnchor ''
+          ${cfg.package}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!"
+          chown unbound ${stateDir} ${rootTrustAnchorFile}
+        ''}
+        touch ${stateDir}/dev/random
+        ${pkgs.utillinux}/bin/mount --bind -n /dev/urandom ${stateDir}/dev/random
+      '';
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/unbound -d -c ${stateDir}/unbound.conf";
+        ExecStopPost="${pkgs.utillinux}/bin/umount ${stateDir}/dev/random";
+
+        ProtectSystem = true;
+        ProtectHome = true;
+        PrivateDevices = true;
+        Restart = "always";
+        RestartSec = "5s";
+      };
+    };
+
+    # If networkmanager is enabled, ask it to interface with unbound.
+    networking.networkmanager.dns = "unbound";
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/unifi.nix b/nixpkgs/nixos/modules/services/networking/unifi.nix
new file mode 100644
index 000000000000..4bdfa8143dce
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/unifi.nix
@@ -0,0 +1,180 @@
+{ config, lib, pkgs, utils, ... }:
+with lib;
+let
+  cfg = config.services.unifi;
+  stateDir = "/var/lib/unifi";
+  cmd = ''
+    @${cfg.jrePackage}/bin/java java \
+        ${optionalString (cfg.initialJavaHeapSize != null) "-Xms${(toString cfg.initialJavaHeapSize)}m"} \
+        ${optionalString (cfg.maximumJavaHeapSize != null) "-Xmx${(toString cfg.maximumJavaHeapSize)}m"} \
+        -jar ${stateDir}/lib/ace.jar
+  '';
+  mountPoints = [
+    {
+      what = "${cfg.unifiPackage}/dl";
+      where = "${stateDir}/dl";
+    }
+    {
+      what = "${cfg.unifiPackage}/lib";
+      where = "${stateDir}/lib";
+    }
+    {
+      what = "${cfg.mongodbPackage}/bin";
+      where = "${stateDir}/bin";
+    }
+    {
+      what = "${cfg.dataDir}";
+      where = "${stateDir}/data";
+    }
+  ];
+  systemdMountPoints = map (m: "${utils.escapeSystemdPath m.where}.mount") mountPoints;
+in
+{
+
+  options = {
+
+    services.unifi.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether or not to enable the unifi controller service.
+      '';
+    };
+
+    services.unifi.jrePackage = mkOption {
+      type = types.package;
+      default = pkgs.jre8;
+      defaultText = "pkgs.jre8";
+      description = ''
+        The JRE package to use. Check the release notes to ensure it is supported.
+      '';
+    };
+
+    services.unifi.unifiPackage = mkOption {
+      type = types.package;
+      default = pkgs.unifiLTS;
+      defaultText = "pkgs.unifiLTS";
+      description = ''
+        The unifi package to use.
+      '';
+    };
+
+    services.unifi.mongodbPackage = mkOption {
+      type = types.package;
+      default = pkgs.mongodb;
+      defaultText = "pkgs.mongodb";
+      description = ''
+        The mongodb package to use.
+      '';
+    };
+
+    services.unifi.dataDir = mkOption {
+      type = types.str;
+      default = "${stateDir}/data";
+      description = ''
+        Where to store the database and other data.
+
+        This directory will be bind-mounted to ${stateDir}/data as part of the service startup.
+      '';
+    };
+
+    services.unifi.openPorts = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether or not to open the minimum required ports on the firewall.
+
+        This is necessary to allow firmware upgrades and device discovery to
+        work. For remote login, you should additionally open (or forward) port
+        8443.
+      '';
+    };
+
+    services.unifi.initialJavaHeapSize = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      example = 1024;
+      description = ''
+        Set the initial heap size for the JVM in MB. If this option isn't set, the
+        JVM will decide this value at runtime.
+      '';
+    };
+
+    services.unifi.maximumJavaHeapSize = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      example = 4096;
+      description = ''
+        Set the maximimum heap size for the JVM in MB. If this option isn't set, the
+        JVM will decide this value at runtime.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    users.users.unifi = {
+      uid = config.ids.uids.unifi;
+      description = "UniFi controller daemon user";
+      home = "${stateDir}";
+    };
+
+    networking.firewall = mkIf cfg.openPorts {
+      # https://help.ubnt.com/hc/en-us/articles/218506997
+      allowedTCPPorts = [
+        8080  # Port for UAP to inform controller.
+        8880  # Port for HTTP portal redirect, if guest portal is enabled.
+        8843  # Port for HTTPS portal redirect, ditto.
+        6789  # Port for UniFi mobile speed test.
+      ];
+      allowedUDPPorts = [
+        3478  # UDP port used for STUN.
+        10001 # UDP port used for device discovery.
+      ];
+    };
+
+    # We must create the binary directories as bind mounts instead of symlinks
+    # This is because the controller resolves all symlinks to absolute paths
+    # to be used as the working directory.
+    systemd.mounts = map ({ what, where }: {
+        bindsTo = [ "unifi.service" ];
+        partOf = [ "unifi.service" ];
+        unitConfig.RequiresMountsFor = stateDir;
+        options = "bind";
+        what = what;
+        where = where;
+      }) mountPoints;
+
+    systemd.tmpfiles.rules = [
+      "d '${stateDir}' 0700 unifi - - -"
+      "d '${stateDir}/data' 0700 unifi - - -"
+      "d '${stateDir}/webapps' 0700 unifi - - -"
+      "L+ '${stateDir}/webapps/ROOT' - - - - ${cfg.unifiPackage}/webapps/ROOT"
+    ];
+
+    systemd.services.unifi = {
+      description = "UniFi controller daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ] ++ systemdMountPoints;
+      partOf = systemdMountPoints;
+      bindsTo = systemdMountPoints;
+      unitConfig.RequiresMountsFor = stateDir;
+      # This a HACK to fix missing dependencies of dynamic libs extracted from jars
+      environment.LD_LIBRARY_PATH = with pkgs.stdenv; "${cc.cc.lib}/lib";
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = "${(removeSuffix "\n" cmd)} start";
+        ExecStop = "${(removeSuffix "\n" cmd)} stop";
+        Restart = "on-failure";
+        User = "unifi";
+        UMask = "0077";
+        WorkingDirectory = "${stateDir}";
+      };
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ erictapen ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/v2ray.nix b/nixpkgs/nixos/modules/services/networking/v2ray.nix
new file mode 100644
index 000000000000..6a924a16449a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/v2ray.nix
@@ -0,0 +1,87 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  options = {
+
+    services.v2ray = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to run v2ray server.
+
+          Either <literal>configFile</literal> or <literal>config</literal> must be specified.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/etc/v2ray/config.json";
+        description = ''
+          The absolute path to the configuration file.
+
+          Either <literal>configFile</literal> or <literal>config</literal> must be specified.
+
+          See <link xlink:href="https://v2ray.com/en/configuration/overview.html"/>.
+        '';
+      };
+
+      config = mkOption {
+        type = types.nullOr (types.attrsOf types.unspecified);
+        default = null;
+        example = {
+          inbounds = [{
+            port = 1080;
+            listen = "127.0.0.1";
+            protocol = "http";
+          }];
+          outbounds = [{
+            protocol = "freedom";
+          }];
+        };
+        description = ''
+          The configuration object.
+
+          Either `configFile` or `config` must be specified.
+
+          See <link xlink:href="https://v2ray.com/en/configuration/overview.html"/>.
+        '';
+      };
+    };
+
+  };
+
+  config = let
+    cfg = config.services.v2ray;
+    configFile = if cfg.configFile != null
+      then cfg.configFile
+      else pkgs.writeTextFile {
+        name = "v2ray.json";
+        text = builtins.toJSON cfg.config;
+        checkPhase = ''
+          ${pkgs.v2ray}/bin/v2ray -test -config $out
+        '';
+      };
+
+  in mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = (cfg.configFile == null) != (cfg.config == null);
+        message = "Either but not both `configFile` and `config` should be specified for v2ray.";
+      }
+    ];
+
+    systemd.services.v2ray = {
+      description = "v2ray Daemon";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.v2ray ];
+      script = ''
+        exec v2ray -config ${configFile}
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/vsftpd.nix b/nixpkgs/nixos/modules/services/networking/vsftpd.nix
new file mode 100644
index 000000000000..c57994533c17
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/vsftpd.nix
@@ -0,0 +1,326 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  /* minimal secure setup:
+
+   enable = true;
+   forceLocalLoginsSSL = true;
+   forceLocalDataSSL = true;
+   userlistDeny = false;
+   localUsers = true;
+   userlist = ["non-root-user" "other-non-root-user"];
+   rsaCertFile = "/var/vsftpd/vsftpd.pem";
+
+  */
+
+  cfg = config.services.vsftpd;
+
+  inherit (pkgs) vsftpd;
+
+  yesNoOption = nixosName: vsftpdName: default: description: {
+    cfgText = "${vsftpdName}=${if getAttr nixosName cfg then "YES" else "NO"}";
+
+    nixosOption = {
+      type = types.bool;
+      name = nixosName;
+      value = mkOption {
+        inherit description default;
+        type = types.bool;
+      };
+    };
+  };
+
+  optionDescription = [
+    (yesNoOption "allowWriteableChroot" "allow_writeable_chroot" false ''
+      Allow the use of writeable root inside chroot().
+    '')
+    (yesNoOption "virtualUseLocalPrivs" "virtual_use_local_privs" false ''
+      If enabled, virtual users will use the same privileges as local
+      users. By default, virtual users will use the same privileges as
+      anonymous users, which tends to be more restrictive (especially
+      in terms of write access).
+    '')
+    (yesNoOption "anonymousUser" "anonymous_enable" false ''
+      Whether to enable the anonymous FTP user.
+    '')
+    (yesNoOption "anonymousUserNoPassword" "no_anon_password" false ''
+      Whether to disable the password for the anonymous FTP user.
+    '')
+    (yesNoOption "localUsers" "local_enable" false ''
+      Whether to enable FTP for local users.
+    '')
+    (yesNoOption "writeEnable" "write_enable" false ''
+      Whether any write activity is permitted to users.
+    '')
+    (yesNoOption "anonymousUploadEnable" "anon_upload_enable" false ''
+      Whether any uploads are permitted to anonymous users.
+    '')
+    (yesNoOption "anonymousMkdirEnable" "anon_mkdir_write_enable" false ''
+      Whether any uploads are permitted to anonymous users.
+    '')
+    (yesNoOption "chrootlocalUser" "chroot_local_user" false ''
+      Whether local users are confined to their home directory.
+    '')
+    (yesNoOption "userlistEnable" "userlist_enable" false ''
+      Whether users are included.
+    '')
+    (yesNoOption "userlistDeny" "userlist_deny" false ''
+      Specifies whether <option>userlistFile</option> is a list of user
+      names to allow or deny access.
+      The default <literal>false</literal> means whitelist/allow.
+    '')
+    (yesNoOption "forceLocalLoginsSSL" "force_local_logins_ssl" false ''
+      Only applies if <option>sslEnable</option> is true. Non anonymous (local) users
+      must use a secure SSL connection to send a password.
+    '')
+    (yesNoOption "forceLocalDataSSL" "force_local_data_ssl" false ''
+      Only applies if <option>sslEnable</option> is true. Non anonymous (local) users
+      must use a secure SSL connection for sending/receiving data on data connection.
+    '')
+    (yesNoOption "portPromiscuous" "port_promiscuous" false ''
+      Set to YES if you want to disable the PORT security check that ensures that
+      outgoing data connections can only connect to the client. Only enable if you
+      know what you are doing!
+    '')
+    (yesNoOption "ssl_tlsv1" "ssl_tlsv1" true  ''
+      Only applies if <option>ssl_enable</option> is activated. If
+      enabled, this option will permit TLS v1 protocol connections.
+      TLS v1 connections are preferred.
+    '')
+    (yesNoOption "ssl_sslv2" "ssl_sslv2" false ''
+      Only applies if <option>ssl_enable</option> is activated. If
+      enabled, this option will permit SSL v2 protocol connections.
+      TLS v1 connections are preferred.
+    '')
+    (yesNoOption "ssl_sslv3" "ssl_sslv3" false ''
+      Only applies if <option>ssl_enable</option> is activated. If
+      enabled, this option will permit SSL v3 protocol connections.
+      TLS v1 connections are preferred.
+    '')
+  ];
+
+  configFile = pkgs.writeText "vsftpd.conf"
+    ''
+      ${concatMapStrings (x: "${x.cfgText}\n") optionDescription}
+      ${optionalString (cfg.rsaCertFile != null) ''
+        ssl_enable=YES
+        rsa_cert_file=${cfg.rsaCertFile}
+      ''}
+      ${optionalString (cfg.rsaKeyFile != null) ''
+        rsa_private_key_file=${cfg.rsaKeyFile}
+      ''}
+      ${optionalString (cfg.userlistFile != null) ''
+        userlist_file=${cfg.userlistFile}
+      ''}
+      background=YES
+      listen=NO
+      listen_ipv6=YES
+      nopriv_user=vsftpd
+      secure_chroot_dir=/var/empty
+      ${optionalString (cfg.localRoot != null) ''
+        local_root=${cfg.localRoot}
+      ''}
+      syslog_enable=YES
+      ${optionalString (pkgs.stdenv.hostPlatform.system == "x86_64-linux") ''
+        seccomp_sandbox=NO
+      ''}
+      anon_umask=${cfg.anonymousUmask}
+      ${optionalString cfg.anonymousUser ''
+        anon_root=${cfg.anonymousUserHome}
+      ''}
+      ${optionalString cfg.enableVirtualUsers ''
+        guest_enable=YES
+        guest_username=vsftpd
+      ''}
+      pam_service_name=vsftpd
+      ${cfg.extraConfig}
+    '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.vsftpd = {
+
+      enable = mkEnableOption "vsftpd";
+
+      userlist = mkOption {
+        default = [];
+        description = "See <option>userlistFile</option>.";
+      };
+
+      userlistFile = mkOption {
+        type = types.path;
+        default = pkgs.writeText "userlist" (concatMapStrings (x: "${x}\n") cfg.userlist);
+        defaultText = "pkgs.writeText \"userlist\" (concatMapStrings (x: \"\${x}\n\") cfg.userlist)";
+        description = ''
+          Newline separated list of names to be allowed/denied if <option>userlistEnable</option>
+          is <literal>true</literal>. Meaning see <option>userlistDeny</option>.
+
+          The default is a file containing the users from <option>userlist</option>.
+
+          If explicitely set to null userlist_file will not be set in vsftpd's config file.
+        '';
+      };
+
+      enableVirtualUsers = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the <literal>pam_userdb</literal>-based
+          virtual user system
+        '';
+      };
+
+      userDbPath = mkOption {
+        type = types.nullOr types.str;
+        example = "/etc/vsftpd/userDb";
+        default = null;
+        description = ''
+          Only applies if <option>enableVirtualUsers</option> is true.
+          Path pointing to the <literal>pam_userdb</literal> user
+          database used by vsftpd to authenticate the virtual users.
+
+          This user list should be stored in the Berkeley DB database
+          format.
+
+          To generate a new user database, create a text file, add
+          your users using the following format:
+          <programlisting>
+          user1
+          password1
+          user2
+          password2
+          </programlisting>
+
+          You can then install <literal>pkgs.db</literal> to generate
+          the Berkeley DB using
+          <programlisting>
+          db_load -T -t hash -f logins.txt userDb.db
+          </programlisting>
+
+          Caution: <literal>pam_userdb</literal> will automatically
+          append a <literal>.db</literal> suffix to the filename you
+          provide though this option. This option shouldn't include
+          this filetype suffix.
+        '';
+      };
+
+      localRoot = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/www/$USER";
+        description = ''
+          This option represents a directory which vsftpd will try to
+          change into after a local (i.e. non- anonymous) login.
+
+          Failure is silently ignored.
+        '';
+      };
+
+      anonymousUserHome = mkOption {
+        type = types.path;
+        default = "/home/ftp/";
+        description = ''
+          Directory to consider the HOME of the anonymous user.
+        '';
+      };
+
+      rsaCertFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "RSA certificate file.";
+      };
+
+      rsaKeyFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "RSA private key file.";
+      };
+
+      anonymousUmask = mkOption {
+        type = types.str;
+        default = "077";
+        example = "002";
+        description = "Anonymous write umask.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = "ftpd_banner=Hello";
+        description = "Extra configuration to add at the bottom of the generated configuration file.";
+      };
+
+    } // (listToAttrs (catAttrs "nixosOption" optionDescription));
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion =
+              (cfg.forceLocalLoginsSSL -> cfg.rsaCertFile != null)
+          &&  (cfg.forceLocalDataSSL -> cfg.rsaCertFile != null);
+        message = "vsftpd: If forceLocalLoginsSSL or forceLocalDataSSL is true then a rsaCertFile must be provided!";
+      }
+      {
+        assertion = (cfg.enableVirtualUsers -> cfg.userDbPath != null)
+                 && (cfg.enableVirtualUsers -> cfg.localUsers != null);
+        message = "vsftpd: If enableVirtualUsers is true, you need to setup both the userDbPath and localUsers options.";
+      }];
+
+    users.users = {
+      "vsftpd" = {
+        uid = config.ids.uids.vsftpd;
+        description = "VSFTPD user";
+        home = if cfg.localRoot != null
+               then cfg.localRoot # <= Necessary for virtual users.
+               else "/homeless-shelter";
+      };
+    } // optionalAttrs cfg.anonymousUser {
+      "ftp" = { name = "ftp";
+          uid = config.ids.uids.ftp;
+          group = "ftp";
+          description = "Anonymous FTP user";
+          home = cfg.anonymousUserHome;
+        };
+    };
+
+    users.groups.ftp.gid = config.ids.gids.ftp;
+
+    # If you really have to access root via FTP use mkOverride or userlistDeny
+    # = false and whitelist root
+    services.vsftpd.userlist = if cfg.userlistDeny then ["root"] else [];
+
+    systemd = {
+      tmpfiles.rules = optional cfg.anonymousUser
+        #Type Path                       Mode User   Gr    Age Arg
+        "d    '${builtins.toString cfg.anonymousUserHome}' 0555 'ftp'  'ftp' -   -";
+      services.vsftpd = {
+        description = "Vsftpd Server";
+
+        wantedBy = [ "multi-user.target" ];
+
+        serviceConfig.ExecStart = "@${vsftpd}/sbin/vsftpd vsftpd ${configFile}";
+        serviceConfig.Restart = "always";
+        serviceConfig.Type = "forking";
+      };
+    };
+
+    security.pam.services.vsftpd.text = mkIf (cfg.enableVirtualUsers && cfg.userDbPath != null)''
+      auth required pam_userdb.so db=${cfg.userDbPath}
+      account required pam_userdb.so db=${cfg.userDbPath}
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wakeonlan.nix b/nixpkgs/nixos/modules/services/networking/wakeonlan.nix
new file mode 100644
index 000000000000..ebfba263cd8f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wakeonlan.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  interfaces = config.services.wakeonlan.interfaces;
+
+  ethtool = "${pkgs.ethtool}/sbin/ethtool";
+
+  passwordParameter = password : if (password == "") then "" else
+    "sopass ${password}";
+
+  methodParameter = {method, password} :
+    if method == "magicpacket" then "wol g"
+    else if method == "password" then "wol s so ${passwordParameter password}"
+    else throw "Wake-On-Lan method not supported";
+
+  line = { interface, method ? "magicpacket", password ? "" }: ''
+    ${ethtool} -s ${interface} ${methodParameter {inherit method password;}}
+  '';
+
+  concatStrings = fold (x: y: x + y) "";
+  lines = concatStrings (map (l: line l) interfaces);
+
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.wakeonlan.interfaces = mkOption {
+      default = [ ];
+      example = [
+        {
+          interface = "eth0";
+          method = "password";
+          password = "00:11:22:33:44:55";
+        }
+      ];
+      description = ''
+        Interfaces where to enable Wake-On-LAN, and how. Two methods available:
+        "magicpacket" and "password". The password has the shape of six bytes
+        in hexadecimal separated by a colon each. For more information,
+        check the ethtool manual.
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config.powerManagement.powerDownCommands = lines;
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/websockify.nix b/nixpkgs/nixos/modules/services/networking/websockify.nix
new file mode 100644
index 000000000000..d9177df65bd6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/websockify.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.networking.websockify; in {
+  options = {
+    services.networking.websockify = {
+      enable = mkOption {  
+        description = "Whether to enable websockify to forward websocket connections to TCP connections.";
+
+        default = false;   
+
+        type = types.bool; 
+      };
+
+      sslCert = mkOption {
+        description = "Path to the SSL certificate.";
+        type = types.path;
+      };
+
+      sslKey = mkOption {
+        description = "Path to the SSL key.";
+        default = cfg.sslCert;
+        defaultText = "config.services.networking.websockify.sslCert";
+        type = types.path;
+      };
+
+      portMap = mkOption {
+        description = "Ports to map by default.";
+        default = {};
+        type = types.attrsOf types.int;
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services."websockify@" = {
+      description = "Service to forward websocket connections to TCP connections (from port:to port %I)";
+      script = ''
+        IFS=':' read -a array <<< "$1"
+        ${pkgs.pythonPackages.websockify}/bin/websockify --ssl-only \
+          --cert=${cfg.sslCert} --key=${cfg.sslKey} 0.0.0.0:''${array[0]} 0.0.0.0:''${array[1]}
+      '';
+      scriptArgs = "%i";
+    };
+
+    systemd.targets.default-websockify = {
+      description = "Target to start all default websockify@ services";
+      unitConfig.X-StopOnReconfiguration = true;
+      wants = mapAttrsToList (name: value: "websockify@${name}:${toString value}.service") cfg.portMap;
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wg-quick.nix b/nixpkgs/nixos/modules/services/networking/wg-quick.nix
new file mode 100644
index 000000000000..ff1bdeed9f48
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wg-quick.nix
@@ -0,0 +1,312 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.networking.wg-quick;
+
+  kernel = config.boot.kernelPackages;
+
+  # interface options
+
+  interfaceOpts = { ... }: {
+    options = {
+      address = mkOption {
+        example = [ "192.168.2.1/24" ];
+        default = [];
+        type = with types; listOf str;
+        description = "The IP addresses of the interface.";
+      };
+
+      dns = mkOption {
+        example = [ "192.168.2.2" ];
+        default = [];
+        type = with types; listOf str;
+        description = "The IP addresses of DNS servers to configure.";
+      };
+
+      privateKey = mkOption {
+        example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Base64 private key generated by wg genkey.
+
+          Warning: Consider using privateKeyFile instead if you do not
+          want to store the key in the world-readable Nix store.
+        '';
+      };
+
+      privateKeyFile = mkOption {
+        example = "/private/wireguard_key";
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Private key file as generated by wg genkey.
+        '';
+      };
+
+      listenPort = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        example = 51820;
+        description = ''
+          16-bit port for listening. Optional; if not specified,
+          automatically generated based on interface name.
+        '';
+      };
+
+      preUp = mkOption {
+        example = literalExample ''
+          ${pkgs.iproute}/bin/ip netns add foo
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Commands called at the start of the interface setup.
+        '';
+      };
+
+      preDown = mkOption {
+        example = literalExample ''
+          ${pkgs.iproute}/bin/ip netns del foo
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Command called before the interface is taken down.
+        '';
+      };
+
+      postUp = mkOption {
+        example = literalExample ''
+          ${pkgs.iproute}/bin/ip netns add foo
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Commands called after the interface setup.
+        '';
+      };
+
+      postDown = mkOption {
+        example = literalExample ''
+          ${pkgs.iproute}/bin/ip netns del foo
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Command called after the interface is taken down.
+        '';
+      };
+
+      table = mkOption {
+        example = "main";
+        default = null;
+        type = with types; nullOr str;
+        description = ''
+          The kernel routing table to add this interface's
+          associated routes to. Setting this is useful for e.g. policy routing
+          ("ip rule") or virtual routing and forwarding ("ip vrf"). Both numeric
+          table IDs and table names (/etc/rt_tables) can be used. Defaults to
+          "main".
+        '';
+      };
+
+      mtu = mkOption {
+        example = 1248;
+        default = null;
+        type = with types; nullOr int;
+        description = ''
+          If not specified, the MTU is automatically determined
+          from the endpoint addresses or the system default route, which is usually
+          a sane choice. However, to manually specify an MTU to override this
+          automatic discovery, this value may be specified explicitly.
+        '';
+      };
+
+      peers = mkOption {
+        default = [];
+        description = "Peers linked to the interface.";
+        type = with types; listOf (submodule peerOpts);
+      };
+    };
+  };
+
+  # peer options
+
+  peerOpts = {
+    options = {
+      publicKey = mkOption {
+        example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+        type = types.str;
+        description = "The base64 public key the peer.";
+      };
+
+      presharedKey = mkOption {
+        default = null;
+        example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
+        type = with types; nullOr str;
+        description = ''
+          Base64 preshared key generated by wg genpsk. Optional,
+          and may be omitted. This option adds an additional layer of
+          symmetric-key cryptography to be mixed into the already existing
+          public-key cryptography, for post-quantum resistance.
+
+          Warning: Consider using presharedKeyFile instead if you do not
+          want to store the key in the world-readable Nix store.
+        '';
+      };
+
+      presharedKeyFile = mkOption {
+        default = null;
+        example = "/private/wireguard_psk";
+        type = with types; nullOr str;
+        description = ''
+          File pointing to preshared key as generated by wg pensk. Optional,
+          and may be omitted. This option adds an additional layer of
+          symmetric-key cryptography to be mixed into the already existing
+          public-key cryptography, for post-quantum resistance.
+        '';
+      };
+
+      allowedIPs = mkOption {
+        example = [ "10.192.122.3/32" "10.192.124.1/24" ];
+        type = with types; listOf str;
+        description = ''List of IP (v4 or v6) addresses with CIDR masks from
+        which this peer is allowed to send incoming traffic and to which
+        outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
+        be specified for matching all IPv4 addresses, and ::/0 may be specified
+        for matching all IPv6 addresses.'';
+      };
+
+      endpoint = mkOption {
+        default = null;
+        example = "demo.wireguard.io:12913";
+        type = with types; nullOr str;
+        description = ''Endpoint IP or hostname of the peer, followed by a colon,
+        and then a port number of the peer.'';
+      };
+
+      persistentKeepalive = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        example = 25;
+        description = ''This is optional and is by default off, because most
+        users will not need it. It represents, in seconds, between 1 and 65535
+        inclusive, how often to send an authenticated empty packet to the peer,
+        for the purpose of keeping a stateful firewall or NAT mapping valid
+        persistently. For example, if the interface very rarely sends traffic,
+        but it might at anytime receive traffic from a peer, and it is behind
+        NAT, the interface might benefit from having a persistent keepalive
+        interval of 25 seconds; however, most users will not need this.'';
+      };
+    };
+  };
+
+  writeScriptFile = name: text: ((pkgs.writeShellScriptBin name text) + "/bin/${name}");
+
+  generateUnit = name: values:
+    assert assertMsg ((values.privateKey != null) != (values.privateKeyFile != null)) "Only one of privateKey or privateKeyFile may be set";
+    let
+      preUpFile = if values.preUp != "" then writeScriptFile "preUp.sh" values.preUp else null;
+      postUp =
+            optional (values.privateKeyFile != null) "wg set ${name} private-key <(cat ${values.privateKeyFile})" ++
+            (concatMap (peer: optional (peer.presharedKeyFile != null) "wg set ${name} peer ${peer.publicKey} preshared-key <(cat ${peer.presharedKeyFile})") values.peers) ++
+            optional (values.postUp != null) values.postUp;
+      postUpFile = if postUp != [] then writeScriptFile "postUp.sh" (concatMapStringsSep "\n" (line: line) postUp) else null;
+      preDownFile = if values.preDown != "" then writeScriptFile "preDown.sh" values.preDown else null;
+      postDownFile = if values.postDown != "" then writeScriptFile "postDown.sh" values.postDown else null;
+      configDir = pkgs.writeTextFile {
+        name = "config-${name}";
+        executable = false;
+        destination = "/${name}.conf";
+        text =
+        ''
+        [interface]
+        ${concatMapStringsSep "\n" (address:
+          "Address = ${address}"
+        ) values.address}
+        ${concatMapStringsSep "\n" (dns:
+          "DNS = ${dns}"
+        ) values.dns}
+        '' +
+        optionalString (values.table != null) "Table = ${values.table}\n" +
+        optionalString (values.mtu != null) "MTU = ${toString values.mtu}\n" +
+        optionalString (values.privateKey != null) "PrivateKey = ${values.privateKey}\n" +
+        optionalString (values.listenPort != null) "ListenPort = ${toString values.listenPort}\n" +
+        optionalString (preUpFile != null) "PreUp = ${preUpFile}\n" +
+        optionalString (postUpFile != null) "PostUp = ${postUpFile}\n" +
+        optionalString (preDownFile != null) "PreDown = ${preDownFile}\n" +
+        optionalString (postDownFile != null) "PostDown = ${postDownFile}\n" +
+        concatMapStringsSep "\n" (peer:
+          assert assertMsg (!((peer.presharedKeyFile != null) && (peer.presharedKey != null))) "Only one of presharedKey or presharedKeyFile may be set";
+          "[Peer]\n" +
+          "PublicKey = ${peer.publicKey}\n" +
+          optionalString (peer.presharedKey != null) "PresharedKey = ${peer.presharedKey}\n" +
+          optionalString (peer.endpoint != null) "Endpoint = ${peer.endpoint}\n" +
+          optionalString (peer.persistentKeepalive != null) "PersistentKeepalive = ${toString peer.persistentKeepalive}\n" +
+          optionalString (peer.allowedIPs != []) "AllowedIPs = ${concatStringsSep "," peer.allowedIPs}\n"
+        ) values.peers;
+      };
+      configPath = "${configDir}/${name}.conf";
+    in
+    nameValuePair "wg-quick-${name}"
+      {
+        description = "wg-quick WireGuard Tunnel - ${name}";
+        requires = [ "network-online.target" ];
+        after = [ "network.target" "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
+        environment.DEVICE = name;
+        path = [ pkgs.kmod pkgs.wireguard-tools ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        script = ''
+          ${optionalString (!config.boot.isContainer) "modprobe wireguard"}
+          wg-quick up ${configPath}
+        '';
+
+        preStop = ''
+          wg-quick down ${configPath}
+        '';
+      };
+in {
+
+  ###### interface
+
+  options = {
+    networking.wg-quick = {
+      interfaces = mkOption {
+        description = "Wireguard interfaces.";
+        default = {};
+        example = {
+          wg0 = {
+            address = [ "192.168.20.4/24" ];
+            privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+            peers = [
+              { allowedIPs = [ "192.168.20.1/32" ];
+                publicKey  = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+                endpoint   = "demo.wireguard.io:12913"; }
+            ];
+          };
+        };
+        type = with types; attrsOf (submodule interfaceOpts);
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf (cfg.interfaces != {}) {
+    boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
+    environment.systemPackages = [ pkgs.wireguard-tools ];
+    # This is forced to false for now because the default "--validmark" rpfilter we apply on reverse path filtering
+    # breaks the wg-quick routing because wireguard packets leave with a fwmark from wireguard.
+    networking.firewall.checkReversePath = false;
+    systemd.services = mapAttrs' generateUnit cfg.interfaces;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wicd.nix b/nixpkgs/nixos/modules/services/networking/wicd.nix
new file mode 100644
index 000000000000..aa10a50f876a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wicd.nix
@@ -0,0 +1,40 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    networking.wicd.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to start <command>wicd</command>. Wired and
+        wireless network configurations can then be managed by
+        wicd-client.
+      '';
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.networking.wicd.enable {
+
+    environment.systemPackages = [pkgs.wicd];
+
+    systemd.services.wicd = {
+      after = [ "network-pre.target" ];
+      before = [ "network.target" ];
+      wants = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      script = "${pkgs.wicd}/sbin/wicd -f";
+    };
+
+    services.dbus.enable = true;
+    services.dbus.packages = [pkgs.wicd];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wireguard.nix b/nixpkgs/nixos/modules/services/networking/wireguard.nix
new file mode 100644
index 000000000000..e8f83f6dd8bf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wireguard.nix
@@ -0,0 +1,452 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.networking.wireguard;
+
+  kernel = config.boot.kernelPackages;
+
+  # interface options
+
+  interfaceOpts = { ... }: {
+
+    options = {
+
+      ips = mkOption {
+        example = [ "192.168.2.1/24" ];
+        default = [];
+        type = with types; listOf str;
+        description = "The IP addresses of the interface.";
+      };
+
+      privateKey = mkOption {
+        example = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Base64 private key generated by <command>wg genkey</command>.
+
+          Warning: Consider using privateKeyFile instead if you do not
+          want to store the key in the world-readable Nix store.
+        '';
+      };
+
+      generatePrivateKeyFile = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Automatically generate a private key with
+          <command>wg genkey</command>, at the privateKeyFile location.
+        '';
+      };
+
+      privateKeyFile = mkOption {
+        example = "/private/wireguard_key";
+        type = with types; nullOr str;
+        default = null;
+        description = ''
+          Private key file as generated by <command>wg genkey</command>.
+        '';
+      };
+
+      listenPort = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        example = 51820;
+        description = ''
+          16-bit port for listening. Optional; if not specified,
+          automatically generated based on interface name.
+        '';
+      };
+
+      preSetup = mkOption {
+        example = literalExample ''
+          ${pkgs.iproute}/bin/ip netns add foo
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = ''
+          Commands called at the start of the interface setup.
+        '';
+      };
+
+      postSetup = mkOption {
+        example = literalExample ''
+          printf "nameserver 10.200.100.1" | ${pkgs.openresolv}/bin/resolvconf -a wg0 -m 0
+        '';
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = "Commands called at the end of the interface setup.";
+      };
+
+      postShutdown = mkOption {
+        example = literalExample "${pkgs.openresolv}/bin/resolvconf -d wg0";
+        default = "";
+        type = with types; coercedTo (listOf str) (concatStringsSep "\n") lines;
+        description = "Commands called after shutting down the interface.";
+      };
+
+      table = mkOption {
+        default = "main";
+        type = types.str;
+        description = ''The kernel routing table to add this interface's
+        associated routes to. Setting this is useful for e.g. policy routing
+        ("ip rule") or virtual routing and forwarding ("ip vrf"). Both numeric
+        table IDs and table names (/etc/rt_tables) can be used. Defaults to
+        "main".'';
+      };
+
+      peers = mkOption {
+        default = [];
+        description = "Peers linked to the interface.";
+        type = with types; listOf (submodule peerOpts);
+      };
+
+      allowedIPsAsRoutes = mkOption {
+        example = false;
+        default = true;
+        type = types.bool;
+        description = ''
+          Determines whether to add allowed IPs as routes or not.
+        '';
+      };
+
+      socketNamespace = mkOption {
+        default = null;
+        type = with types; nullOr str;
+        example = "container";
+        description = ''The pre-existing network namespace in which the
+        WireGuard interface is created, and which retains the socket even if the
+        interface is moved via <option>interfaceNamespace</option>. When
+        <literal>null</literal>, the interface is created in the init namespace.
+        See <link
+        xlink:href="https://www.wireguard.com/netns/">documentation</link>.
+        '';
+      };
+
+      interfaceNamespace = mkOption {
+        default = null;
+        type = with types; nullOr str;
+        example = "init";
+        description = ''The pre-existing network namespace the WireGuard
+        interface is moved to. The special value <literal>init</literal> means
+        the init namespace. When <literal>null</literal>, the interface is not
+        moved.
+        See <link
+        xlink:href="https://www.wireguard.com/netns/">documentation</link>.
+        '';
+      };
+    };
+
+  };
+
+  # peer options
+
+  peerOpts = {
+
+    options = {
+
+      publicKey = mkOption {
+        example = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+        type = types.str;
+        description = "The base64 public key of the peer.";
+      };
+
+      presharedKey = mkOption {
+        default = null;
+        example = "rVXs/Ni9tu3oDBLS4hOyAUAa1qTWVA3loR8eL20os3I=";
+        type = with types; nullOr str;
+        description = ''
+          Base64 preshared key generated by <command>wg genpsk</command>.
+          Optional, and may be omitted. This option adds an additional layer of
+          symmetric-key cryptography to be mixed into the already existing
+          public-key cryptography, for post-quantum resistance.
+
+          Warning: Consider using presharedKeyFile instead if you do not
+          want to store the key in the world-readable Nix store.
+        '';
+      };
+
+      presharedKeyFile = mkOption {
+        default = null;
+        example = "/private/wireguard_psk";
+        type = with types; nullOr str;
+        description = ''
+          File pointing to preshared key as generated by <command>wg pensk</command>.
+          Optional, and may be omitted. This option adds an additional layer of
+          symmetric-key cryptography to be mixed into the already existing
+          public-key cryptography, for post-quantum resistance.
+        '';
+      };
+
+      allowedIPs = mkOption {
+        example = [ "10.192.122.3/32" "10.192.124.1/24" ];
+        type = with types; listOf str;
+        description = ''List of IP (v4 or v6) addresses with CIDR masks from
+        which this peer is allowed to send incoming traffic and to which
+        outgoing traffic for this peer is directed. The catch-all 0.0.0.0/0 may
+        be specified for matching all IPv4 addresses, and ::/0 may be specified
+        for matching all IPv6 addresses.'';
+      };
+
+      endpoint = mkOption {
+        default = null;
+        example = "demo.wireguard.io:12913";
+        type = with types; nullOr str;
+        description = ''Endpoint IP or hostname of the peer, followed by a colon,
+        and then a port number of the peer.'';
+      };
+
+      persistentKeepalive = mkOption {
+        default = null;
+        type = with types; nullOr int;
+        example = 25;
+        description = ''This is optional and is by default off, because most
+        users will not need it. It represents, in seconds, between 1 and 65535
+        inclusive, how often to send an authenticated empty packet to the peer,
+        for the purpose of keeping a stateful firewall or NAT mapping valid
+        persistently. For example, if the interface very rarely sends traffic,
+        but it might at anytime receive traffic from a peer, and it is behind
+        NAT, the interface might benefit from having a persistent keepalive
+        interval of 25 seconds; however, most users will not need this.'';
+      };
+
+    };
+
+  };
+
+
+  generatePathUnit = name: values:
+    assert (values.privateKey == null);
+    assert (values.privateKeyFile != null);
+    nameValuePair "wireguard-${name}"
+      {
+        description = "WireGuard Tunnel - ${name} - Private Key";
+        requiredBy = [ "wireguard-${name}.service" ];
+        before = [ "wireguard-${name}.service" ];
+        pathConfig.PathExists = values.privateKeyFile;
+      };
+
+  generateKeyServiceUnit = name: values:
+    assert values.generatePrivateKeyFile;
+    nameValuePair "wireguard-${name}-key"
+      {
+        description = "WireGuard Tunnel - ${name} - Key Generator";
+        wantedBy = [ "wireguard-${name}.service" ];
+        requiredBy = [ "wireguard-${name}.service" ];
+        before = [ "wireguard-${name}.service" ];
+        path = with pkgs; [ wireguard ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        script = ''
+          mkdir --mode 0644 -p "${dirOf values.privateKeyFile}"
+          if [ ! -f "${values.privateKeyFile}" ]; then
+            touch "${values.privateKeyFile}"
+            chmod 0600 "${values.privateKeyFile}"
+            wg genkey > "${values.privateKeyFile}"
+            chmod 0400 "${values.privateKeyFile}"
+          fi
+        '';
+      };
+
+  generatePeerUnit = { interfaceName, interfaceCfg, peer }:
+    let
+      keyToUnitName = replaceChars
+        [ "/" "-"    " "     "+"     "="      ]
+        [ "-" "\\x2d" "\\x20" "\\x2b" "\\x3d" ];
+      unitName = keyToUnitName peer.publicKey;
+      psk =
+        if peer.presharedKey != null
+          then pkgs.writeText "wg-psk" peer.presharedKey
+          else peer.presharedKeyFile;
+      src = interfaceCfg.socketNamespace;
+      dst = interfaceCfg.interfaceNamespace;
+      ip = nsWrap "ip" src dst;
+      wg = nsWrap "wg" src dst;
+    in nameValuePair "wireguard-${interfaceName}-peer-${unitName}"
+      {
+        description = "WireGuard Peer - ${interfaceName} - ${peer.publicKey}";
+        requires = [ "wireguard-${interfaceName}.service" ];
+        after = [ "wireguard-${interfaceName}.service" ];
+        wantedBy = [ "multi-user.target" "wireguard-${interfaceName}.service" ];
+        environment.DEVICE = interfaceName;
+        environment.WG_ENDPOINT_RESOLUTION_RETRIES = "infinity";
+        path = with pkgs; [ iproute wireguard-tools ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        script = let
+          wg_setup = "${wg} set ${interfaceName} peer ${peer.publicKey}" +
+            optionalString (psk != null) " preshared-key ${psk}" +
+            optionalString (peer.endpoint != null) " endpoint ${peer.endpoint}" +
+            optionalString (peer.persistentKeepalive != null) " persistent-keepalive ${toString peer.persistentKeepalive}" +
+            optionalString (peer.allowedIPs != []) " allowed-ips ${concatStringsSep "," peer.allowedIPs}";
+          route_setup =
+            optionalString interfaceCfg.allowedIPsAsRoutes
+              (concatMapStringsSep "\n"
+                (allowedIP:
+                  "${ip} route replace ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+                ) peer.allowedIPs);
+        in ''
+          ${wg_setup}
+          ${route_setup}
+        '';
+
+        postStop = let
+          route_destroy = optionalString interfaceCfg.allowedIPsAsRoutes
+            (concatMapStringsSep "\n"
+              (allowedIP:
+                "${ip} route delete ${allowedIP} dev ${interfaceName} table ${interfaceCfg.table}"
+              ) peer.allowedIPs);
+        in ''
+          ${wg} set ${interfaceName} peer ${peer.publicKey} remove
+          ${route_destroy}
+        '';
+      };
+
+  generateInterfaceUnit = name: values:
+    # exactly one way to specify the private key must be set
+    #assert (values.privateKey != null) != (values.privateKeyFile != null);
+    let privKey = if values.privateKeyFile != null then values.privateKeyFile else pkgs.writeText "wg-key" values.privateKey;
+        src = values.socketNamespace;
+        dst = values.interfaceNamespace;
+        ipPreMove  = nsWrap "ip" src null;
+        ipPostMove = nsWrap "ip" src dst;
+        wg = nsWrap "wg" src dst;
+        ns = if dst == "init" then "1" else dst;
+
+    in
+    nameValuePair "wireguard-${name}"
+      {
+        description = "WireGuard Tunnel - ${name}";
+        requires = [ "network-online.target" ];
+        after = [ "network.target" "network-online.target" ];
+        wantedBy = [ "multi-user.target" ];
+        environment.DEVICE = name;
+        path = with pkgs; [ kmod iproute wireguard-tools ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+
+        script = ''
+          ${optionalString (!config.boot.isContainer) "modprobe wireguard || true"}
+
+          ${values.preSetup}
+
+          ${ipPreMove} link add dev ${name} type wireguard
+          ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) "${ipPreMove} link set ${name} netns ${ns}"}
+
+          ${concatMapStringsSep "\n" (ip:
+            "${ipPostMove} address add ${ip} dev ${name}"
+          ) values.ips}
+
+          ${wg} set ${name} private-key ${privKey} ${
+            optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}"}
+
+          ${ipPostMove} link set up dev ${name}
+
+          ${values.postSetup}
+        '';
+
+        postStop = ''
+          ${ipPostMove} link del dev ${name}
+          ${values.postShutdown}
+        '';
+      };
+
+  nsWrap = cmd: src: dst:
+    let
+      nsList = filter (ns: ns != null) [ src dst ];
+      ns = last nsList;
+    in
+      if (length nsList > 0 && ns != "init") then "ip netns exec ${ns} ${cmd}" else cmd;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    networking.wireguard = {
+
+      enable = mkOption {
+        description = "Whether to enable WireGuard.";
+        type = types.bool;
+        # 2019-05-25: Backwards compatibility.
+        default = cfg.interfaces != {};
+        example = true;
+      };
+
+      interfaces = mkOption {
+        description = "WireGuard interfaces.";
+        default = {};
+        example = {
+          wg0 = {
+            ips = [ "192.168.20.4/24" ];
+            privateKey = "yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=";
+            peers = [
+              { allowedIPs = [ "192.168.20.1/32" ];
+                publicKey  = "xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=";
+                endpoint   = "demo.wireguard.io:12913"; }
+            ];
+          };
+        };
+        type = with types; attrsOf (submodule interfaceOpts);
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable (let
+    all_peers = flatten
+      (mapAttrsToList (interfaceName: interfaceCfg:
+        map (peer: { inherit interfaceName interfaceCfg peer;}) interfaceCfg.peers
+      ) cfg.interfaces);
+  in {
+
+    assertions = (attrValues (
+        mapAttrs (name: value: {
+          assertion = (value.privateKey != null) != (value.privateKeyFile != null);
+          message = "Either networking.wireguard.interfaces.${name}.privateKey or networking.wireguard.interfaces.${name}.privateKeyFile must be set.";
+        }) cfg.interfaces))
+      ++ (attrValues (
+        mapAttrs (name: value: {
+          assertion = value.generatePrivateKeyFile -> (value.privateKey == null);
+          message = "networking.wireguard.interfaces.${name}.generatePrivateKeyFile must not be set if networking.wireguard.interfaces.${name}.privateKey is set.";
+        }) cfg.interfaces))
+        ++ map ({ interfaceName, peer, ... }: {
+          assertion = (peer.presharedKey == null) || (peer.presharedKeyFile == null);
+          message = "networking.wireguard.interfaces.${interfaceName} peer «${peer.publicKey}» has both presharedKey and presharedKeyFile set, but only one can be used.";
+        }) all_peers;
+
+    boot.extraModulePackages = optional (versionOlder kernel.kernel.version "5.6") kernel.wireguard;
+    environment.systemPackages = [ pkgs.wireguard-tools ];
+
+    systemd.services =
+      (mapAttrs' generateInterfaceUnit cfg.interfaces)
+      // (listToAttrs (map generatePeerUnit all_peers))
+      // (mapAttrs' generateKeyServiceUnit
+      (filterAttrs (name: value: value.generatePrivateKeyFile) cfg.interfaces));
+
+    systemd.paths = mapAttrs' generatePathUnit
+      (filterAttrs (name: value: value.privateKeyFile != null) cfg.interfaces);
+
+  });
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix b/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
new file mode 100644
index 000000000000..a7dea95056a0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/wpa_supplicant.nix
@@ -0,0 +1,266 @@
+{ config, lib, pkgs, utils, ... }:
+
+with lib;
+
+let
+  cfg = config.networking.wireless;
+  configFile = if cfg.networks != {} then pkgs.writeText "wpa_supplicant.conf" ''
+    ${optionalString cfg.userControlled.enable ''
+      ctrl_interface=DIR=/run/wpa_supplicant GROUP=${cfg.userControlled.group}
+      update_config=1''}
+    ${cfg.extraConfig}
+    ${concatStringsSep "\n" (mapAttrsToList (ssid: config: with config; let
+      key = if psk != null
+        then ''"${psk}"''
+        else pskRaw;
+      baseAuth = if key != null
+        then ''psk=${key}''
+        else ''key_mgmt=NONE'';
+    in ''
+      network={
+        ssid="${ssid}"
+        ${optionalString (priority != null) ''priority=${toString priority}''}
+        ${optionalString hidden "scan_ssid=1"}
+        ${if (auth != null) then auth else baseAuth}
+        ${extraConfig}
+      }
+    '') cfg.networks)}
+  '' else "/etc/wpa_supplicant.conf";
+in {
+  options = {
+    networking.wireless = {
+      enable = mkEnableOption "wpa_supplicant";
+
+      interfaces = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "wlan0" "wlan1" ];
+        description = ''
+          The interfaces <command>wpa_supplicant</command> will use. If empty, it will
+          automatically use all wireless interfaces.
+        '';
+      };
+
+      driver = mkOption {
+        type = types.str;
+        default = "nl80211,wext";
+        description = "Force a specific wpa_supplicant driver.";
+      };
+
+      networks = mkOption {
+        type = types.attrsOf (types.submodule {
+          options = {
+            psk = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                The network's pre-shared key in plaintext defaulting
+                to being a network without any authentication.
+
+                Be aware that these will be written to the nix store
+                in plaintext!
+
+                Mutually exclusive with <varname>pskRaw</varname>.
+              '';
+            };
+
+            pskRaw = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                The network's pre-shared key in hex defaulting
+                to being a network without any authentication.
+
+                Mutually exclusive with <varname>psk</varname>.
+              '';
+            };
+
+            auth = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = ''
+                key_mgmt=WPA-EAP
+                eap=PEAP
+                identity="user@example.com"
+                password="secret"
+              '';
+              description = ''
+                Use this option to configure advanced authentication methods like EAP.
+                See
+                <citerefentry>
+                  <refentrytitle>wpa_supplicant.conf</refentrytitle>
+                  <manvolnum>5</manvolnum>
+                </citerefentry>
+                for example configurations.
+
+                Mutually exclusive with <varname>psk</varname> and <varname>pskRaw</varname>.
+              '';
+            };
+
+            hidden = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Set this to <literal>true</literal> if the SSID of the network is hidden.
+              '';
+              example = literalExample ''
+                { echelon = {
+                    hidden = true;
+                    psk = "abcdefgh";
+                  };
+                }
+              '';
+            };
+
+            priority = mkOption {
+              type = types.nullOr types.int;
+              default = null;
+              description = ''
+                By default, all networks will get same priority group (0). If some of the
+                networks are more desirable, this field can be used to change the order in
+                which wpa_supplicant goes through the networks when selecting a BSS. The
+                priority groups will be iterated in decreasing priority (i.e., the larger the
+                priority value, the sooner the network is matched against the scan results).
+                Within each priority group, networks will be selected based on security
+                policy, signal strength, etc.
+              '';
+            };
+
+            extraConfig = mkOption {
+              type = types.str;
+              default = "";
+              example = ''
+                bssid_blacklist=02:11:22:33:44:55 02:22:aa:44:55:66
+              '';
+              description = ''
+                Extra configuration lines appended to the network block.
+                See
+                <citerefentry>
+                  <refentrytitle>wpa_supplicant.conf</refentrytitle>
+                  <manvolnum>5</manvolnum>
+                </citerefentry>
+                for available options.
+              '';
+            };
+
+          };
+        });
+        description = ''
+          The network definitions to automatically connect to when
+           <command>wpa_supplicant</command> is running. If this
+           parameter is left empty wpa_supplicant will use
+          /etc/wpa_supplicant.conf as the configuration file.
+        '';
+        default = {};
+        example = literalExample ''
+          { echelon = {                   # SSID with no spaces or special characters
+              psk = "abcdefgh";
+            };
+            "echelon's AP" = {            # SSID with spaces and/or special characters
+               psk = "ijklmnop";
+            };
+            "free.wifi" = {};             # Public wireless network
+          }
+        '';
+      };
+
+      userControlled = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
+            This is useful for laptop users that switch networks a lot and don't want
+            to depend on a large package such as NetworkManager just to pick nearby
+            access points.
+
+            When using a declarative network specification you cannot persist any
+            settings via wpa_gui or wpa_cli.
+          '';
+        };
+
+        group = mkOption {
+          type = types.str;
+          default = "wheel";
+          example = "network";
+          description = "Members of this group can control wpa_supplicant.";
+        };
+      };
+      extraConfig = mkOption {
+        type = types.str;
+        default = "";
+        example = ''
+          p2p_disabled=1
+        '';
+        description = ''
+          Extra lines appended to the configuration file.
+          See
+          <citerefentry>
+            <refentrytitle>wpa_supplicant.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for available options.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = flip mapAttrsToList cfg.networks (name: cfg: {
+      assertion = with cfg; count (x: x != null) [ psk pskRaw auth ] <= 1;
+      message = ''options networking.wireless."${name}".{psk,pskRaw,auth} are mutually exclusive'';
+    });
+
+    environment.systemPackages =  [ pkgs.wpa_supplicant ];
+
+    services.dbus.packages = [ pkgs.wpa_supplicant ];
+    services.udev.packages = [ pkgs.crda ];
+
+    # FIXME: start a separate wpa_supplicant instance per interface.
+    systemd.services.wpa_supplicant = let
+      ifaces = cfg.interfaces;
+      deviceUnit = interface: [ "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device" ];
+    in {
+      description = "WPA Supplicant";
+
+      after = lib.concatMap deviceUnit ifaces;
+      before = [ "network.target" ];
+      wants = [ "network.target" ];
+      requires = lib.concatMap deviceUnit ifaces;
+      wantedBy = [ "multi-user.target" ];
+      stopIfChanged = false;
+
+      path = [ pkgs.wpa_supplicant ];
+
+      script = ''
+        iface_args="-s -u -D${cfg.driver} -c ${configFile}"
+        ${if ifaces == [] then ''
+          for i in $(cd /sys/class/net && echo *); do
+            DEVTYPE=
+            UEVENT_PATH=/sys/class/net/$i/uevent
+            if [ -e "$UEVENT_PATH" ]; then
+              source "$UEVENT_PATH"
+              if [ "$DEVTYPE" = "wlan" -o -e /sys/class/net/$i/wireless ]; then
+                args+="''${args:+ -N} -i$i $iface_args"
+              fi
+            fi
+          done
+        '' else ''
+          args="${concatMapStringsSep " -N " (i: "-i${i} $iface_args") ifaces}"
+        ''}
+        exec wpa_supplicant $args
+      '';
+    };
+
+    powerManagement.resumeCommands = ''
+      /run/current-system/systemd/bin/systemctl try-restart wpa_supplicant
+    '';
+
+    # Restart wpa_supplicant when a wlan device appears or disappears.
+    services.udev.extraRules = ''
+      ACTION=="add|remove", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", RUN+="/run/current-system/systemd/bin/systemctl try-restart wpa_supplicant.service"
+    '';
+  };
+
+  meta.maintainers = with lib.maintainers; [ globin ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/xandikos.nix b/nixpkgs/nixos/modules/services/networking/xandikos.nix
new file mode 100644
index 000000000000..87c029156b9e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/xandikos.nix
@@ -0,0 +1,148 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xandikos;
+in
+{
+
+  options = {
+    services.xandikos = {
+      enable = mkEnableOption "Xandikos CalDAV and CardDAV server";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.xandikos;
+        defaultText = "pkgs.xandikos";
+        description = "The Xandikos package to use.";
+      };
+
+      address = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          The IP address on which Xandikos will listen.
+          By default listens on localhost.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 8080;
+        description = "The port of the Xandikos web application";
+      };
+
+      routePrefix = mkOption {
+        type = types.str;
+        default = "/";
+        description = ''
+          Path to Xandikos.
+          Useful when Xandikos is behind a reverse proxy.
+        '';
+      };
+
+      extraOptions = mkOption {
+        default = [];
+        type = types.listOf types.str;
+        example = literalExample ''
+          [ "--autocreate"
+            "--defaults"
+            "--current-user-principal user"
+            "--dump-dav-xml"
+          ]
+        '';
+        description = ''
+          Extra command line arguments to pass to xandikos.
+        '';
+      };
+
+      nginx = mkOption {
+        default = {};
+        description = ''
+          Configuration for nginx reverse proxy.
+        '';
+
+        type = types.submodule {
+          options = {
+            enable = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Configure the nginx reverse proxy settings.
+              '';
+            };
+
+            hostName = mkOption {
+              type = types.str;
+              description = ''
+                The hostname use to setup the virtualhost configuration
+              '';
+            };
+          };
+        };
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable (
+    mkMerge [
+      {
+        meta.maintainers = [ lib.maintainers."0x4A6F" ];
+
+        systemd.services.xandikos = {
+          description = "A Simple Calendar and Contact Server";
+          after = [ "network.target" ];
+          wantedBy = [ "multi-user.target" ];
+
+          serviceConfig = {
+            User = "xandikos";
+            Group = "xandikos";
+            DynamicUser = "yes";
+            RuntimeDirectory = "xandikos";
+            StateDirectory = "xandikos";
+            StateDirectoryMode = "0700";
+            PrivateDevices = true;
+            # Sandboxing
+            CapabilityBoundingSet = "CAP_NET_RAW CAP_NET_ADMIN";
+            ProtectSystem = "strict";
+            ProtectHome = true;
+            PrivateTmp = true;
+            ProtectKernelTunables = true;
+            ProtectKernelModules = true;
+            ProtectControlGroups = true;
+            RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_PACKET AF_NETLINK";
+            RestrictNamespaces = true;
+            LockPersonality = true;
+            MemoryDenyWriteExecute = true;
+            RestrictRealtime = true;
+            RestrictSUIDSGID = true;
+            ExecStart = ''
+              ${cfg.package}/bin/xandikos \
+                --directory /var/lib/xandikos \
+                --listen_address ${cfg.address} \
+                --port ${toString cfg.port} \
+                --route-prefix ${cfg.routePrefix} \
+                ${lib.concatStringsSep " " cfg.extraOptions}
+            '';
+          };
+        };
+      }
+
+      (
+        mkIf cfg.nginx.enable {
+          services.nginx = {
+            enable = true;
+            virtualHosts."${cfg.nginx.hostName}" = {
+              locations."/" = {
+                proxyPass = "http://${cfg.address}:${toString cfg.port}/";
+              };
+            };
+          };
+        }
+      )
+    ]
+  );
+}
diff --git a/nixpkgs/nixos/modules/services/networking/xinetd.nix b/nixpkgs/nixos/modules/services/networking/xinetd.nix
new file mode 100644
index 000000000000..2f527ab156aa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/xinetd.nix
@@ -0,0 +1,147 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xinetd;
+
+  configFile = pkgs.writeText "xinetd.conf"
+    ''
+      defaults
+      {
+        log_type       = SYSLOG daemon info
+        log_on_failure = HOST
+        log_on_success = PID HOST DURATION EXIT
+        ${cfg.extraDefaults}
+      }
+
+      ${concatMapStrings makeService cfg.services}
+    '';
+
+  makeService = srv:
+    ''
+      service ${srv.name}
+      {
+        protocol    = ${srv.protocol}
+        ${optionalString srv.unlisted "type        = UNLISTED"}
+        ${optionalString (srv.flags != "") "flags = ${srv.flags}"}
+        socket_type = ${if srv.protocol == "udp" then "dgram" else "stream"}
+        ${if srv.port != 0 then "port        = ${toString srv.port}" else ""}
+        wait        = ${if srv.protocol == "udp" then "yes" else "no"}
+        user        = ${srv.user}
+        server      = ${srv.server}
+        ${optionalString (srv.serverArgs != "") "server_args = ${srv.serverArgs}"}
+        ${srv.extraConfig}
+      }
+    '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.xinetd.enable = mkEnableOption "the xinetd super-server daemon";
+
+    services.xinetd.extraDefaults = mkOption {
+      default = "";
+      type = types.lines;
+      description = ''
+        Additional configuration lines added to the default section of xinetd's configuration.
+      '';
+    };
+
+    services.xinetd.services = mkOption {
+      default = [];
+      description = ''
+        A list of services provided by xinetd.
+      '';
+
+      type = with types; listOf (submodule ({
+
+        options = {
+
+          name = mkOption {
+            type = types.str;
+            example = "login";
+            description = "Name of the service.";
+          };
+
+          protocol = mkOption {
+            type = types.str;
+            default = "tcp";
+            description =
+              "Protocol of the service.  Usually <literal>tcp</literal> or <literal>udp</literal>.";
+          };
+
+          port = mkOption {
+            type = types.int;
+            default = 0;
+            example = 123;
+            description = "Port number of the service.";
+          };
+
+          user = mkOption {
+            type = types.str;
+            default = "nobody";
+            description = "User account for the service";
+          };
+
+          server = mkOption {
+            type = types.str;
+            example = "/foo/bin/ftpd";
+            description = "Path of the program that implements the service.";
+          };
+
+          serverArgs = mkOption {
+            type = types.separatedString " ";
+            default = "";
+            description = "Command-line arguments for the server program.";
+          };
+
+          flags = mkOption {
+            type = types.str;
+            default = "";
+            description = "";
+          };
+
+          unlisted = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Whether this server is listed in
+              <filename>/etc/services</filename>.  If so, the port
+              number can be omitted.
+            '';
+          };
+
+          extraConfig = mkOption {
+            type = types.lines;
+            default = "";
+            description = "Extra configuration-lines added to the section of the service.";
+          };
+
+        };
+
+      }));
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.xinetd = {
+      description = "xinetd server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.xinetd ];
+      script = "exec xinetd -syslog daemon -dontfork -stayalive -f ${configFile}";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/xl2tpd.nix b/nixpkgs/nixos/modules/services/networking/xl2tpd.nix
new file mode 100644
index 000000000000..7dbe51422d96
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/xl2tpd.nix
@@ -0,0 +1,143 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+  options = {
+    services.xl2tpd = {
+      enable = mkEnableOption "xl2tpd, the Layer 2 Tunnelling Protocol Daemon";
+
+      serverIp = mkOption {
+        type        = types.str;
+        description = "The server-side IP address.";
+        default     = "10.125.125.1";
+      };
+
+      clientIpRange = mkOption {
+        type        = types.str;
+        description = "The range from which client IPs are drawn.";
+        default     = "10.125.125.2-11";
+      };
+
+      extraXl2tpOptions = mkOption {
+        type        = types.lines;
+        description = "Adds extra lines to the xl2tpd configuration file.";
+        default     = "";
+      };
+
+      extraPppdOptions = mkOption {
+        type        = types.lines;
+        description = "Adds extra lines to the pppd options file.";
+        default     = "";
+        example     = ''
+          ms-dns 8.8.8.8
+          ms-dns 8.8.4.4
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.xl2tpd.enable {
+    systemd.services.xl2tpd = let
+      cfg = config.services.xl2tpd;
+
+      # Config files from https://help.ubuntu.com/community/L2TPServer
+      xl2tpd-conf = pkgs.writeText "xl2tpd.conf" ''
+        [global]
+        ipsec saref = no
+
+        [lns default]
+        local ip = ${cfg.serverIp}
+        ip range = ${cfg.clientIpRange}
+        pppoptfile = ${pppd-options}
+        length bit = yes
+
+        ; Extra
+        ${cfg.extraXl2tpOptions}
+      '';
+
+      pppd-options = pkgs.writeText "ppp-options-xl2tpd.conf" ''
+        refuse-pap
+        refuse-chap
+        refuse-mschap
+        require-mschap-v2
+        # require-mppe-128
+        asyncmap 0
+        auth
+        crtscts
+        idle 1800
+        mtu 1200
+        mru 1200
+        lock
+        hide-password
+        local
+        # debug
+        name xl2tpd
+        # proxyarp
+        lcp-echo-interval 30
+        lcp-echo-failure 4
+
+        # Extra:
+        ${cfg.extraPppdOptions}
+      '';
+
+      xl2tpd-ppp-wrapped = pkgs.stdenv.mkDerivation {
+        name         = "xl2tpd-ppp-wrapped";
+        phases       = [ "installPhase" ];
+        buildInputs  = with pkgs; [ makeWrapper ];
+        installPhase = ''
+          mkdir -p $out/bin
+
+          makeWrapper ${pkgs.ppp}/sbin/pppd $out/bin/pppd \
+            --set LD_PRELOAD    "${pkgs.libredirect}/lib/libredirect.so" \
+            --set NIX_REDIRECTS "/etc/ppp=/etc/xl2tpd/ppp"
+
+          makeWrapper ${pkgs.xl2tpd}/bin/xl2tpd $out/bin/xl2tpd \
+            --set LD_PRELOAD    "${pkgs.libredirect}/lib/libredirect.so" \
+            --set NIX_REDIRECTS "${pkgs.ppp}/sbin/pppd=$out/bin/pppd"
+        '';
+      };
+    in {
+      description = "xl2tpd server";
+
+      requires = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        mkdir -p -m 700 /etc/xl2tpd
+
+        pushd /etc/xl2tpd > /dev/null
+
+        mkdir -p -m 700 ppp
+
+        [ -f ppp/chap-secrets ] || cat > ppp/chap-secrets << EOF
+        # Secrets for authentication using CHAP
+        # client	server	secret		IP addresses
+        #username	xl2tpd	password	*
+        EOF
+
+        chown root.root ppp/chap-secrets
+        chmod 600 ppp/chap-secrets
+
+        # The documentation says this file should be present but doesn't explain why and things work even if not there:
+        [ -f l2tp-secrets ] || (echo -n "* * "; ${pkgs.apg}/bin/apg -n 1 -m 32 -x 32 -a 1 -M LCN) > l2tp-secrets
+        chown root.root l2tp-secrets
+        chmod 600 l2tp-secrets
+
+        popd > /dev/null
+
+        mkdir -p /run/xl2tpd
+        chown root.root /run/xl2tpd
+        chmod 700       /run/xl2tpd
+      '';
+
+      serviceConfig = {
+        ExecStart = "${xl2tpd-ppp-wrapped}/bin/xl2tpd -D -c ${xl2tpd-conf} -s /etc/xl2tpd/l2tp-secrets -p /run/xl2tpd/pid -C /run/xl2tpd/control";
+        KillMode  = "process";
+        Restart   = "on-success";
+        Type      = "simple";
+        PIDFile   = "/run/xl2tpd/pid";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/xrdp.nix b/nixpkgs/nixos/modules/services/networking/xrdp.nix
new file mode 100644
index 000000000000..b7dd1c5d99dd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/xrdp.nix
@@ -0,0 +1,171 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xrdp;
+  confDir = pkgs.runCommand "xrdp.conf" { preferLocalBuild = true; } ''
+    mkdir $out
+
+    cp ${cfg.package}/etc/xrdp/{km-*,xrdp,sesman,xrdp_keyboard}.ini $out
+
+    cat > $out/startwm.sh <<EOF
+    #!/bin/sh
+    . /etc/profile
+    ${cfg.defaultWindowManager}
+    EOF
+    chmod +x $out/startwm.sh
+
+    substituteInPlace $out/xrdp.ini \
+      --replace "#rsakeys_ini=" "rsakeys_ini=/run/xrdp/rsakeys.ini" \
+      --replace "certificate=" "certificate=${cfg.sslCert}" \
+      --replace "key_file=" "key_file=${cfg.sslKey}" \
+      --replace LogFile=xrdp.log LogFile=/dev/null \
+      --replace EnableSyslog=true EnableSyslog=false
+
+    substituteInPlace $out/sesman.ini \
+      --replace LogFile=xrdp-sesman.log LogFile=/dev/null \
+      --replace EnableSyslog=1 EnableSyslog=0
+
+    # Ensure that clipboard works for non-ASCII characters
+    sed -i -e '/.*SessionVariables.*/ a\
+    LANG=${config.i18n.defaultLocale}\
+    LOCALE_ARCHIVE=${config.i18n.glibcLocales}/lib/locale/locale-archive
+    ' $out/sesman.ini
+  '';
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.xrdp = {
+
+      enable = mkEnableOption "xrdp, the Remote Desktop Protocol server";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.xrdp;
+        defaultText = "pkgs.xrdp";
+        description = ''
+          The package to use for the xrdp daemon's binary.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 3389;
+        description = ''
+          Specifies on which port the xrdp daemon listens.
+        '';
+      };
+
+      sslKey = mkOption {
+        type = types.str;
+        default = "/etc/xrdp/key.pem";
+        example = "/path/to/your/key.pem";
+        description = ''
+          ssl private key path
+          A self-signed certificate will be generated if file not exists.
+        '';
+      };
+
+      sslCert = mkOption {
+        type = types.str;
+        default = "/etc/xrdp/cert.pem";
+        example = "/path/to/your/cert.pem";
+        description = ''
+          ssl certificate path
+          A self-signed certificate will be generated if file not exists.
+        '';
+      };
+
+      defaultWindowManager = mkOption {
+        type = types.str;
+        default = "xterm";
+        example = "xfce4-session";
+        description = ''
+          The script to run when user log in, usually a window manager, e.g. "icewm", "xfce4-session"
+          This is per-user overridable, if file ~/startwm.sh exists it will be used instead.
+        '';
+      };
+
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    # xrdp can run X11 program even if "services.xserver.enable = false"
+    xdg = {
+      autostart.enable = true;
+      menus.enable = true;
+      mime.enable = true;
+      icons.enable = true;
+    };
+
+    fonts.enableDefaultFonts = mkDefault true;
+
+    systemd = {
+      services.xrdp = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        description = "xrdp daemon";
+        requires = [ "xrdp-sesman.service" ];
+        preStart = ''
+          # prepare directory for unix sockets (the sockets will be owned by loggedinuser:xrdp)
+          mkdir -p /tmp/.xrdp || true
+          chown xrdp:xrdp /tmp/.xrdp
+          chmod 3777 /tmp/.xrdp
+
+          # generate a self-signed certificate
+          if [ ! -s ${cfg.sslCert} -o ! -s ${cfg.sslKey} ]; then
+            mkdir -p $(dirname ${cfg.sslCert}) || true
+            mkdir -p $(dirname ${cfg.sslKey}) || true
+            ${pkgs.openssl.bin}/bin/openssl req -x509 -newkey rsa:2048 -sha256 -nodes -days 365 \
+              -subj /C=US/ST=CA/L=Sunnyvale/O=xrdp/CN=www.xrdp.org \
+              -config ${cfg.package}/share/xrdp/openssl.conf \
+              -keyout ${cfg.sslKey} -out ${cfg.sslCert}
+            chown root:xrdp ${cfg.sslKey} ${cfg.sslCert}
+            chmod 440 ${cfg.sslKey} ${cfg.sslCert}
+          fi
+          if [ ! -s /run/xrdp/rsakeys.ini ]; then
+            mkdir -p /run/xrdp
+            ${cfg.package}/bin/xrdp-keygen xrdp /run/xrdp/rsakeys.ini
+          fi
+        '';
+        serviceConfig = {
+          User = "xrdp";
+          Group = "xrdp";
+          PermissionsStartOnly = true;
+          ExecStart = "${cfg.package}/bin/xrdp --nodaemon --port ${toString cfg.port} --config ${confDir}/xrdp.ini";
+        };
+      };
+
+      services.xrdp-sesman = {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" ];
+        description = "xrdp session manager";
+        restartIfChanged = false; # do not restart on "nixos-rebuild switch". like "display-manager", it can have many interactive programs as children
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/xrdp-sesman --nodaemon --config ${confDir}/sesman.ini";
+          ExecStop  = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+        };
+      };
+
+    };
+
+    users.users.xrdp = {
+      description   = "xrdp daemon user";
+      isSystemUser  = true;
+      group         = "xrdp";
+    };
+    users.groups.xrdp = {};
+
+    security.pam.services.xrdp-sesman = { allowNullPassword = true; startSession = true; };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/networking/yggdrasil.nix b/nixpkgs/nixos/modules/services/networking/yggdrasil.nix
new file mode 100644
index 000000000000..0fe9a200a1b6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/yggdrasil.nix
@@ -0,0 +1,199 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  keysPath = "/var/lib/yggdrasil/keys.json";
+
+  cfg = config.services.yggdrasil;
+  configProvided = cfg.config != { };
+  configFileProvided = cfg.configFile != null;
+
+in {
+  options = with types; {
+    services.yggdrasil = {
+      enable = mkEnableOption "the yggdrasil system service";
+
+      config = mkOption {
+        type = attrs;
+        default = {};
+        example = {
+          Peers = [
+            "tcp://aa.bb.cc.dd:eeeee"
+            "tcp://[aaaa:bbbb:cccc:dddd::eeee]:fffff"
+          ];
+          Listen = [
+            "tcp://0.0.0.0:xxxxx"
+          ];
+        };
+        description = ''
+          Configuration for yggdrasil, as a Nix attribute set.
+
+          Warning: this is stored in the WORLD-READABLE Nix store!
+          Therefore, it is not appropriate for private keys. If you
+          wish to specify the keys, use <option>configFile</option>.
+
+          If the <option>persistentKeys</option> is enabled then the
+          keys that are generated during activation will override
+          those in <option>config</option> or
+          <option>configFile</option>.
+
+          If no keys are specified then ephemeral keys are generated
+          and the Yggdrasil interface will have a random IPv6 address
+          each time the service is started, this is the default.
+
+          If both <option>configFile</option> and <option>config</option>
+          are supplied, they will be combined, with values from
+          <option>configFile</option> taking precedence.
+
+          You can use the command <code>nix-shell -p yggdrasil --run
+          "yggdrasil -genconf"</code> to generate default
+          configuration values with documentation.
+        '';
+      };
+
+      configFile = mkOption {
+        type = nullOr path;
+        default = null;
+        example = "/run/keys/yggdrasil.conf";
+        description = ''
+          A file which contains JSON configuration for yggdrasil.
+          See the <option>config</option> option for more information.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "root";
+        example = "wheel";
+        description = "Group to grant acces to the Yggdrasil control socket.";
+      };
+
+      openMulticastPort = mkOption {
+        type = bool;
+        default = false;
+        description = ''
+          Whether to open the UDP port used for multicast peer
+          discovery. The NixOS firewall blocks link-local
+          communication, so in order to make local peering work you
+          will also need to set <code>LinkLocalTCPPort</code> in your
+          yggdrasil configuration (<option>config</option> or
+          <option>configFile</option>) to a port number other than 0,
+          and then add that port to
+          <option>networking.firewall.allowedTCPPorts</option>.
+        '';
+      };
+
+      denyDhcpcdInterfaces = mkOption {
+        type = listOf str;
+        default = [];
+        example = [ "tap*" ];
+        description = ''
+          Disable the DHCP client for any interface whose name matches
+          any of the shell glob patterns in this list.  Use this
+          option to prevent the DHCP client from broadcasting requests
+          on the yggdrasil network.  It is only necessary to do so
+          when yggdrasil is running in TAP mode, because TUN
+          interfaces do not support broadcasting.
+        '';
+      };
+
+      package = mkOption {
+        type = package;
+        default = pkgs.yggdrasil;
+        defaultText = "pkgs.yggdrasil";
+        description = "Yggdrasil package to use.";
+      };
+
+      persistentKeys = mkEnableOption ''
+        If enabled then keys will be generated once and Yggdrasil
+        will retain the same IPv6 address when the service is
+        restarted. Keys are stored at ${keysPath}.
+      '';
+
+    };
+  };
+
+  config = mkIf cfg.enable (let binYggdrasil = cfg.package + "/bin/yggdrasil";
+  in {
+    assertions = [{
+      assertion = config.networking.enableIPv6;
+      message = "networking.enableIPv6 must be true for yggdrasil to work";
+    }];
+
+    system.activationScripts.yggdrasil = mkIf cfg.persistentKeys ''
+      if [ ! -e ${keysPath} ]
+      then
+        mkdir -p ${builtins.dirOf keysPath}
+        ${binYggdrasil} -genconf -json \
+          | ${pkgs.jq}/bin/jq \
+              'to_entries|map(select(.key|endswith("Key")))|from_entries' \
+          > ${keysPath}
+        chmod 600 ${keysPath}
+      fi
+    '';
+
+    systemd.services.yggdrasil = {
+      description = "Yggdrasil Network Service";
+      bindsTo = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart =
+        (if configProvided || configFileProvided || cfg.persistentKeys then
+          "echo "
+
+          + (lib.optionalString configProvided
+            "'${builtins.toJSON cfg.config}'")
+          + (lib.optionalString configFileProvided "$(cat ${cfg.configFile})")
+          + (lib.optionalString cfg.persistentKeys "$(cat ${keysPath})")
+          + " | ${pkgs.jq}/bin/jq -s add | ${binYggdrasil} -normaliseconf -useconf"
+        else
+          "${binYggdrasil} -genconf") + " > /run/yggdrasil/yggdrasil.conf";
+
+      serviceConfig = {
+        ExecStart =
+          "${binYggdrasil} -useconffile /run/yggdrasil/yggdrasil.conf";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+
+        Group = cfg.group;
+        RuntimeDirectory = "yggdrasil";
+        RuntimeDirectoryMode = "0750";
+        BindReadOnlyPaths = lib.optional configFileProvided cfg.configFile
+          ++ lib.optional cfg.persistentKeys keysPath;
+
+        # TODO: as of yggdrasil 0.3.8 and systemd 243, yggdrasil fails
+        # to set up the network adapter when DynamicUser is set.  See
+        # github.com/yggdrasil-network/yggdrasil-go/issues/557.  The
+        # following options are implied by DynamicUser according to
+        # the systemd.exec documentation, and can be removed if the
+        # upstream issue is fixed and DynamicUser is set to true:
+        PrivateTmp = true;
+        RemoveIPC = true;
+        NoNewPrivileges = true;
+        ProtectSystem = "strict";
+        RestrictSUIDSGID = true;
+        # End of list of options implied by DynamicUser.
+
+        AmbientCapabilities = "CAP_NET_ADMIN";
+        CapabilityBoundingSet = "CAP_NET_ADMIN";
+        MemoryDenyWriteExecute = true;
+        ProtectControlGroups = true;
+        ProtectHome = "tmpfs";
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "~@clock @cpu-emulation @debug @keyring @module @mount @obsolete @raw-io @resources";
+      };
+    };
+
+    networking.dhcpcd.denyInterfaces = cfg.denyDhcpcdInterfaces;
+    networking.firewall.allowedUDPPorts = mkIf cfg.openMulticastPort [ 9001 ];
+
+    # Make yggdrasilctl available on the command line.
+    environment.systemPackages = [ cfg.package ];
+  });
+  meta.maintainers = with lib.maintainers; [ gazally ehmry ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/zerobin.nix b/nixpkgs/nixos/modules/services/networking/zerobin.nix
new file mode 100644
index 000000000000..78de246a816f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/zerobin.nix
@@ -0,0 +1,102 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+  cfg = config.services.zerobin;
+
+  zerobin_config = pkgs.writeText "zerobin-config.py" ''
+  PASTE_FILES_ROOT = "${cfg.dataDir}"
+  ${cfg.extraConfig}
+  '';
+
+in
+  {
+    options = {
+      services.zerobin = {
+        enable = mkEnableOption "0bin";
+
+        dataDir = mkOption {
+          type = types.str;
+          default = "/var/lib/zerobin";
+          description = ''
+          Path to the 0bin data directory
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "zerobin";
+          description = ''
+          The user 0bin should run as
+          '';
+        };
+
+        group = mkOption {
+          type = types.str;
+          default = "zerobin";
+          description = ''
+          The group 0bin should run as
+          '';
+        };
+
+        listenPort = mkOption {
+          type = types.int;
+          default = 8000;
+          example = 1357;
+          description = ''
+          The port zerobin should listen on
+          '';
+        };
+
+        listenAddress = mkOption {
+          type = types.str;
+          default = "localhost";
+          example = "127.0.0.1";
+          description = ''
+          The address zerobin should listen to
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          example = ''
+          MENU = (
+          ('Home', '/'),
+          )
+          COMPRESSED_STATIC_FILE = True
+          '';
+          description = ''
+          Extra configuration to be appended to the 0bin config file
+          (see https://0bin.readthedocs.org/en/latest/en/options.html)
+          '';
+        };
+      };
+    };
+
+    config = mkIf (cfg.enable) {
+      users.users.${cfg.user} =
+      if cfg.user == "zerobin" then {
+        isSystemUser = true;
+        group = cfg.group;
+        home = cfg.dataDir;
+        createHome = true;
+      }
+      else {};
+      users.groups.${cfg.group} = {};
+
+      systemd.services.zerobin = {
+        enable = true;
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig.ExecStart = "${pkgs.pythonPackages.zerobin}/bin/zerobin ${cfg.listenAddress} ${toString cfg.listenPort} false ${cfg.user} ${cfg.group} ${zerobin_config}";
+        serviceConfig.PrivateTmp="yes";
+        serviceConfig.User = cfg.user;
+        serviceConfig.Group = cfg.group;
+        preStart = ''
+          mkdir -p ${cfg.dataDir}
+          chown ${cfg.user} ${cfg.dataDir}
+        '';
+      };
+    };
+  }
+
diff --git a/nixpkgs/nixos/modules/services/networking/zeronet.nix b/nixpkgs/nixos/modules/services/networking/zeronet.nix
new file mode 100644
index 000000000000..f354a9d42c79
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/zeronet.nix
@@ -0,0 +1,96 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) generators literalExample mkEnableOption mkIf mkOption recursiveUpdate types;
+  cfg = config.services.zeronet;
+  dataDir = "/var/lib/zeronet";
+  configFile = pkgs.writeText "zeronet.conf" (generators.toINI {} (recursiveUpdate defaultSettings cfg.settings));
+
+  defaultSettings = {
+    global = {
+      data_dir = dataDir;
+      log_dir = dataDir;
+      ui_port = cfg.port;
+      fileserver_port = cfg.fileserverPort;
+      tor = if !cfg.tor then "disable" else if cfg.torAlways then "always" else "enable";
+    };
+  };
+in with lib; {
+  options.services.zeronet = {
+    enable = mkEnableOption "zeronet";
+
+    settings = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
+      default = {};
+      example = literalExample "global.tor = enable;";
+
+      description = ''
+        <filename>zeronet.conf</filename> configuration. Refer to
+        <link xlink:href="https://zeronet.readthedocs.io/en/latest/faq/#is-it-possible-to-use-a-configuration-file"/>
+        for details on supported values;
+      '';
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 43110;
+      example = 43110;
+      description = "Optional zeronet web UI port.";
+    };
+
+    fileserverPort = mkOption {
+      # Not optional: when absent zeronet tries to write one to the
+      # read-only config file and crashes
+      type = types.int;
+      default = 12261;
+      example = 12261;
+      description = "Zeronet fileserver port.";
+    };
+
+    tor = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Use TOR for zeronet traffic where possible.";
+    };
+
+    torAlways = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Use TOR for all zeronet traffic.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.tor = mkIf cfg.tor {
+      enable = true;
+      controlPort = 9051;
+
+      extraConfig = ''
+        CacheDirectoryGroupReadable 1
+        CookieAuthentication 1
+        CookieAuthFileGroupReadable 1
+      '';
+    };
+
+    systemd.services.zeronet = {
+      description = "zeronet";
+      after = [ "network.target" (optionalString cfg.tor "tor.service") ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User = "zeronet";
+        DynamicUser = true;
+        StateDirectory = "zeronet";
+        SupplementaryGroups = mkIf cfg.tor [ "tor" ];
+        ExecStart = "${pkgs.zeronet}/bin/zeronet --config_file ${configFile}";
+      };
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "zeronet" "dataDir" ] "Zeronet will store data by default in /var/lib/zeronet")
+    (mkRemovedOptionModule [ "services" "zeronet" "logDir" ] "Zeronet will log by default in /var/lib/zeronet")
+  ];
+
+  meta.maintainers = with maintainers; [ chiiruno ];
+}
diff --git a/nixpkgs/nixos/modules/services/networking/zerotierone.nix b/nixpkgs/nixos/modules/services/networking/zerotierone.nix
new file mode 100644
index 000000000000..cf39ed065a76
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/zerotierone.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.zerotierone;
+in
+{
+  options.services.zerotierone.enable = mkEnableOption "ZeroTierOne";
+
+  options.services.zerotierone.joinNetworks = mkOption {
+    default = [];
+    example = [ "a8a2c3c10c1a68de" ];
+    type = types.listOf types.str;
+    description = ''
+      List of ZeroTier Network IDs to join on startup
+    '';
+  };
+
+  options.services.zerotierone.port = mkOption {
+    default = 9993;
+    example = 9993;
+    type = types.int;
+    description = ''
+      Network port used by ZeroTier.
+    '';
+  };
+
+  options.services.zerotierone.package = mkOption {
+    default = pkgs.zerotierone;
+    defaultText = "pkgs.zerotierone";
+    type = types.package;
+    description = ''
+      ZeroTier One package to use.
+    '';
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.zerotierone = {
+      description = "ZeroTierOne";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      wants = [ "network-online.target" ];
+
+      path = [ cfg.package ];
+
+      preStart = ''
+        mkdir -p /var/lib/zerotier-one/networks.d
+        chmod 700 /var/lib/zerotier-one
+        chown -R root:root /var/lib/zerotier-one
+      '' + (concatMapStrings (netId: ''
+        touch "/var/lib/zerotier-one/networks.d/${netId}.conf"
+      '') cfg.joinNetworks);
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/zerotier-one -p${toString cfg.port}";
+        Restart = "always";
+        KillMode = "process";
+        TimeoutStopSec = 5;
+      };
+    };
+
+    # ZeroTier does not issue DHCP leases, but some strangers might...
+    networking.dhcpcd.denyInterfaces = [ "zt*" ];
+
+    # ZeroTier receives UDP transmissions
+    networking.firewall.allowedUDPPorts = [ cfg.port ];
+
+    environment.systemPackages = [ cfg.package ];
+
+    # Prevent systemd from potentially changing the MAC address
+    systemd.network.links."50-zerotier" = {
+      matchConfig = {
+        OriginalName = "zt*";
+      };
+      linkConfig = {
+        AutoNegotiation = false;
+        MACAddressPolicy = "none";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/znc/default.nix b/nixpkgs/nixos/modules/services/networking/znc/default.nix
new file mode 100644
index 000000000000..a7315896c506
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/znc/default.nix
@@ -0,0 +1,308 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+
+  cfg = config.services.znc;
+
+  defaultUser = "znc";
+
+  modules = pkgs.buildEnv {
+    name = "znc-modules";
+    paths = cfg.modulePackages;
+  };
+
+  listenerPorts = concatMap (l: optional (l ? Port) l.Port)
+    (attrValues (cfg.config.Listener or {}));
+
+  # Converts the config option to a string
+  semanticString = let
+
+      sortedAttrs = set: sort (l: r:
+        if l == "extraConfig" then false # Always put extraConfig last
+        else if isAttrs set.${l} == isAttrs set.${r} then l < r
+        else isAttrs set.${r} # Attrsets should be last, makes for a nice config
+        # This last case occurs when any side (but not both) is an attrset
+        # The order of these is correct when the attrset is on the right
+        # which we're just returning
+      ) (attrNames set);
+
+      # Specifies an attrset that encodes the value according to its type
+      encode = name: value: {
+          null = [];
+          bool = [ "${name} = ${boolToString value}" ];
+          int = [ "${name} = ${toString value}" ];
+
+          # extraConfig should be inserted verbatim
+          string = [ (if name == "extraConfig" then value else "${name} = ${value}") ];
+
+          # Values like `Foo = [ "bar" "baz" ];` should be transformed into
+          #   Foo=bar
+          #   Foo=baz
+          list = concatMap (encode name) value;
+
+          # Values like `Foo = { bar = { Baz = "baz"; Qux = "qux"; Florps = null; }; };` should be transmed into
+          #   <Foo bar>
+          #     Baz=baz
+          #     Qux=qux
+          #   </Foo>
+          set = concatMap (subname: optionals (value.${subname} != null) ([
+              "<${name} ${subname}>"
+            ] ++ map (line: "\t${line}") (toLines value.${subname}) ++ [
+              "</${name}>"
+            ])) (filter (v: v != null) (attrNames value));
+
+        }.${builtins.typeOf value};
+
+      # One level "above" encode, acts upon a set and uses encode on each name,value pair
+      toLines = set: concatMap (name: encode name set.${name}) (sortedAttrs set);
+
+    in
+      concatStringsSep "\n" (toLines cfg.config);
+
+  semanticTypes = with types; rec {
+    zncAtom = nullOr (oneOf [ int bool str ]);
+    zncAttr = attrsOf (nullOr zncConf);
+    zncAll = oneOf [ zncAtom (listOf zncAtom) zncAttr ];
+    zncConf = attrsOf (zncAll // {
+      # Since this is a recursive type and the description by default contains
+      # the description of its subtypes, infinite recursion would occur without
+      # explicitly breaking this cycle
+      description = "znc values (null, atoms (str, int, bool), list of atoms, or attrsets of znc values)";
+    });
+  };
+
+in
+
+{
+
+  imports = [ ./options.nix ];
+
+  options = {
+    services.znc = {
+      enable = mkEnableOption "ZNC";
+
+      user = mkOption {
+        default = "znc";
+        example = "john";
+        type = types.str;
+        description = ''
+          The name of an existing user account to use to own the ZNC server
+          process. If not specified, a default user will be created.
+        '';
+      };
+
+      group = mkOption {
+        default = defaultUser;
+        example = "users";
+        type = types.str;
+        description = ''
+          Group to own the ZNC process.
+        '';
+      };
+
+      dataDir = mkOption {
+        default = "/var/lib/znc/";
+        example = "/home/john/.znc/";
+        type = types.path;
+        description = ''
+          The state directory for ZNC. The config and the modules will be linked
+          to from this directory as well.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open ports in the firewall for ZNC. Does work with
+          ports for listeners specified in
+          <option>services.znc.config.Listener</option>.
+        '';
+      };
+
+      config = mkOption {
+        type = semanticTypes.zncConf;
+        default = {};
+        example = literalExample ''
+          {
+            LoadModule = [ "webadmin" "adminlog" ];
+            User.paul = {
+              Admin = true;
+              Nick = "paul";
+              AltNick = "paul1";
+              LoadModule = [ "chansaver" "controlpanel" ];
+              Network.freenode = {
+                Server = "chat.freenode.net +6697";
+                LoadModule = [ "simple_away" ];
+                Chan = {
+                  "#nixos" = { Detached = false; };
+                  "##linux" = { Disabled = true; };
+                };
+              };
+              Pass.password = {
+                Method = "sha256";
+                Hash = "e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93";
+                Salt = "l5Xryew4g*!oa(ECfX2o";
+              };
+            };
+          }
+        '';
+        description = ''
+          Configuration for ZNC, see
+          <link xlink:href="https://wiki.znc.in/Configuration"/> for details. The
+          Nix value declared here will be translated directly to the xml-like
+          format ZNC expects. This is much more flexible than the legacy options
+          under <option>services.znc.confOptions.*</option>, but also can't do
+          any type checking.
+          </para>
+          <para>
+          You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+          to view the current value. By default it contains a listener for port
+          5000 with SSL enabled.
+          </para>
+          <para>
+          Nix attributes called <literal>extraConfig</literal> will be inserted
+          verbatim into the resulting config file.
+          </para>
+          <para>
+          If <option>services.znc.useLegacyConfig</option> is turned on, the
+          option values in <option>services.znc.confOptions.*</option> will be
+          gracefully be applied to this option.
+          </para>
+          <para>
+          If you intend to update the configuration through this option, be sure
+          to enable <option>services.znc.mutable</option>, otherwise none of the
+          changes here will be applied after the initial deploy.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        example = "~/.znc/configs/znc.conf";
+        description = ''
+          Configuration file for ZNC. It is recommended to use the
+          <option>config</option> option instead.
+          </para>
+          <para>
+          Setting this option will override any auto-generated config file
+          through the <option>confOptions</option> or <option>config</option>
+          options.
+        '';
+      };
+
+      modulePackages = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        example = literalExample "[ pkgs.zncModules.fish pkgs.zncModules.push ]";
+        description = ''
+          A list of global znc module packages to add to znc.
+        '';
+      };
+
+      mutable = mkOption {
+        default = true; # TODO: Default to true when config is set, make sure to not delete the old config if present
+        type = types.bool;
+        description = ''
+          Indicates whether to allow the contents of the
+          <literal>dataDir</literal> directory to be changed by the user at
+          run-time.
+          </para>
+          <para>
+          If enabled, modifications to the ZNC configuration after its initial
+          creation are not overwritten by a NixOS rebuild. If disabled, the
+          ZNC configuration is rebuilt on every NixOS rebuild.
+          </para>
+          <para>
+          If the user wants to manage the ZNC service using the web admin
+          interface, this option should be enabled.
+        '';
+      };
+
+      extraFlags = mkOption {
+        default = [ ];
+        example = [ "--debug" ];
+        type = types.listOf types.str;
+        description = ''
+          Extra arguments to use for executing znc.
+        '';
+      };
+    };
+  };
+
+
+  ###### Implementation
+
+  config = mkIf cfg.enable {
+
+    services.znc = {
+      configFile = mkDefault (pkgs.writeText "znc-generated.conf" semanticString);
+      config = {
+        Version = lib.getVersion pkgs.znc;
+        Listener.l.Port = mkDefault 5000;
+        Listener.l.SSL = mkDefault true;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall listenerPorts;
+
+    systemd.services.znc = {
+      description = "ZNC Server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        Restart = "always";
+        ExecStart = "${pkgs.znc}/bin/znc --foreground --datadir ${cfg.dataDir} ${escapeShellArgs cfg.extraFlags}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+      };
+      preStart = ''
+        mkdir -p ${cfg.dataDir}/configs
+
+        # If mutable, regenerate conf file every time.
+        ${optionalString (!cfg.mutable) ''
+          echo "znc is set to be system-managed. Now deleting old znc.conf file to be regenerated."
+          rm -f ${cfg.dataDir}/configs/znc.conf
+        ''}
+
+        # Ensure essential files exist.
+        if [[ ! -f ${cfg.dataDir}/configs/znc.conf ]]; then
+            echo "No znc.conf file found in ${cfg.dataDir}. Creating one now."
+            cp --no-clobber ${cfg.configFile} ${cfg.dataDir}/configs/znc.conf
+            chmod u+rw ${cfg.dataDir}/configs/znc.conf
+            chown ${cfg.user} ${cfg.dataDir}/configs/znc.conf
+        fi
+
+        if [[ ! -f ${cfg.dataDir}/znc.pem ]]; then
+          echo "No znc.pem file found in ${cfg.dataDir}. Creating one now."
+          ${pkgs.znc}/bin/znc --makepem --datadir ${cfg.dataDir}
+        fi
+
+        # Symlink modules
+        rm ${cfg.dataDir}/modules || true
+        ln -fs ${modules}/lib/znc ${cfg.dataDir}/modules
+      '';
+    };
+
+    users.users = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} =
+        { description = "ZNC server daemon owner";
+          group = defaultUser;
+          uid = config.ids.uids.znc;
+          home = cfg.dataDir;
+          createHome = true;
+        };
+      };
+
+    users.groups = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} =
+        { gid = config.ids.gids.znc;
+          members = [ defaultUser ];
+        };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/networking/znc/options.nix b/nixpkgs/nixos/modules/services/networking/znc/options.nix
new file mode 100644
index 000000000000..048dbd738630
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/networking/znc/options.nix
@@ -0,0 +1,270 @@
+{ lib, config, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.znc;
+
+  networkOpts = {
+    options = {
+
+      server = mkOption {
+        type = types.str;
+        example = "chat.freenode.net";
+        description = ''
+          IRC server address.
+        '';
+      };
+
+      port = mkOption {
+        type = types.ints.u16;
+        default = 6697;
+        description = ''
+          IRC server port.
+        '';
+      };
+
+      password = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          IRC server password, such as for a Slack gateway.
+        '';
+      };
+
+      useSSL = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to use SSL to connect to the IRC server.
+        '';
+      };
+
+      modules = mkOption {
+        type = types.listOf types.str;
+        default = [ "simple_away" ];
+        example = literalExample "[ simple_away sasl ]";
+        description = ''
+          ZNC network modules to load.
+        '';
+      };
+
+      channels = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "nixos" ];
+        description = ''
+          IRC channels to join.
+        '';
+      };
+
+      hasBitlbeeControlChannel = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to add the special Bitlbee operations channel.
+        '';
+      };
+
+      extraConf = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''
+          Encoding = ^UTF-8
+          FloodBurst = 4
+          FloodRate = 1.00
+          IRCConnectEnabled = true
+          Ident = johntron
+          JoinDelay = 0
+          Nick = johntron
+        '';
+        description = ''
+          Extra config for the network. Consider using
+          <option>services.znc.config</option> instead.
+        '';
+      };
+    };
+  };
+
+in
+
+{
+
+  options = {
+    services.znc = {
+
+      useLegacyConfig = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Whether to propagate the legacy options under
+          <option>services.znc.confOptions.*</option> to the znc config. If this
+          is turned on, the znc config will contain a user with the default name
+          "znc", global modules "webadmin" and "adminlog" will be enabled by
+          default, and more, all controlled through the
+          <option>services.znc.confOptions.*</option> options.
+          You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+          to view the current value of the config.
+          </para>
+          <para>
+          In any case, if you need more flexibility,
+          <option>services.znc.config</option> can be used to override/add to
+          all of the legacy options.
+        '';
+      };
+
+      confOptions = {
+        modules = mkOption {
+          type = types.listOf types.str;
+          default = [ "webadmin" "adminlog" ];
+          example = [ "partyline" "webadmin" "adminlog" "log" ];
+          description = ''
+            A list of modules to include in the `znc.conf` file.
+          '';
+        };
+
+        userModules = mkOption {
+          type = types.listOf types.str;
+          default = [ "chansaver" "controlpanel" ];
+          example = [ "chansaver" "controlpanel" "fish" "push" ];
+          description = ''
+            A list of user modules to include in the `znc.conf` file.
+          '';
+        };
+
+        userName = mkOption {
+          default = "znc";
+          example = "johntron";
+          type = types.str;
+          description = ''
+            The user name used to log in to the ZNC web admin interface.
+          '';
+        };
+
+        networks = mkOption {
+          default = { };
+          type = with types; attrsOf (submodule networkOpts);
+          description = ''
+            IRC networks to connect the user to.
+          '';
+          example = literalExample ''
+            {
+              "freenode" = {
+                server = "chat.freenode.net";
+                port = 6697;
+                useSSL = true;
+                modules = [ "simple_away" ];
+              };
+            };
+          '';
+        };
+
+        nick = mkOption {
+          default = "znc-user";
+          example = "john";
+          type = types.str;
+          description = ''
+            The IRC nick.
+          '';
+        };
+
+        passBlock = mkOption {
+          example = literalExample ''
+            &lt;Pass password&gt;
+               Method = sha256
+               Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93
+               Salt = l5Xryew4g*!oa(ECfX2o
+            &lt;/Pass&gt;
+          '';
+          type = types.str;
+          description = ''
+            Generate with `nix-shell -p znc --command "znc --makepass"`.
+            This is the password used to log in to the ZNC web admin interface.
+            You can also set this through
+            <option>services.znc.config.User.&lt;username&gt;.Pass.Method</option>
+            and co.
+          '';
+        };
+
+        port = mkOption {
+          default = 5000;
+          type = types.int;
+          description = ''
+            Specifies the port on which to listen.
+          '';
+        };
+
+        useSSL = mkOption {
+          default = true;
+          type = types.bool;
+          description = ''
+            Indicates whether the ZNC server should use SSL when listening on
+            the specified port. A self-signed certificate will be generated.
+          '';
+        };
+
+        uriPrefix = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "/znc/";
+          description = ''
+            An optional URI prefix for the ZNC web interface. Can be
+            used to make ZNC available behind a reverse proxy.
+          '';
+        };
+
+        extraZncConf = mkOption {
+          default = "";
+          type = types.lines;
+          description = ''
+            Extra config to `znc.conf` file.
+          '';
+        };
+      };
+
+    };
+  };
+
+  config = mkIf cfg.useLegacyConfig {
+
+    services.znc.config = let
+      c = cfg.confOptions;
+      # defaults here should override defaults set in the non-legacy part
+      mkDefault = mkOverride 900;
+    in {
+      LoadModule = mkDefault c.modules;
+      Listener.l = {
+        Port = mkDefault c.port;
+        IPv4 = mkDefault true;
+        IPv6 = mkDefault true;
+        SSL = mkDefault c.useSSL;
+        URIPrefix = c.uriPrefix;
+      };
+      User.${c.userName} = {
+        Admin = mkDefault true;
+        Nick = mkDefault c.nick;
+        AltNick = mkDefault "${c.nick}_";
+        Ident = mkDefault c.nick;
+        RealName = mkDefault c.nick;
+        LoadModule = mkDefault c.userModules;
+        Network = mapAttrs (name: net: {
+          LoadModule = mkDefault net.modules;
+          Server = mkDefault "${net.server} ${optionalString net.useSSL "+"}${toString net.port} ${net.password}";
+          Chan = optionalAttrs net.hasBitlbeeControlChannel { "&bitlbee" = mkDefault {}; } //
+            listToAttrs (map (n: nameValuePair "#${n}" (mkDefault {})) net.channels);
+          extraConfig = if net.extraConf == "" then mkDefault null else net.extraConf;
+        }) c.networks;
+        extraConfig = [ c.passBlock ];
+      };
+      extraConfig = optional (c.extraZncConf != "") c.extraZncConf;
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule ["services" "znc" "zncConf"] ''
+      Instead of `services.znc.zncConf = "... foo ...";`, use
+      `services.znc.configFile = pkgs.writeText "znc.conf" "... foo ...";`.
+    '')
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/printing/cupsd.nix b/nixpkgs/nixos/modules/services/printing/cupsd.nix
new file mode 100644
index 000000000000..e67badfcd29e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/printing/cupsd.nix
@@ -0,0 +1,460 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  inherit (pkgs) cups cups-pk-helper cups-filters;
+
+  cfg = config.services.printing;
+
+  avahiEnabled = config.services.avahi.enable;
+  polkitEnabled = config.security.polkit.enable;
+
+  additionalBackends = pkgs.runCommand "additional-cups-backends" {
+      preferLocalBuild = true;
+    } ''
+      mkdir -p $out
+      if [ ! -e ${cups.out}/lib/cups/backend/smb ]; then
+        mkdir -p $out/lib/cups/backend
+        ln -sv ${pkgs.samba}/bin/smbspool $out/lib/cups/backend/smb
+      fi
+
+      # Provide support for printing via HTTPS.
+      if [ ! -e ${cups.out}/lib/cups/backend/https ]; then
+        mkdir -p $out/lib/cups/backend
+        ln -sv ${cups.out}/lib/cups/backend/ipp $out/lib/cups/backend/https
+      fi
+    '';
+
+  # Here we can enable additional backends, filters, etc. that are not
+  # part of CUPS itself, e.g. the SMB backend is part of Samba.  Since
+  # we can't update ${cups.out}/lib/cups itself, we create a symlink tree
+  # here and add the additional programs.  The ServerBin directive in
+  # cups-files.conf tells cupsd to use this tree.
+  bindir = pkgs.buildEnv {
+    name = "cups-progs";
+    paths =
+      [ cups.out additionalBackends cups-filters pkgs.ghostscript ]
+      ++ cfg.drivers;
+    pathsToLink = [ "/lib" "/share/cups" "/bin" ];
+    postBuild = cfg.bindirCmds;
+    ignoreCollisions = true;
+  };
+
+  writeConf = name: text: pkgs.writeTextFile {
+    inherit name text;
+    destination = "/etc/cups/${name}";
+  };
+
+  cupsFilesFile = writeConf "cups-files.conf" ''
+    SystemGroup root wheel
+
+    ServerBin ${bindir}/lib/cups
+    DataDir ${bindir}/share/cups
+    DocumentRoot ${cups.out}/share/doc/cups
+
+    AccessLog syslog
+    ErrorLog syslog
+    PageLog syslog
+
+    TempDir ${cfg.tempDir}
+
+    SetEnv PATH /var/lib/cups/path/lib/cups/filter:/var/lib/cups/path/bin
+
+    # User and group used to run external programs, including
+    # those that actually send the job to the printer.  Note that
+    # Udev sets the group of printer devices to `lp', so we want
+    # these programs to run as `lp' as well.
+    User cups
+    Group lp
+
+    ${cfg.extraFilesConf}
+  '';
+
+  cupsdFile = writeConf "cupsd.conf" ''
+    ${concatMapStrings (addr: ''
+      Listen ${addr}
+    '') cfg.listenAddresses}
+    Listen /run/cups/cups.sock
+
+    DefaultShared ${if cfg.defaultShared then "Yes" else "No"}
+
+    Browsing ${if cfg.browsing then "Yes" else "No"}
+
+    WebInterface ${if cfg.webInterface then "Yes" else "No"}
+
+    LogLevel ${cfg.logLevel}
+
+    ${cfg.extraConf}
+  '';
+
+  browsedFile = writeConf "cups-browsed.conf" cfg.browsedConf;
+
+  rootdir = pkgs.buildEnv {
+    name = "cups-progs";
+    paths = [
+      cupsFilesFile
+      cupsdFile
+      (writeConf "client.conf" cfg.clientConf)
+      (writeConf "snmp.conf" cfg.snmpConf)
+    ] ++ optional avahiEnabled browsedFile
+      ++ 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
+
+{
+
+  imports = [
+    (mkChangedOptionModule [ "services" "printing" "gutenprint" ] [ "services" "printing" "drivers" ]
+      (config:
+        let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config;
+        in if enabled then [ pkgs.gutenprint ] else [ ]))
+    (mkRemovedOptionModule [ "services" "printing" "cupsFilesConf" ] "")
+    (mkRemovedOptionModule [ "services" "printing" "cupsdConf" ] "")
+  ];
+
+  ###### interface
+
+  options = {
+    services.printing = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable printing support through the CUPS daemon.
+        '';
+      };
+
+      startWhenNeeded = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If set, CUPS is socket-activated; that is,
+          instead of having it permanently running as a daemon,
+          systemd will start it on the first incoming connection.
+        '';
+      };
+
+      listenAddresses = mkOption {
+        type = types.listOf types.str;
+        default = [ "localhost:631" ];
+        example = [ "*:631" ];
+        description = ''
+          A list of addresses and ports on which to listen.
+        '';
+      };
+
+      allowFrom = mkOption {
+        type = types.listOf types.str;
+        default = [ "localhost" ];
+        example = [ "all" ];
+        apply = concatMapStringsSep "\n" (x: "Allow ${x}");
+        description = ''
+          From which hosts to allow unconditional access.
+        '';
+      };
+
+      bindirCmds = mkOption {
+        type = types.lines;
+        internal = true;
+        default = "";
+        description = ''
+          Additional commands executed while creating the directory
+          containing the CUPS server binaries.
+        '';
+      };
+
+      defaultShared = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Specifies whether local printers are shared by default.
+        '';
+      };
+
+      browsing = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Specifies whether shared printers are advertised.
+        '';
+      };
+
+      webInterface = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Specifies whether the web interface is enabled.
+        '';
+      };
+
+      logLevel = mkOption {
+        type = types.str;
+        default = "info";
+        example = "debug";
+        description = ''
+          Specifies the cupsd logging verbosity.
+        '';
+      };
+
+      extraFilesConf = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra contents of the configuration file of the CUPS daemon
+          (<filename>cups-files.conf</filename>).
+        '';
+      };
+
+      extraConf = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            BrowsePoll cups.example.com
+            MaxCopies 42
+          '';
+        description = ''
+          Extra contents of the configuration file of the CUPS daemon
+          (<filename>cupsd.conf</filename>).
+        '';
+      };
+
+      clientConf = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            ServerName server.example.com
+            Encryption Never
+          '';
+        description = ''
+          The contents of the client configuration.
+          (<filename>client.conf</filename>)
+        '';
+      };
+
+      browsedConf = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            BrowsePoll cups.example.com
+          '';
+        description = ''
+          The contents of the configuration. file of the CUPS Browsed daemon
+          (<filename>cups-browsed.conf</filename>)
+        '';
+      };
+
+      snmpConf = mkOption {
+        type = types.lines;
+        default = ''
+          Address @LOCAL
+        '';
+        description = ''
+          The contents of <filename>/etc/cups/snmp.conf</filename>. See "man
+          cups-snmp.conf" for a complete description.
+        '';
+      };
+
+      drivers = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        example = literalExample "with pkgs; [ gutenprint hplip splix cups-googlecloudprint ]";
+        description = ''
+          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.
+        '';
+      };
+
+      tempDir = mkOption {
+        type = types.path;
+        default = "/tmp";
+        example = "/tmp/cups";
+        description = ''
+          CUPSd temporary directory.
+        '';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.printing.enable {
+
+    users.users.cups =
+      { uid = config.ids.uids.cups;
+        group = "lp";
+        description = "CUPS printing services";
+      };
+
+    environment.systemPackages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
+    environment.etc.cups.source = "/var/lib/cups";
+
+    services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
+
+    # Allow asswordless printer admin for members of wheel group
+    security.polkit.extraConfig = mkIf polkitEnabled ''
+      polkit.addRule(function(action, subject) {
+          if (action.id == "org.opensuse.cupspkhelper.mechanism.all-edit" &&
+              subject.isInGroup("wheel")){
+              return polkit.Result.YES;
+          }
+      });
+    '';
+
+    # Cups uses libusb to talk to printers, and does not use the
+    # linux kernel driver. If the driver is not in a black list, it
+    # gets loaded, and then cups cannot access the printers.
+    boot.blacklistedKernelModules = [ "usblp" ];
+
+    # Some programs like print-manager rely on this value to get
+    # printer test pages.
+    environment.sessionVariables.CUPS_DATADIR = "${bindir}/share/cups";
+
+    systemd.packages = [ cups.out ];
+
+    systemd.sockets.cups = mkIf cfg.startWhenNeeded {
+      wantedBy = [ "sockets.target" ];
+      listenStreams = [ "/run/cups/cups.sock" ]
+        ++ map (x: replaceStrings ["localhost"] ["127.0.0.1"] (removePrefix "*:" x)) cfg.listenAddresses;
+    };
+
+    systemd.services.cups =
+      { wantedBy = optionals (!cfg.startWhenNeeded) [ "multi-user.target" ];
+        wants = [ "network.target" ];
+        after = [ "network.target" ];
+
+        path = [ cups.out ];
+
+        preStart =
+          ''
+            mkdir -m 0700 -p /var/cache/cups
+            mkdir -m 0700 -p /var/spool/cups
+            mkdir -m 0755 -p ${cfg.tempDir}
+
+            mkdir -m 0755 -p /var/lib/cups
+            # While cups will automatically create self-signed certificates if accessed via TLS,
+            # this directory to store the certificates needs to be created manually.
+            mkdir -m 0700 -p /var/lib/cups/ssl
+
+            # Backwards compatibility
+            if [ ! -L /etc/cups ]; then
+              mv /etc/cups/* /var/lib/cups
+              rmdir /etc/cups
+              ln -s /var/lib/cups /etc/cups
+            fi
+            # First, clean existing symlinks
+            if [ -n "$(ls /var/lib/cups)" ]; then
+              for i in /var/lib/cups/*; do
+                [ -L "$i" ] && rm "$i"
+              done
+            fi
+            # Then, populate it with static files
+            cd ${rootdir}/etc/cups
+            for i in *; do
+              [ ! -e "/var/lib/cups/$i" ] && ln -s "${rootdir}/etc/cups/$i" "/var/lib/cups/$i"
+            done
+
+            #update path reference
+            [ -L /var/lib/cups/path ] && \
+              rm /var/lib/cups/path
+            [ ! -e /var/lib/cups/path ] && \
+              ln -s ${bindir} /var/lib/cups/path
+
+            ${optionalString (containsGutenprint cfg.drivers) ''
+              if [ -d /var/lib/cups/ppd ]; then
+                ${getGutenprint cfg.drivers}/bin/cups-genppdupdate -p /var/lib/cups/ppd
+              fi
+            ''}
+          '';
+
+          serviceConfig = {
+            PrivateTmp = true;
+            RuntimeDirectory = [ "cups" ];
+          };
+      };
+
+    systemd.services.cups-browsed = mkIf avahiEnabled
+      { description = "CUPS Remote Printer Discovery";
+
+        wantedBy = [ "multi-user.target" ];
+        wants = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
+        bindsTo = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
+        partOf = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
+        after = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
+
+        path = [ cups ];
+
+        serviceConfig.ExecStart = "${cups-filters}/bin/cups-browsed";
+
+        restartTriggers = [ browsedFile ];
+      };
+
+    services.printing.extraConf =
+      ''
+        DefaultAuthType Basic
+
+        <Location />
+          Order allow,deny
+          ${cfg.allowFrom}
+        </Location>
+
+        <Location /admin>
+          Order allow,deny
+          ${cfg.allowFrom}
+        </Location>
+
+        <Location /admin/conf>
+          AuthType Basic
+          Require user @SYSTEM
+          Order allow,deny
+          ${cfg.allowFrom}
+        </Location>
+
+        <Policy default>
+          <Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job CUPS-Move-Job>
+            Require user @OWNER @SYSTEM
+            Order deny,allow
+          </Limit>
+
+          <Limit Pause-Printer Resume-Printer Set-Printer-Attributes Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After CUPS-Add-Printer CUPS-Delete-Printer CUPS-Add-Class CUPS-Delete-Class CUPS-Accept-Jobs CUPS-Reject-Jobs CUPS-Set-Default>
+            AuthType Basic
+            Require user @SYSTEM
+            Order deny,allow
+          </Limit>
+
+          <Limit Cancel-Job CUPS-Authenticate-Job>
+            Require user @OWNER @SYSTEM
+            Order deny,allow
+          </Limit>
+
+          <Limit All>
+            Order deny,allow
+          </Limit>
+        </Policy>
+      '';
+
+    security.pam.services.cups = {};
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ matthewbauer ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/atd.nix b/nixpkgs/nixos/modules/services/scheduling/atd.nix
new file mode 100644
index 000000000000..cefe72b0e999
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/atd.nix
@@ -0,0 +1,109 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.atd;
+
+  inherit (pkgs) at;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.atd.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable the <command>at</command> daemon, a command scheduler.
+      '';
+    };
+
+    services.atd.allowEveryone = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to make <filename>/var/spool/at{jobs,spool}</filename>
+        writeable by everyone (and sticky).  This is normally not
+        needed since the <command>at</command> commands are
+        setuid/setgid <literal>atd</literal>.
+     '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    # Not wrapping "batch" because it's a shell script (kernel drops perms
+    # anyway) and it's patched to invoke the "at" setuid wrapper.
+    security.wrappers = builtins.listToAttrs (
+      map (program: { name = "${program}"; value = {
+      source = "${at}/bin/${program}";
+      owner = "atd";
+      group = "atd";
+      setuid = true;
+      setgid = true;
+    };}) [ "at" "atq" "atrm" ]);
+
+    environment.systemPackages = [ at ];
+
+    security.pam.services.atd = {};
+
+    users.users.atd =
+      { uid = config.ids.uids.atd;
+        description = "atd user";
+        home = "/var/empty";
+      };
+
+    users.groups.atd.gid = config.ids.gids.atd;
+
+    systemd.services.atd = {
+      description = "Job Execution Daemon (atd)";
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ at ];
+
+      preStart = ''
+        # Snippets taken and adapted from the original `install' rule of
+        # the makefile.
+
+        # We assume these values are those actually used in Nixpkgs for
+        # `at'.
+        spooldir=/var/spool/atspool
+        jobdir=/var/spool/atjobs
+        etcdir=/etc/at
+
+        for dir in "$spooldir" "$jobdir" "$etcdir"; do
+          if [ ! -d "$dir" ]; then
+              mkdir -p "$dir"
+              chown atd:atd "$dir"
+          fi
+        done
+        chmod 1770 "$spooldir" "$jobdir"
+        ${if cfg.allowEveryone then ''chmod a+rwxt "$spooldir" "$jobdir" '' else ""}
+        if [ ! -f "$etcdir"/at.deny ]; then
+            touch "$etcdir"/at.deny
+            chown root:atd "$etcdir"/at.deny
+            chmod 640 "$etcdir"/at.deny
+        fi
+        if [ ! -f "$jobdir"/.SEQ ]; then
+            touch "$jobdir"/.SEQ
+            chown atd:atd "$jobdir"/.SEQ
+            chmod 600 "$jobdir"/.SEQ
+        fi
+      '';
+
+      script = "atd";
+
+      serviceConfig.Type = "forking";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/chronos.nix b/nixpkgs/nixos/modules/services/scheduling/chronos.nix
new file mode 100644
index 000000000000..9a8ed4c09ac1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/chronos.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.chronos;
+
+in {
+
+  ###### interface
+
+  options.services.chronos = {
+    enable = mkOption {
+      description = "Whether to enable graphite web frontend.";
+      default = false;
+      type = types.bool;
+    };
+
+    httpPort = mkOption {
+      description = "Chronos listening port";
+      default = 4400;
+      type = types.int;
+    };
+
+    master = mkOption {
+      description = "Chronos mesos master zookeeper address";
+      default = "zk://${head cfg.zookeeperHosts}/mesos";
+      type = types.str;
+    };
+
+    zookeeperHosts = mkOption {
+      description = "Chronos mesos zookepper addresses";
+      default = [ "localhost:2181" ];
+      type = types.listOf types.str;
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.chronos = {
+      description = "Chronos Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "zookeeper.service" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.chronos}/bin/chronos --master ${cfg.master} --zk_hosts ${concatStringsSep "," cfg.zookeeperHosts} --http_port ${toString cfg.httpPort}";
+        User = "chronos";
+      };
+    };
+
+    users.users.chronos.uid = config.ids.uids.chronos;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/cron.nix b/nixpkgs/nixos/modules/services/scheduling/cron.nix
new file mode 100644
index 000000000000..3bc31832946b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/cron.nix
@@ -0,0 +1,133 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  # Put all the system cronjobs together.
+  systemCronJobsFile = pkgs.writeText "system-crontab"
+    ''
+      SHELL=${pkgs.bash}/bin/bash
+      PATH=${config.system.path}/bin:${config.system.path}/sbin
+      ${optionalString (config.services.cron.mailto != null) ''
+        MAILTO="${config.services.cron.mailto}"
+      ''}
+      NIX_CONF_DIR=/etc/nix
+      ${lib.concatStrings (map (job: job + "\n") config.services.cron.systemCronJobs)}
+    '';
+
+  # Vixie cron requires build-time configuration for the sendmail path.
+  cronNixosPkg = pkgs.cron.override {
+    # The mail.nix nixos module, if there is any local mail system enabled,
+    # should have sendmail in this path.
+    sendmailPath = "/run/wrappers/bin/sendmail";
+  };
+
+  allFiles =
+    optional (config.services.cron.systemCronJobs != []) systemCronJobsFile
+    ++ config.services.cron.cronFiles;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.cron = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the Vixie cron daemon.";
+      };
+
+      mailto = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Email address to which job output will be mailed.";
+      };
+
+      systemCronJobs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = literalExample ''
+          [ "* * * * *  test   ls -l / > /tmp/cronout 2>&1"
+            "* * * * *  eelco  echo Hello World > /home/eelco/cronout"
+          ]
+        '';
+        description = ''
+          A list of Cron jobs to be appended to the system-wide
+          crontab.  See the manual page for crontab for the expected
+          format. If you want to get the results mailed you must setuid
+          sendmail. See <option>security.wrappers</option>
+
+          If neither /var/cron/cron.deny nor /var/cron/cron.allow exist only root
+          is allowed to have its own crontab file. The /var/cron/cron.deny file
+          is created automatically for you, so every user can use a crontab.
+
+          Many nixos modules set systemCronJobs, so if you decide to disable vixie cron
+          and enable another cron daemon, you may want it to get its system crontab
+          based on systemCronJobs.
+        '';
+      };
+
+      cronFiles = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+          A list of extra crontab files that will be read and appended to the main
+          crontab file when the cron service starts.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkMerge [
+
+    { services.cron.enable = mkDefault (allFiles != []); }
+    (mkIf (config.services.cron.enable) {
+      security.wrappers.crontab.source = "${cronNixosPkg}/bin/crontab";
+      environment.systemPackages = [ cronNixosPkg ];
+      environment.etc.crontab =
+        { source = pkgs.runCommand "crontabs" { inherit allFiles; preferLocalBuild = true; }
+            ''
+              touch $out
+              for i in $allFiles; do
+                cat "$i" >> $out
+              done
+            '';
+          mode = "0600"; # Cron requires this.
+        };
+
+      systemd.services.cron =
+        { description = "Cron Daemon";
+
+          wantedBy = [ "multi-user.target" ];
+
+          preStart =
+            ''
+              mkdir -m 710 -p /var/cron
+
+              # By default, allow all users to create a crontab.  This
+              # is denoted by the existence of an empty cron.deny file.
+              if ! test -e /var/cron/cron.allow -o -e /var/cron/cron.deny; then
+                  touch /var/cron/cron.deny
+              fi
+            '';
+
+          restartTriggers = [ config.time.timeZone ];
+          serviceConfig.ExecStart = "${cronNixosPkg}/bin/cron -n";
+        };
+
+    })
+
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/fcron.nix b/nixpkgs/nixos/modules/services/scheduling/fcron.nix
new file mode 100644
index 000000000000..42bed21bf25b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/fcron.nix
@@ -0,0 +1,167 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.fcron;
+
+  queuelen = if cfg.queuelen == null then "" else "-q ${toString cfg.queuelen}";
+
+  # Duplicate code, also found in cron.nix. Needs deduplication.
+  systemCronJobs =
+    ''
+      SHELL=${pkgs.bash}/bin/bash
+      PATH=${config.system.path}/bin:${config.system.path}/sbin
+      ${optionalString (config.services.cron.mailto != null) ''
+        MAILTO="${config.services.cron.mailto}"
+      ''}
+      NIX_CONF_DIR=/etc/nix
+      ${lib.concatStrings (map (job: job + "\n") config.services.cron.systemCronJobs)}
+    '';
+
+  allowdeny = target: users:
+    { source = pkgs.writeText "fcron.${target}" (concatStringsSep "\n" users);
+      target = "fcron.${target}";
+      mode = "644";
+      gid = config.ids.gids.fcron;
+    };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.fcron = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the <command>fcron</command> daemon.";
+      };
+
+      allow = mkOption {
+        type = types.listOf types.str;
+        default = [ "all" ];
+        description = ''
+          Users allowed to use fcrontab and fcrondyn (one name per
+          line, <literal>all</literal> for everyone).
+        '';
+      };
+
+      deny = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Users forbidden from using fcron.";
+      };
+
+      maxSerialJobs = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Maximum number of serial jobs which can run simultaneously.";
+      };
+
+      queuelen = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = "Number of jobs the serial queue and the lavg queue can contain.";
+      };
+
+      systab = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''The "system" crontab contents.'';
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.fcron.systab = systemCronJobs;
+
+    environment.etc = listToAttrs
+      (map (x: { name = x.target; value = x; })
+      [ (allowdeny "allow" (cfg.allow))
+        (allowdeny "deny" cfg.deny)
+        # see man 5 fcron.conf
+        { source =
+            let
+              isSendmailWrapped =
+                lib.hasAttr "sendmail" config.security.wrappers;
+              sendmailPath =
+                if isSendmailWrapped then "/run/wrappers/bin/sendmail"
+                else "${config.system.path}/bin/sendmail";
+            in
+            pkgs.writeText "fcron.conf" ''
+              fcrontabs   =       /var/spool/fcron
+              pidfile     =       /run/fcron.pid
+              fifofile    =       /run/fcron.fifo
+              fcronallow  =       /etc/fcron.allow
+              fcrondeny   =       /etc/fcron.deny
+              shell       =       /bin/sh
+              sendmail    =       ${sendmailPath}
+              editor      =       ${pkgs.vim}/bin/vim
+            '';
+          target = "fcron.conf";
+          gid = config.ids.gids.fcron;
+          mode = "0644";
+        }
+      ]);
+
+    environment.systemPackages = [ pkgs.fcron ];
+    users.users.fcron = {
+      uid = config.ids.uids.fcron;
+      home = "/var/spool/fcron";
+      group = "fcron";
+    };
+    users.groups.fcron.gid = config.ids.gids.fcron;
+
+    security.wrappers = {
+      fcrontab = {
+        source = "${pkgs.fcron}/bin/fcrontab";
+        owner = "fcron";
+        group = "fcron";
+        setgid = true;
+        setuid = true;
+      };
+      fcrondyn = {
+        source = "${pkgs.fcron}/bin/fcrondyn";
+        owner = "fcron";
+        group = "fcron";
+        setgid = true;
+      };
+      fcronsighup = {
+        source = "${pkgs.fcron}/bin/fcronsighup";
+        group = "fcron";
+      };
+    };
+    systemd.services.fcron = {
+      description = "fcron daemon";
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ pkgs.fcron ];
+
+      preStart = ''
+        install \
+          --mode 0770 \
+          --owner fcron \
+          --group fcron \
+          --directory /var/spool/fcron
+        # load system crontab file
+        /run/wrappers/bin/fcrontab -u systab - < ${pkgs.writeText "systab" cfg.systab}
+      '';
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.fcron}/sbin/fcron -m ${toString cfg.maxSerialJobs} ${queuelen}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/scheduling/marathon.nix b/nixpkgs/nixos/modules/services/scheduling/marathon.nix
new file mode 100644
index 000000000000..2e0d20c64b23
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/scheduling/marathon.nix
@@ -0,0 +1,98 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.marathon;
+
+in {
+
+  ###### interface
+
+  options.services.marathon = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+	Whether to enable the marathon mesos framework.
+      '';
+    };
+
+    master = mkOption {
+      type = types.str;
+      default = "zk://${concatStringsSep "," cfg.zookeeperHosts}/mesos";
+      example = "zk://1.2.3.4:2181,2.3.4.5:2181,3.4.5.6:2181/mesos";
+      description = ''
+	Mesos master address. See <link xlink:href="https://mesosphere.github.io/marathon/docs/"/> for details.
+      '';
+    };
+
+    zookeeperHosts = mkOption {
+      type = types.listOf types.str;
+      default = [ "localhost:2181" ];
+      example = [ "1.2.3.4:2181" "2.3.4.5:2181" "3.4.5.6:2181" ];
+      description = ''
+	ZooKeeper hosts' addresses.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "marathon";
+      example = "root";
+      description = ''
+	The user that the Marathon framework will be launched as. If the user doesn't exist it will be created.
+	If you want to run apps that require root access or you want to launch apps using arbitrary users, that
+	is using the `--mesos_user` flag then you need to change this to `root`.
+      '';
+    };
+
+    httpPort = mkOption {
+      type = types.int;
+      default = 8080;
+      description = ''
+	Marathon listening port for HTTP connections.
+      '';
+    };
+
+    extraCmdLineOptions = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "--https_port=8443" "--zk_timeout=10000" "--marathon_store_timeout=2000" ];
+      description = ''
+	Extra command line options to pass to Marathon.
+	See <link xlink:href="https://mesosphere.github.io/marathon/docs/command-line-flags.html"/> for all possible flags.
+      '';
+    };
+
+    environment = mkOption {
+      default = { };
+      type = types.attrs;
+      example = { JAVA_OPTS = "-Xmx512m"; MESOSPHERE_HTTP_CREDENTIALS = "username:password"; };
+      description = ''
+	Environment variables passed to Marathon.
+      '';
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.marathon = {
+      description = "Marathon Service";
+      environment = cfg.environment;
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "zookeeper.service" "mesos-master.service" "mesos-slave.service" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.marathon}/bin/marathon --master ${cfg.master} --zk zk://${concatStringsSep "," cfg.zookeeperHosts}/marathon --http_port ${toString cfg.httpPort} ${concatStringsSep " " cfg.extraCmdLineOptions}";
+        User = cfg.user;
+        Restart = "always";
+        RestartSec = "2";
+      };
+    };
+
+    users.users.${cfg.user}.isSystemUser = true;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix b/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix
new file mode 100644
index 000000000000..9620c3e0b6d4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/elasticsearch-curator.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+    cfg = config.services.elasticsearch-curator;
+    curatorConfig = pkgs.writeTextFile {
+      name = "config.yaml";
+      text = ''
+        ---
+        # Remember, leave a key empty if there is no value.  None will be a string,
+        # not a Python "NoneType"
+        client:
+          hosts: ${builtins.toJSON cfg.hosts}
+          port: ${toString cfg.port}
+          url_prefix:
+          use_ssl: False
+          certificate:
+          client_cert:
+          client_key:
+          ssl_no_validate: False
+          http_auth:
+          timeout: 30
+          master_only: False
+        logging:
+          loglevel: INFO
+          logfile:
+          logformat: default
+          blacklist: ['elasticsearch', 'urllib3']
+        '';
+    };
+    curatorAction = pkgs.writeTextFile {
+      name = "action.yaml";
+      text = cfg.actionYAML;
+    };
+in {
+
+  options.services.elasticsearch-curator = {
+
+    enable = mkEnableOption "elasticsearch curator";
+    interval = mkOption {
+      description = "The frequency to run curator, a systemd.time such as 'hourly'";
+      default = "hourly";
+      type = types.str;
+    };
+    hosts = mkOption {
+      description = "a list of elasticsearch hosts to connect to";
+      type = types.listOf types.str;
+      default = ["localhost"];
+    };
+    port = mkOption {
+      description = "the port that elasticsearch is listening on";
+      type = types.int;
+      default = 9200;
+    };
+    actionYAML = mkOption {
+      description = "curator action.yaml file contents, alternatively use curator-cli which takes a simple action command";
+      example = ''
+        ---
+        actions:
+          1:
+            action: delete_indices
+            description: >-
+              Delete indices older than 45 days (based on index name), for logstash-
+              prefixed indices. Ignore the error if the filter does not result in an
+              actionable list of indices (ignore_empty_list) and exit cleanly.
+            options:
+              ignore_empty_list: True
+              disable_action: False
+            filters:
+            - filtertype: pattern
+              kind: prefix
+              value: logstash-
+            - filtertype: age
+              source: name
+              direction: older
+              timestring: '%Y.%m.%d'
+              unit: days
+              unit_count: 45
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.elasticsearch-curator = {
+      startAt = cfg.interval;
+      serviceConfig = {
+        ExecStart =
+          "${pkgs.elasticsearch-curator}/bin/curator" +
+          " --config ${curatorConfig} ${curatorAction}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/search/elasticsearch.nix b/nixpkgs/nixos/modules/services/search/elasticsearch.nix
new file mode 100644
index 000000000000..91d8f544e16b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/elasticsearch.nix
@@ -0,0 +1,208 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.elasticsearch;
+
+  es6 = builtins.compareVersions cfg.package.version "6" >= 0;
+
+  esConfig = ''
+    network.host: ${cfg.listenAddress}
+    cluster.name: ${cfg.cluster_name}
+
+    http.port: ${toString cfg.port}
+    transport.tcp.port: ${toString cfg.tcp_port}
+
+    ${cfg.extraConf}
+  '';
+
+  configDir = cfg.dataDir + "/config";
+
+  elasticsearchYml = pkgs.writeTextFile {
+    name = "elasticsearch.yml";
+    text = esConfig;
+  };
+
+  loggingConfigFilename = "log4j2.properties";
+  loggingConfigFile = pkgs.writeTextFile {
+    name = loggingConfigFilename;
+    text = cfg.logging;
+  };
+
+  esPlugins = pkgs.buildEnv {
+    name = "elasticsearch-plugins";
+    paths = cfg.plugins;
+    postBuild = "${pkgs.coreutils}/bin/mkdir -p $out/plugins";
+  };
+
+in {
+
+  ###### interface
+
+  options.services.elasticsearch = {
+    enable = mkOption {
+      description = "Whether to enable elasticsearch.";
+      default = false;
+      type = types.bool;
+    };
+
+    package = mkOption {
+      description = "Elasticsearch package to use.";
+      default = pkgs.elasticsearch;
+      defaultText = "pkgs.elasticsearch";
+      type = types.package;
+    };
+
+    listenAddress = mkOption {
+      description = "Elasticsearch listen address.";
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    port = mkOption {
+      description = "Elasticsearch port to listen for HTTP traffic.";
+      default = 9200;
+      type = types.int;
+    };
+
+    tcp_port = mkOption {
+      description = "Elasticsearch port for the node to node communication.";
+      default = 9300;
+      type = types.int;
+    };
+
+    cluster_name = mkOption {
+      description = "Elasticsearch name that identifies your cluster for auto-discovery.";
+      default = "elasticsearch";
+      type = types.str;
+    };
+
+    extraConf = mkOption {
+      description = "Extra configuration for elasticsearch.";
+      default = "";
+      type = types.str;
+      example = ''
+        node.name: "elasticsearch"
+        node.master: true
+        node.data: false
+      '';
+    };
+
+    logging = mkOption {
+      description = "Elasticsearch logging configuration.";
+      default = ''
+        logger.action.name = org.elasticsearch.action
+        logger.action.level = info
+
+        appender.console.type = Console
+        appender.console.name = console
+        appender.console.layout.type = PatternLayout
+        appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
+
+        rootLogger.level = info
+        rootLogger.appenderRef.console.ref = console
+      '';
+      type = types.str;
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/elasticsearch";
+      description = ''
+        Data directory for elasticsearch.
+      '';
+    };
+
+    extraCmdLineOptions = mkOption {
+      description = "Extra command line options for the elasticsearch launcher.";
+      default = [];
+      type = types.listOf types.str;
+    };
+
+    extraJavaOptions = mkOption {
+      description = "Extra command line options for Java.";
+      default = [];
+      type = types.listOf types.str;
+      example = [ "-Djava.net.preferIPv4Stack=true" ];
+    };
+
+    plugins = mkOption {
+      description = "Extra elasticsearch plugins";
+      default = [];
+      type = types.listOf types.package;
+      example = lib.literalExample "[ pkgs.elasticsearchPlugins.discovery-ec2 ]";
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.elasticsearch = {
+      description = "Elasticsearch Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pkgs.inetutils ];
+      environment = {
+        ES_HOME = cfg.dataDir;
+        ES_JAVA_OPTS = toString ( optional (!es6) [ "-Des.path.conf=${configDir}" ]
+                                  ++ cfg.extraJavaOptions);
+      } // optionalAttrs es6 {
+        ES_PATH_CONF = configDir;
+      };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/elasticsearch ${toString cfg.extraCmdLineOptions}";
+        User = "elasticsearch";
+        PermissionsStartOnly = true;
+        LimitNOFILE = "1024000";
+      };
+      preStart = ''
+        ${optionalString (!config.boot.isContainer) ''
+          # Only set vm.max_map_count if lower than ES required minimum
+          # This avoids conflict if configured via boot.kernel.sysctl
+          if [ `${pkgs.procps}/bin/sysctl -n vm.max_map_count` -lt 262144 ]; then
+            ${pkgs.procps}/bin/sysctl -w vm.max_map_count=262144
+          fi
+        ''}
+
+        mkdir -m 0700 -p ${cfg.dataDir}
+
+        # Install plugins
+        ln -sfT ${esPlugins}/plugins ${cfg.dataDir}/plugins
+        ln -sfT ${cfg.package}/lib ${cfg.dataDir}/lib
+        ln -sfT ${cfg.package}/modules ${cfg.dataDir}/modules
+
+        # elasticsearch needs to create the elasticsearch.keystore in the config directory
+        # so this directory needs to be writable.
+        mkdir -m 0700 -p ${configDir}
+
+        # Note that we copy config files from the nix store instead of symbolically linking them
+        # because otherwise X-Pack Security will raise the following exception:
+        # java.security.AccessControlException:
+        # access denied ("java.io.FilePermission" "/var/lib/elasticsearch/config/elasticsearch.yml" "read")
+
+        cp ${elasticsearchYml} ${configDir}/elasticsearch.yml
+        # Make sure the logging configuration for old elasticsearch versions is removed:
+        rm -f "${configDir}/logging.yml"
+        cp ${loggingConfigFile} ${configDir}/${loggingConfigFilename}
+        mkdir -p ${configDir}/scripts
+        ${optionalString es6 "cp ${cfg.package}/config/jvm.options ${configDir}/jvm.options"}
+
+        if [ "$(id -u)" = 0 ]; then chown -R elasticsearch:elasticsearch ${cfg.dataDir}; fi
+      '';
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    users = {
+      groups.elasticsearch.gid = config.ids.gids.elasticsearch;
+      users.elasticsearch = {
+        uid = config.ids.uids.elasticsearch;
+        description = "Elasticsearch daemon user";
+        home = cfg.dataDir;
+        group = "elasticsearch";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/search/hound.nix b/nixpkgs/nixos/modules/services/search/hound.nix
new file mode 100644
index 000000000000..7a44489efe61
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/hound.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.hound;
+in {
+  options = {
+    services.hound = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the hound code search daemon.
+        '';
+      };
+
+      user = mkOption {
+        default = "hound";
+        type = types.str;
+        description = ''
+          User the hound daemon should execute under.
+        '';
+      };
+
+      group = mkOption {
+        default = "hound";
+        type = types.str;
+        description = ''
+          Group the hound daemon should execute under.
+        '';
+      };
+
+      extraGroups = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "dialout" ];
+        description = ''
+          List of extra groups that the "hound" user should be a part of.
+        '';
+      };
+
+      home = mkOption {
+        default = "/var/lib/hound";
+        type = types.path;
+        description = ''
+          The path to use as hound's $HOME. If the default user
+          "hound" is configured then this is the home of the "hound"
+          user.
+        '';
+      };
+
+      package = mkOption {
+        default = pkgs.hound;
+        defaultText = "pkgs.hound";
+        type = types.package;
+        description = ''
+          Package for running hound.
+        '';
+      };
+
+      config = mkOption {
+        type = types.str;
+        description = ''
+          The full configuration of the Hound daemon. Note the dbpath
+          should be an absolute path to a writable location on disk.
+        '';
+        example = ''
+          {
+             "max-concurrent-indexers" : 2,
+             "dbpath" : "''${services.hound.home}/data",
+             "repos" : {
+                "nixpkgs": {
+                   "url" : "https://www.github.com/NixOS/nixpkgs.git"
+                }
+             }
+          }
+        '';
+      };
+
+      listen = mkOption {
+        type = types.str;
+        default = "0.0.0.0:6080";
+        example = "127.0.0.1:6080 or just :6080";
+        description = ''
+          Listen on this IP:port / :port
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.groups = optionalAttrs (cfg.group == "hound") {
+      hound.gid = config.ids.gids.hound;
+    };
+
+    users.users = optionalAttrs (cfg.user == "hound") {
+      hound = {
+        description = "hound code search";
+        createHome = true;
+        home = cfg.home;
+        group = cfg.group;
+        extraGroups = cfg.extraGroups;
+        uid = config.ids.uids.hound;
+      };
+    };
+
+    systemd.services.hound = {
+      description = "Hound Code Search";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        WorkingDirectory = cfg.home;
+        ExecStartPre = "${pkgs.git}/bin/git config --global --replace-all http.sslCAinfo /etc/ssl/certs/ca-certificates.crt";
+        ExecStart = "${cfg.package}/bin/houndd" +
+                    " -addr ${cfg.listen}" +
+                    " -conf ${pkgs.writeText "hound.json" cfg.config}";
+
+      };
+      path = [ pkgs.git pkgs.mercurial pkgs.openssh ];
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/search/kibana.nix b/nixpkgs/nixos/modules/services/search/kibana.nix
new file mode 100644
index 000000000000..2beb265ee5d1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/kibana.nix
@@ -0,0 +1,208 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.kibana;
+
+  ge7 = builtins.compareVersions cfg.package.version "7" >= 0;
+  lt6_6 = builtins.compareVersions cfg.package.version "6.6" < 0;
+
+  cfgFile = pkgs.writeText "kibana.json" (builtins.toJSON (
+    (filterAttrsRecursive (n: v: v != null && v != []) ({
+      server.host = cfg.listenAddress;
+      server.port = cfg.port;
+      server.ssl.certificate = cfg.cert;
+      server.ssl.key = cfg.key;
+
+      kibana.index = cfg.index;
+      kibana.defaultAppId = cfg.defaultAppId;
+
+      elasticsearch.url = cfg.elasticsearch.url;
+      elasticsearch.hosts = cfg.elasticsearch.hosts;
+      elasticsearch.username = cfg.elasticsearch.username;
+      elasticsearch.password = cfg.elasticsearch.password;
+
+      elasticsearch.ssl.certificate = cfg.elasticsearch.cert;
+      elasticsearch.ssl.key = cfg.elasticsearch.key;
+      elasticsearch.ssl.certificateAuthorities = cfg.elasticsearch.certificateAuthorities;
+    } // cfg.extraConf)
+  )));
+
+in {
+  options.services.kibana = {
+    enable = mkEnableOption "kibana service";
+
+    listenAddress = mkOption {
+      description = "Kibana listening host";
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    port = mkOption {
+      description = "Kibana listening port";
+      default = 5601;
+      type = types.int;
+    };
+
+    cert = mkOption {
+      description = "Kibana ssl certificate.";
+      default = null;
+      type = types.nullOr types.path;
+    };
+
+    key = mkOption {
+      description = "Kibana ssl key.";
+      default = null;
+      type = types.nullOr types.path;
+    };
+
+    index = mkOption {
+      description = "Elasticsearch index to use for saving kibana config.";
+      default = ".kibana";
+      type = types.str;
+    };
+
+    defaultAppId = mkOption {
+      description = "Elasticsearch default application id.";
+      default = "discover";
+      type = types.str;
+    };
+
+    elasticsearch = {
+      url = mkOption {
+        description = ''
+          Elasticsearch url.
+
+          Defaults to <literal>"http://localhost:9200"</literal>.
+
+          Don't set this when using Kibana >= 7.0.0 because it will result in a
+          configuration error. Use <option>services.kibana.elasticsearch.hosts</option>
+          instead.
+        '';
+        default = null;
+        type = types.nullOr types.str;
+      };
+
+      hosts = mkOption {
+        description = ''
+          The URLs of the Elasticsearch instances to use for all your queries.
+          All nodes listed here must be on the same cluster.
+
+          Defaults to <literal>[ "http://localhost:9200" ]</literal>.
+
+          This option is only valid when using kibana >= 6.6.
+        '';
+        default = null;
+        type = types.nullOr (types.listOf types.str);
+      };
+
+      username = mkOption {
+        description = "Username for elasticsearch basic auth.";
+        default = null;
+        type = types.nullOr types.str;
+      };
+
+      password = mkOption {
+        description = "Password for elasticsearch basic auth.";
+        default = null;
+        type = types.nullOr types.str;
+      };
+
+      ca = mkOption {
+        description = ''
+          CA file to auth against elasticsearch.
+
+          It's recommended to use the <option>certificateAuthorities</option> option
+          when using kibana-5.4 or newer.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      certificateAuthorities = mkOption {
+        description = ''
+          CA files to auth against elasticsearch.
+
+          Please use the <option>ca</option> option when using kibana &lt; 5.4
+          because those old versions don't support setting multiple CA's.
+
+          This defaults to the singleton list [ca] when the <option>ca</option> option is defined.
+        '';
+        default = if cfg.elasticsearch.ca == null then [] else [ca];
+        type = types.listOf types.path;
+      };
+
+      cert = mkOption {
+        description = "Certificate file to auth against elasticsearch.";
+        default = null;
+        type = types.nullOr types.path;
+      };
+
+      key = mkOption {
+        description = "Key file to auth against elasticsearch.";
+        default = null;
+        type = types.nullOr types.path;
+      };
+    };
+
+    package = mkOption {
+      description = "Kibana package to use";
+      default = pkgs.kibana;
+      defaultText = "pkgs.kibana";
+      example = "pkgs.kibana";
+      type = types.package;
+    };
+
+    dataDir = mkOption {
+      description = "Kibana data directory";
+      default = "/var/lib/kibana";
+      type = types.path;
+    };
+
+    extraConf = mkOption {
+      description = "Kibana extra configuration";
+      default = {};
+      type = types.attrs;
+    };
+  };
+
+  config = mkIf (cfg.enable) {
+    assertions = [
+      {
+        assertion = ge7 -> cfg.elasticsearch.url == null;
+        message =
+          "The option services.kibana.elasticsearch.url has been removed when using kibana >= 7.0.0. " +
+          "Please use option services.kibana.elasticsearch.hosts instead.";
+      }
+      {
+        assertion = lt6_6 -> cfg.elasticsearch.hosts == null;
+        message =
+          "The option services.kibana.elasticsearch.hosts is only valid for kibana >= 6.6.";
+      }
+    ];
+    systemd.services.kibana = {
+      description = "Kibana Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "elasticsearch.service" ];
+      environment = { BABEL_CACHE_PATH = "${cfg.dataDir}/.babelcache.json"; };
+      serviceConfig = {
+        ExecStart =
+          "${cfg.package}/bin/kibana" +
+          " --config ${cfgFile}" +
+          " --path.data ${cfg.dataDir}";
+        User = "kibana";
+        WorkingDirectory = cfg.dataDir;
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    users.users.kibana = {
+      uid = config.ids.uids.kibana;
+      description = "Kibana service user";
+      home = cfg.dataDir;
+      createHome = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/search/solr.nix b/nixpkgs/nixos/modules/services/search/solr.nix
new file mode 100644
index 000000000000..a8615a20a1cf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/search/solr.nix
@@ -0,0 +1,110 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.solr;
+
+in
+
+{
+  options = {
+    services.solr = {
+      enable = mkEnableOption "Solr";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.solr;
+        defaultText = "pkgs.solr";
+        description = "Which Solr package to use.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8983;
+        description = "Port on which Solr is ran.";
+      };
+
+      stateDir = mkOption {
+        type = types.path;
+        default = "/var/lib/solr";
+        description = "The solr home directory containing config, data, and logging files.";
+      };
+
+      extraJavaOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Extra command line options given to the java process running Solr.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "solr";
+        description = "User under which Solr is ran.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "solr";
+        description = "Group under which Solr is ran.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.solr = {
+      after = [ "network.target" "remote-fs.target" "nss-lookup.target" "systemd-journald-dev-log.socket" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        SOLR_HOME = "${cfg.stateDir}/data";
+        LOG4J_PROPS = "${cfg.stateDir}/log4j2.xml";
+        SOLR_LOGS_DIR = "${cfg.stateDir}/logs";
+        SOLR_PORT = "${toString cfg.port}";
+      };
+      path = with pkgs; [
+        gawk
+        procps
+      ];
+      preStart = ''
+        mkdir -p "${cfg.stateDir}/data";
+        mkdir -p "${cfg.stateDir}/logs";
+
+        if ! test -e "${cfg.stateDir}/data/solr.xml"; then
+          install -D -m0640 ${cfg.package}/server/solr/solr.xml "${cfg.stateDir}/data/solr.xml"
+          install -D -m0640 ${cfg.package}/server/solr/zoo.cfg "${cfg.stateDir}/data/zoo.cfg"
+        fi
+
+        if ! test -e "${cfg.stateDir}/log4j2.xml"; then
+          install -D -m0640 ${cfg.package}/server/resources/log4j2.xml "${cfg.stateDir}/log4j2.xml"
+        fi
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart="${cfg.package}/bin/solr start -f -a \"${concatStringsSep " " cfg.extraJavaOptions}\"";
+        ExecStop="${cfg.package}/bin/solr stop";
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == "solr") {
+      solr = {
+        group = cfg.group;
+        home = cfg.stateDir;
+        createHome = true;
+        uid = config.ids.uids.solr;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "solr") {
+      solr.gid = config.ids.gids.solr;
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh b/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh
new file mode 100644
index 000000000000..264a7da9cbb6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/bitwarden_rs/backup.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+# Based on: https://github.com/dani-garcia/bitwarden_rs/wiki/Backing-up-your-vault
+if ! mkdir -p "$BACKUP_FOLDER"; then
+  echo "Could not create backup folder '$BACKUP_FOLDER'" >&2
+  exit 1
+fi
+
+if [[ ! -f "$DATA_FOLDER"/db.sqlite3 ]]; then
+  echo "Could not find SQLite database file '$DATA_FOLDER/db.sqlite3'" >&2
+  exit 1
+fi
+
+sqlite3 "$DATA_FOLDER"/db.sqlite3 ".backup '$BACKUP_FOLDER/db.sqlite3'"
+cp "$DATA_FOLDER"/rsa_key.{der,pem,pub.der} "$BACKUP_FOLDER"
+cp -r "$DATA_FOLDER"/attachments "$BACKUP_FOLDER"
+cp -r "$DATA_FOLDER"/icon_cache "$BACKUP_FOLDER"
diff --git a/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix b/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix
new file mode 100644
index 000000000000..903a53270377
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/bitwarden_rs/default.nix
@@ -0,0 +1,145 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.bitwarden_rs;
+  user = config.users.users.bitwarden_rs.name;
+  group = config.users.groups.bitwarden_rs.name;
+
+  # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
+  nameToEnvVar = name:
+    let
+      parts = builtins.split "([A-Z0-9]+)" name;
+      partsToEnvVar = parts: foldl' (key: x: let last = stringLength key - 1; in
+        if isList x then key + optionalString (key != "" && substring last 1 key != "_") "_" + head x
+        else if key != "" && elem (substring 0 1 x) lowerChars then # to handle e.g. [ "disable" [ "2FAR" ] "emember" ]
+          substring 0 last key + optionalString (substring (last - 1) 1 key != "_") "_" + substring last 1 key + toUpper x
+        else key + toUpper x) "" parts;
+    in if builtins.match "[A-Z0-9_]+" name != null then name else partsToEnvVar parts;
+
+  # Due to the different naming schemes allowed for config keys,
+  # we can only check for values consistently after converting them to their corresponding environment variable name.
+  configEnv =
+    let
+      configEnv = listToAttrs (concatLists (mapAttrsToList (name: value:
+        if value != null then [ (nameValuePair (nameToEnvVar name) (if isBool value then boolToString value else toString value)) ] else []
+      ) cfg.config));
+    in { DATA_FOLDER = "/var/lib/bitwarden_rs"; } // optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
+      WEB_VAULT_FOLDER = "${pkgs.bitwarden_rs-vault}/share/bitwarden_rs/vault";
+    } // configEnv;
+
+  configFile = pkgs.writeText "bitwarden_rs.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
+
+  bitwarden_rs = pkgs.bitwarden_rs.override { inherit (cfg) dbBackend; };
+
+in {
+  options.services.bitwarden_rs = with types; {
+    enable = mkEnableOption "bitwarden_rs";
+
+    dbBackend = mkOption {
+      type = enum [ "sqlite" "mysql" "postgresql" ];
+      default = "sqlite";
+      description = ''
+        Which database backend bitwarden_rs will be using.
+      '';
+    };
+
+    backupDir = mkOption {
+      type = nullOr str;
+      default = null;
+      description = ''
+        The directory under which bitwarden_rs will backup its persistent data.
+      '';
+    };
+
+    config = mkOption {
+      type = attrsOf (nullOr (oneOf [ bool int str ]));
+      default = {};
+      example = literalExample ''
+        {
+          domain = "https://bw.domain.tld:8443";
+          signupsAllowed = true;
+          rocketPort = 8222;
+          rocketLog = "critical";
+        }
+      '';
+      description = ''
+        The configuration of bitwarden_rs is done through environment variables,
+        therefore the names are converted from camel case (e.g. disable2FARemember)
+        to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
+        In this conversion digits (0-9) are handled just like upper case characters,
+        so foo2 would be converted to FOO_2.
+        Names already in this format remain unchanged, so FOO2 remains FOO2 if passed as such,
+        even though foo2 would have been converted to FOO_2.
+        This allows working around any potential future conflicting naming conventions.
+
+        Based on the attributes passed to this config option an environment file will be generated
+        that is passed to bitwarden_rs's systemd service.
+
+        The available configuration options can be found in
+        <link xlink:href="https://github.com/dani-garcia/bitwarden_rs/blob/${bitwarden_rs.version}/.env.template">the environment template file</link>.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [ {
+      assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite";
+      message = "Backups for database backends other than sqlite will need customization";
+    } ];
+
+    users.users.bitwarden_rs = {
+      inherit group;
+      isSystemUser = true;
+    };
+    users.groups.bitwarden_rs = { };
+
+    systemd.services.bitwarden_rs = {
+      after = [ "network.target" ];
+      path = with pkgs; [ openssl ];
+      serviceConfig = {
+        User = user;
+        Group = group;
+        EnvironmentFile = configFile;
+        ExecStart = "${bitwarden_rs}/bin/bitwarden_rs";
+        LimitNOFILE = "1048576";
+        LimitNPROC = "64";
+        PrivateTmp = "true";
+        PrivateDevices = "true";
+        ProtectHome = "true";
+        ProtectSystem = "strict";
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        StateDirectory = "bitwarden_rs";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services.backup-bitwarden_rs = mkIf (cfg.backupDir != null) {
+      description = "Backup bitwarden_rs";
+      environment = {
+        DATA_FOLDER = "/var/lib/bitwarden_rs";
+        BACKUP_FOLDER = cfg.backupDir;
+      };
+      path = with pkgs; [ sqlite ];
+      serviceConfig = {
+        SyslogIdentifier = "backup-bitwarden_rs";
+        Type = "oneshot";
+        User = mkDefault user;
+        Group = mkDefault group;
+        ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.timers.backup-bitwarden_rs = mkIf (cfg.backupDir != null) {
+      description = "Backup bitwarden_rs on time";
+      timerConfig = {
+        OnCalendar = mkDefault "23:00";
+        Persistent = "true";
+        Unit = "backup-bitwarden_rs.service";
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/certmgr.nix b/nixpkgs/nixos/modules/services/security/certmgr.nix
new file mode 100644
index 000000000000..94c0ba141179
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/certmgr.nix
@@ -0,0 +1,201 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.certmgr;
+
+  specs = mapAttrsToList (n: v: rec {
+    name = n + ".json";
+    path = if isAttrs v then pkgs.writeText name (builtins.toJSON v) else v;
+  }) cfg.specs;
+
+  allSpecs = pkgs.linkFarm "certmgr.d" specs;
+
+  certmgrYaml = pkgs.writeText "certmgr.yaml" (builtins.toJSON {
+    dir = allSpecs;
+    default_remote = cfg.defaultRemote;
+    svcmgr = cfg.svcManager;
+    before = cfg.validMin;
+    interval = cfg.renewInterval;
+    inherit (cfg) metricsPort metricsAddress;
+  });
+
+  specPaths = map dirOf (concatMap (spec:
+    if isAttrs spec then
+      collect isString (filterAttrsRecursive (n: v: isAttrs v || n == "path") spec)
+    else
+      [ spec ]
+  ) (attrValues cfg.specs));
+
+  preStart = ''
+    ${concatStringsSep " \\\n" (["mkdir -p"] ++ map escapeShellArg specPaths)}
+    ${cfg.package}/bin/certmgr -f ${certmgrYaml} check
+  '';
+in
+{
+  options.services.certmgr = {
+    enable = mkEnableOption "certmgr";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.certmgr;
+      defaultText = "pkgs.certmgr";
+      description = "Which certmgr package to use in the service.";
+    };
+
+    defaultRemote = mkOption {
+      type = types.str;
+      default = "127.0.0.1:8888";
+      description = "The default CA host:port to use.";
+    };
+
+    validMin = mkOption {
+      default = "72h";
+      type = types.str;
+      description = "The interval before a certificate expires to start attempting to renew it.";
+    };
+
+    renewInterval = mkOption {
+      default = "30m";
+      type = types.str;
+      description = "How often to check certificate expirations and how often to update the cert_next_expires metric.";
+    };
+
+    metricsAddress = mkOption {
+      default = "127.0.0.1";
+      type = types.str;
+      description = "The address for the Prometheus HTTP endpoint.";
+    };
+
+    metricsPort = mkOption {
+      default = 9488;
+      type = types.ints.u16;
+      description = "The port for the Prometheus HTTP endpoint.";
+    };
+
+    specs = mkOption {
+      default = {};
+      example = literalExample ''
+      {
+        exampleCert =
+        let
+          domain = "example.com";
+          secret = name: "/var/lib/secrets/''${name}.pem";
+        in {
+          service = "nginx";
+          action = "reload";
+          authority = {
+            file.path = secret "ca";
+          };
+          certificate = {
+            path = secret domain;
+          };
+          private_key = {
+            owner = "root";
+            group = "root";
+            mode = "0600";
+            path = secret "''${domain}-key";
+          };
+          request = {
+            CN = domain;
+            hosts = [ "mail.''${domain}" "www.''${domain}" ];
+            key = {
+              algo = "rsa";
+              size = 2048;
+            };
+            names = {
+              O = "Example Organization";
+              C = "USA";
+            };
+          };
+        };
+        otherCert = "/var/certmgr/specs/other-cert.json";
+      }
+      '';
+      type = with types; attrsOf (either path (submodule {
+        options = {
+          service = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "The service on which to perform &lt;action&gt; after fetching.";
+          };
+
+          action = mkOption {
+            type = addCheck str (x: cfg.svcManager == "command" || elem x ["restart" "reload" "nop"]);
+            default = "nop";
+            description = "The action to take after fetching.";
+          };
+
+          # These ought all to be specified according to certmgr spec def.
+          authority = mkOption {
+            type = attrs;
+            description = "certmgr spec authority object.";
+          };
+
+          certificate = mkOption {
+            type = nullOr attrs;
+            description = "certmgr spec certificate object.";
+          };
+
+          private_key = mkOption {
+            type = nullOr attrs;
+            description = "certmgr spec private_key object.";
+          };
+
+          request = mkOption {
+            type = nullOr attrs;
+            description = "certmgr spec request object.";
+          };
+        };
+    }));
+      description = ''
+        Certificate specs as described by:
+        <link xlink:href="https://github.com/cloudflare/certmgr#certificate-specs" />
+        These will be added to the Nix store, so they will be world readable.
+      '';
+    };
+
+    svcManager = mkOption {
+      default = "systemd";
+      type = types.enum [ "circus" "command" "dummy" "openrc" "systemd" "sysv" ];
+      description = ''
+        This specifies the service manager to use for restarting or reloading services.
+        See: <link xlink:href="https://github.com/cloudflare/certmgr#certmgryaml" />.
+        For how to use the "command" service manager in particular,
+        see: <link xlink:href="https://github.com/cloudflare/certmgr#command-svcmgr-and-how-to-use-it" />.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.specs != {};
+        message = "Certmgr specs cannot be empty.";
+      }
+      {
+        assertion = !any (hasAttrByPath [ "authority" "auth_key" ]) (attrValues cfg.specs);
+        message = ''
+          Inline services.certmgr.specs are added to the Nix store rendering them world readable.
+          Specify paths as specs, if you want to use include auth_key - or use the auth_key_file option."
+        '';
+      }
+    ];
+
+    systemd.services.certmgr = {
+      description = "certmgr";
+      path = mkIf (cfg.svcManager == "command") [ pkgs.bash ];
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      inherit preStart;
+
+      serviceConfig = {
+        Restart = "always";
+        RestartSec = "10s";
+        ExecStart = "${cfg.package}/bin/certmgr -f ${certmgrYaml}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/cfssl.nix b/nixpkgs/nixos/modules/services/security/cfssl.nix
new file mode 100644
index 000000000000..ee6d5d91fe15
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/cfssl.nix
@@ -0,0 +1,209 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cfssl;
+in {
+  options.services.cfssl = {
+    enable = mkEnableOption "the CFSSL CA api-server";
+
+    dataDir = mkOption {
+      default = "/var/lib/cfssl";
+      type = types.path;
+      description = "Cfssl work directory.";
+    };
+
+    address = mkOption {
+      default = "127.0.0.1";
+      type = types.str;
+      description = "Address to bind.";
+    };
+
+    port = mkOption {
+      default = 8888;
+      type = types.ints.u16;
+      description = "Port to bind.";
+    };
+
+    ca = mkOption {
+      defaultText = "\${cfg.dataDir}/ca.pem";
+      type = types.str;
+      description = "CA used to sign the new certificate -- accepts '[file:]fname' or 'env:varname'.";
+    };
+
+    caKey = mkOption {
+      defaultText = "file:\${cfg.dataDir}/ca-key.pem";
+      type = types.str;
+      description = "CA private key -- accepts '[file:]fname' or 'env:varname'.";
+    };
+
+    caBundle = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "Path to root certificate store.";
+    };
+
+    intBundle = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "Path to intermediate certificate store.";
+    };
+
+    intDir = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "Intermediates directory.";
+    };
+
+    metadata = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = ''
+        Metadata file for root certificate presence.
+        The content of the file is a json dictionary (k,v): each key k is
+        a SHA-1 digest of a root certificate while value v is a list of key
+        store filenames.
+      '';
+    };
+
+    remote = mkOption {
+      default = null;
+      type = types.nullOr types.str;
+      description = "Remote CFSSL server.";
+    };
+
+    configFile = mkOption {
+      default = null;
+      type = types.nullOr types.str;
+      description = "Path to configuration file. Do not put this in nix-store as it might contain secrets.";
+    };
+
+    responder = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "Certificate for OCSP responder.";
+    };
+
+    responderKey = mkOption {
+      default = null;
+      type = types.nullOr types.str;
+      description = "Private key for OCSP responder certificate. Do not put this in nix-store.";
+    };
+
+    tlsKey = mkOption {
+      default = null;
+      type = types.nullOr types.str;
+      description = "Other endpoint's CA private key. Do not put this in nix-store.";
+    };
+
+    tlsCert = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "Other endpoint's CA to set up TLS protocol.";
+    };
+
+    mutualTlsCa = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "Mutual TLS - require clients be signed by this CA.";
+    };
+
+    mutualTlsCn = mkOption {
+      default = null;
+      type = types.nullOr types.str;
+      description = "Mutual TLS - regex for whitelist of allowed client CNs.";
+    };
+
+    tlsRemoteCa = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "CAs to trust for remote TLS requests.";
+    };
+
+    mutualTlsClientCert = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "Mutual TLS - client certificate to call remote instance requiring client certs.";
+    };
+
+    mutualTlsClientKey = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "Mutual TLS - client key to call remote instance requiring client certs. Do not put this in nix-store.";
+    };
+
+    dbConfig = mkOption {
+      default = null;
+      type = types.nullOr types.path;
+      description = "Certificate db configuration file. Path must be writeable.";
+    };
+
+    logLevel = mkOption {
+      default = 1;
+      type = types.enum [ 0 1 2 3 4 5 ];
+      description = "Log level (0 = DEBUG, 5 = FATAL).";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.extraGroups.cfssl = {
+      gid = config.ids.gids.cfssl;
+    };
+
+    users.extraUsers.cfssl = {
+      description = "cfssl user";
+      createHome = true;
+      home = cfg.dataDir;
+      group = "cfssl";
+      uid = config.ids.uids.cfssl;
+    };
+
+    systemd.services.cfssl = {
+      description = "CFSSL CA API server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        WorkingDirectory = cfg.dataDir;
+        StateDirectory = cfg.dataDir;
+        StateDirectoryMode = 700;
+        Restart = "always";
+        User = "cfssl";
+
+        ExecStart = with cfg; let
+          opt = n: v: optionalString (v != null) ''-${n}="${v}"'';
+        in
+          lib.concatStringsSep " \\\n" [
+            "${pkgs.cfssl}/bin/cfssl serve"
+            (opt "address" address)
+            (opt "port" (toString port))
+            (opt "ca" ca)
+            (opt "ca-key" caKey)
+            (opt "ca-bundle" caBundle)
+            (opt "int-bundle" intBundle)
+            (opt "int-dir" intDir)
+            (opt "metadata" metadata)
+            (opt "remote" remote)
+            (opt "config" configFile)
+            (opt "responder" responder)
+            (opt "responder-key" responderKey)
+            (opt "tls-key" tlsKey)
+            (opt "tls-cert" tlsCert)
+            (opt "mutual-tls-ca" mutualTlsCa)
+            (opt "mutual-tls-cn" mutualTlsCn)
+            (opt "mutual-tls-client-key" mutualTlsClientKey)
+            (opt "mutual-tls-client-cert" mutualTlsClientCert)
+            (opt "tls-remote-ca" tlsRemoteCa)
+            (opt "db-config" dbConfig)
+            (opt "loglevel" (toString logLevel))
+          ];
+      };
+    };
+
+    services.cfssl = {
+      ca = mkDefault "${cfg.dataDir}/ca.pem";
+      caKey = mkDefault "${cfg.dataDir}/ca-key.pem";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/clamav.nix b/nixpkgs/nixos/modules/services/security/clamav.nix
new file mode 100644
index 000000000000..aaf6fb0479ba
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/clamav.nix
@@ -0,0 +1,147 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  clamavUser = "clamav";
+  stateDir = "/var/lib/clamav";
+  runDir = "/run/clamav";
+  clamavGroup = clamavUser;
+  cfg = config.services.clamav;
+  pkg = pkgs.clamav;
+
+  clamdConfigFile = pkgs.writeText "clamd.conf" ''
+    DatabaseDirectory ${stateDir}
+    LocalSocket ${runDir}/clamd.ctl
+    PidFile ${runDir}/clamd.pid
+    TemporaryDirectory /tmp
+    User clamav
+    Foreground yes
+
+    ${cfg.daemon.extraConfig}
+  '';
+
+  freshclamConfigFile = pkgs.writeText "freshclam.conf" ''
+    DatabaseDirectory ${stateDir}
+    Foreground yes
+    Checks ${toString cfg.updater.frequency}
+
+    ${cfg.updater.extraConfig}
+
+    DatabaseMirror database.clamav.net
+  '';
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "clamav" "updater" "config" ] [ "services" "clamav" "updater" "extraConfig" ])
+  ];
+
+  options = {
+    services.clamav = {
+      daemon = {
+        enable = mkEnableOption "ClamAV clamd daemon";
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Extra configuration for clamd. Contents will be added verbatim to the
+            configuration file.
+          '';
+        };
+      };
+      updater = {
+        enable = mkEnableOption "ClamAV freshclam updater";
+
+        frequency = mkOption {
+          type = types.int;
+          default = 12;
+          description = ''
+            Number of database checks per day.
+          '';
+        };
+
+        interval = mkOption {
+          type = types.str;
+          default = "hourly";
+          description = ''
+            How often freshclam is invoked. See systemd.time(7) for more
+            information about the format.
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Extra configuration for freshclam. Contents will be added verbatim to the
+            configuration file.
+          '';
+        };
+      };
+    };
+  };
+
+  config = mkIf (cfg.updater.enable || cfg.daemon.enable) {
+    environment.systemPackages = [ pkg ];
+
+    users.users.${clamavUser} = {
+      uid = config.ids.uids.clamav;
+      group = clamavGroup;
+      description = "ClamAV daemon user";
+      home = stateDir;
+    };
+
+    users.groups.${clamavGroup} =
+      { gid = config.ids.gids.clamav; };
+
+    environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
+    environment.etc."clamav/clamd.conf".source = clamdConfigFile;
+
+    systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
+      description = "ClamAV daemon (clamd)";
+      after = optional cfg.updater.enable "clamav-freshclam.service";
+      requires = optional cfg.updater.enable "clamav-freshclam.service";
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ clamdConfigFile ];
+
+      preStart = ''
+        mkdir -m 0755 -p ${runDir}
+        chown ${clamavUser}:${clamavGroup} ${runDir}
+      '';
+
+      serviceConfig = {
+        ExecStart = "${pkg}/bin/clamd";
+        ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+        PrivateNetwork = "yes";
+      };
+    };
+
+    systemd.timers.clamav-freshclam = mkIf cfg.updater.enable {
+      description = "Timer for ClamAV virus database updater (freshclam)";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = cfg.updater.interval;
+        Unit = "clamav-freshclam.service";
+      };
+    };
+
+    systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
+      description = "ClamAV virus database updater (freshclam)";
+      restartTriggers = [ freshclamConfigFile ];
+
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+        chown ${clamavUser}:${clamavGroup} ${stateDir}
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkg}/bin/freshclam";
+        SuccessExitStatus = "1"; # if databases are up to date
+        PrivateTmp = "yes";
+        PrivateDevices = "yes";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/fail2ban.nix b/nixpkgs/nixos/modules/services/security/fail2ban.nix
new file mode 100644
index 000000000000..3f84f9c2560c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/fail2ban.nix
@@ -0,0 +1,306 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.fail2ban;
+
+  fail2banConf = pkgs.writeText "fail2ban.local" cfg.daemonConfig;
+
+  jailConf = pkgs.writeText "jail.local" ''
+    [INCLUDES]
+
+    before = paths-nixos.conf
+
+    ${concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def:
+      optionalString (def != "")
+        ''
+          [${name}]
+          ${def}
+        '')))}
+  '';
+
+  pathsConf = pkgs.writeText "paths-nixos.conf" ''
+    # NixOS
+
+    [INCLUDES]
+
+    before = paths-common.conf
+
+    after  = paths-overrides.local
+
+    [DEFAULT]
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.fail2ban = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to enable the fail2ban service.";
+      };
+
+      package = mkOption {
+        default = pkgs.fail2ban;
+        type = types.package;
+        example = "pkgs.fail2ban_0_11";
+        description = "The fail2ban package to use for running the fail2ban service.";
+      };
+
+      packageFirewall = mkOption {
+        default = pkgs.iptables;
+        type = types.package;
+        example = "pkgs.nftables";
+        description = "The firewall package used by fail2ban service.";
+      };
+
+      banaction = mkOption {
+        default = "iptables-multiport";
+        type = types.str;
+        example = "nftables-multiport";
+        description = ''
+          Default banning action (e.g. iptables, iptables-new, iptables-multiport,
+          shorewall, etc) It is used to define action_* variables. Can be overridden
+          globally or per section within jail.local file
+        '';
+      };
+
+      banaction-allports = mkOption {
+        default = "iptables-allport";
+        type = types.str;
+        example = "nftables-allport";
+        description = ''
+          Default banning action (e.g. iptables, iptables-new, iptables-multiport,
+          shorewall, etc) It is used to define action_* variables. Can be overridden
+          globally or per section within jail.local file
+        '';
+      };
+
+      bantime-increment.enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Allows to use database for searching of previously banned ip's to increase
+          a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32...
+        '';
+      };
+
+      bantime-increment.rndtime = mkOption {
+        default = "4m";
+        type = types.str;
+        example = "8m";
+        description = ''
+          "bantime-increment.rndtime" is the max number of seconds using for mixing with random time
+          to prevent "clever" botnets calculate exact time IP can be unbanned again
+        '';
+      };
+
+      bantime-increment.maxtime = mkOption {
+        default = "10h";
+        type = types.str;
+        example = "48h";
+        description = ''
+          "bantime-increment.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
+        '';
+      };
+
+      bantime-increment.factor = mkOption {
+        default = "1";
+        type = types.str;
+        example = "4";
+        description = ''
+          "bantime-increment.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
+          default value of factor is 1 and with default value of formula, the ban time grows by 1, 2, 4, 8, 16 ...
+        '';
+      };
+
+      bantime-increment.formula = mkOption {
+        default = "ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor";
+        type = types.str;
+        example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
+        description = ''
+          "bantime-increment.formula" used by default to calculate next value of ban time, default value bellow,
+          the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32...
+        '';
+      };
+
+      bantime-increment.multipliers = mkOption {
+        default = "1 2 4 8 16 32 64";
+        type = types.str;
+        example = "2 4 16 128";
+        description = ''
+          "bantime-increment.multipliers" used to calculate next value of ban time instead of formula, coresponding
+          previously ban count and given "bantime.factor" (for multipliers default is 1);
+          following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count,
+          always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours
+        '';
+      };
+
+      bantime-increment.overalljails = mkOption {
+        default = false;
+        type = types.bool;
+        example = true;
+        description = ''
+          "bantime-increment.overalljails"  (if true) specifies the search of IP in the database will be executed
+          cross over all jails, if false (dafault), only current jail of the ban IP will be searched
+        '';
+      };
+
+      ignoreIP = mkOption {
+        default = [ ];
+        type = types.listOf types.str;
+        example = [ "192.168.0.0/16" "2001:DB8::42" ];
+        description = ''
+          "ignoreIP" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban will not ban a host which
+          matches an address in this list. Several addresses can be defined using space (and/or comma) separator.
+        '';
+      };
+
+      daemonConfig = mkOption {
+        default = ''
+          [Definition]
+          logtarget = SYSLOG
+          socket    = /run/fail2ban/fail2ban.sock
+          pidfile   = /run/fail2ban/fail2ban.pid
+          dbfile    = /var/lib/fail2ban/fail2ban.sqlite3
+        '';
+        type = types.lines;
+        description = ''
+          The contents of Fail2ban's main configuration file.  It's
+          generally not necessary to change it.
+       '';
+      };
+
+      jails = mkOption {
+        default = { };
+        example = literalExample ''
+          { apache-nohome-iptables = '''
+              # Block an IP address if it accesses a non-existent
+              # home directory more than 5 times in 10 minutes,
+              # since that indicates that it's scanning.
+              filter   = apache-nohome
+              action   = iptables-multiport[name=HTTP, port="http,https"]
+              logpath  = /var/log/httpd/error_log*
+              findtime = 600
+              bantime  = 600
+              maxretry = 5
+            ''';
+          }
+        '';
+        type = types.attrsOf types.lines;
+        description = ''
+          The configuration of each Fail2ban “jail”.  A jail
+          consists of an action (such as blocking a port using
+          <command>iptables</command>) that is triggered when a
+          filter applied to a log file triggers more than a certain
+          number of times in a certain time period.  Actions are
+          defined in <filename>/etc/fail2ban/action.d</filename>,
+          while filters are defined in
+          <filename>/etc/fail2ban/filter.d</filename>.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    warnings = mkIf (config.networking.firewall.enable == false && config.networking.nftables.enable == false) [
+      "fail2ban can not be used without a firewall"
+    ];
+
+    environment.systemPackages = [ cfg.package ];
+
+    environment.etc = {
+      "fail2ban/fail2ban.local".source = fail2banConf;
+      "fail2ban/jail.local".source = jailConf;
+      "fail2ban/fail2ban.conf".source = "${cfg.package}/etc/fail2ban/fail2ban.conf";
+      "fail2ban/jail.conf".source = "${cfg.package}/etc/fail2ban/jail.conf";
+      "fail2ban/paths-common.conf".source = "${cfg.package}/etc/fail2ban/paths-common.conf";
+      "fail2ban/paths-nixos.conf".source = pathsConf;
+      "fail2ban/action.d".source = "${cfg.package}/etc/fail2ban/action.d/*.conf";
+      "fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf";
+    };
+
+    systemd.services.fail2ban = {
+      description = "Fail2ban Intrusion Prevention System";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      partOf = optional config.networking.firewall.enable "firewall.service";
+
+      restartTriggers = [ fail2banConf jailConf pathsConf ];
+      reloadIfChanged = true;
+
+      path = [ cfg.package cfg.packageFirewall pkgs.iproute ];
+
+      unitConfig.Documentation = "man:fail2ban(1)";
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/fail2ban-server -xf start";
+        ExecStop = "${cfg.package}/bin/fail2ban-server stop";
+        ExecReload = "${cfg.package}/bin/fail2ban-server reload";
+        Type = "simple";
+        Restart = "on-failure";
+        PIDFile = "/run/fail2ban/fail2ban.pid";
+        # Capabilities
+        CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
+        # Security
+        NoNewPrivileges = true;
+        # Directory
+        RuntimeDirectory = "fail2ban";
+        RuntimeDirectoryMode = "0750";
+        StateDirectory = "fail2ban";
+        StateDirectoryMode = "0750";
+        LogsDirectory = "fail2ban";
+        LogsDirectoryMode = "0750";
+        # Sandboxing
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+      };
+    };
+
+    # Add some reasonable default jails.  The special "DEFAULT" jail
+    # sets default values for all other jails.
+    services.fail2ban.jails.DEFAULT = ''
+      ${optionalString cfg.bantime-increment.enable ''
+        # Bantime incremental
+        bantime.increment    = ${if cfg.bantime-increment.enable then "true" else "false"}
+        bantime.maxtime      = ${cfg.bantime-increment.maxtime}
+        bantime.factor       = ${cfg.bantime-increment.factor}
+        bantime.formula      = ${cfg.bantime-increment.formula}
+        bantime.multipliers  = ${cfg.bantime-increment.multipliers}
+        bantime.overalljails = ${if cfg.bantime-increment.overalljails then "true" else "false"}
+      ''}
+      # Miscellaneous options
+      ignoreip    = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}
+      maxretry    = 3
+      backend     = systemd
+      # Actions
+      banaction   = ${cfg.banaction}
+      banaction_allports = ${cfg.banaction-allports}
+    '';
+    # Block SSH if there are too many failing connection attempts.
+    services.fail2ban.jails.sshd = mkDefault ''
+      enabled = true
+      port    = ${concatMapStringsSep "," (p: toString p) config.services.openssh.ports}
+    '';
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/fprintd.nix b/nixpkgs/nixos/modules/services/security/fprintd.nix
new file mode 100644
index 000000000000..cbac4ef05b8d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/fprintd.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.fprintd;
+
+in
+
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.fprintd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable fprintd daemon and PAM module for fingerprint readers handling.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.fprintd;
+        defaultText = "pkgs.fprintd";
+        description = ''
+          fprintd package to use.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.dbus.packages = [ pkgs.fprintd ];
+
+    environment.systemPackages = [ pkgs.fprintd ];
+
+    systemd.packages = [ cfg.package ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/fprot.nix b/nixpkgs/nixos/modules/services/security/fprot.nix
new file mode 100644
index 000000000000..3a0b08b3c6d8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/fprot.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  fprotUser = "fprot";
+  stateDir = "/var/lib/fprot";
+  fprotGroup = fprotUser;
+  cfg = config.services.fprot;
+in {
+  options = {
+
+    services.fprot = {
+      updater = {
+        enable = mkEnableOption "automatic F-Prot virus definitions database updates";
+
+        productData = mkOption {
+          description = ''
+            product.data file. Defaults to the one supplied with installation package.
+          '';
+        };
+
+        frequency = mkOption {
+          default = 30;
+          description = ''
+            Update virus definitions every X minutes.
+          '';
+        };
+
+        licenseKeyfile = mkOption {
+          description = ''
+            License keyfile. Defaults to the one supplied with installation package.
+          '';
+        };
+
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.updater.enable {
+
+    services.fprot.updater.productData = mkDefault "${pkgs.fprot}/opt/f-prot/product.data";
+    services.fprot.updater.licenseKeyfile = mkDefault "${pkgs.fprot}/opt/f-prot/license.key";
+
+    environment.systemPackages = [ pkgs.fprot ];
+    environment.etc."f-prot.conf" = {
+      source = "${pkgs.fprot}/opt/f-prot/f-prot.conf";
+    };
+
+    users.users.${fprotUser} =
+      { uid = config.ids.uids.fprot;
+        description = "F-Prot daemon user";
+        home = stateDir;
+      };
+
+    users.groups.${fprotGroup} =
+      { gid = config.ids.gids.fprot; };
+
+    services.cron.systemCronJobs = [ "*/${toString cfg.updater.frequency} * * * * root start fprot-updater" ];
+
+    systemd.services.fprot-updater = {
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = false;
+      };
+      wantedBy = [ "multi-user.target" ];
+
+      # have to copy fpupdate executable because it insists on storing the virus database in the same dir
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+        chown ${fprotUser}:${fprotGroup} ${stateDir}
+        cp ${pkgs.fprot}/opt/f-prot/fpupdate ${stateDir}
+        ln -sf ${cfg.updater.productData} ${stateDir}/product.data
+      '';
+
+      script = "/var/lib/fprot/fpupdate --keyfile ${cfg.updater.licenseKeyfile}";
+    };
+ };
+}
diff --git a/nixpkgs/nixos/modules/services/security/haka.nix b/nixpkgs/nixos/modules/services/security/haka.nix
new file mode 100644
index 000000000000..618e689924fd
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/haka.nix
@@ -0,0 +1,156 @@
+# This module defines global configuration for Haka.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.haka;
+
+  haka = cfg.package;
+
+  hakaConf = pkgs.writeText "haka.conf"
+  ''
+    [general]
+    configuration = ${if lib.strings.hasPrefix "/" cfg.configFile
+      then "${cfg.configFile}"
+      else "${haka}/share/haka/sample/${cfg.configFile}"}
+    ${optionalString (builtins.lessThan 0 cfg.threads) "thread = ${cfg.threads}"}
+
+    [packet]
+    ${optionalString cfg.pcap ''module = "packet/pcap"''}
+    ${optionalString cfg.nfqueue ''module = "packet/nqueue"''}
+    ${optionalString cfg.dump.enable ''dump = "yes"''}
+    ${optionalString cfg.dump.enable ''dump_input = "${cfg.dump.input}"''}
+    ${optionalString cfg.dump.enable ''dump_output = "${cfg.dump.output}"''}
+
+    interfaces = "${lib.strings.concatStringsSep "," cfg.interfaces}"
+
+    [log]
+    # Select the log module
+    module = "log/syslog"
+
+    # Set the default logging level
+    #level = "info,packet=debug"
+
+    [alert]
+    # Select the alert module
+    module = "alert/syslog"
+
+    # Disable alert on standard output
+    #alert_on_stdout = no
+
+    # alert/file module option
+    #file = "/dev/null"
+  '';
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.haka = {
+
+      enable = mkEnableOption "Haka";
+
+      package = mkOption {
+        default = pkgs.haka;
+        defaultText = "pkgs.haka";
+        type = types.package;
+        description = "
+          Which Haka derivation to use.
+        ";
+      };
+
+      configFile = mkOption {
+        default = "empty.lua";
+        example = "/srv/haka/myfilter.lua";
+        type = types.str;
+        description = ''
+          Specify which configuration file Haka uses.
+          It can be absolute path or a path relative to the sample directory of
+          the haka git repo.
+        '';
+      };
+
+      interfaces = mkOption {
+        default = [ "eth0" ];
+        example = [ "any" ];
+        type = with types; listOf str;
+        description = ''
+          Specify which interface(s) Haka listens to.
+          Use 'any' to listen to all interfaces.
+        '';
+      };
+
+      threads = mkOption {
+        default = 0;
+        example = 4;
+        type = types.int;
+        description = ''
+          The number of threads that will be used.
+          All system threads are used by default.
+        '';
+      };
+
+      pcap = mkOption {
+        default = true;
+        type = types.bool;
+        description = "Whether to enable pcap";
+      };
+
+      nfqueue = mkEnableOption "nfqueue";
+
+      dump.enable = mkEnableOption "dump";
+      dump.input  = mkOption {
+        default = "/tmp/input.pcap";
+        example = "/path/to/file.pcap";
+        type = types.path;
+        description = "Path to file where incoming packets are dumped";
+      };
+
+      dump.output  = mkOption {
+        default = "/tmp/output.pcap";
+        example = "/path/to/file.pcap";
+        type = types.path;
+        description = "Path to file where outgoing packets are dumped";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.pcap != cfg.nfqueue;
+        message = "either pcap or nfqueue can be enabled, not both.";
+      }
+      { assertion = cfg.nfqueue -> !dump.enable;
+        message = "dump can only be used with nfqueue.";
+      }
+      { assertion = cfg.interfaces != [];
+        message = "at least one interface must be specified.";
+      }];
+
+
+    environment.systemPackages = [ haka ];
+
+    systemd.services.haka = {
+      description = "Haka";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      serviceConfig = {
+        ExecStart = "${haka}/bin/haka -c ${hakaConf}";
+        ExecStop = "${haka}/bin/hakactl stop";
+        User = "root";
+        Type = "forking";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/haveged.nix b/nixpkgs/nixos/modules/services/security/haveged.nix
new file mode 100644
index 000000000000..eca529188810
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/haveged.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.haveged;
+
+in
+
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.haveged = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable to haveged entropy daemon, which refills 
+          /dev/random when low.
+        '';
+      };
+      
+      refill_threshold = mkOption {
+        type = types.int;
+        default = 1024;
+        description = ''
+          The number of bits of available entropy beneath which
+          haveged should refill the entropy pool.
+        '';
+      };
+      
+    };
+    
+  };
+  
+  
+  ###### implementation
+  
+  config = mkIf cfg.enable {
+  
+    systemd.services.haveged =
+      { description = "Entropy Harvesting Daemon";
+        unitConfig.Documentation = "man:haveged(8)";
+        wantedBy = [ "multi-user.target" ];
+
+        path = [ pkgs.haveged ];
+
+        serviceConfig = {
+          ExecStart = "${pkgs.haveged}/bin/haveged -F -w ${toString cfg.refill_threshold} -v 1";
+          SuccessExitStatus = 143;
+          PrivateTmp = true;
+          PrivateDevices = true;
+          PrivateNetwork = true;
+          ProtectSystem = "full";
+          ProtectHome = true;
+        };
+      };
+
+  };
+  
+}
diff --git a/nixpkgs/nixos/modules/services/security/hologram-agent.nix b/nixpkgs/nixos/modules/services/security/hologram-agent.nix
new file mode 100644
index 000000000000..e37334b3cf5e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/hologram-agent.nix
@@ -0,0 +1,58 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+
+let
+  cfg = config.services.hologram-agent;
+
+  cfgFile = pkgs.writeText "hologram-agent.json" (builtins.toJSON {
+    host = cfg.dialAddress;
+  });
+in {
+  options = {
+    services.hologram-agent = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the Hologram agent for AWS instance credentials";
+      };
+
+      dialAddress = mkOption {
+        type        = types.str;
+        default     = "localhost:3100";
+        description = "Hologram server and port.";
+      };
+
+      httpPort = mkOption {
+        type        = types.str;
+        default     = "80";
+        description = "Port for metadata service to listen on.";
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "dummy" ];
+
+    networking.interfaces.dummy0.ipv4.addresses = [
+      { address = "169.254.169.254"; prefixLength = 32; }
+    ];
+
+    systemd.services.hologram-agent = {
+      description = "Provide EC2 instance credentials to machines outside of EC2";
+      after       = [ "network.target" ];
+      wantedBy    = [ "multi-user.target" ];
+      requires    = [ "network-link-dummy0.service" "network-addresses-dummy0.service" ];
+      preStart = ''
+        /run/current-system/sw/bin/rm -fv /run/hologram.sock
+      '';
+      serviceConfig = {
+        ExecStart = "${pkgs.hologram}/bin/hologram-agent -debug -conf ${cfgFile} -port ${cfg.httpPort}";
+      };
+    };
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ nand0p ];
+}
diff --git a/nixpkgs/nixos/modules/services/security/hologram-server.nix b/nixpkgs/nixos/modules/services/security/hologram-server.nix
new file mode 100644
index 000000000000..4acf6ae0e218
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/hologram-server.nix
@@ -0,0 +1,130 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+
+let
+  cfg = config.services.hologram-server;
+
+  cfgFile = pkgs.writeText "hologram-server.json" (builtins.toJSON {
+    ldap = {
+      host = cfg.ldapHost;
+      bind = {
+        dn       = cfg.ldapBindDN;
+        password = cfg.ldapBindPassword;
+      };
+      insecureldap    = cfg.ldapInsecure;
+      userattr        = cfg.ldapUserAttr;
+      baseDN          = cfg.ldapBaseDN;
+      enableldapRoles = cfg.enableLdapRoles;
+      roleAttr        = cfg.roleAttr;
+      groupClassAttr  = cfg.groupClassAttr;
+    };
+    aws = {
+      account     = cfg.awsAccount;
+      defaultrole = cfg.awsDefaultRole;
+    };
+    stats        = cfg.statsAddress;
+    listen       = cfg.listenAddress;
+    cachetimeout = cfg.cacheTimeoutSeconds;
+  });
+in {
+  options = {
+    services.hologram-server = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the Hologram server for AWS instance credentials";
+      };
+
+      listenAddress = mkOption {
+        type        = types.str;
+        default     = "0.0.0.0:3100";
+        description = "Address and port to listen on";
+      };
+
+      ldapHost = mkOption {
+        type        = types.str;
+        description = "Address of the LDAP server to use";
+      };
+
+      ldapInsecure = mkOption {
+        type        = types.bool;
+        default     = false;
+        description = "Whether to connect to LDAP over SSL or not";
+      };
+
+      ldapUserAttr = mkOption {
+        type        = types.str;
+        default     = "cn";
+        description = "The LDAP attribute for usernames";
+      };
+
+      ldapBaseDN = mkOption {
+        type        = types.str;
+        description = "The base DN for your Hologram users";
+      };
+
+      ldapBindDN = mkOption {
+        type        = types.str;
+        description = "DN of account to use to query the LDAP server";
+      };
+
+      ldapBindPassword = mkOption {
+        type        = types.str;
+        description = "Password of account to use to query the LDAP server";
+      };
+
+      enableLdapRoles = mkOption {
+        type        = types.bool;
+        default     = false;
+        description = "Whether to assign user roles based on the user's LDAP group memberships";
+      };
+
+      groupClassAttr = mkOption {
+        type = types.str;
+        default = "groupOfNames";
+        description = "The objectclass attribute to search for groups when enableLdapRoles is true";
+      };
+
+      roleAttr = mkOption {
+        type        = types.str;
+        default     = "businessCategory";
+        description = "Which LDAP group attribute to search for authorized role ARNs";
+      };
+
+      awsAccount = mkOption {
+        type        = types.str;
+        description = "AWS account number";
+      };
+
+      awsDefaultRole = mkOption {
+        type        = types.str;
+        description = "AWS default role";
+      };
+
+      statsAddress = mkOption {
+        type        = types.str;
+        default     = "";
+        description = "Address of statsd server";
+      };
+
+      cacheTimeoutSeconds = mkOption {
+        type        = types.int;
+        default     = 3600;
+        description = "How often (in seconds) to refresh the LDAP cache";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.hologram-server = {
+      description = "Provide EC2 instance credentials to machines outside of EC2";
+      after       = [ "network.target" ];
+      wantedBy    = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.hologram}/bin/hologram-server --debug --conf ${cfgFile}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/munge.nix b/nixpkgs/nixos/modules/services/security/munge.nix
new file mode 100644
index 000000000000..891788864710
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/munge.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.munge;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.munge = {
+      enable = mkEnableOption "munge service";
+
+      password = mkOption {
+        default = "/etc/munge/munge.key";
+        type = types.path;
+        description = ''
+          The path to a daemon's secret key.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.munge ];
+
+    users.users.munge = {
+      description   = "Munge daemon user";
+      isSystemUser  = true;
+      group         = "munge";
+    };
+
+    users.groups.munge = {};
+
+    systemd.services.munged = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      path = [ pkgs.munge pkgs.coreutils ];
+
+      serviceConfig = {
+        ExecStartPre = "+${pkgs.coreutils}/bin/chmod 0400 ${cfg.password}";
+        ExecStart = "${pkgs.munge}/bin/munged --syslog --key-file ${cfg.password}";
+        PIDFile = "/run/munge/munged.pid";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        User = "munge";
+        Group = "munge";
+        StateDirectory = "munge";
+        StateDirectoryMode = "0711";
+        RuntimeDirectory = "munge";
+      };
+
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/nginx-sso.nix b/nixpkgs/nixos/modules/services/security/nginx-sso.nix
new file mode 100644
index 000000000000..d792f90abe64
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/nginx-sso.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nginx.sso;
+  pkg = getBin pkgs.nginx-sso;
+  configYml = pkgs.writeText "nginx-sso.yml" (builtins.toJSON cfg.configuration);
+in {
+  options.services.nginx.sso = {
+    enable = mkEnableOption "nginx-sso service";
+
+    configuration = mkOption {
+      type = types.attrsOf types.unspecified;
+      default = {};
+      example = literalExample ''
+        {
+          listen = { addr = "127.0.0.1"; port = 8080; };
+
+          providers.token.tokens = {
+            myuser = "MyToken";
+          };
+
+          acl = {
+            rule_sets = [
+              {
+                rules = [ { field = "x-application"; equals = "MyApp"; } ];
+                allow = [ "myuser" ];
+              }
+            ];
+          };
+        }
+      '';
+      description = ''
+        nginx-sso configuration
+        (<link xlink:href="https://github.com/Luzifer/nginx-sso/wiki/Main-Configuration">documentation</link>)
+        as a Nix attribute set.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.nginx-sso = {
+      description = "Nginx SSO Backend";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${pkg}/bin/nginx-sso \
+            --config ${configYml} \
+            --frontend-dir ${pkg}/share/frontend
+        '';
+        Restart = "always";
+        DynamicUser = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix b/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix
new file mode 100644
index 000000000000..d5c5437329ea
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/oauth2_proxy.nix
@@ -0,0 +1,586 @@
+# NixOS module for oauth2_proxy.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.oauth2_proxy;
+
+  # oauth2_proxy provides many options that are only relevant if you are using
+  # a certain provider. This set maps from provider name to a function that
+  # takes the configuration and returns a string that can be inserted into the
+  # command-line to launch oauth2_proxy.
+  providerSpecificOptions = {
+    azure = cfg: {
+      azure-tenant = cfg.azure.tenant;
+      resource = cfg.azure.resource;
+    };
+
+    github = cfg: { github = {
+      inherit (cfg.github) org team;
+    }; };
+
+    google = cfg: { google = with cfg.google; optionalAttrs (groups != []) {
+      admin-email = adminEmail;
+      service-account = serviceAccountJSON;
+      group = groups;
+    }; };
+  };
+
+  authenticatedEmailsFile = pkgs.writeText "authenticated-emails" cfg.email.addresses;
+
+  getProviderOptions = cfg: provider: providerSpecificOptions.${provider} or (_: {}) cfg;
+
+  allConfig = with cfg; {
+    inherit (cfg) provider scope upstream;
+    approval-prompt = approvalPrompt;
+    basic-auth-password = basicAuthPassword;
+    client-id = clientID;
+    client-secret = clientSecret;
+    custom-templates-dir = customTemplatesDir;
+    email-domain = email.domains;
+    http-address = httpAddress;
+    login-url = loginURL;
+    pass-access-token = passAccessToken;
+    pass-basic-auth = passBasicAuth;
+    pass-host-header = passHostHeader;
+    reverse-proxy = reverseProxy;
+    proxy-prefix = proxyPrefix;
+    profile-url = profileURL;
+    redeem-url = redeemURL;
+    redirect-url = redirectURL;
+    request-logging = requestLogging;
+    skip-auth-regex = skipAuthRegexes;
+    signature-key = signatureKey;
+    validate-url = validateURL;
+    htpasswd-file = htpasswd.file;
+    cookie = {
+      inherit (cookie) domain secure expire name secret refresh;
+      httponly = cookie.httpOnly;
+    };
+    set-xauthrequest = setXauthrequest;
+  } // lib.optionalAttrs (cfg.email.addresses != null) {
+    authenticated-emails-file = authenticatedEmailsFile;
+  } // lib.optionalAttrs (cfg.passBasicAuth) {
+    basic-auth-password = cfg.basicAuthPassword;
+  } // lib.optionalAttrs (cfg.htpasswd.file != null) {
+    display-htpasswd-file = cfg.htpasswd.displayForm;
+  } // lib.optionalAttrs tls.enable {
+    tls-cert-file = tls.certificate;
+    tls-key-file = tls.key;
+    https-address = tls.httpsAddress;
+  } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig;
+
+  mapConfig = key: attr:
+  if attr != null && attr != [] then (
+    if isDerivation attr then mapConfig key (toString attr) else
+    if (builtins.typeOf attr) == "set" then concatStringsSep " "
+      (mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else
+    if (builtins.typeOf attr) == "list" then concatMapStringsSep " " (mapConfig key) attr else
+    if (builtins.typeOf attr) == "bool" then "--${key}=${boolToString attr}" else
+    if (builtins.typeOf attr) == "string" then "--${key}='${attr}'" else
+    "--${key}=${toString attr}")
+    else "";
+
+  configString = concatStringsSep " " (mapAttrsToList mapConfig allConfig);
+in
+{
+  options.services.oauth2_proxy = {
+    enable = mkEnableOption "oauth2_proxy";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.oauth2_proxy;
+      defaultText = "pkgs.oauth2_proxy";
+      description = ''
+        The package that provides oauth2_proxy.
+      '';
+    };
+
+    ##############################################
+    # PROVIDER configuration
+    # Taken from: https://github.com/pusher/oauth2_proxy/blob/master/providers/providers.go
+    provider = mkOption {
+      type = types.enum [
+        "google"
+        "azure"
+        "facebook"
+        "github"
+        "keycloak"
+        "gitlab"
+        "linkedin"
+        "login.gov"
+        "bitbucket"
+        "nextcloud"
+        "digitalocean"
+        "oidc"
+      ];
+      default = "google";
+      description = ''
+        OAuth provider.
+      '';
+    };
+
+    approvalPrompt = mkOption {
+      type = types.enum ["force" "auto"];
+      default = "force";
+      description = ''
+        OAuth approval_prompt.
+      '';
+    };
+
+    clientID = mkOption {
+      type = types.nullOr types.str;
+      description = ''
+        The OAuth Client ID.
+      '';
+      example = "123456.apps.googleusercontent.com";
+    };
+
+    clientSecret = mkOption {
+      type = types.nullOr types.str;
+      description = ''
+        The OAuth Client Secret.
+      '';
+    };
+
+    skipAuthRegexes = mkOption {
+     type = types.listOf types.str;
+     default = [];
+     description = ''
+       Skip authentication for requests matching any of these regular
+       expressions.
+     '';
+    };
+
+    # XXX: Not clear whether these two options are mutually exclusive or not.
+    email = {
+      domains = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Authenticate emails with the specified domains. Use
+          <literal>*</literal> to authenticate any email.
+        '';
+      };
+
+      addresses = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = ''
+          Line-separated email addresses that are allowed to authenticate.
+        '';
+      };
+    };
+
+    loginURL = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Authentication endpoint.
+
+        You only need to set this if you are using a self-hosted provider (e.g.
+        Github Enterprise). If you're using a publicly hosted provider
+        (e.g github.com), then the default works.
+      '';
+      example = "https://provider.example.com/oauth/authorize";
+    };
+
+    redeemURL = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Token redemption endpoint.
+
+        You only need to set this if you are using a self-hosted provider (e.g.
+        Github Enterprise). If you're using a publicly hosted provider
+        (e.g github.com), then the default works.
+      '';
+      example = "https://provider.example.com/oauth/token";
+    };
+
+    validateURL = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Access token validation endpoint.
+
+        You only need to set this if you are using a self-hosted provider (e.g.
+        Github Enterprise). If you're using a publicly hosted provider
+        (e.g github.com), then the default works.
+      '';
+      example = "https://provider.example.com/user/emails";
+    };
+
+    redirectURL = mkOption {
+      # XXX: jml suspects this is always necessary, but the command-line
+      # doesn't require it so making it optional.
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The OAuth2 redirect URL.
+      '';
+      example = "https://internalapp.yourcompany.com/oauth2/callback";
+    };
+
+    azure = {
+      tenant = mkOption {
+        type = types.str;
+        default = "common";
+        description = ''
+          Go to a tenant-specific or common (tenant-independent) endpoint.
+        '';
+      };
+
+      resource = mkOption {
+        type = types.str;
+        description = ''
+          The resource that is protected.
+        '';
+      };
+    };
+
+    google = {
+      adminEmail = mkOption {
+        type = types.str;
+        description = ''
+          The Google Admin to impersonate for API calls.
+
+          Only users with access to the Admin APIs can access the Admin SDK
+          Directory API, thus the service account needs to impersonate one of
+          those users to access the Admin SDK Directory API.
+
+          See <link xlink:href="https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account" />.
+        '';
+      };
+
+      groups = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Restrict logins to members of these Google groups.
+        '';
+      };
+
+      serviceAccountJSON = mkOption {
+        type = types.path;
+        description = ''
+          The path to the service account JSON credentials.
+        '';
+      };
+    };
+
+    github = {
+      org = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Restrict logins to members of this organisation.
+        '';
+      };
+
+      team = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Restrict logins to members of this team.
+        '';
+      };
+    };
+
+
+    ####################################################
+    # UPSTREAM Configuration
+    upstream = mkOption {
+      type = with types; coercedTo str (x: [x]) (listOf str);
+      default = [];
+      description = ''
+        The http url(s) of the upstream endpoint or <literal>file://</literal>
+        paths for static files. Routing is based on the path.
+      '';
+    };
+
+    passAccessToken = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Pass OAuth access_token to upstream via X-Forwarded-Access-Token header.
+      '';
+    };
+
+    passBasicAuth = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream.
+      '';
+    };
+
+    basicAuthPassword = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The password to set when passing the HTTP Basic Auth header.
+      '';
+    };
+
+    passHostHeader = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Pass the request Host Header to upstream.
+      '';
+    };
+
+    signatureKey = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        GAP-Signature request signature key.
+      '';
+      example = "sha1:secret0";
+    };
+
+    cookie = {
+      domain = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          An optional cookie domain to force cookies to.
+        '';
+        example = ".yourcompany.com";
+      };
+
+      expire = mkOption {
+        type = types.str;
+        default = "168h0m0s";
+        description = ''
+          Expire timeframe for cookie.
+        '';
+      };
+
+      httpOnly = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Set HttpOnly cookie flag.
+        '';
+      };
+
+      name = mkOption {
+        type = types.str;
+        default = "_oauth2_proxy";
+        description = ''
+          The name of the cookie that the oauth_proxy creates.
+        '';
+      };
+
+      refresh = mkOption {
+        # XXX: Unclear what the behavior is when this is not specified.
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Refresh the cookie after this duration; 0 to disable.
+        '';
+        example = "168h0m0s";
+      };
+
+      secret = mkOption {
+        type = types.nullOr types.str;
+        description = ''
+          The seed string for secure cookies.
+        '';
+      };
+
+      secure = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Set secure (HTTPS) cookie flag.
+        '';
+      };
+    };
+
+    ####################################################
+    # OAUTH2 PROXY configuration
+
+    httpAddress = mkOption {
+      type = types.str;
+      default = "http://127.0.0.1:4180";
+      description = ''
+        HTTPS listening address.  This module does not expose the port by
+        default. If you want this URL to be accessible to other machines, please
+        add the port to <literal>networking.firewall.allowedTCPPorts</literal>.
+      '';
+    };
+
+    htpasswd = {
+      file = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          Additionally authenticate against a htpasswd file. Entries must be
+          created with <literal>htpasswd -s</literal> for SHA encryption.
+        '';
+      };
+
+      displayForm = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Display username / password login form if an htpasswd file is provided.
+        '';
+      };
+    };
+
+    customTemplatesDir = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Path to custom HTML templates.
+      '';
+    };
+
+    reverseProxy = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        In case when running behind a reverse proxy, controls whether headers
+	like <literal>X-Real-Ip</literal> are accepted. Usage behind a reverse
+        proxy will require this flag to be set to avoid logging the reverse
+        proxy IP address.
+      '';
+    };
+
+    proxyPrefix = mkOption {
+      type = types.str;
+      default = "/oauth2";
+      description = ''
+        The url root path that this proxy should be nested under.
+      '';
+    };
+
+    tls = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to serve over TLS.
+        '';
+      };
+
+      certificate = mkOption {
+        type = types.path;
+        description = ''
+          Path to certificate file.
+        '';
+      };
+
+      key = mkOption {
+        type = types.path;
+        description = ''
+          Path to private key file.
+        '';
+      };
+
+      httpsAddress = mkOption {
+        type = types.str;
+        default = ":443";
+        description = ''
+          <literal>addr:port</literal> to listen on for HTTPS clients.
+
+          Remember to add <literal>port</literal> to
+          <literal>allowedTCPPorts</literal> if you want other machines to be
+          able to connect to it.
+        '';
+      };
+    };
+
+    requestLogging = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Log requests to stdout.
+      '';
+    };
+
+    ####################################################
+    # UNKNOWN
+
+    # XXX: Is this mandatory? Is it part of another group? Is it part of the provider specification?
+    scope = mkOption {
+      # XXX: jml suspects this is always necessary, but the command-line
+      # doesn't require it so making it optional.
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        OAuth scope specification.
+      '';
+    };
+
+    profileURL = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+      	Profile access endpoint.
+      '';
+    };
+
+    setXauthrequest = mkOption {
+      type = types.nullOr types.bool;
+      default = false;
+      description = ''
+        Set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode). Setting this to 'null' means using the upstream default (false).
+      '';
+    };
+
+    extraConfig = mkOption {
+      default = {};
+      description = ''
+        Extra config to pass to oauth2_proxy.
+      '';
+    };
+
+    keyFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        oauth2_proxy allows passing sensitive configuration via environment variables.
+        Make a file that contains lines like
+        OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com
+        and specify the path here.
+      '';
+      example = "/run/keys/oauth2_proxy";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    services.oauth2_proxy = mkIf (cfg.keyFile != null) {
+      clientID = mkDefault null;
+      clientSecret = mkDefault null;
+      cookie.secret = mkDefault null;
+    };
+
+    users.users.oauth2_proxy = {
+      description = "OAuth2 Proxy";
+      isSystemUser = true;
+    };
+
+    systemd.services.oauth2_proxy = {
+      description = "OAuth2 Proxy";
+      path = [ cfg.package ];
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        User = "oauth2_proxy";
+        Restart = "always";
+        ExecStart = "${cfg.package}/bin/oauth2_proxy ${configString}";
+        EnvironmentFile = mkIf (cfg.keyFile != null) cfg.keyFile;
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix b/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix
new file mode 100644
index 000000000000..be6734f439f3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/oauth2_proxy_nginx.nix
@@ -0,0 +1,64 @@
+{ config, lib, ... }:
+with lib;
+let
+  cfg = config.services.oauth2_proxy.nginx;
+in
+{
+  options.services.oauth2_proxy.nginx = {
+    proxy = mkOption {
+      type = types.str;
+      default = config.services.oauth2_proxy.httpAddress;
+      description = ''
+        The address of the reverse proxy endpoint for oauth2_proxy
+      '';
+    };
+    virtualHosts = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        A list of nginx virtual hosts to put behind the oauth2 proxy
+      '';
+    };
+  };
+  config.services.oauth2_proxy = mkIf (cfg.virtualHosts != [] && (hasPrefix "127.0.0.1:" cfg.proxy)) {
+    enable = true;
+  };
+  config.services.nginx = mkMerge ((optional (cfg.virtualHosts != []) {
+    recommendedProxySettings = true; # needed because duplicate headers
+  }) ++ (map (vhost: {
+    virtualHosts.${vhost} = {
+      locations."/oauth2/" = {
+        proxyPass = cfg.proxy;
+        extraConfig = ''
+          proxy_set_header X-Scheme                $scheme;
+          proxy_set_header X-Auth-Request-Redirect $request_uri;
+        '';
+      };
+      locations."/oauth2/auth" = {
+        proxyPass = cfg.proxy;
+        extraConfig = ''
+          proxy_set_header X-Scheme         $scheme;
+          # nginx auth_request includes headers but not body
+          proxy_set_header Content-Length   "";
+          proxy_pass_request_body           off;
+        '';
+      };
+      locations."/".extraConfig = ''
+        auth_request /oauth2/auth;
+        error_page 401 = /oauth2/sign_in;
+
+        # pass information via X-User and X-Email headers to backend,
+        # requires running with --set-xauthrequest flag
+        auth_request_set $user   $upstream_http_x_auth_request_user;
+        auth_request_set $email  $upstream_http_x_auth_request_email;
+        proxy_set_header X-User  $user;
+        proxy_set_header X-Email $email;
+
+        # if you enabled --cookie-refresh, this is needed for it to work with auth_request
+        auth_request_set $auth_cookie $upstream_http_set_cookie;
+        add_header Set-Cookie $auth_cookie;
+      '';
+
+    };
+  }) cfg.virtualHosts));
+}
diff --git a/nixpkgs/nixos/modules/services/security/physlock.nix b/nixpkgs/nixos/modules/services/security/physlock.nix
new file mode 100644
index 000000000000..690eb70079d8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/physlock.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.physlock;
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.physlock = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the <command>physlock</command> screen locking mechanism.
+
+          Enable this and then run <command>systemctl start physlock</command>
+          to securely lock the screen.
+
+          This will switch to a new virtual terminal, turn off console
+          switching and disable SysRq mechanism (when
+          <option>services.physlock.disableSysRq</option> is set)
+          until the root or user password is given.
+        '';
+      };
+
+      allowAnyUser = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow any user to lock the screen. This will install a
+          setuid wrapper to allow any user to start physlock as root, which
+          is a minor security risk. Call the physlock binary to use this instead
+          of using the systemd service.
+
+          Note that you might need to relog to have the correct binary in your
+          PATH upon changing this option.
+        '';
+      };
+
+      disableSysRq = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to disable SysRq when locked with physlock.
+        '';
+      };
+
+      lockOn = {
+
+        suspend = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to lock screen with physlock just before suspend.
+          '';
+        };
+
+        hibernate = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to lock screen with physlock just before hibernate.
+          '';
+        };
+
+        extraTargets = mkOption {
+          type = types.listOf types.str;
+          default = [];
+          example = [ "display-manager.service" ];
+          description = ''
+            Other targets to lock the screen just before.
+
+            Useful if you want to e.g. both autologin to X11 so that
+            your <filename>~/.xsession</filename> gets executed and
+            still to have the screen locked so that the system can be
+            booted relatively unattended.
+          '';
+        };
+
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable (mkMerge [
+    {
+
+      # for physlock -l and physlock -L
+      environment.systemPackages = [ pkgs.physlock ];
+
+      systemd.services.physlock = {
+        enable = true;
+        description = "Physlock";
+        wantedBy = optional cfg.lockOn.suspend   "suspend.target"
+                ++ optional cfg.lockOn.hibernate "hibernate.target"
+                ++ cfg.lockOn.extraTargets;
+        before   = optional cfg.lockOn.suspend   "systemd-suspend.service"
+                ++ optional cfg.lockOn.hibernate "systemd-hibernate.service"
+                ++ optional (cfg.lockOn.hibernate || cfg.lockOn.suspend) "systemd-suspend-then-hibernate.service"
+                ++ cfg.lockOn.extraTargets;
+        serviceConfig = {
+          Type = "forking";
+          ExecStart = "${pkgs.physlock}/bin/physlock -d${optionalString cfg.disableSysRq "s"}";
+        };
+      };
+
+      security.pam.services.physlock = {};
+
+    }
+
+    (mkIf cfg.allowAnyUser {
+
+      security.wrappers.physlock = { source = "${pkgs.physlock}/bin/physlock"; user = "root"; };
+
+    })
+  ]);
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/privacyidea.nix b/nixpkgs/nixos/modules/services/security/privacyidea.nix
new file mode 100644
index 000000000000..d6abfd0e2718
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/privacyidea.nix
@@ -0,0 +1,279 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.privacyidea;
+
+  uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; };
+  python = uwsgi.python3;
+  penv = python.withPackages (ps: [ ps.privacyidea ]);
+  logCfg = pkgs.writeText "privacyidea-log.cfg" ''
+    [formatters]
+    keys=detail
+
+    [handlers]
+    keys=stream
+
+    [formatter_detail]
+    class=privacyidea.lib.log.SecureFormatter
+    format=[%(asctime)s][%(process)d][%(thread)d][%(levelname)s][%(name)s:%(lineno)d] %(message)s
+
+    [handler_stream]
+    class=StreamHandler
+    level=NOTSET
+    formatter=detail
+    args=(sys.stdout,)
+
+    [loggers]
+    keys=root,privacyidea
+
+    [logger_privacyidea]
+    handlers=stream
+    qualname=privacyidea
+    level=INFO
+
+    [logger_root]
+    handlers=stream
+    level=ERROR
+  '';
+
+  piCfgFile = pkgs.writeText "privacyidea.cfg" ''
+    SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ]
+    SQLALCHEMY_DATABASE_URI = 'postgresql:///privacyidea'
+    SECRET_KEY = '${cfg.secretKey}'
+    PI_PEPPER = '${cfg.pepper}'
+    PI_ENCFILE = '${cfg.encFile}'
+    PI_AUDIT_KEY_PRIVATE = '${cfg.auditKeyPrivate}'
+    PI_AUDIT_KEY_PUBLIC = '${cfg.auditKeyPublic}'
+    PI_LOGCONFIG = '${logCfg}'
+    ${cfg.extraConfig}
+  '';
+
+in
+
+{
+  options = {
+    services.privacyidea = {
+      enable = mkEnableOption "PrivacyIDEA";
+
+      stateDir = mkOption {
+        type = types.str;
+        default = "/var/lib/privacyidea";
+        description = ''
+          Directory where all PrivacyIDEA files will be placed by default.
+        '';
+      };
+
+      superuserRealm = mkOption {
+        type = types.listOf types.str;
+        default = [ "super" "administrators" ];
+        description = ''
+          The realm where users are allowed to login as administrators.
+        '';
+      };
+
+      secretKey = mkOption {
+        type = types.str;
+        example = "t0p s3cr3t";
+        description = ''
+          This is used to encrypt the auth_token.
+        '';
+      };
+
+      pepper = mkOption {
+        type = types.str;
+        example = "Never know...";
+        description = ''
+          This is used to encrypt the admin passwords.
+        '';
+      };
+
+      encFile = mkOption {
+        type = types.str;
+        default = "${cfg.stateDir}/enckey";
+        description = ''
+          This is used to encrypt the token data and token passwords
+        '';
+      };
+
+      auditKeyPrivate = mkOption {
+        type = types.str;
+        default = "${cfg.stateDir}/private.pem";
+        description = ''
+          Private Key for signing the audit log.
+        '';
+      };
+
+      auditKeyPublic = mkOption {
+        type = types.str;
+        default = "${cfg.stateDir}/public.pem";
+        description = ''
+          Public key for checking signatures of the audit log.
+        '';
+      };
+
+      adminPasswordFile = mkOption {
+        type = types.path;
+        description = "File containing password for the admin user";
+      };
+
+      adminEmail = mkOption {
+        type = types.str;
+        example = "admin@example.com";
+        description = "Mail address for the admin user";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration options for pi.cfg.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "privacyidea";
+        description = "User account under which PrivacyIDEA runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "privacyidea";
+        description = "Group account under which PrivacyIDEA runs.";
+      };
+
+      ldap-proxy = {
+        enable = mkEnableOption "PrivacyIDEA LDAP Proxy";
+
+        configFile = mkOption {
+          type = types.path;
+          default = "";
+          description = ''
+            Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini).
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "pi-ldap-proxy";
+          description = "User account under which PrivacyIDEA LDAP proxy runs.";
+        };
+
+        group = mkOption {
+          type = types.str;
+          default = "pi-ldap-proxy";
+          description = "Group account under which PrivacyIDEA LDAP proxy runs.";
+        };
+      };
+    };
+  };
+
+  config = mkMerge [
+
+    (mkIf cfg.enable {
+
+      environment.systemPackages = [ python.pkgs.privacyidea ];
+
+      services.postgresql.enable = mkDefault true;
+
+      systemd.services.privacyidea = let
+        piuwsgi = pkgs.writeText "uwsgi.json" (builtins.toJSON {
+          uwsgi = {
+            plugins = [ "python3" ];
+            pythonpath = "${penv}/${uwsgi.python3.sitePackages}";
+            socket = "/run/privacyidea/socket";
+            uid = cfg.user;
+            gid = cfg.group;
+            chmod-socket = 770;
+            chown-socket = "${cfg.user}:nginx";
+            chdir = cfg.stateDir;
+            wsgi-file = "${penv}/etc/privacyidea/privacyideaapp.wsgi";
+            processes = 4;
+            harakiri = 60;
+            reload-mercy = 8;
+            stats = "/run/privacyidea/stats.socket";
+            max-requests = 2000;
+            limit-as = 1024;
+            reload-on-as = 512;
+            reload-on-rss = 256;
+            no-orphans = true;
+            vacuum = true;
+          };
+        });
+      in {
+        wantedBy = [ "multi-user.target" ];
+        after = [ "postgresql.service" ];
+        path = with pkgs; [ openssl ];
+        environment.PRIVACYIDEA_CONFIGFILE = piCfgFile;
+        preStart = let
+          pi-manage = "${pkgs.sudo}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage";
+          pgsu = config.services.postgresql.superUser;
+          psql = config.services.postgresql.package;
+        in ''
+          mkdir -p ${cfg.stateDir} /run/privacyidea
+          chown ${cfg.user}:${cfg.group} -R ${cfg.stateDir} /run/privacyidea
+          if ! test -e "${cfg.stateDir}/db-created"; then
+            ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user}
+            ${pkgs.sudo}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea
+            ${pi-manage} create_enckey
+            ${pi-manage} create_audit_keys
+            ${pi-manage} createdb
+            ${pi-manage} admin add admin -e ${cfg.adminEmail} -p "$(cat ${cfg.adminPasswordFile})"
+            ${pi-manage} db stamp head -d ${penv}/lib/privacyidea/migrations
+            touch "${cfg.stateDir}/db-created"
+            chmod g+r "${cfg.stateDir}/enckey" "${cfg.stateDir}/private.pem"
+          fi
+          ${pi-manage} db upgrade -d ${penv}/lib/privacyidea/migrations
+        '';
+        serviceConfig = {
+          Type = "notify";
+          ExecStart = "${uwsgi}/bin/uwsgi --json ${piuwsgi}";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+          ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+          NotifyAccess = "main";
+          KillSignal = "SIGQUIT";
+          StandardError = "syslog";
+        };
+      };
+
+      users.users.privacyidea = mkIf (cfg.user == "privacyidea") {
+        group = cfg.group;
+      };
+
+      users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {};
+    })
+
+    (mkIf cfg.ldap-proxy.enable {
+
+      systemd.services.privacyidea-ldap-proxy = let
+        ldap-proxy-env = pkgs.python2.withPackages (ps: [ ps.privacyidea-ldap-proxy ]);
+      in {
+        description = "privacyIDEA LDAP proxy";
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = cfg.ldap-proxy.user;
+          Group = cfg.ldap-proxy.group;
+          ExecStart = ''
+            ${ldap-proxy-env}/bin/twistd \
+              --nodaemon \
+              --pidfile= \
+              -u ${cfg.ldap-proxy.user} \
+              -g ${cfg.ldap-proxy.group} \
+              ldap-proxy \
+              -c ${cfg.ldap-proxy.configFile}
+          '';
+          Restart = "always";
+        };
+      };
+
+      users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") {
+        group = cfg.ldap-proxy.group;
+      };
+
+      users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {};
+    })
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix b/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix
new file mode 100644
index 000000000000..5908f727d535
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/shibboleth-sp.nix
@@ -0,0 +1,75 @@
+{pkgs, config, lib, ...}:
+
+with lib;
+let
+  cfg = config.services.shibboleth-sp;
+in {
+  options = {
+    services.shibboleth-sp = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the shibboleth service";
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        example = "${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml";
+        description = "Path to shibboleth config file";
+      };
+
+      fastcgi.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to include the shibauthorizer and shibresponder FastCGI processes";
+      };
+
+      fastcgi.shibAuthorizerPort = mkOption {
+        type = types.int;
+        default = 9100;
+        description = "Port for shibauthorizer FastCGI proccess to bind to";
+      };
+
+      fastcgi.shibResponderPort = mkOption {
+        type = types.int;
+        default = 9101;
+        description = "Port for shibauthorizer FastCGI proccess to bind to";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.shibboleth-sp = {
+      description = "Provides SSO and federation for web applications";
+      after       = lib.optionals cfg.fastcgi.enable [ "shibresponder.service" "shibauthorizer.service" ];
+      wantedBy    = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.shibboleth-sp}/bin/shibd -F -d ${pkgs.shibboleth-sp} -c ${cfg.configFile}";
+      };
+    };
+
+    systemd.services.shibresponder = mkIf cfg.fastcgi.enable {
+      description = "Provides SSO through Shibboleth via FastCGI";
+      after       = [ "network.target" ];
+      wantedBy    = [ "multi-user.target" ];
+      path    	  = [ "${pkgs.spawn_fcgi}" ];
+      environment.SHIBSP_CONFIG = "${cfg.configFile}";
+      serviceConfig = {
+        ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibResponderPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibresponder";
+      };
+    };
+
+    systemd.services.shibauthorizer = mkIf cfg.fastcgi.enable {
+      description = "Provides SSO through Shibboleth via FastCGI";
+      after       = [ "network.target" ];
+      wantedBy    = [ "multi-user.target" ];
+      path    	  = [ "${pkgs.spawn_fcgi}" ];
+      environment.SHIBSP_CONFIG = "${cfg.configFile}";
+      serviceConfig = {
+        ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibAuthorizerPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibauthorizer";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ jammerful ];
+}
diff --git a/nixpkgs/nixos/modules/services/security/sks.nix b/nixpkgs/nixos/modules/services/security/sks.nix
new file mode 100644
index 000000000000..a91060dc659a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/sks.nix
@@ -0,0 +1,146 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sks;
+  sksPkg = cfg.package;
+  dbConfig = pkgs.writeText "DB_CONFIG" ''
+    ${cfg.extraDbConfig}
+  '';
+
+in {
+  meta.maintainers = with maintainers; [ primeos calbrecht jcumming ];
+
+  options = {
+
+    services.sks = {
+
+      enable = mkEnableOption ''
+        SKS (synchronizing key server for OpenPGP) and start the database
+        server. You need to create "''${dataDir}/dump/*.gpg" for the initial
+        import'';
+
+      package = mkOption {
+        default = pkgs.sks;
+        defaultText = "pkgs.sks";
+        type = types.package;
+        description = "Which SKS derivation to use.";
+      };
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/db/sks";
+        example = "/var/lib/sks";
+        # TODO: The default might change to "/var/lib/sks" as this is more
+        # common. There's also https://github.com/NixOS/nixpkgs/issues/26256
+        # and "/var/db" is not FHS compliant (seems to come from BSD).
+        description = ''
+          Data directory (-basedir) for SKS, where the database and all
+          configuration files are located (e.g. KDB, PTree, membership and
+          sksconf).
+        '';
+      };
+
+      extraDbConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Set contents of the files "KDB/DB_CONFIG" and "PTree/DB_CONFIG" within
+          the ''${dataDir} directory. This is used to configure options for the
+          database for the sks key server.
+
+          Documentation of available options are available in the file named
+          "sampleConfig/DB_CONFIG" in the following repository:
+          https://bitbucket.org/skskeyserver/sks-keyserver/src
+        '';
+      };
+
+      hkpAddress = mkOption {
+        default = [ "127.0.0.1" "::1" ];
+        type = types.listOf types.str;
+        description = ''
+          Domain names, IPv4 and/or IPv6 addresses to listen on for HKP
+          requests.
+        '';
+      };
+
+      hkpPort = mkOption {
+        default = 11371;
+        type = types.ints.u16;
+        description = "HKP port to listen on.";
+      };
+
+      webroot = mkOption {
+        type = types.nullOr types.path;
+        default = "${sksPkg.webSamples}/OpenPKG";
+        defaultText = "\${pkgs.sks.webSamples}/OpenPKG";
+        description = ''
+          Source directory (will be symlinked, if not null) for the files the
+          built-in webserver should serve. SKS (''${pkgs.sks.webSamples})
+          provides the following examples: "HTML5", "OpenPKG", and "XHTML+ES".
+          The index file can be named index.html, index.htm, index.xhtm, or
+          index.xhtml. Files with the extensions .css, .es, .js, .jpg, .jpeg,
+          .png, or .gif are supported. Subdirectories and filenames with
+          anything other than alphanumeric characters and the '.' character
+          will be ignored.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users = {
+      users.sks = {
+        isSystemUser = true;
+        description = "SKS user";
+        home = cfg.dataDir;
+        createHome = true;
+        group = "sks";
+        useDefaultShell = true;
+        packages = [ sksPkg pkgs.db ];
+      };
+      groups.sks = { };
+    };
+
+    systemd.services = let
+      hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ;
+      hkpPort = builtins.toString cfg.hkpPort;
+    in {
+      sks-db = {
+        description = "SKS database server";
+        after = [ "network.target" ];
+        wantedBy = [ "multi-user.target" ];
+        preStart = ''
+          ${lib.optionalString (cfg.webroot != null)
+            "ln -sfT \"${cfg.webroot}\" web"}
+          mkdir -p dump
+          ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/
+          ${sksPkg}/bin/sks cleandb || true
+          ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
+          # Check that both database configs are symlinks before overwriting them
+          # TODO: The initial build will be without DB_CONFIG, but this will
+          # hopefully not cause any significant problems. It might be better to
+          # create both directories manually but we have to check that this does
+          # not affect the initial build of the DB.
+          for CONFIG_FILE in KDB/DB_CONFIG PTree/DB_CONFIG; do
+            if [ -e $CONFIG_FILE ] && [ ! -L $CONFIG_FILE ]; then
+              echo "$CONFIG_FILE exists but is not a symlink." >&2
+              echo "Please remove $PWD/$CONFIG_FILE manually to continue." >&2
+              exit 1
+            fi
+            ln -sf ${dbConfig} $CONFIG_FILE
+          done
+        '';
+        serviceConfig = {
+          WorkingDirectory = "~";
+          User = "sks";
+          Group = "sks";
+          Restart = "always";
+          ExecStart = "${sksPkg}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}";
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/sshguard.nix b/nixpkgs/nixos/modules/services/security/sshguard.nix
new file mode 100644
index 000000000000..e7a9cefdef30
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/sshguard.nix
@@ -0,0 +1,155 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.sshguard;
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.sshguard = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether to enable the sshguard service.";
+      };
+
+      attack_threshold = mkOption {
+        default = 30;
+        type = types.int;
+        description = ''
+            Block attackers when their cumulative attack score exceeds threshold. Most attacks have a score of 10.
+          '';
+      };
+
+      blacklist_threshold = mkOption {
+        default = null;
+        example = 120;
+        type = types.nullOr types.int;
+        description = ''
+            Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
+          '';
+      };
+
+      blacklist_file = mkOption {
+        default = "/var/lib/sshguard/blacklist.db";
+        type = types.path;
+        description = ''
+            Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
+          '';
+      };
+
+      blocktime = mkOption {
+        default = 120;
+        type = types.int;
+        description = ''
+            Block attackers for initially blocktime seconds after exceeding threshold. Subsequent blocks increase by a factor of 1.5.
+
+            sshguard unblocks attacks at random intervals, so actual block times will be longer.
+          '';
+      };
+
+      detection_time = mkOption {
+        default = 1800;
+        type = types.int;
+        description = ''
+            Remember potential attackers for up to detection_time seconds before resetting their score.
+          '';
+      };
+
+      whitelist = mkOption {
+        default = [ ];
+        example = [ "198.51.100.56" "198.51.100.2" ];
+        type = types.listOf types.str;
+        description = ''
+            Whitelist a list of addresses, hostnames, or address blocks.
+          '';
+      };
+
+      services = mkOption {
+        default = [ "sshd" ];
+        example = [ "sshd" "exim" ];
+        type = types.listOf types.str;
+        description = ''
+            Systemd services sshguard should receive logs of.
+          '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.etc."sshguard.conf".text = let
+      args = lib.concatStringsSep " " ([
+        "-afb"
+        "-p info"
+        "-o cat"
+        "-n1"
+      ] ++ (map (name: "-t ${escapeShellArg name}") cfg.services));
+      backend = if config.networking.nftables.enable
+        then "sshg-fw-nft-sets"
+        else "sshg-fw-ipset";
+    in ''
+      BACKEND="${pkgs.sshguard}/libexec/${backend}"
+      LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl ${args}"
+    '';
+
+    systemd.services.sshguard = {
+      description = "SSHGuard brute-force attacks protection system";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      partOf = optional config.networking.firewall.enable "firewall.service";
+
+      path = with pkgs; if config.networking.nftables.enable
+        then [ nftables iproute systemd ]
+        else [ iptables ipset iproute systemd ];
+
+      # The sshguard ipsets must exist before we invoke
+      # iptables. sshguard creates the ipsets after startup if
+      # necessary, but if we let sshguard do it, we can't reliably add
+      # the iptables rules because postStart races with the creation
+      # of the ipsets. So instead, we create both the ipsets and
+      # firewall rules before sshguard starts.
+      preStart = optionalString config.networking.firewall.enable ''
+        ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:net family inet
+        ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:net family inet6
+        ${pkgs.iptables}/bin/iptables  -I INPUT -m set --match-set sshguard4 src -j DROP
+        ${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP
+      '';
+
+      postStop = optionalString config.networking.firewall.enable ''
+        ${pkgs.iptables}/bin/iptables  -D INPUT -m set --match-set sshguard4 src -j DROP
+        ${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP
+        ${pkgs.ipset}/bin/ipset -quiet destroy sshguard4
+        ${pkgs.ipset}/bin/ipset -quiet destroy sshguard6
+      '';
+
+      unitConfig.Documentation = "man:sshguard(8)";
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = let
+          args = lib.concatStringsSep " " ([
+            "-a ${toString cfg.attack_threshold}"
+            "-p ${toString cfg.blocktime}"
+            "-s ${toString cfg.detection_time}"
+            (optionalString (cfg.blacklist_threshold != null) "-b ${toString cfg.blacklist_threshold}:${cfg.blacklist_file}")
+          ] ++ (map (name: "-w ${escapeShellArg name}") cfg.whitelist));
+        in "${pkgs.sshguard}/bin/sshguard ${args}";
+        Restart = "always";
+        ProtectSystem = "strict";
+        ProtectHome = "tmpfs";
+        RuntimeDirectory = "sshguard";
+        StateDirectory = "sshguard";
+        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/tor.nix b/nixpkgs/nixos/modules/services/security/tor.nix
new file mode 100644
index 000000000000..18c105b2f576
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/tor.nix
@@ -0,0 +1,789 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.tor;
+  torDirectory = "/var/lib/tor";
+  torRunDirectory = "/run/tor";
+
+  opt    = name: value: optionalString (value != null) "${name} ${value}";
+  optint = name: value: optionalString (value != null && value != 0)    "${name} ${toString value}";
+
+  isolationOptions = {
+    type = types.listOf (types.enum [
+      "IsolateClientAddr"
+      "IsolateSOCKSAuth"
+      "IsolateClientProtocol"
+      "IsolateDestPort"
+      "IsolateDestAddr"
+    ]);
+    default = [];
+    example = [
+      "IsolateClientAddr"
+      "IsolateSOCKSAuth"
+      "IsolateClientProtocol"
+      "IsolateDestPort"
+      "IsolateDestAddr"
+    ];
+    description = "Tor isolation options";
+  };
+
+
+  torRc = ''
+    User tor
+    DataDirectory ${torDirectory}
+    ${optionalString cfg.enableGeoIP ''
+      GeoIPFile ${pkgs.tor.geoip}/share/tor/geoip
+      GeoIPv6File ${pkgs.tor.geoip}/share/tor/geoip6
+    ''}
+
+    ${optint "ControlPort" cfg.controlPort}
+    ${optionalString cfg.controlSocket.enable "ControlPort unix:${torRunDirectory}/control GroupWritable RelaxDirModeCheck"}
+  ''
+  # Client connection config
+  + optionalString cfg.client.enable ''
+    SOCKSPort ${cfg.client.socksListenAddress} ${toString cfg.client.socksIsolationOptions}
+    SOCKSPort ${cfg.client.socksListenAddressFaster}
+    ${opt "SocksPolicy" cfg.client.socksPolicy}
+
+    ${optionalString cfg.client.transparentProxy.enable ''
+    TransPort ${cfg.client.transparentProxy.listenAddress} ${toString cfg.client.transparentProxy.isolationOptions}
+    ''}
+
+    ${optionalString cfg.client.dns.enable ''
+    DNSPort ${cfg.client.dns.listenAddress} ${toString cfg.client.dns.isolationOptions}
+    AutomapHostsOnResolve 1
+    AutomapHostsSuffixes ${concatStringsSep "," cfg.client.dns.automapHostsSuffixes}
+    ''}
+  ''
+  # Explicitly disable the SOCKS server if the client is disabled.  In
+  # particular, this makes non-anonymous hidden services possible.
+  + optionalString (! cfg.client.enable) ''
+  SOCKSPort 0
+  ''
+  # Relay config
+  + optionalString cfg.relay.enable ''
+    ORPort ${toString cfg.relay.port}
+    ${opt "Address" cfg.relay.address}
+    ${opt "Nickname" cfg.relay.nickname}
+    ${opt "ContactInfo" cfg.relay.contactInfo}
+
+    ${optint "RelayBandwidthRate" cfg.relay.bandwidthRate}
+    ${optint "RelayBandwidthBurst" cfg.relay.bandwidthBurst}
+    ${opt "AccountingMax" cfg.relay.accountingMax}
+    ${opt "AccountingStart" cfg.relay.accountingStart}
+
+    ${if (cfg.relay.role == "exit") then
+        opt "ExitPolicy" cfg.relay.exitPolicy
+      else
+        "ExitPolicy reject *:*"}
+
+    ${optionalString (elem cfg.relay.role ["bridge" "private-bridge"]) ''
+      BridgeRelay 1
+      ServerTransportPlugin ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${pkgs.obfs4}/bin/obfs4proxy managed
+      ExtORPort auto
+      ${optionalString (cfg.relay.role == "private-bridge") ''
+        ExtraInfoStatistics 0
+        PublishServerDescriptor 0
+      ''}
+    ''}
+  ''
+  # Hidden services
+  + concatStrings (flip mapAttrsToList cfg.hiddenServices (n: v: ''
+    HiddenServiceDir ${torDirectory}/onion/${v.name}
+    ${optionalString (v.version != null) "HiddenServiceVersion ${toString v.version}"}
+    ${flip concatMapStrings v.map (p: ''
+      HiddenServicePort ${toString p.port} ${p.destination}
+    '')}
+    ${optionalString (v.authorizeClient != null) ''
+      HiddenServiceAuthorizeClient ${v.authorizeClient.authType} ${concatStringsSep "," v.authorizeClient.clientNames}
+    ''}
+  ''))
+  + cfg.extraConfig;
+
+  torRcFile = pkgs.writeText "torrc" torRc;
+
+in
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ])
+    (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.")
+    (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.")
+  ];
+
+  options = {
+    services.tor = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the Tor daemon. By default, the daemon is run without
+          relay, exit, bridge or client connectivity.
+        '';
+      };
+
+      enableGeoIP = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whenever to configure Tor daemon to use GeoIP databases.
+
+          Disabling this will disable by-country statistics for
+          bridges and relays and some client and third-party software
+          functionality.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration. Contents will be added verbatim to the
+          configuration file at the end.
+        '';
+      };
+
+      controlPort = mkOption {
+        type = types.nullOr (types.either types.int types.str);
+        default = null;
+        example = 9051;
+        description = ''
+          If set, Tor will accept connections on the specified port
+          and allow them to control the tor process.
+        '';
+      };
+
+      controlSocket = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Wheter to enable Tor control socket. Control socket is created
+            in <literal>${torRunDirectory}/control</literal>
+          '';
+        };
+      };
+
+      client = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to enable Tor daemon to route application
+            connections.  You might want to disable this if you plan
+            running a dedicated Tor relay.
+          '';
+        };
+
+        socksListenAddress = mkOption {
+          type = types.str;
+          default = "127.0.0.1:9050";
+          example = "192.168.0.1:9100";
+          description = ''
+            Bind to this address to listen for connections from
+            Socks-speaking applications. Provides strong circuit
+            isolation, separate circuit per IP address.
+          '';
+        };
+
+        socksListenAddressFaster = mkOption {
+          type = types.str;
+          default = "127.0.0.1:9063";
+          example = "192.168.0.1:9101";
+          description = ''
+            Bind to this address to listen for connections from
+            Socks-speaking applications. Same as
+            <option>socksListenAddress</option> but uses weaker
+            circuit isolation to provide performance suitable for a
+            web browser.
+           '';
+         };
+
+        socksPolicy = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "accept 192.168.0.0/16, reject *";
+          description = ''
+            Entry policies to allow/deny SOCKS requests based on IP
+            address. First entry that matches wins. If no SocksPolicy
+            is set, we accept all (and only) requests from
+            <option>socksListenAddress</option>.
+          '';
+        };
+
+        socksIsolationOptions = mkOption (isolationOptions // {
+          default = ["IsolateDestAddr"];
+        });
+
+        transparentProxy = {
+          enable = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether to enable tor transparent proxy";
+          };
+
+          listenAddress = mkOption {
+            type = types.str;
+            default = "127.0.0.1:9040";
+            example = "192.168.0.1:9040";
+            description = ''
+              Bind transparent proxy to this address.
+            '';
+          };
+
+          isolationOptions = mkOption isolationOptions;
+        };
+
+        dns = {
+          enable = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether to enable tor dns resolver";
+          };
+
+          listenAddress = mkOption {
+            type = types.str;
+            default = "127.0.0.1:9053";
+            example = "192.168.0.1:9053";
+            description = ''
+              Bind tor dns to this address.
+            '';
+          };
+
+          isolationOptions = mkOption isolationOptions;
+
+          automapHostsSuffixes = mkOption {
+            type = types.listOf types.str;
+            default = [".onion" ".exit"];
+            example = [".onion"];
+            description = "List of suffixes to use with automapHostsOnResolve";
+          };
+        };
+
+        privoxy.enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to enable and configure the system Privoxy to use Tor's
+            faster port, suitable for HTTP.
+
+            To have anonymity, protocols need to be scrubbed of identifying
+            information, and this can be accomplished for HTTP by Privoxy.
+
+            Privoxy can also be useful for KDE torification. A good setup would be:
+            setting SOCKS proxy to the default Tor port, providing maximum
+            circuit isolation where possible; and setting HTTP proxy to Privoxy
+            to route HTTP traffic over faster, but less isolated port.
+          '';
+        };
+      };
+
+      relay = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to enable relaying TOR traffic for others.
+
+            See <link xlink:href="https://www.torproject.org/docs/tor-doc-relay" />
+            for details.
+
+            Setting this to true requires setting
+            <option>services.tor.relay.role</option>
+            and
+            <option>services.tor.relay.port</option>
+            options.
+          '';
+        };
+
+        role = mkOption {
+          type = types.enum [ "exit" "relay" "bridge" "private-bridge" ];
+          description = ''
+            Your role in Tor network. There're several options:
+
+            <variablelist>
+            <varlistentry>
+              <term><literal>exit</literal></term>
+              <listitem>
+                <para>
+                  An exit relay. This allows Tor users to access regular
+                  Internet services through your public IP.
+                </para>
+
+                <important><para>
+                  Running an exit relay may expose you to abuse
+                  complaints. See
+                  <link xlink:href="https://www.torproject.org/faq.html.en#ExitPolicies" />
+                  for more info.
+                </para></important>
+
+                <para>
+                  You can specify which services Tor users may access via
+                  your exit relay using <option>exitPolicy</option> option.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><literal>relay</literal></term>
+              <listitem>
+                <para>
+                  Regular relay. This allows Tor users to relay onion
+                  traffic to other Tor nodes, but not to public
+                  Internet.
+                </para>
+
+                <important><para>
+                  Note that some misconfigured and/or disrespectful
+                  towards privacy sites will block you even if your
+                  relay is not an exit relay. That is, just being listed
+                  in a public relay directory can have unwanted
+                  consequences.
+
+                  Which means you might not want to use
+                  this role if you browse public Internet from the same
+                  network as your relay, unless you want to write
+                  e-mails to those sites (you should!).
+                </para></important>
+
+                <para>
+                  See
+                  <link xlink:href="https://www.torproject.org/docs/tor-doc-relay.html.en" />
+                  for more info.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><literal>bridge</literal></term>
+              <listitem>
+                <para>
+                  Regular bridge. Works like a regular relay, but
+                  doesn't list you in the public relay directory and
+                  hides your Tor node behind obfs4proxy.
+                </para>
+
+                <para>
+                  Using this option will make Tor advertise your bridge
+                  to users through various mechanisms like
+                  <link xlink:href="https://bridges.torproject.org/" />, though.
+                </para>
+
+                <important>
+                  <para>
+                    WARNING: THE FOLLOWING PARAGRAPH IS NOT LEGAL ADVICE.
+                    Consult with your lawer when in doubt.
+                  </para>
+
+                  <para>
+                    This role should be safe to use in most situations
+                    (unless the act of forwarding traffic for others is
+                    a punishable offence under your local laws, which
+                    would be pretty insane as it would make ISP
+                    illegal).
+                  </para>
+                </important>
+
+                <para>
+                  See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
+                  for more info.
+                </para>
+              </listitem>
+            </varlistentry>
+
+            <varlistentry>
+              <term><literal>private-bridge</literal></term>
+              <listitem>
+                <para>
+                  Private bridge. Works like regular bridge, but does
+                  not advertise your node in any way.
+                </para>
+
+                <para>
+                  Using this role means that you won't contribute to Tor
+                  network in any way unless you advertise your node
+                  yourself in some way.
+                </para>
+
+                <para>
+                  Use this if you want to run a private bridge, for
+                  example because you'll give out your bridge address
+                  manually to your friends.
+                </para>
+
+                <para>
+                  Switching to this role after measurable time in
+                  "bridge" role is pretty useless as some Tor users
+                  would have learned about your node already. In the
+                  latter case you can still change
+                  <option>port</option> option.
+                </para>
+
+                <para>
+                  See <link xlink:href="https://www.torproject.org/docs/bridges.html.en" />
+                  for more info.
+                </para>
+              </listitem>
+            </varlistentry>
+            </variablelist>
+          '';
+        };
+
+        bridgeTransports = mkOption {
+          type = types.listOf types.str;
+          default = ["obfs4"];
+          example = ["obfs2" "obfs3" "obfs4" "scramblesuit"];
+          description = "List of pluggable transports";
+        };
+
+        nickname = mkOption {
+          type = types.str;
+          default = "anonymous";
+          description = ''
+            A unique handle for your TOR relay.
+          '';
+        };
+
+        contactInfo = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "admin@relay.com";
+          description = ''
+            Contact information for the relay owner (e.g. a mail
+            address and GPG key ID).
+          '';
+        };
+
+        accountingMax = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "450 GBytes";
+          description = ''
+            Specify maximum bandwidth allowed during an accounting period. This
+            allows you to limit overall tor bandwidth over some time period.
+            See the <literal>AccountingMax</literal> option by looking at the
+            tor manual <citerefentry><refentrytitle>tor</refentrytitle>
+            <manvolnum>1</manvolnum></citerefentry> for more.
+
+            Note this limit applies individually to upload and
+            download; if you specify <literal>"500 GBytes"</literal>
+            here, then you may transfer up to 1 TBytes of overall
+            bandwidth (500 GB upload, 500 GB download).
+          '';
+        };
+
+        accountingStart = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "month 1 1:00";
+          description = ''
+            Specify length of an accounting period. This allows you to limit
+            overall tor bandwidth over some time period. See the
+            <literal>AccountingStart</literal> option by looking at the tor
+            manual <citerefentry><refentrytitle>tor</refentrytitle>
+            <manvolnum>1</manvolnum></citerefentry> for more.
+          '';
+        };
+
+        bandwidthRate = mkOption {
+          type = types.nullOr types.int;
+          default = null;
+          example = 100;
+          description = ''
+            Specify this to limit the bandwidth usage of relayed (server)
+            traffic. Your own traffic is still unthrottled. Units: bytes/second.
+          '';
+        };
+
+        bandwidthBurst = mkOption {
+          type = types.nullOr types.int;
+          default = cfg.relay.bandwidthRate;
+          example = 200;
+          description = ''
+            Specify this to allow bursts of the bandwidth usage of relayed (server)
+            traffic. The average usage will still be as specified in relayBandwidthRate.
+            Your own traffic is still unthrottled. Units: bytes/second.
+          '';
+        };
+
+        address = mkOption {
+          type    = types.nullOr types.str;
+          default = null;
+          example = "noname.example.com";
+          description = ''
+            The IP address or full DNS name for advertised address of your relay.
+            Leave unset and Tor will guess.
+          '';
+        };
+
+        port = mkOption {
+          type    = types.either types.int types.str;
+          example = 143;
+          description = ''
+            What port to advertise for Tor connections. This corresponds to the
+            <literal>ORPort</literal> section in the Tor manual; see
+            <citerefentry><refentrytitle>tor</refentrytitle>
+            <manvolnum>1</manvolnum></citerefentry> for more details.
+
+            At a minimum, you should just specify the port for the
+            relay to listen on; a common one like 143, 22, 80, or 443
+            to help Tor users who may have very restrictive port-based
+            firewalls.
+          '';
+        };
+
+        exitPolicy = mkOption {
+          type    = types.nullOr types.str;
+          default = null;
+          example = "accept *:6660-6667,reject *:*";
+          description = ''
+            A comma-separated list of exit policies. They're
+            considered first to last, and the first match wins. If you
+            want to _replace_ the default exit policy, end this with
+            either a reject *:* or an accept *:*. Otherwise, you're
+            _augmenting_ (prepending to) the default exit policy.
+            Leave commented to just use the default, which is
+            available in the man page or at
+            <link xlink:href="https://www.torproject.org/documentation.html" />.
+
+            Look at
+            <link xlink:href="https://www.torproject.org/faq-abuse.html#TypicalAbuses" />
+            for issues you might encounter if you use the default
+            exit policy.
+
+            If certain IPs and ports are blocked externally, e.g. by
+            your firewall, you should update your exit policy to
+            reflect this -- otherwise Tor users will be told that
+            those destinations are down.
+          '';
+        };
+      };
+
+      hiddenServices = mkOption {
+        description = ''
+          A set of static hidden services that terminate their Tor
+          circuits at this node.
+
+          Every element in this set declares a virtual onion host.
+
+          You can specify your onion address by putting corresponding
+          private key to an appropriate place in ${torDirectory}.
+
+          For services without private keys in ${torDirectory} Tor
+          daemon will generate random key pairs (which implies random
+          onion addresses) on restart. The latter could take a while,
+          please be patient.
+
+          <note><para>
+            Hidden services can be useful even if you don't intend to
+            actually <emphasis>hide</emphasis> them, since they can
+            also be seen as a kind of NAT traversal mechanism.
+
+            E.g. the example will make your sshd, whatever runs on
+            "8080" and your mail server available from anywhere where
+            the Tor network is available (which, with the help from
+            bridges, is pretty much everywhere), even if both client
+            and server machines are behind NAT you have no control
+            over.
+          </para></note>
+        '';
+        default = {};
+        example = literalExample ''
+          { "my-hidden-service-example".map = [
+              { port = 22; }                # map ssh port to this machine's ssh
+              { port = 80; toPort = 8080; } # map http port to whatever runs on 8080
+              { port = "sip"; toHost = "mail.example.com"; toPort = "imap"; } # because we can
+            ];
+          }
+        '';
+        type = types.loaOf (types.submodule ({name, ...}: {
+          options = {
+
+             name = mkOption {
+               type = types.str;
+               description = ''
+                 Name of this tor hidden service.
+
+                 This is purely descriptive.
+
+                 After restarting Tor daemon you should be able to
+                 find your .onion address in
+                 <literal>${torDirectory}/onion/$name/hostname</literal>.
+               '';
+             };
+
+             map = mkOption {
+               default = [];
+               description = "Port mapping for this hidden service.";
+               type = types.listOf (types.submodule ({config, ...}: {
+                 options = {
+
+                   port = mkOption {
+                     type = types.either types.int types.str;
+                     example = 80;
+                     description = ''
+                       Hidden service port to "bind to".
+                     '';
+                   };
+
+                   destination = mkOption {
+                     internal = true;
+                     type = types.str;
+                     description = "Forward these connections where?";
+                   };
+
+                   toHost = mkOption {
+                     type = types.str;
+                     default = "127.0.0.1";
+                     description = "Mapping destination host.";
+                   };
+
+                   toPort = mkOption {
+                     type = types.either types.int types.str;
+                     example = 8080;
+                     description = "Mapping destination port.";
+                   };
+
+                 };
+
+                 config = {
+                   toPort = mkDefault config.port;
+                   destination = mkDefault "${config.toHost}:${toString config.toPort}";
+                 };
+               }));
+             };
+
+             authorizeClient = mkOption {
+               default = null;
+               description = "If configured, the hidden service is accessible for authorized clients only.";
+               type = types.nullOr (types.submodule ({...}: {
+
+                 options = {
+
+                   authType = mkOption {
+                     type = types.enum [ "basic" "stealth" ];
+                     description = ''
+                       Either <literal>"basic"</literal> for a general-purpose authorization protocol
+                       or <literal>"stealth"</literal> for a less scalable protocol
+                       that also hides service activity from unauthorized clients.
+                     '';
+                   };
+
+                   clientNames = mkOption {
+                     type = types.nonEmptyListOf (types.strMatching "[A-Za-z0-9+-_]+");
+                     description = ''
+                       Only clients that are listed here are authorized to access the hidden service.
+                       Generated authorization data can be found in <filename>${torDirectory}/onion/$name/hostname</filename>.
+                       Clients need to put this authorization data in their configuration file using <literal>HidServAuth</literal>.
+                     '';
+                   };
+                 };
+               }));
+             };
+
+             version = mkOption {
+               default = null;
+               description = "Rendezvous service descriptor version to publish for the hidden service. Currently, versions 2 and 3 are supported. (Default: 2)";
+               type = types.nullOr (types.enum [ 2 3 ]);
+             };
+          };
+
+          config = {
+            name = mkDefault name;
+          };
+        }));
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # Not sure if `cfg.relay.role == "private-bridge"` helps as tor
+    # sends a lot of stats
+    warnings = optional (cfg.relay.enable && cfg.hiddenServices != {})
+      ''
+        Running Tor hidden services on a public relay makes the
+        presence of hidden services visible through simple statistical
+        analysis of publicly available data.
+
+        You can safely ignore this warning if you don't intend to
+        actually hide your hidden services. In either case, you can
+        always create a container/VM with a separate Tor daemon instance.
+      '';
+
+    users.groups.tor.gid = config.ids.gids.tor;
+    users.users.tor =
+      { description = "Tor Daemon User";
+        createHome  = true;
+        home        = torDirectory;
+        group       = "tor";
+        uid         = config.ids.uids.tor;
+      };
+
+    # We have to do this instead of using RuntimeDirectory option in
+    # the service below because systemd has no way to set owners of
+    # RuntimeDirectory and putting this into the service below
+    # requires that service to relax it's sandbox since this needs
+    # writable /run
+    systemd.services.tor-init =
+      { description = "Tor Daemon Init";
+        wantedBy = [ "tor.service" ];
+        script = ''
+          install -m 0700 -o tor -g tor -d ${torDirectory} ${torDirectory}/onion
+          install -m 0750 -o tor -g tor -d ${torRunDirectory}
+        '';
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+      };
+
+    systemd.services.tor =
+      { description = "Tor Daemon";
+        path = [ pkgs.tor ];
+
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "tor-init.service" "network.target" ];
+        restartTriggers = [ torRcFile ];
+
+        serviceConfig =
+          { Type         = "simple";
+            # Translated from the upstream contrib/dist/tor.service.in
+            ExecStartPre = "${pkgs.tor}/bin/tor -f ${torRcFile} --verify-config";
+            ExecStart    = "${pkgs.tor}/bin/tor -f ${torRcFile}";
+            ExecReload   = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+            KillSignal   = "SIGINT";
+            TimeoutSec   = 30;
+            Restart      = "on-failure";
+            LimitNOFILE  = 32768;
+
+            # Hardening
+            # this seems to unshare /run despite what systemd.exec(5) says
+            PrivateTmp              = mkIf (!cfg.controlSocket.enable) "yes";
+            PrivateDevices          = "yes";
+            ProtectHome             = "yes";
+            ProtectSystem           = "strict";
+            InaccessiblePaths       = "/home";
+            ReadOnlyPaths           = "/";
+            ReadWritePaths          = [ torDirectory torRunDirectory ];
+            NoNewPrivileges         = "yes";
+
+            # tor.service.in has this in, but this line it fails to spawn a namespace when using hidden services
+            #CapabilityBoundingSet   = "CAP_SETUID CAP_SETGID CAP_NET_BIND_SERVICE";
+          };
+      };
+
+    environment.systemPackages = [ pkgs.tor ];
+
+    services.privoxy = mkIf (cfg.client.enable && cfg.client.privoxy.enable) {
+      enable = true;
+      extraConfig = ''
+        forward-socks4a / ${cfg.client.socksListenAddressFaster} .
+        toggle  1
+        enable-remote-toggle 0
+        enable-edit-actions 0
+        enable-remote-http-toggle 0
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/torify.nix b/nixpkgs/nixos/modules/services/security/torify.nix
new file mode 100644
index 000000000000..39551190dd33
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/torify.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+
+  cfg = config.services.tor;
+
+  torify = pkgs.writeTextFile {
+    name = "tsocks";
+    text = ''
+        #!${pkgs.runtimeShell}
+        TSOCKS_CONF_FILE=${pkgs.writeText "tsocks.conf" cfg.tsocks.config} LD_PRELOAD="${pkgs.tsocks}/lib/libtsocks.so $LD_PRELOAD" "$@"
+    '';
+    executable = true;
+    destination = "/bin/tsocks";
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.tor.tsocks = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to build tsocks wrapper script to relay application traffic via Tor.
+
+          <important>
+            <para>You shouldn't use this unless you know what you're
+            doing because your installation of Tor already comes with
+            its own superior (doesn't leak DNS queries)
+            <literal>torsocks</literal> wrapper which does pretty much
+            exactly the same thing as this.</para>
+          </important>
+        '';
+      };
+
+      server = mkOption {
+        type = types.str;
+        default = "localhost:9050";
+        example = "192.168.0.20";
+        description = ''
+          IP address of TOR client to use.
+        '';
+      };
+
+      config = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration. Contents will be added verbatim to TSocks
+          configuration file.
+        '';
+      };
+
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.tsocks.enable {
+
+    environment.systemPackages = [ torify ];  # expose it to the users
+
+    services.tor.tsocks.config = ''
+      server = ${toString(head (splitString ":" cfg.tsocks.server))}
+      server_port = ${toString(tail (splitString ":" cfg.tsocks.server))}
+
+      local = 127.0.0.0/255.128.0.0
+      local = 127.128.0.0/255.192.0.0
+    '';
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/security/torsocks.nix b/nixpkgs/nixos/modules/services/security/torsocks.nix
new file mode 100644
index 000000000000..47ac95c4626e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/torsocks.nix
@@ -0,0 +1,120 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.tor.torsocks;
+  optionalNullStr = b: v: optionalString (b != null) v;
+
+  configFile = server: ''
+    TorAddress ${toString (head (splitString ":" server))}
+    TorPort    ${toString (tail (splitString ":" server))}
+
+    OnionAddrRange ${cfg.onionAddrRange}
+
+    ${optionalNullStr cfg.socks5Username
+        "SOCKS5Username ${cfg.socks5Username}"}
+    ${optionalNullStr cfg.socks5Password
+        "SOCKS5Password ${cfg.socks5Password}"}
+
+    AllowInbound ${if cfg.allowInbound then "1" else "0"}
+  '';
+
+  wrapTorsocks = name: server: pkgs.writeTextFile {
+    name = name;
+    text = ''
+        #!${pkgs.runtimeShell}
+        TORSOCKS_CONF_FILE=${pkgs.writeText "torsocks.conf" (configFile server)} ${pkgs.torsocks}/bin/torsocks "$@"
+    '';
+    executable = true;
+    destination = "/bin/${name}";
+  };
+
+in
+{
+  options = {
+    services.tor.torsocks = {
+      enable = mkOption {
+        type        = types.bool;
+        default     = config.services.tor.enable && config.services.tor.client.enable;
+        description = ''
+          Whether to build <literal>/etc/tor/torsocks.conf</literal>
+          containing the specified global torsocks configuration.
+        '';
+      };
+
+      server = mkOption {
+        type    = types.str;
+        default = "127.0.0.1:9050";
+        example = "192.168.0.20:1234";
+        description = ''
+          IP/Port of the Tor SOCKS server. Currently, hostnames are
+          NOT supported by torsocks.
+        '';
+      };
+
+      fasterServer = mkOption {
+        type    = types.str;
+        default = "127.0.0.1:9063";
+        example = "192.168.0.20:1234";
+        description = ''
+          IP/Port of the Tor SOCKS server for torsocks-faster wrapper suitable for HTTP.
+          Currently, hostnames are NOT supported by torsocks.
+        '';
+      };
+
+      onionAddrRange = mkOption {
+        type    = types.str;
+        default = "127.42.42.0/24";
+        description = ''
+          Tor hidden sites do not have real IP addresses. This
+          specifies what range of IP addresses will be handed to the
+          application as "cookies" for .onion names.  Of course, you
+          should pick a block of addresses which you aren't going to
+          ever need to actually connect to. This is similar to the
+          MapAddress feature of the main tor daemon.
+        '';
+      };
+
+      socks5Username = mkOption {
+        type    = types.nullOr types.str;
+        default = null;
+        example = "bob";
+        description = ''
+          SOCKS5 username. The <literal>TORSOCKS_USERNAME</literal>
+          environment variable overrides this option if it is set.
+        '';
+      };
+
+      socks5Password = mkOption {
+        type    = types.nullOr types.str;
+        default = null;
+        example = "sekret";
+        description = ''
+          SOCKS5 password. The <literal>TORSOCKS_PASSWORD</literal>
+          environment variable overrides this option if it is set.
+        '';
+      };
+
+      allowInbound = mkOption {
+        type    = types.bool;
+        default = false;
+        description = ''
+          Set Torsocks to accept inbound connections. If set to
+          <literal>true</literal>, listen() and accept() will be
+          allowed to be used with non localhost address.
+        '';
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.torsocks (wrapTorsocks "torsocks-faster" cfg.fasterServer) ];
+
+    environment.etc."tor/torsocks.conf" =
+      {
+        source = pkgs.writeText "torsocks.conf" (configFile cfg.server);
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/usbguard.nix b/nixpkgs/nixos/modules/services/security/usbguard.nix
new file mode 100644
index 000000000000..f4118eb87fc7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/usbguard.nix
@@ -0,0 +1,236 @@
+{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";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.usbguard;
+        defaultText = "pkgs.usbguard";
+        description = ''
+          The usbguard package to use. If you do not need the Qt GUI, use
+          <literal>pkgs.usbguard-nox</literal> to save disk space.
+        '';
+      };
+
+      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.lines;
+        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 = [ cfg.package ];
+
+    systemd.services.usbguard = {
+      description = "USBGuard daemon";
+
+      wantedBy = [ "basic.target" ];
+      wants = [ "systemd-udevd.service" ];
+
+      # make sure an empty rule file and required directories exist
+      preStart = ''
+        mkdir -p $(dirname "${cfg.ruleFile}") $(dirname "${cfg.auditFilePath}") "${cfg.IPCAccessControlFiles}" \
+          && ([ -f "${cfg.ruleFile}" ] || touch ${cfg.ruleFile})
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = ''${cfg.package}/bin/usbguard-daemon -P -k -c ${daemonConfFile}'';
+        Restart = "on-failure";
+
+        AmbientCapabilities = "";
+        CapabilityBoundingSet = "CAP_CHOWN CAP_FOWNER";
+        DeviceAllow = "/dev/null rw";
+        DevicePolicy = "strict";
+        IPAddressDeny = "any";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectKernelModules = true;
+        ProtectSystem = true;
+        ReadOnlyPaths = "-/";
+        ReadWritePaths = "-/dev/shm -${dirOf cfg.auditFilePath} -/tmp -${dirOf cfg.ruleFile}";
+        RestrictAddressFamilies = "AF_UNIX AF_NETLINK";
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        SystemCallArchitectures = "native";
+        SystemCallFilter = "@system-service";
+        UMask = "0077";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/security/vault.nix b/nixpkgs/nixos/modules/services/security/vault.nix
new file mode 100644
index 000000000000..6a8a3a93327e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/security/vault.nix
@@ -0,0 +1,156 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.vault;
+
+  configFile = pkgs.writeText "vault.hcl" ''
+    listener "tcp" {
+      address = "${cfg.address}"
+      ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then ''
+          tls_disable = "true"
+        '' else ''
+          tls_cert_file = "${cfg.tlsCertFile}"
+          tls_key_file = "${cfg.tlsKeyFile}"
+        ''}
+      ${cfg.listenerExtraConfig}
+    }
+    storage "${cfg.storageBackend}" {
+      ${optionalString (cfg.storagePath   != null) ''path = "${cfg.storagePath}"''}
+      ${optionalString (cfg.storageConfig != null) cfg.storageConfig}
+    }
+    ${optionalString (cfg.telemetryConfig != "") ''
+        telemetry {
+          ${cfg.telemetryConfig}
+        }
+      ''}
+    ${cfg.extraConfig}
+  '';
+in
+
+{
+  options = {
+    services.vault = {
+      enable = mkEnableOption "Vault daemon";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.vault;
+        defaultText = "pkgs.vault";
+        description = "This option specifies the vault package to use.";
+      };
+
+      address = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8200";
+        description = "The name of the ip interface to listen to";
+      };
+
+      tlsCertFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/path/to/your/cert.pem";
+        description = "TLS certificate file. TLS will be disabled unless this option is set";
+      };
+
+      tlsKeyFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/path/to/your/key.pem";
+        description = "TLS private key file. TLS will be disabled unless this option is set";
+      };
+
+      listenerExtraConfig = mkOption {
+        type = types.lines;
+        default = ''
+          tls_min_version = "tls12"
+        '';
+        description = "Extra text appended to the listener section.";
+      };
+
+      storageBackend = mkOption {
+        type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" "raft" ];
+        default = "inmem";
+        description = "The name of the type of storage backend";
+      };
+
+      storagePath = mkOption {
+        type = types.nullOr types.path;
+        default = if cfg.storageBackend == "file" then "/var/lib/vault" else null;
+        description = "Data directory for file backend";
+      };
+
+      storageConfig = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        description = "Storage configuration";
+      };
+
+      telemetryConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Telemetry configuration";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Extra text appended to <filename>vault.hcl</filename>.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
+        message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig'';
+      }
+      { assertion = (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) && (cfg.storagePath != null -> cfg.storageBackend == "file");
+        message = ''You must set services.vault.storagePath only when using the "file" backend'';
+      }
+    ];
+
+    users.users.vault = {
+      name = "vault";
+      group = "vault";
+      uid = config.ids.uids.vault;
+      description = "Vault daemon user";
+    };
+    users.groups.vault.gid = config.ids.gids.vault;
+
+    systemd.tmpfiles.rules = optional (cfg.storagePath != null)
+      "d '${cfg.storagePath}' 0700 vault vault - -";
+
+    systemd.services.vault = {
+      description = "Vault server daemon";
+
+      wantedBy = ["multi-user.target"];
+      after = [ "network.target" ]
+           ++ optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
+
+      restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
+
+      serviceConfig = {
+        User = "vault";
+        Group = "vault";
+        ExecStart = "${cfg.package}/bin/vault server -config ${configFile}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+        PrivateDevices = true;
+        PrivateTmp = true;
+        ProtectSystem = "full";
+        ProtectHome = "read-only";
+        AmbientCapabilities = "cap_ipc_lock";
+        NoNewPrivileges = true;
+        KillSignal = "SIGINT";
+        TimeoutStopSec = "30s";
+        Restart = "on-failure";
+        StartLimitInterval = "60s";
+        StartLimitBurst = 3;
+      };
+
+      unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/system/cloud-init.nix b/nixpkgs/nixos/modules/services/system/cloud-init.nix
new file mode 100644
index 000000000000..15fe822aec67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/cloud-init.nix
@@ -0,0 +1,180 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.cloud-init;
+    path = with pkgs; [
+      cloud-init
+      iproute
+      nettools
+      openssh
+      shadow
+      utillinux
+    ] ++ optional cfg.btrfs.enable btrfs-progs
+      ++ optional cfg.ext4.enable e2fsprogs
+    ;
+in
+{
+  options = {
+    services.cloud-init = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the cloud-init service. This services reads
+          configuration metadata in a cloud environment and configures
+          the machine according to this metadata.
+
+          This configuration is not completely compatible with the
+          NixOS way of doing configuration, as configuration done by
+          cloud-init might be overriden by a subsequent nixos-rebuild
+          call. However, some parts of cloud-init fall outside of
+          NixOS's responsibility, like filesystem resizing and ssh
+          public key provisioning, and cloud-init is useful for that
+          parts. Thus, be wary that using cloud-init in NixOS might
+          come as some cost.
+        '';
+      };
+
+      btrfs.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Allow the cloud-init service to operate `btrfs` filesystem.
+        '';
+      };
+
+      ext4.enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Allow the cloud-init service to operate `ext4` filesystem.
+        '';
+      };
+
+      config = mkOption {
+        type = types.str;
+        default = ''
+          system_info:
+            distro: nixos
+          users:
+             - root
+
+          disable_root: false
+          preserve_hostname: false
+
+          cloud_init_modules:
+           - migrator
+           - seed_random
+           - bootcmd
+           - write-files
+           - growpart
+           - resizefs
+           - update_etc_hosts
+           - ca-certs
+           - rsyslog
+           - users-groups
+
+          cloud_config_modules:
+           - disk_setup
+           - mounts
+           - ssh-import-id
+           - set-passwords
+           - timezone
+           - disable-ec2-metadata
+           - runcmd
+           - ssh
+
+          cloud_final_modules:
+           - rightscale_userdata
+           - scripts-vendor
+           - scripts-per-once
+           - scripts-per-boot
+           - scripts-per-instance
+           - scripts-user
+           - ssh-authkey-fingerprints
+           - keys-to-console
+           - phone-home
+           - final-message
+           - power-state-change
+          '';
+        description = ''cloud-init configuration.'';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.etc."cloud/cloud.cfg".text = cfg.config;
+
+    systemd.services.cloud-init-local =
+      { description = "Initial cloud-init job (pre-networking)";
+        wantedBy = [ "multi-user.target" ];
+        path = path;
+        serviceConfig =
+          { Type = "oneshot";
+            ExecStart = "${pkgs.cloud-init}/bin/cloud-init init --local";
+            RemainAfterExit = "yes";
+            TimeoutSec = "infinity";
+            StandardOutput = "journal+console";
+          };
+      };
+
+    systemd.services.cloud-init =
+      { description = "Initial cloud-init job (metadata service crawler)";
+        wantedBy = [ "multi-user.target" ];
+        wants = [ "network-online.target" "cloud-init-local.service"
+                  "sshd.service" "sshd-keygen.service" ];
+        after = [ "network-online.target" "cloud-init-local.service" ];
+        before = [ "sshd.service" "sshd-keygen.service" ];
+        requires = [ "network.target "];
+        path = path;
+        serviceConfig =
+          { Type = "oneshot";
+            ExecStart = "${pkgs.cloud-init}/bin/cloud-init init";
+            RemainAfterExit = "yes";
+            TimeoutSec = "infinity";
+            StandardOutput = "journal+console";
+          };
+      };
+
+    systemd.services.cloud-config =
+      { description = "Apply the settings specified in cloud-config";
+        wantedBy = [ "multi-user.target" ];
+        wants = [ "network-online.target" ];
+        after = [ "network-online.target" "syslog.target" "cloud-config.target" ];
+
+        path = path;
+        serviceConfig =
+          { Type = "oneshot";
+            ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=config";
+            RemainAfterExit = "yes";
+            TimeoutSec = "infinity";
+            StandardOutput = "journal+console";
+          };
+      };
+
+    systemd.services.cloud-final =
+      { description = "Execute cloud user/final scripts";
+        wantedBy = [ "multi-user.target" ];
+        wants = [ "network-online.target" ];
+        after = [ "network-online.target" "syslog.target" "cloud-config.service" "rc-local.service" ];
+        requires = [ "cloud-config.target" ];
+        path = path;
+        serviceConfig =
+          { Type = "oneshot";
+            ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=final";
+            RemainAfterExit = "yes";
+            TimeoutSec = "infinity";
+            StandardOutput = "journal+console";
+          };
+      };
+
+    systemd.targets.cloud-config =
+      { description = "Cloud-config availability";
+        requires = [ "cloud-init-local.service" "cloud-init.service" ];
+      };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/dbus.nix b/nixpkgs/nixos/modules/services/system/dbus.nix
new file mode 100644
index 000000000000..4a60fec1ca80
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/dbus.nix
@@ -0,0 +1,116 @@
+# D-Bus configuration and system bus daemon.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.dbus;
+
+  homeDir = "/run/dbus";
+
+  configDir = pkgs.makeDBusConf {
+    suidHelper = "${config.security.wrapperDir}/dbus-daemon-launch-helper";
+    serviceDirectories = cfg.packages;
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.dbus = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        internal = true;
+        description = ''
+          Whether to start the D-Bus message bus daemon, which is
+          required by many other system services and applications.
+        '';
+      };
+
+      packages = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        description = ''
+          Packages whose D-Bus configuration files should be included in
+          the configuration of the D-Bus system-wide or session-wide
+          message bus.  Specifically, files in the following directories
+          will be included into their respective DBus configuration paths:
+          <filename><replaceable>pkg</replaceable>/etc/dbus-1/system.d</filename>
+          <filename><replaceable>pkg</replaceable>/share/dbus-1/system.d</filename>
+          <filename><replaceable>pkg</replaceable>/share/dbus-1/system-services</filename>
+          <filename><replaceable>pkg</replaceable>/etc/dbus-1/session.d</filename>
+          <filename><replaceable>pkg</replaceable>/share/dbus-1/session.d</filename>
+          <filename><replaceable>pkg</replaceable>/share/dbus-1/services</filename>
+        '';
+      };
+
+      socketActivated = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Make the user instance socket activated.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.dbus.daemon pkgs.dbus ];
+
+    environment.etc."dbus-1".source = configDir;
+
+    users.users.messagebus = {
+      uid = config.ids.uids.messagebus;
+      description = "D-Bus system message bus daemon user";
+      home = homeDir;
+      group = "messagebus";
+    };
+
+    users.groups.messagebus.gid = config.ids.gids.messagebus;
+
+    systemd.packages = [ pkgs.dbus.daemon ];
+
+    security.wrappers.dbus-daemon-launch-helper = {
+      source = "${pkgs.dbus.daemon}/libexec/dbus-daemon-launch-helper";
+      owner = "root";
+      group = "messagebus";
+      setuid = true;
+      setgid = false;
+      permissions = "u+rx,g+rx,o-rx";
+    };
+
+    services.dbus.packages = [
+      pkgs.dbus.out
+      config.system.path
+    ];
+
+    systemd.services.dbus = {
+      # Don't restart dbus-daemon. Bad things tend to happen if we do.
+      reloadIfChanged = true;
+      restartTriggers = [ configDir ];
+      environment = { LD_LIBRARY_PATH = config.system.nssModules.path; };
+    };
+
+    systemd.user = {
+      services.dbus = {
+        # Don't restart dbus-daemon. Bad things tend to happen if we do.
+        reloadIfChanged = true;
+        restartTriggers = [ configDir ];
+      };
+      sockets.dbus.wantedBy = mkIf cfg.socketActivated [ "sockets.target" ];
+    };
+
+    environment.pathsToLink = [ "/etc/dbus-1" "/share/dbus-1" ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/earlyoom.nix b/nixpkgs/nixos/modules/services/system/earlyoom.nix
new file mode 100644
index 000000000000..c6a001d30eeb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/earlyoom.nix
@@ -0,0 +1,124 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  ecfg = config.services.earlyoom;
+in
+{
+  options = {
+    services.earlyoom = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable early out of memory killing.
+        '';
+      };
+
+      freeMemThreshold = mkOption {
+        type = types.int;
+        default = 10;
+        description = ''
+          Minimum of availabe memory (in percent).
+          If the free memory falls below this threshold and the analog is true for
+          <option>services.earlyoom.freeSwapThreshold</option>
+          the killing begins.
+        '';
+      };
+
+      freeSwapThreshold = mkOption {
+        type = types.int;
+        default = 10;
+        description = ''
+          Minimum of availabe swap space (in percent).
+          If the available swap space falls below this threshold and the analog
+          is true for <option>services.earlyoom.freeMemThreshold</option>
+          the killing begins.
+        '';
+      };
+
+      useKernelOOMKiller= mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Use kernel OOM killer instead of own user-space implementation.
+        '';
+      };
+
+      ignoreOOMScoreAdjust = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Ignore oom_score_adjust values of processes.
+          User-space implementation only.
+        '';
+      };
+
+      enableDebugInfo = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable debugging messages.
+        '';
+      };
+
+      notificationsCommand = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          This option is deprecated and ignored by earlyoom since 1.6.
+          Use <option>services.earlyoom.enableNotifications</option> instead.
+        '';
+      };
+
+      enableNotifications = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Send notifications about killed processes via the system d-bus.
+          To actually see the notifications in your GUI session, you need to have
+          <literal>systembus-notify</literal> running as your user.
+
+          See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details.
+        '';
+      };
+    };
+  };
+
+  config = mkIf ecfg.enable {
+    assertions = [
+      { assertion = ecfg.freeMemThreshold > 0 && ecfg.freeMemThreshold <= 100;
+        message = "Needs to be a positive percentage"; }
+      { assertion = ecfg.freeSwapThreshold > 0 && ecfg.freeSwapThreshold <= 100;
+        message = "Needs to be a positive percentage"; }
+      { assertion = !ecfg.useKernelOOMKiller || !ecfg.ignoreOOMScoreAdjust;
+        message = "Both options in conjunction do not make sense"; }
+    ];
+
+    warnings = optional (ecfg.notificationsCommand != null)
+      "`services.earlyoom.notificationsCommand` is deprecated and ignored by earlyoom since 1.6.";
+
+    systemd.services.earlyoom = {
+      description = "Early OOM Daemon for Linux";
+      wantedBy = [ "multi-user.target" ];
+      path = optional ecfg.enableNotifications pkgs.dbus;
+      serviceConfig = {
+        StandardOutput = "null";
+        StandardError = "syslog";
+        ExecStart = ''
+          ${pkgs.earlyoom}/bin/earlyoom \
+          -m ${toString ecfg.freeMemThreshold} \
+          -s ${toString ecfg.freeSwapThreshold} \
+          ${optionalString ecfg.useKernelOOMKiller "-k"} \
+          ${optionalString ecfg.ignoreOOMScoreAdjust "-i"} \
+          ${optionalString ecfg.enableDebugInfo "-d"} \
+          ${optionalString ecfg.enableNotifications "-n"}
+        '';
+      };
+    };
+
+    environment.systemPackages = optional ecfg.enableNotifications pkgs.systembus-notify;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/kerberos/default.nix b/nixpkgs/nixos/modules/services/system/kerberos/default.nix
new file mode 100644
index 000000000000..9a1e67399010
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/kerberos/default.nix
@@ -0,0 +1,75 @@
+{config, lib, ...}:
+
+let
+  inherit (lib) mkOption mkIf types length attrNames;
+  cfg = config.services.kerberos_server;
+  kerberos = config.krb5.kerberos;
+
+  aclEntry = {
+    options = {
+      principal = mkOption {
+        type = types.str;
+        description = "Which principal the rule applies to";
+      };
+      access = mkOption {
+        type = types.either
+          (types.listOf (types.enum ["add" "cpw" "delete" "get" "list" "modify"]))
+          (types.enum ["all"]);
+        default = "all";
+        description = "The changes the principal is allowed to make.";
+      };
+      target = mkOption {
+        type = types.str;
+        default = "*";
+        description = "The principals that 'access' applies to.";
+      };
+    };
+  };
+
+  realm = {
+    options = {
+      acl = mkOption {
+        type = types.listOf (types.submodule aclEntry);
+        default = [
+          { principal = "*/admin"; access = "all"; }
+          { principal = "admin"; access = "all"; }
+        ];
+        description = ''
+          The privileges granted to a user.
+        '';
+      };
+    };
+  };
+in
+
+{
+  imports = [
+    ./mit.nix
+    ./heimdal.nix
+  ];
+
+  ###### interface
+  options = {
+    services.kerberos_server = {
+      enable = lib.mkEnableOption "the kerberos authentification server";
+
+      realms = mkOption {
+        type = types.attrsOf (types.submodule realm);
+        description = ''
+          The realm(s) to serve keys for.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ kerberos ];
+    assertions = [{
+      assertion = length (attrNames cfg.realms) <= 1;
+      message = "Only one realm per server is currently supported.";
+    }];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/kerberos/heimdal.nix b/nixpkgs/nixos/modules/services/system/kerberos/heimdal.nix
new file mode 100644
index 000000000000..f0e56c7951a4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/kerberos/heimdal.nix
@@ -0,0 +1,68 @@
+{ pkgs, config, lib, ... } :
+
+let
+  inherit (lib) mkIf concatStringsSep concatMapStrings toList mapAttrs
+    mapAttrsToList;
+  cfg = config.services.kerberos_server;
+  kerberos = config.krb5.kerberos;
+  stateDir = "/var/heimdal";
+  aclFiles = mapAttrs
+    (name: {acl, ...}: pkgs.writeText "${name}.acl" (concatMapStrings ((
+      {principal, access, target, ...} :
+      "${principal}\t${concatStringsSep "," (toList access)}\t${target}\n"
+    )) acl)) cfg.realms;
+
+  kdcConfigs = mapAttrsToList (name: value: ''
+    database = {
+      dbname = ${stateDir}/heimdal
+      acl_file = ${value}
+    }
+  '') aclFiles;
+  kdcConfFile = pkgs.writeText "kdc.conf" ''
+    [kdc]
+    ${concatStringsSep "\n" kdcConfigs}
+  '';
+in
+
+{
+  # No documentation about correct triggers, so guessing at them.
+
+  config = mkIf (cfg.enable && kerberos == pkgs.heimdalFull) {
+    systemd.services.kadmind = {
+      description = "Kerberos Administration Daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig.ExecStart =
+        "${kerberos}/libexec/heimdal/kadmind --config-file=/etc/heimdal-kdc/kdc.conf";
+      restartTriggers = [ kdcConfFile ];
+    };
+
+    systemd.services.kdc = {
+      description = "Key Distribution Center daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig.ExecStart =
+        "${kerberos}/libexec/heimdal/kdc --config-file=/etc/heimdal-kdc/kdc.conf";
+      restartTriggers = [ kdcConfFile ];
+    };
+
+    systemd.services.kpasswdd = {
+      description = "Kerberos Password Changing daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig.ExecStart = "${kerberos}/libexec/heimdal/kpasswdd";
+      restartTriggers = [ kdcConfFile ];
+    };
+
+    environment.etc = {
+      # Can be set via the --config-file option to KDC
+      "heimdal-kdc/kdc.conf".source = kdcConfFile;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/kerberos/mit.nix b/nixpkgs/nixos/modules/services/system/kerberos/mit.nix
new file mode 100644
index 000000000000..25d7d51e808a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/kerberos/mit.nix
@@ -0,0 +1,68 @@
+{ pkgs, config, lib, ... } :
+
+let
+  inherit (lib) mkIf concatStrings concatStringsSep concatMapStrings toList
+    mapAttrs mapAttrsToList;
+  cfg = config.services.kerberos_server;
+  kerberos = config.krb5.kerberos;
+  stateDir = "/var/lib/krb5kdc";
+  PIDFile = "/run/kdc.pid";
+  aclMap = {
+    add = "a"; cpw = "c"; delete = "d"; get = "i"; list = "l"; modify = "m";
+    all = "*";
+  };
+  aclFiles = mapAttrs
+    (name: {acl, ...}: (pkgs.writeText "${name}.acl" (concatMapStrings (
+      {principal, access, target, ...} :
+      let access_code = map (a: aclMap.${a}) (toList access); in
+      "${principal} ${concatStrings access_code} ${target}\n"
+    ) acl))) cfg.realms;
+  kdcConfigs = mapAttrsToList (name: value: ''
+    ${name} = {
+      acl_file = ${value}
+    }
+  '') aclFiles;
+  kdcConfFile = pkgs.writeText "kdc.conf" ''
+    [realms]
+    ${concatStringsSep "\n" kdcConfigs}
+  '';
+  env = {
+    # What Debian uses, could possibly link directly to Nix store?
+    KRB5_KDC_PROFILE = "/etc/krb5kdc/kdc.conf";
+  };
+in
+
+{
+  config = mkIf (cfg.enable && kerberos == pkgs.krb5Full) {
+    systemd.services.kadmind = {
+      description = "Kerberos Administration Daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig.ExecStart = "${kerberos}/bin/kadmind -nofork";
+      restartTriggers = [ kdcConfFile ];
+      environment = env;
+    };
+
+    systemd.services.kdc = {
+      description = "Key Distribution Center daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig = {
+        Type = "forking";
+        PIDFile = PIDFile;
+        ExecStart = "${kerberos}/bin/krb5kdc -P ${PIDFile}";
+      };
+      restartTriggers = [ kdcConfFile ];
+      environment = env;
+    };
+
+    environment.etc = {
+      "krb5kdc/kdc.conf".source = kdcConfFile;
+    };
+    environment.variables = env;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/localtime.nix b/nixpkgs/nixos/modules/services/system/localtime.nix
new file mode 100644
index 000000000000..8f8e2e2e9339
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/localtime.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.localtime;
+in {
+  options = {
+    services.localtime = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable <literal>localtime</literal>, simple daemon for keeping the system
+          timezone up-to-date based on the current location. It uses geoclue2 to
+          determine the current location and systemd-timedated to actually set
+          the timezone.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.geoclue2 = {
+      enable = true;
+      appConfig.localtime = {
+        isAllowed = true;
+        isSystem = true;
+      };
+    };
+
+    # We use the 'out' output, since localtime has its 'bin' output
+    # first, so that is what we get if we use the derivation bare.
+    # Install the polkit rules.
+    environment.systemPackages = [ pkgs.localtime.out ];
+    # Install the systemd unit.
+    systemd.packages = [ pkgs.localtime.out ];
+
+    users.users.localtimed = {
+      description = "Taskserver user";
+    };
+
+    systemd.services.localtime = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.Restart = "on-failure";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/nscd.conf b/nixpkgs/nixos/modules/services/system/nscd.conf
new file mode 100644
index 000000000000..722b883ba420
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/nscd.conf
@@ -0,0 +1,34 @@
+# We basically use nscd as a proxy for forwarding nss requests to appropriate
+# nss modules, as we run nscd with LD_LIBRARY_PATH set to the directory
+# containing all such modules
+# Note that we can not use `enable-cache no` As this will actually cause nscd
+# to just reject the nss requests it receives, which then causes glibc to
+# fallback to trying to handle the request by itself. Which won't work as glibc
+# is not aware of the path in which the nss modules live.  As a workaround, we
+# have `enable-cache yes` with an explicit ttl of 0
+server-user             nscd
+
+enable-cache            passwd          yes
+positive-time-to-live   passwd          0
+negative-time-to-live   passwd          0
+shared                  passwd          yes
+
+enable-cache            group           yes
+positive-time-to-live   group           0
+negative-time-to-live   group           0
+shared                  group           yes
+
+enable-cache            netgroup        yes
+positive-time-to-live   netgroup        0
+negative-time-to-live   netgroup        0
+shared                  netgroup        yes
+
+enable-cache            hosts           yes
+positive-time-to-live   hosts           0
+negative-time-to-live   hosts           0
+shared                  hosts           yes
+
+enable-cache            services        yes
+positive-time-to-live   services        0
+negative-time-to-live   services        0
+shared                  services        yes
diff --git a/nixpkgs/nixos/modules/services/system/nscd.nix b/nixpkgs/nixos/modules/services/system/nscd.nix
new file mode 100644
index 000000000000..d720f254b813
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/nscd.nix
@@ -0,0 +1,85 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  nssModulesPath = config.system.nssModules.path;
+  cfg = config.services.nscd;
+
+  nscd = if pkgs.stdenv.hostPlatform.libc == "glibc"
+         then pkgs.stdenv.cc.libc.bin
+         else pkgs.glibc.bin;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.nscd = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable the Name Service Cache Daemon.
+          Disabling this is strongly discouraged, as this effectively disables NSS Lookups
+          from all non-glibc NSS modules, including the ones provided by systemd.
+        '';
+      };
+
+      config = mkOption {
+        type = types.lines;
+        default = builtins.readFile ./nscd.conf;
+        description = "Configuration to use for Name Service Cache Daemon.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.etc."nscd.conf".text = cfg.config;
+
+    systemd.services.nscd =
+      { description = "Name Service Cache Daemon";
+
+        wantedBy = [ "nss-lookup.target" "nss-user-lookup.target" ];
+
+        environment = { LD_LIBRARY_PATH = nssModulesPath; };
+
+        restartTriggers = [
+          config.environment.etc.hosts.source
+          config.environment.etc."nsswitch.conf".source
+          config.environment.etc."nscd.conf".source
+        ];
+
+        # We use DynamicUser because in default configurations nscd doesn't
+        # create any files that need to survive restarts. However, in some
+        # configurations, nscd needs to be started as root; it will drop
+        # privileges after all the NSS modules have read their configuration
+        # files. So prefix the ExecStart command with "!" to prevent systemd
+        # from dropping privileges early. See ExecStart in systemd.service(5).
+        serviceConfig =
+          { ExecStart = "!@${nscd}/sbin/nscd nscd";
+            Type = "forking";
+            DynamicUser = true;
+            RuntimeDirectory = "nscd";
+            PIDFile = "/run/nscd/nscd.pid";
+            Restart = "always";
+            ExecReload =
+              [ "${nscd}/sbin/nscd --invalidate passwd"
+                "${nscd}/sbin/nscd --invalidate group"
+                "${nscd}/sbin/nscd --invalidate hosts"
+              ];
+          };
+      };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/saslauthd.nix b/nixpkgs/nixos/modules/services/system/saslauthd.nix
new file mode 100644
index 000000000000..8fcf4fb91fc4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/saslauthd.nix
@@ -0,0 +1,62 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.saslauthd;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.saslauthd = {
+
+      enable = mkEnableOption "saslauthd, the Cyrus SASL authentication daemon";
+
+      package = mkOption {
+        default = pkgs.cyrus_sasl.bin;
+        defaultText = "pkgs.cyrus_sasl.bin";
+        type = types.package;
+        description = "Cyrus SASL package to use.";
+      };
+
+      mechanism = mkOption {
+        type = types.str;
+        default = "pam";
+        description = "Auth mechanism to use";
+      };
+
+      config = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Configuration to use for Cyrus SASL authentication daemon.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.saslauthd = {
+      description = "Cyrus SASL authentication daemon";
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/saslauthd saslauthd -a ${cfg.mechanism} -O ${pkgs.writeText "saslauthd.conf" cfg.config}";
+        Type = "forking";
+        PIDFile = "/run/saslauthd/saslauthd.pid";
+        Restart = "always";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/system/uptimed.nix b/nixpkgs/nixos/modules/services/system/uptimed.nix
new file mode 100644
index 000000000000..1e256c51408e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/system/uptimed.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.uptimed;
+  stateDir = "/var/spool/uptimed";
+in
+{
+  options = {
+    services.uptimed = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable <literal>uptimed</literal>, allowing you to track
+          your highest uptimes.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.uptimed = {
+      description = "Uptimed daemon user";
+      home        = stateDir;
+      createHome  = true;
+      uid         = config.ids.uids.uptimed;
+    };
+
+    systemd.services.uptimed = {
+      unitConfig.Documentation = "man:uptimed(8) man:uprecords(1)";
+      description = "uptimed service";
+      wantedBy    = [ "multi-user.target" ];
+
+      serviceConfig = {
+        Restart                 = "on-failure";
+        User                    = "uptimed";
+        Nice                    = 19;
+        IOSchedulingClass       = "idle";
+        PrivateTmp              = "yes";
+        PrivateNetwork          = "yes";
+        NoNewPrivileges         = "yes";
+        ReadWriteDirectories    = stateDir;
+        InaccessibleDirectories = "/home";
+        ExecStart               = "${pkgs.uptimed}/sbin/uptimed -f -p ${stateDir}/pid";
+      };
+
+      preStart = ''
+        if ! test -f ${stateDir}/bootid ; then
+          ${pkgs.uptimed}/sbin/uptimed -b
+        fi
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/deluge.nix b/nixpkgs/nixos/modules/services/torrent/deluge.nix
new file mode 100644
index 000000000000..45398cb26138
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/deluge.nix
@@ -0,0 +1,271 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.deluge;
+  cfg_web = config.services.deluge.web;
+  isDeluge1 = versionOlder cfg.package.version "2.0.0";
+
+  openFilesLimit = 4096;
+  listenPortsDefault = [ 6881 6889 ];
+
+  listToRange = x: { from = elemAt x 0; to = elemAt x 1; };
+
+  configDir = "${cfg.dataDir}/.config/deluge";
+  configFile = pkgs.writeText "core.conf" (builtins.toJSON cfg.config);
+  declarativeLockFile = "${configDir}/.declarative";
+
+  preStart = if cfg.declarative then ''
+    if [ -e ${declarativeLockFile} ]; then
+      # Was declarative before, no need to back up anything
+      ${if isDeluge1 then "ln -sf" else "cp"} ${configFile} ${configDir}/core.conf
+      ln -sf ${cfg.authFile} ${configDir}/auth
+    else
+      # Declarative for the first time, backup stateful files
+      ${if isDeluge1 then "ln -s" else "cp"} -b --suffix=.stateful ${configFile} ${configDir}/core.conf
+      ln -sb --suffix=.stateful ${cfg.authFile} ${configDir}/auth
+      echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
+        > ${declarativeLockFile}
+    fi
+  '' else ''
+    if [ -e ${declarativeLockFile} ]; then
+      rm ${declarativeLockFile}
+    fi
+  '';
+in {
+  options = {
+    services = {
+      deluge = {
+        enable = mkEnableOption "Deluge daemon";
+
+        openFilesLimit = mkOption {
+          default = openFilesLimit;
+          description = ''
+            Number of files to allow deluged to open.
+          '';
+        };
+
+        config = mkOption {
+          type = types.attrs;
+          default = {};
+          example = literalExample ''
+            {
+              download_location = "/srv/torrents/";
+              max_upload_speed = "1000.0";
+              share_ratio_limit = "2.0";
+              allow_remote = true;
+              daemon_port = 58846;
+              listen_ports = [ ${toString listenPortsDefault} ];
+            }
+          '';
+          description = ''
+            Deluge core configuration for the core.conf file. Only has an effect
+            when <option>services.deluge.declarative</option> is set to
+            <literal>true</literal>. String values must be quoted, integer and
+            boolean values must not. See
+            <link xlink:href="https://git.deluge-torrent.org/deluge/tree/deluge/core/preferencesmanager.py#n41"/>
+            for the availaible options.
+          '';
+        };
+
+        declarative = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether to use a declarative deluge configuration.
+            Only if set to <literal>true</literal>, the options
+            <option>services.deluge.config</option>,
+            <option>services.deluge.openFirewall</option> and
+            <option>services.deluge.authFile</option> will be
+            applied.
+          '';
+        };
+
+        openFirewall = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            Whether to open the firewall for the ports in
+            <option>services.deluge.config.listen_ports</option>. It only takes effet if
+            <option>services.deluge.declarative</option> is set to
+            <literal>true</literal>.
+
+            It does NOT apply to the daemon port nor the web UI port. To access those
+            ports secuerly check the documentation
+            <link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient#CreateSSHTunnel"/>
+            or use a VPN or configure certificates for deluge.
+          '';
+        };
+
+        dataDir = mkOption {
+          type = types.path;
+          default = "/var/lib/deluge";
+          description = ''
+            The directory where deluge will create files.
+          '';
+        };
+
+        authFile = mkOption {
+          type = types.path;
+          example = "/run/keys/deluge-auth";
+          description = ''
+            The file managing the authentication for deluge, the format of this
+            file is straightforward, each line contains a
+            username:password:level tuple in plaintext. It only has an effect
+            when <option>services.deluge.declarative</option> is set to
+            <literal>true</literal>.
+            See <link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/Authentication"/> for
+            more informations.
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "deluge";
+          description = ''
+            User account under which deluge runs.
+          '';
+        };
+
+        group = mkOption {
+          type = types.str;
+          default = "deluge";
+          description = ''
+            Group under which deluge runs.
+          '';
+        };
+
+        extraPackages = mkOption {
+          type = types.listOf types.package;
+          default = [];
+          description = ''
+            Extra packages available at runtime to enable Deluge's plugins. For example,
+            extraction utilities are required for the built-in "Extractor" plugin.
+            This always contains unzip, gnutar, xz and bzip2.
+          '';
+        };
+
+        package = mkOption {
+          type = types.package;
+          example = literalExample "pkgs.deluge-1_x";
+          description = ''
+            Deluge package to use.
+          '';
+        };
+      };
+
+      deluge.web = {
+        enable = mkEnableOption "Deluge Web daemon";
+
+        port = mkOption {
+          type = types.port;
+          default = 8112;
+          description = ''
+            Deluge web UI port.
+          '';
+        };
+
+        openFirewall = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Open ports in the firewall for deluge web daemon
+          '';
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.deluge.package = mkDefault (
+      if versionAtLeast config.system.stateVersion "20.09" then
+        pkgs.deluge-2_x
+      else
+        pkgs.deluge-1_x
+    );
+
+    # Provide a default set of `extraPackages`.
+    services.deluge.extraPackages = with pkgs; [ unzip gnutar xz bzip2 ];
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group}"
+      "d '${cfg.dataDir}/.config' 0770 ${cfg.user} ${cfg.group}"
+      "d '${cfg.dataDir}/.config/deluge' 0770 ${cfg.user} ${cfg.group}"
+    ]
+    ++ optional (cfg.config ? download_location)
+      "d '${cfg.config.download_location}' 0770 ${cfg.user} ${cfg.group}"
+    ++ optional (cfg.config ? torrentfiles_location)
+      "d '${cfg.config.torrentfiles_location}' 0770 ${cfg.user} ${cfg.group}"
+    ++ optional (cfg.config ? move_completed_path)
+      "d '${cfg.config.move_completed_path}' 0770 ${cfg.user} ${cfg.group}";
+
+    systemd.services.deluged = {
+      after = [ "network.target" ];
+      description = "Deluge BitTorrent Daemon";
+      wantedBy = [ "multi-user.target" ];
+      path = [ cfg.package ] ++ cfg.extraPackages;
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/deluged \
+            --do-not-daemonize \
+            --config ${configDir}
+        '';
+        # To prevent "Quit & shutdown daemon" from working; we want systemd to
+        # manage it!
+        Restart = "on-success";
+        User = cfg.user;
+        Group = cfg.group;
+        UMask = "0002";
+        LimitNOFILE = cfg.openFilesLimit;
+      };
+      preStart = preStart;
+    };
+
+    systemd.services.delugeweb = mkIf cfg_web.enable {
+      after = [ "network.target" "deluged.service"];
+      requires = [ "deluged.service" ];
+      description = "Deluge BitTorrent WebUI";
+      wantedBy = [ "multi-user.target" ];
+      path = [ cfg.package ];
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/deluge-web \
+            ${optionalString (!isDeluge1) "--do-not-daemonize"} \
+            --config ${configDir} \
+            --port ${toString cfg.web.port}
+        '';
+        User = cfg.user;
+        Group = cfg.group;
+      };
+    };
+
+    networking.firewall = mkMerge [
+      (mkIf (cfg.declarative && cfg.openFirewall && !(cfg.config.random_port or true)) {
+        allowedTCPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
+        allowedUDPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
+      })
+      (mkIf (cfg.web.openFirewall) {
+        allowedTCPPorts = [ cfg.web.port ];
+      })
+    ];
+
+    environment.systemPackages = [ cfg.package ];
+
+    users.users = mkIf (cfg.user == "deluge") {
+      deluge = {
+        group = cfg.group;
+        uid = config.ids.uids.deluge;
+        home = cfg.dataDir;
+        description = "Deluge Daemon user";
+      };
+    };
+
+    users.groups = mkIf (cfg.group == "deluge") {
+      deluge = {
+        gid = config.ids.gids.deluge;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/flexget.nix b/nixpkgs/nixos/modules/services/torrent/flexget.nix
new file mode 100644
index 000000000000..6ac85f8fa178
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/flexget.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.flexget;
+  pkg = pkgs.flexget;
+  ymlFile = pkgs.writeText "flexget.yml" ''
+    ${cfg.config}
+
+    ${optionalString cfg.systemScheduler "schedules: no"}
+'';
+  configFile = "${toString cfg.homeDir}/flexget.yml";
+in {
+  options = {
+    services.flexget = {
+      enable = mkEnableOption "Run FlexGet Daemon";
+
+      user = mkOption {
+        default = "deluge";
+        example = "some_user";
+        type = types.str;
+        description = "The user under which to run flexget.";
+      };
+
+      homeDir = mkOption {
+        default = "/var/lib/deluge";
+        example = "/home/flexget";
+        type = types.path;
+        description = "Where files live.";
+      };
+
+      interval = mkOption {
+        default = "10m";
+        example = "1h";
+        type = types.str;
+        description = "When to perform a <command>flexget</command> run. See <command>man 7 systemd.time</command> for the format.";
+      };
+
+      systemScheduler = mkOption {
+        default = true;
+        example = "false";
+        type = types.bool;
+        description = "When true, execute the runs via the flexget-runner.timer. If false, you have to specify the settings yourself in the YML file.";
+      };
+
+      config = mkOption {
+        default = "";
+        type = types.lines;
+        description = "The YAML configuration for FlexGet.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkg ];
+
+    systemd.services = {
+      flexget = {
+        description = "FlexGet Daemon";
+        path = [ pkg ];
+        serviceConfig = {
+          User = cfg.user;
+          Environment = "TZ=${config.time.timeZone}";
+          ExecStartPre = "${pkgs.coreutils}/bin/install -m644 ${ymlFile} ${configFile}";
+          ExecStart = "${pkg}/bin/flexget -c ${configFile} daemon start";
+          ExecStop = "${pkg}/bin/flexget -c ${configFile} daemon stop";
+          ExecReload = "${pkg}/bin/flexget -c ${configFile} daemon reload";
+          Restart = "on-failure";
+          PrivateTmp = true;
+          WorkingDirectory = toString cfg.homeDir;
+        };
+        wantedBy = [ "multi-user.target" ];
+      };
+
+      flexget-runner = mkIf cfg.systemScheduler {
+        description = "FlexGet Runner";
+        after = [ "flexget.service" ];
+        wants = [ "flexget.service" ];
+        serviceConfig = {
+          User = cfg.user;
+          ExecStart = "${pkg}/bin/flexget -c ${configFile} execute";
+          PrivateTmp = true;
+          WorkingDirectory = toString cfg.homeDir;
+        };
+      };
+    };
+
+    systemd.timers.flexget-runner = mkIf cfg.systemScheduler {
+      description = "Run FlexGet every ${cfg.interval}";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnBootSec = "5m";
+        OnUnitInactiveSec = cfg.interval;
+        Unit = "flexget-runner.service";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/magnetico.nix b/nixpkgs/nixos/modules/services/torrent/magnetico.nix
new file mode 100644
index 000000000000..7465c10e002c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/magnetico.nix
@@ -0,0 +1,218 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.magnetico;
+
+  dataDir = "/var/lib/magnetico";
+
+  credFile = with cfg.web;
+    if credentialsFile != null
+      then credentialsFile
+      else pkgs.writeText "magnetico-credentials"
+        (concatStrings (mapAttrsToList
+          (user: hash: "${user}:${hash}\n")
+          cfg.web.credentials));
+
+  # default options in magneticod/main.go
+  dbURI = concatStrings
+    [ "sqlite3://${dataDir}/database.sqlite3"
+      "?_journal_mode=WAL"
+      "&_busy_timeout=3000"
+      "&_foreign_keys=true"
+    ];
+
+  crawlerArgs = with cfg.crawler; escapeShellArgs
+    ([ "--database=${dbURI}"
+       "--indexer-addr=${address}:${toString port}"
+       "--indexer-max-neighbors=${toString maxNeighbors}"
+       "--leech-max-n=${toString maxLeeches}"
+     ] ++ extraOptions);
+
+  webArgs = with cfg.web; escapeShellArgs
+    ([ "--database=${dbURI}"
+       (if (cfg.web.credentialsFile != null || cfg.web.credentials != { })
+         then "--credentials=${toString credFile}"
+         else "--no-auth")
+       "--addr=${address}:${toString port}"
+     ] ++ extraOptions);
+
+in {
+
+  ###### interface
+
+  options.services.magnetico = {
+    enable = mkEnableOption "Magnetico, Bittorrent DHT crawler";
+
+    crawler.address = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      example = "1.2.3.4";
+      description = ''
+        Address to be used for indexing DHT nodes.
+      '';
+    };
+
+    crawler.port = mkOption {
+      type = types.port;
+      default = 0;
+      description = ''
+        Port to be used for indexing DHT nodes.
+        This port should be added to
+        <option>networking.firewall.allowedTCPPorts</option>.
+      '';
+    };
+
+    crawler.maxNeighbors = mkOption {
+      type = types.ints.positive;
+      default = 1000;
+      description = ''
+        Maximum number of simultaneous neighbors of an indexer.
+        Be careful changing this number: high values can very
+        easily cause your network to be congested or even crash
+        your router.
+      '';
+    };
+
+    crawler.maxLeeches = mkOption {
+      type = types.ints.positive;
+      default = 200;
+      description = ''
+        Maximum number of simultaneous leeches.
+      '';
+    };
+
+    crawler.extraOptions = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra command line arguments to pass to magneticod.
+      '';
+    };
+
+    web.address = mkOption {
+      type = types.str;
+      default = "localhost";
+      example = "1.2.3.4";
+      description = ''
+        Address the web interface will listen to.
+      '';
+    };
+
+    web.port = mkOption {
+      type = types.port;
+      default = 8080;
+      description = ''
+        Port the web interface will listen to.
+      '';
+    };
+
+    web.credentials = mkOption {
+      type = types.attrsOf types.str;
+      default = {};
+      example = lib.literalExample ''
+        {
+          myuser = "$2y$12$YE01LZ8jrbQbx6c0s2hdZO71dSjn2p/O9XsYJpz.5968yCysUgiaG";
+        }
+      '';
+      description = ''
+        The credentials to access the web interface, in case authentication is
+        enabled, in the format <literal>username:hash</literal>. If unset no
+        authentication will be required.
+
+        Usernames must start with a lowercase ([a-z]) ASCII character, might
+        contain non-consecutive underscores except at the end, and consists of
+        small-case a-z characters and digits 0-9.  The
+        <command>htpasswd</command> tool from the <package>apacheHttpd
+        </package> package may be used to generate the hash: <command>htpasswd
+        -bnBC 12 username password</command>
+
+        <warning>
+        <para>
+          The hashes will be stored world-readable in the nix store.
+          Consider using the <literal>credentialsFile</literal> option if you
+          don't want this.
+        </para>
+        </warning>
+      '';
+    };
+
+    web.credentialsFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        The path to the file holding the credentials to access the web
+        interface. If unset no authentication will be required.
+
+        The file must constain user names and password hashes in the format
+        <literal>username:hash </literal>, one for each line.  Usernames must
+        start with a lowecase ([a-z]) ASCII character, might contain
+        non-consecutive underscores except at the end, and consists of
+        small-case a-z characters and digits 0-9.
+        The <command>htpasswd</command> tool from the <package>apacheHttpd
+        </package> package may be used to generate the hash:
+        <command>htpasswd -bnBC 12 username password</command>
+      '';
+    };
+
+    web.extraOptions = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra command line arguments to pass to magneticow.
+      '';
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users.magnetico = {
+      description = "Magnetico daemons user";
+      isSystemUser = true;
+    };
+
+    systemd.services.magneticod = {
+      description = "Magnetico DHT crawler";
+      wantedBy = [ "multi-user.target" ];
+      after    = [ "network.target" ];
+
+      serviceConfig = {
+        User      = "magnetico";
+        Restart   = "on-failure";
+        ExecStart = "${pkgs.magnetico}/bin/magneticod ${crawlerArgs}";
+      };
+    };
+
+    systemd.services.magneticow = {
+      description = "Magnetico web interface";
+      wantedBy = [ "multi-user.target" ];
+      after    = [ "network.target" "magneticod.service"];
+
+      serviceConfig = {
+        User           = "magnetico";
+        StateDirectory = "magnetico";
+        Restart        = "on-failure";
+        ExecStart      = "${pkgs.magnetico}/bin/magneticow ${webArgs}";
+      };
+    };
+
+    assertions =
+    [
+      {
+        assertion = cfg.web.credentialsFile == null || cfg.web.credentials == { };
+        message = ''
+          The options services.magnetico.web.credentialsFile and
+          services.magnetico.web.credentials are mutually exclusives.
+        '';
+      }
+    ];
+
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/opentracker.nix b/nixpkgs/nixos/modules/services/torrent/opentracker.nix
new file mode 100644
index 000000000000..74f443381d92
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/opentracker.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.opentracker;
+in {
+  options.services.opentracker = {
+    enable = mkEnableOption "opentracker";
+
+    package = mkOption {
+      type = types.package;
+      description = ''
+        opentracker package to use
+      '';
+      default = pkgs.opentracker;
+      defaultText = "pkgs.opentracker";
+    };
+
+    extraOptions = mkOption {
+      type = types.separatedString " ";
+      description = ''
+        Configuration Arguments for opentracker
+        See https://erdgeist.org/arts/software/opentracker/ for all params
+      '';
+      default = "";
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    systemd.services.opentracker = {
+      description = "opentracker server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      restartIfChanged = true;
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/opentracker ${cfg.extraOptions}";
+        PrivateTmp = true;
+        WorkingDirectory = "/var/empty";
+        # By default opentracker drops all privileges and runs in chroot after starting up as root.
+      };
+    };
+  };
+}
+
diff --git a/nixpkgs/nixos/modules/services/torrent/peerflix.nix b/nixpkgs/nixos/modules/services/torrent/peerflix.nix
new file mode 100644
index 000000000000..a74f65984328
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/peerflix.nix
@@ -0,0 +1,65 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.peerflix;
+
+  configFile = pkgs.writeText "peerflix-config.json" ''
+    {
+      "connections": 50,
+      "tmp": "${cfg.downloadDir}"
+    }
+  '';
+
+in {
+
+  ###### interface
+
+  options.services.peerflix = {
+    enable = mkOption {
+      description = "Whether to enable peerflix service.";
+      default = false;
+      type = types.bool;
+    };
+
+    stateDir = mkOption {
+      description = "Peerflix state directory.";
+      default = "/var/lib/peerflix";
+      type = types.path;
+    };
+
+    downloadDir = mkOption {
+      description = "Peerflix temporary download directory.";
+      default = "${cfg.stateDir}/torrents";
+      type = types.path;
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - peerflix - - -"
+    ];
+
+    systemd.services.peerflix = {
+      description = "Peerflix Daemon";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      environment.HOME = cfg.stateDir;
+
+      preStart = ''
+        mkdir -p "${cfg.stateDir}"/{torrents,.config/peerflix-server}
+        ln -fs "${configFile}" "${cfg.stateDir}/.config/peerflix-server/config.json"
+      '';
+
+      serviceConfig = {
+        ExecStart = "${pkgs.nodePackages.peerflix-server}/bin/peerflix-server";
+        User = "peerflix";
+      };
+    };
+
+    users.users.peerflix.uid = config.ids.uids.peerflix;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/rtorrent.nix b/nixpkgs/nixos/modules/services/torrent/rtorrent.nix
new file mode 100644
index 000000000000..be57c03b1721
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/rtorrent.nix
@@ -0,0 +1,209 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.rtorrent;
+
+in {
+  options.services.rtorrent = {
+    enable = mkEnableOption "rtorrent";
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/rtorrent";
+      description = ''
+        The directory where rtorrent stores its data files.
+      '';
+    };
+
+    downloadDir = mkOption {
+      type = types.str;
+      default = "${cfg.dataDir}/download";
+      description = ''
+        Where to put downloaded files.
+      '';
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "rtorrent";
+      description = ''
+        User account under which rtorrent runs.
+      '';
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "rtorrent";
+      description = ''
+        Group under which rtorrent runs.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.rtorrent;
+      defaultText = "pkgs.rtorrent";
+      description = ''
+        The rtorrent package to use.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 50000;
+      description = ''
+        The rtorrent port.
+      '';
+    };
+
+    openFirewall = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to open the firewall for the port in <option>services.rtorrent.port</option>.
+      '';
+    };
+
+    rpcSocket = mkOption {
+      type = types.str;
+      readOnly = true;
+      default = "/run/rtorrent/rpc.sock";
+      description = ''
+        RPC socket path.
+      '';
+    };
+
+    configText = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        The content of <filename>rtorrent.rc</filename>. The <link xlink:href="https://rtorrent-docs.readthedocs.io/en/latest/cookbook.html#modernized-configuration-template">modernized configuration template</link> with the values specified in this module will be prepended using mkBefore. You can use mkForce to overwrite the config completly.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    users.groups = mkIf (cfg.group == "rtorrent") {
+      rtorrent = {};
+    };
+
+    users.users = mkIf (cfg.user == "rtorrent") {
+      rtorrent = {
+        group = cfg.group;
+        shell = pkgs.bashInteractive;
+        home = cfg.dataDir;
+        description = "rtorrent Daemon user";
+        isSystemUser = true;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall) [ cfg.port ];
+
+    services.rtorrent.configText = mkBefore ''
+      # Instance layout (base paths)
+      method.insert = cfg.basedir, private|const|string, (cat,"${cfg.dataDir}/")
+      method.insert = cfg.watch,   private|const|string, (cat,(cfg.basedir),"watch/")
+      method.insert = cfg.logs,    private|const|string, (cat,(cfg.basedir),"log/")
+      method.insert = cfg.logfile, private|const|string, (cat,(cfg.logs),(system.time),".log")
+      method.insert = cfg.rpcsock, private|const|string, (cat,"${cfg.rpcSocket}")
+
+      # Create instance directories
+      execute.throw = sh, -c, (cat, "mkdir -p ", (cfg.basedir), "/session ", (cfg.watch), " ", (cfg.logs))
+
+      # Listening port for incoming peer traffic (fixed; you can also randomize it)
+      network.port_range.set = ${toString cfg.port}-${toString cfg.port}
+      network.port_random.set = no
+
+      # Tracker-less torrent and UDP tracker support
+      # (conservative settings for 'private' trackers, change for 'public')
+      dht.mode.set = disable
+      protocol.pex.set = no
+      trackers.use_udp.set = no
+
+      # Peer settings
+      throttle.max_uploads.set = 100
+      throttle.max_uploads.global.set = 250
+
+      throttle.min_peers.normal.set = 20
+      throttle.max_peers.normal.set = 60
+      throttle.min_peers.seed.set = 30
+      throttle.max_peers.seed.set = 80
+      trackers.numwant.set = 80
+
+      protocol.encryption.set = allow_incoming,try_outgoing,enable_retry
+
+      # Limits for file handle resources, this is optimized for
+      # an `ulimit` of 1024 (a common default). You MUST leave
+      # a ceiling of handles reserved for rTorrent's internal needs!
+      network.http.max_open.set = 50
+      network.max_open_files.set = 600
+      network.max_open_sockets.set = 3000
+
+      # Memory resource usage (increase if you have a large number of items loaded,
+      # and/or the available resources to spend)
+      pieces.memory.max.set = 1800M
+      network.xmlrpc.size_limit.set = 4M
+
+      # Basic operational settings (no need to change these)
+      session.path.set = (cat, (cfg.basedir), "session/")
+      directory.default.set = "${cfg.downloadDir}"
+      log.execute = (cat, (cfg.logs), "execute.log")
+      ##log.xmlrpc = (cat, (cfg.logs), "xmlrpc.log")
+      execute.nothrow = sh, -c, (cat, "echo >", (session.path), "rtorrent.pid", " ", (system.pid))
+
+      # Other operational settings (check & adapt)
+      encoding.add = utf8
+      system.umask.set = 0027
+      system.cwd.set = (cfg.basedir)
+      network.http.dns_cache_timeout.set = 25
+      schedule2 = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M))
+
+      # Watch directories (add more as you like, but use unique schedule names)
+      #schedule2 = watch_start, 10, 10, ((load.start, (cat, (cfg.watch), "start/*.torrent")))
+      #schedule2 = watch_load, 11, 10, ((load.normal, (cat, (cfg.watch), "load/*.torrent")))
+
+      # Logging:
+      #   Levels = critical error warn notice info debug
+      #   Groups = connection_* dht_* peer_* rpc_* storage_* thread_* tracker_* torrent_*
+      print = (cat, "Logging to ", (cfg.logfile))
+      log.open_file = "log", (cfg.logfile)
+      log.add_output = "info", "log"
+      ##log.add_output = "tracker_debug", "log"
+
+      # XMLRPC
+      scgi_local = (cfg.rpcsock)
+      schedule = scgi_group,0,0,"execute.nothrow=chown,\":rtorrent\",(cfg.rpcsock)"
+      schedule = scgi_permission,0,0,"execute.nothrow=chmod,\"g+w,o=\",(cfg.rpcsock)"
+    '';
+
+    systemd = {
+      services = {
+        rtorrent = let
+          rtorrentConfigFile = pkgs.writeText "rtorrent.rc" cfg.configText;
+        in {
+          description = "rTorrent system service";
+          after = [ "network.target" ];
+          path = [ cfg.package pkgs.bash ];
+          wantedBy = [ "multi-user.target" ];
+          serviceConfig = {
+            User = cfg.user;
+            Group = cfg.group;
+            Type = "simple";
+            Restart = "on-failure";
+            WorkingDirectory = cfg.dataDir;
+            ExecStartPre=''${pkgs.bash}/bin/bash -c "if test -e ${cfg.dataDir}/session/rtorrent.lock && test -z $(${pkgs.procps}/bin/pidof rtorrent); then rm -f ${cfg.dataDir}/session/rtorrent.lock; fi"'';
+            ExecStart="${cfg.package}/bin/rtorrent -n -o system.daemon.set=true -o import=${rtorrentConfigFile}";
+            RuntimeDirectory = "rtorrent";
+            RuntimeDirectoryMode = 755;
+          };
+        };
+      };
+
+      tmpfiles.rules = [ "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} -" ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/torrent/transmission.nix b/nixpkgs/nixos/modules/services/torrent/transmission.nix
new file mode 100644
index 000000000000..1bfcf2de82f8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/torrent/transmission.nix
@@ -0,0 +1,202 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.transmission;
+  apparmor = config.security.apparmor.enable;
+
+  homeDir = cfg.home;
+  downloadDirPermissions = cfg.downloadDirPermissions;
+  downloadDir = "${homeDir}/Downloads";
+  incompleteDir = "${homeDir}/.incomplete";
+
+  settingsDir = "${homeDir}/config";
+  settingsFile = pkgs.writeText "settings.json" (builtins.toJSON fullSettings);
+
+  # for users in group "transmission" to have access to torrents
+  fullSettings = { umask = 2; download-dir = downloadDir; incomplete-dir = incompleteDir; } // cfg.settings;
+
+  preStart = pkgs.writeScript "transmission-pre-start" ''
+    #!${pkgs.runtimeShell}
+    set -ex
+    cp -f ${settingsFile} ${settingsDir}/settings.json
+  '';
+in
+{
+  options = {
+    services.transmission = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not to enable the headless Transmission BitTorrent daemon.
+
+          Transmission daemon can be controlled via the RPC interface using
+          transmission-remote or the WebUI (http://localhost:9091/ by default).
+
+          Torrents are downloaded to ${downloadDir} by default and are
+          accessible to users in the "transmission" group.
+        '';
+      };
+
+      settings = mkOption {
+        type = types.attrs;
+        default =
+          {
+            download-dir = downloadDir;
+            incomplete-dir = incompleteDir;
+            incomplete-dir-enabled = true;
+          };
+        example =
+          {
+            download-dir = "/srv/torrents/";
+            incomplete-dir = "/srv/torrents/.incomplete/";
+            incomplete-dir-enabled = true;
+            rpc-whitelist = "127.0.0.1,192.168.*.*";
+          };
+        description = ''
+          Attribute set whos fields overwrites fields in settings.json (each
+          time the service starts). String values must be quoted, integer and
+          boolean values must not.
+
+          See https://github.com/transmission/transmission/wiki/Editing-Configuration-Files
+          for documentation.
+        '';
+      };
+
+      downloadDirPermissions = mkOption {
+        type = types.str;
+        default = "770";
+        example = "775";
+        description = ''
+          The permissions to set for download-dir and incomplete-dir.
+          They will be applied on every service start.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 9091;
+        description = "TCP port number to run the RPC/web interface.";
+      };
+
+      home = mkOption {
+        type = types.path;
+        default = "/var/lib/transmission";
+        description = ''
+          The directory where transmission will create files.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "transmission";
+        description = "User account under which Transmission runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "transmission";
+        description = "Group account under which Transmission runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${homeDir}' 0770 '${cfg.user}' '${cfg.group}' - -"
+      "d '${settingsDir}' 0700 '${cfg.user}' '${cfg.group}' - -"
+      "d '${fullSettings.download-dir}' '${downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"
+      "d '${fullSettings.incomplete-dir}' '${downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -"
+    ];
+
+    systemd.services.transmission = {
+      description = "Transmission BitTorrent Service";
+      after = [ "network.target" ] ++ optional apparmor "apparmor.service";
+      requires = mkIf apparmor [ "apparmor.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      # 1) Only the "transmission" user and group have access to torrents.
+      # 2) Optionally update/force specific fields into the configuration file.
+      serviceConfig.ExecStartPre = preStart;
+      serviceConfig.ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --port ${toString config.services.transmission.port} --config-dir ${settingsDir}";
+      serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      serviceConfig.User = cfg.user;
+      serviceConfig.Group = cfg.group;
+      # NOTE: transmission has an internal umask that also must be set (in settings.json)
+      serviceConfig.UMask = "0002";
+    };
+
+    # It's useful to have transmission in path, e.g. for remote control
+    environment.systemPackages = [ pkgs.transmission ];
+
+    users.users = optionalAttrs (cfg.user == "transmission") ({
+      transmission = {
+        name = "transmission";
+        group = cfg.group;
+        uid = config.ids.uids.transmission;
+        description = "Transmission BitTorrent user";
+        home = homeDir;
+        createHome = true;
+      };
+    });
+
+    users.groups = optionalAttrs (cfg.group == "transmission") ({
+      transmission = {
+        name = "transmission";
+        gid = config.ids.gids.transmission;
+      };
+    });
+
+    # AppArmor profile
+    security.apparmor.profiles = mkIf apparmor [
+      (pkgs.writeText "apparmor-transmission-daemon" ''
+        #include <tunables/global>
+
+        ${pkgs.transmission}/bin/transmission-daemon {
+          #include <abstractions/base>
+          #include <abstractions/nameservice>
+
+          ${getLib pkgs.glibc}/lib/*.so                    mr,
+          ${getLib pkgs.libevent}/lib/libevent*.so*        mr,
+          ${getLib pkgs.curl}/lib/libcurl*.so*             mr,
+          ${getLib pkgs.openssl}/lib/libssl*.so*           mr,
+          ${getLib pkgs.openssl}/lib/libcrypto*.so*        mr,
+          ${getLib pkgs.zlib}/lib/libz*.so*                mr,
+          ${getLib pkgs.libssh2}/lib/libssh2*.so*          mr,
+          ${getLib pkgs.systemd}/lib/libsystemd*.so*       mr,
+          ${getLib pkgs.xz}/lib/liblzma*.so*               mr,
+          ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so*      mr,
+          ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so* mr,
+          ${getLib pkgs.nghttp2}/lib/libnghttp2*.so*       mr,
+          ${getLib pkgs.c-ares}/lib/libcares*.so*          mr,
+          ${getLib pkgs.libcap}/lib/libcap*.so*            mr,
+          ${getLib pkgs.attr}/lib/libattr*.so*             mr,
+          ${getLib pkgs.lz4}/lib/liblz4*.so*               mr,
+          ${getLib pkgs.libkrb5}/lib/lib*.so*              mr,
+          ${getLib pkgs.keyutils}/lib/libkeyutils*.so*     mr,
+          ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr,
+          ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr,
+          ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr,
+          ${getLib pkgs.gcc.cc.lib}/lib/libstdc++.so.* mr,
+          ${getLib pkgs.gcc.cc.lib}/lib/libgcc_s.so.* mr,
+
+          @{PROC}/sys/kernel/random/uuid   r,
+          @{PROC}/sys/vm/overcommit_memory r,
+
+          ${pkgs.openssl.out}/etc/**                     r,
+          ${pkgs.transmission}/share/transmission/** r,
+
+          owner ${settingsDir}/** rw,
+
+          ${fullSettings.download-dir}/** rw,
+          ${optionalString fullSettings.incomplete-dir-enabled ''
+            ${fullSettings.incomplete-dir}/** rw,
+          ''}
+        }
+      '')
+    ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/ttys/agetty.nix b/nixpkgs/nixos/modules/services/ttys/agetty.nix
new file mode 100644
index 000000000000..996925134524
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/ttys/agetty.nix
@@ -0,0 +1,139 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mingetty;
+
+  loginArgs = concatStringsSep " " [
+    "--login-program ${pkgs.shadow}/bin/login"
+
+    (optionalString (cfg.autologinUser != null)
+                    "--autologin ${cfg.autologinUser}")
+
+    (optionalString (cfg.loginOptions != null)
+                    "--login-options ${escapeShellArg cfg.loginOptions}")
+  ];
+
+  gettyCmd = extraArgs:
+    "@${pkgs.utillinux}/sbin/agetty agetty ${loginArgs} ${extraArgs}";
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.mingetty = {
+
+      autologinUser = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Username of the account that will be automatically logged in at the console.
+          If unspecified, a login prompt is shown as usual.
+        '';
+      };
+
+      loginOptions = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Template for arguments to be passed to login(1).
+          See agetty(1) for details, including security considerations.
+          If unspecified, agetty will not be invoked with a --login-options option.
+        '';
+        example = "-h darkstar -- \u";
+      };
+
+      greetingLine = mkOption {
+        type = types.str;
+        description = ''
+          Welcome line printed by mingetty.
+          The default shows current NixOS version label, machine type and tty.
+        '';
+      };
+
+      helpLine = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Help line printed by mingetty below the welcome line.
+          Used by the installation CD to give some hints on
+          how to proceed.
+        '';
+      };
+
+      serialSpeed = mkOption {
+        type = types.listOf types.int;
+        default = [ 115200 57600 38400 9600 ];
+        example = [ 38400 9600 ];
+        description = ''
+            Bitrates to allow for agetty's listening on serial ports. Listing more
+            bitrates gives more interoperability but at the cost of long delays
+            for getting a sync on the line.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = {
+    # Note: this is set here rather than up there so that changing
+    # nixos.label would not rebuild manual pages
+    services.mingetty.greetingLine = mkDefault ''<<< Welcome to NixOS ${config.system.nixos.label} (\m) - \l >>>'';
+
+    systemd.services."getty@" =
+      { serviceConfig.ExecStart = [
+          "" # override upstream default with an empty ExecStart
+          (gettyCmd "--noclear --keep-baud %I 115200,38400,9600 $TERM")
+        ];
+        restartIfChanged = false;
+      };
+
+    systemd.services."serial-getty@" =
+      let speeds = concatStringsSep "," (map toString config.services.mingetty.serialSpeed); in
+      { serviceConfig.ExecStart = [
+          "" # override upstream default with an empty ExecStart
+          (gettyCmd "%I ${speeds} $TERM")
+        ];
+        restartIfChanged = false;
+      };
+
+    systemd.services."container-getty@" =
+      { serviceConfig.ExecStart = [
+          "" # override upstream default with an empty ExecStart
+          (gettyCmd "--noclear --keep-baud pts/%I 115200,38400,9600 $TERM")
+        ];
+        restartIfChanged = false;
+      };
+
+    systemd.services.console-getty =
+      { serviceConfig.ExecStart = [
+          "" # override upstream default with an empty ExecStart
+          (gettyCmd "--noclear --keep-baud console 115200,38400,9600 $TERM")
+        ];
+        serviceConfig.Restart = "always";
+        restartIfChanged = false;
+        enable = mkDefault config.boot.isContainer;
+      };
+
+    environment.etc.issue =
+      { # Friendly greeting on the virtual consoles.
+        source = pkgs.writeText "issue" ''
+
+          ${config.services.mingetty.greetingLine}
+          ${config.services.mingetty.helpLine}
+
+        '';
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/ttys/gpm.nix b/nixpkgs/nixos/modules/services/ttys/gpm.nix
new file mode 100644
index 000000000000..308a6d3643a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/ttys/gpm.nix
@@ -0,0 +1,57 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.gpm;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.gpm = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable GPM, the General Purpose Mouse daemon,
+          which enables mouse support in virtual consoles.
+        '';
+      };
+
+      protocol = mkOption {
+        type = types.str;
+        default = "ps/2";
+        description = "Mouse protocol to use.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.services.gpm =
+      { description = "Console Mouse Daemon";
+
+        wantedBy = [ "multi-user.target" ];
+        requires = [ "dev-input-mice.device" ];
+        after = [ "dev-input-mice.device" ];
+
+        serviceConfig.ExecStart = "@${pkgs.gpm}/sbin/gpm gpm -m /dev/input/mice -t ${cfg.protocol}";
+        serviceConfig.Type = "forking";
+        serviceConfig.PIDFile = "/run/gpm.pid";
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/ttys/kmscon.nix b/nixpkgs/nixos/modules/services/ttys/kmscon.nix
new file mode 100644
index 000000000000..dc37f9bee4b3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/ttys/kmscon.nix
@@ -0,0 +1,100 @@
+{ config, pkgs, lib, ... }:
+let
+  inherit (lib) mkOption types mkIf;
+
+  cfg = config.services.kmscon;
+
+  autologinArg = lib.optionalString (cfg.autologinUser != null) "-f ${cfg.autologinUser}";
+
+  configDir = pkgs.writeTextFile { name = "kmscon-config"; destination = "/kmscon.conf"; text = cfg.extraConfig; };
+in {
+  options = {
+    services.kmscon = {
+      enable = mkOption {
+        description = ''
+          Use kmscon as the virtual console instead of gettys.
+          kmscon is a kms/dri-based userspace virtual terminal implementation.
+          It supports a richer feature set than the standard linux console VT,
+          including full unicode support, and when the video card supports drm
+          should be much faster.
+        '';
+        type = types.bool;
+        default = false;
+      };
+
+      hwRender = mkOption {
+        description = "Whether to use 3D hardware acceleration to render the console.";
+        type = types.bool;
+        default = false;
+      };
+
+      extraConfig = mkOption {
+        description = "Extra contents of the kmscon.conf file.";
+        type = types.lines;
+        default = "";
+        example = "font-size=14";
+      };
+
+      extraOptions = mkOption {
+        description = "Extra flags to pass to kmscon.";
+        type = types.separatedString " ";
+        default = "";
+        example = "--term xterm-256color";
+      };
+
+      autologinUser = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Username of the account that will be automatically logged in at the console.
+          If unspecified, a login prompt is shown as usual.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # Largely copied from unit provided with kmscon source
+    systemd.units."kmsconvt@.service".text = ''
+      [Unit]
+      Description=KMS System Console on %I
+      Documentation=man:kmscon(1)
+      After=systemd-user-sessions.service
+      After=plymouth-quit-wait.service
+      After=systemd-logind.service
+      After=systemd-vconsole-setup.service
+      Requires=systemd-logind.service
+      Before=getty.target
+      Conflicts=getty@%i.service
+      OnFailure=getty@%i.service
+      IgnoreOnIsolate=yes
+      ConditionPathExists=/dev/tty0
+
+      [Service]
+      ExecStart=
+      ExecStart=${pkgs.kmscon}/bin/kmscon "--vt=%I" ${cfg.extraOptions} --seats=seat0 --no-switchvt --configdir ${configDir} --login -- ${pkgs.shadow}/bin/login -p ${autologinArg}
+      UtmpIdentifier=%I
+      TTYPath=/dev/%I
+      TTYReset=yes
+      TTYVHangup=yes
+      TTYVTDisallocate=yes
+
+      X-RestartIfChanged=false
+    '';
+
+    systemd.units."autovt@.service".unit = pkgs.runCommand "unit" { preferLocalBuild = true; }
+        ''
+          mkdir -p $out
+          ln -s ${config.systemd.units."kmsconvt@.service".unit}/kmsconvt@.service $out/autovt@.service
+        '';
+
+    systemd.services.systemd-vconsole-setup.enable = false;
+
+    services.kmscon.extraConfig = mkIf cfg.hwRender ''
+      drm
+      hwaccel
+    '';
+
+    hardware.opengl.enable = mkIf cfg.hwRender true;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/wayland/cage.nix b/nixpkgs/nixos/modules/services/wayland/cage.nix
new file mode 100644
index 000000000000..c59ca9983a6c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/wayland/cage.nix
@@ -0,0 +1,99 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cage;
+in {
+  options.services.cage.enable = mkEnableOption "cage kiosk service";
+
+  options.services.cage.user = mkOption {
+    type = types.str;
+    default = "demo";
+    description = ''
+      User to log-in as.
+    '';
+  };
+
+  options.services.cage.extraArguments = mkOption {
+    type = types.listOf types.str;
+    default = [];
+    defaultText = "[]";
+    description = "Additional command line arguments to pass to Cage.";
+    example = ["-d"];
+  };
+
+  options.services.cage.program = mkOption {
+    type = types.path;
+    default = "${pkgs.xterm}/bin/xterm";
+    description = ''
+      Program to run in cage.
+    '';
+  };
+
+  config = mkIf cfg.enable {
+
+    # The service is partially based off of the one provided in the
+    # cage wiki at
+    # https://github.com/Hjdskes/cage/wiki/Starting-Cage-on-boot-with-systemd.
+    systemd.services."cage-tty1" = {
+      enable = true;
+      after = [
+        "systemd-user-sessions.service"
+        "plymouth-start.service"
+        "plymouth-quit.service"
+        "systemd-logind.service"
+        "getty@tty1.service"
+      ];
+      before = [ "graphical.target" ];
+      wants = [ "dbus.socket" "systemd-logind.service" "plymouth-quit.service"];
+      wantedBy = [ "graphical.target" ];
+      conflicts = [ "getty@tty1.service" ];
+
+      restartIfChanged = false;
+      unitConfig.ConditionPathExists = "/dev/tty1";
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.cage}/bin/cage \
+            ${escapeShellArgs cfg.extraArguments} \
+            -- ${cfg.program}
+        '';
+        User = cfg.user;
+
+        IgnoreSIGPIPE = "no";
+
+        # Log this user with utmp, letting it show up with commands 'w' and
+        # 'who'. This is needed since we replace (a)getty.
+        UtmpIdentifier = "%n";
+        UtmpMode = "user";
+        # A virtual terminal is needed.
+        TTYPath = "/dev/tty1";
+        TTYReset = "yes";
+        TTYVHangup = "yes";
+        TTYVTDisallocate = "yes";
+        # Fail to start if not controlling the virtual terminal.
+        StandardInput = "tty-fail";
+        StandardOutput = "syslog";
+        StandardError = "syslog";
+        # Set up a full (custom) user session for the user, required by Cage.
+        PAMName = "cage";
+      };
+    };
+
+    security.pam.services.cage.text = ''
+      auth    required pam_unix.so nullok
+      account required pam_unix.so
+      session required pam_unix.so
+      session required ${pkgs.systemd}/lib/security/pam_systemd.so
+    '';
+
+    hardware.opengl.enable = mkDefault true;
+
+    systemd.targets.graphical.wants = [ "cage-tty1.service" ];
+
+    systemd.defaultUnit = "graphical.target";
+  };
+
+  meta.maintainers = with lib.maintainers; [ matthewbauer flokli ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix
new file mode 100644
index 000000000000..59185fdbd36f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -0,0 +1,197 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.confluence;
+
+  pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
+    enableSSO = cfg.sso.enable;
+    crowdProperties = ''
+      application.name                        ${cfg.sso.applicationName}
+      application.password                    ${cfg.sso.applicationPassword}
+      application.login.url                   ${cfg.sso.crowd}/console/
+
+      crowd.server.url                        ${cfg.sso.crowd}/services/
+      crowd.base.url                          ${cfg.sso.crowd}/
+
+      session.isauthenticated                 session.isauthenticated
+      session.tokenkey                        session.tokenkey
+      session.validationinterval              ${toString cfg.sso.validationInterval}
+      session.lastvalidation                  session.lastvalidation
+    '';
+  });
+
+in
+
+{
+  options = {
+    services.confluence = {
+      enable = mkEnableOption "Atlassian Confluence service";
+
+      user = mkOption {
+        type = types.str;
+        default = "confluence";
+        description = "User which runs confluence.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "confluence";
+        description = "Group which runs confluence.";
+      };
+
+      home = mkOption {
+        type = types.str;
+        default = "/var/lib/confluence";
+        description = "Home directory of the confluence instance.";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "Address to listen on.";
+      };
+
+      listenPort = mkOption {
+        type = types.int;
+        default = 8090;
+        description = "Port to listen on.";
+      };
+
+      catalinaOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "-Xms1024m" "-Xmx2048m" "-Dconfluence.disable.peopledirectory.all=true" ];
+        description = "Java options to pass to catalina/tomcat.";
+      };
+
+      proxy = {
+        enable = mkEnableOption "proxy support";
+
+        name = mkOption {
+          type = types.str;
+          example = "confluence.example.com";
+          description = "Virtual hostname at the proxy";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 443;
+          example = 80;
+          description = "Port used at the proxy";
+        };
+
+        scheme = mkOption {
+          type = types.str;
+          default = "https";
+          example = "http";
+          description = "Protocol used at the proxy.";
+        };
+      };
+
+      sso = {
+        enable = mkEnableOption "SSO with Atlassian Crowd";
+
+        crowd = mkOption {
+          type = types.str;
+          example = "http://localhost:8095/crowd";
+          description = "Crowd Base URL without trailing slash";
+        };
+
+        applicationName = mkOption {
+          type = types.str;
+          example = "jira";
+          description = "Exact name of this Confluence instance in Crowd";
+        };
+
+        applicationPassword = mkOption {
+          type = types.str;
+          description = "Application password of this Confluence instance in Crowd";
+        };
+
+        validationInterval = mkOption {
+          type = types.int;
+          default = 2;
+          example = 0;
+          description = ''
+            Set to 0, if you want authentication checks to occur on each
+            request. Otherwise set to the number of minutes between request
+            to validate if the user is logged in or out of the Crowd SSO
+            server. Setting this value to 1 or higher will increase the
+            performance of Crowd's integration.
+          '';
+        };
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.atlassian-confluence;
+        defaultText = "pkgs.atlassian-confluence";
+        description = "Atlassian Confluence package to use.";
+      };
+
+      jrePackage = mkOption {
+        type = types.package;
+        default = pkgs.oraclejre8;
+        defaultText = "pkgs.oraclejre8";
+        description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.${cfg.user} = {
+      isSystemUser = true;
+      group = cfg.group;
+    };
+
+    users.groups.${cfg.group} = {};
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.home}' - ${cfg.user} - - -"
+      "d /run/confluence - - - - -"
+
+      "L+ /run/confluence/home - - - - ${cfg.home}"
+      "L+ /run/confluence/logs - - - - ${cfg.home}/logs"
+      "L+ /run/confluence/temp - - - - ${cfg.home}/temp"
+      "L+ /run/confluence/work - - - - ${cfg.home}/work"
+      "L+ /run/confluence/server.xml - - - - ${cfg.home}/server.xml"
+    ];
+
+    systemd.services.confluence = {
+      description = "Atlassian Confluence";
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "postgresql.service" ];
+      after = [ "postgresql.service" ];
+
+      path = [ cfg.jrePackage pkgs.bash ];
+
+      environment = {
+        CONF_USER = cfg.user;
+        JAVA_HOME = "${cfg.jrePackage}";
+        CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
+      };
+
+      preStart = ''
+        mkdir -p ${cfg.home}/{logs,work,temp,deploy}
+
+        sed -e 's,port="8090",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
+        '' + (lib.optionalString cfg.proxy.enable ''
+          -e 's,protocol="org.apache.coyote.http11.Http11NioProtocol",protocol="org.apache.coyote.http11.Http11NioProtocol" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}",' \
+        '') + ''
+          ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        PrivateTmp = true;
+        ExecStart = "${pkg}/bin/start-confluence.sh -fg";
+        ExecStop = "${pkg}/bin/stop-confluence.sh";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix
new file mode 100644
index 000000000000..ceab656b15e8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -0,0 +1,164 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.crowd;
+
+  pkg = cfg.package.override {
+    home = cfg.home;
+    port = cfg.listenPort;
+    openidPassword = cfg.openidPassword;
+  } // (optionalAttrs cfg.proxy.enable {
+    proxyUrl = "${cfg.proxy.scheme}://${cfg.proxy.name}:${toString cfg.proxy.port}";
+  });
+
+in
+
+{
+  options = {
+    services.crowd = {
+      enable = mkEnableOption "Atlassian Crowd service";
+
+      user = mkOption {
+        type = types.str;
+        default = "crowd";
+        description = "User which runs Crowd.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "crowd";
+        description = "Group which runs Crowd.";
+      };
+
+      home = mkOption {
+        type = types.str;
+        default = "/var/lib/crowd";
+        description = "Home directory of the Crowd instance.";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "Address to listen on.";
+      };
+
+      listenPort = mkOption {
+        type = types.int;
+        default = 8092;
+        description = "Port to listen on.";
+      };
+
+      openidPassword = mkOption {
+        type = types.str;
+        description = "Application password for OpenID server.";
+      };
+
+      catalinaOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "-Xms1024m" "-Xmx2048m" ];
+        description = "Java options to pass to catalina/tomcat.";
+      };
+
+      proxy = {
+        enable = mkEnableOption "reverse proxy support";
+
+        name = mkOption {
+          type = types.str;
+          example = "crowd.example.com";
+          description = "Virtual hostname at the proxy";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 443;
+          example = 80;
+          description = "Port used at the proxy";
+        };
+
+        scheme = mkOption {
+          type = types.str;
+          default = "https";
+          example = "http";
+          description = "Protocol used at the proxy.";
+        };
+
+        secure = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether the connections to the proxy should be considered secure.";
+        };
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.atlassian-crowd;
+        defaultText = "pkgs.atlassian-crowd";
+        description = "Atlassian Crowd package to use.";
+      };
+
+      jrePackage = mkOption {
+        type = types.package;
+        default = pkgs.oraclejre8;
+        defaultText = "pkgs.oraclejre8";
+        description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.${cfg.user} = {
+      isSystemUser = true;
+      group = cfg.group;
+    };
+
+    users.groups.${cfg.group} = {};
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.home}' - ${cfg.user} ${cfg.group} - -"
+      "d /run/atlassian-crowd - - - - -"
+
+      "L+ /run/atlassian-crowd/database - - - - ${cfg.home}/database"
+      "L+ /run/atlassian-crowd/logs - - - - ${cfg.home}/logs"
+      "L+ /run/atlassian-crowd/work - - - - ${cfg.home}/work"
+      "L+ /run/atlassian-crowd/server.xml - - - - ${cfg.home}/server.xml"
+    ];
+
+    systemd.services.atlassian-crowd = {
+      description = "Atlassian Crowd";
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "postgresql.service" ];
+      after = [ "postgresql.service" ];
+
+      path = [ cfg.jrePackage ];
+
+      environment = {
+        JAVA_HOME = "${cfg.jrePackage}";
+        CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
+        CATALINA_TMPDIR = "/tmp";
+      };
+
+      preStart = ''
+        rm -rf ${cfg.home}/work
+        mkdir -p ${cfg.home}/{logs,database,work}
+
+        sed -e 's,port="8095",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
+        '' + (lib.optionalString cfg.proxy.enable ''
+          -e 's,compression="on",compression="off" protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${boolToString cfg.proxy.secure}",' \
+        '') + ''
+          ${pkg}/apache-tomcat/conf/server.xml.dist > ${cfg.home}/server.xml
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        PrivateTmp = true;
+        ExecStart = "${pkg}/start_crowd.sh -fg";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix b/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix
new file mode 100644
index 000000000000..ce04982e8a9e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -0,0 +1,204 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.jira;
+
+  pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
+    enableSSO = cfg.sso.enable;
+    crowdProperties = ''
+      application.name                        ${cfg.sso.applicationName}
+      application.password                    ${cfg.sso.applicationPassword}
+      application.login.url                   ${cfg.sso.crowd}/console/
+
+      crowd.server.url                        ${cfg.sso.crowd}/services/
+      crowd.base.url                          ${cfg.sso.crowd}/
+
+      session.isauthenticated                 session.isauthenticated
+      session.tokenkey                        session.tokenkey
+      session.validationinterval              ${toString cfg.sso.validationInterval}
+      session.lastvalidation                  session.lastvalidation
+    '';
+  });
+
+in
+
+{
+  options = {
+    services.jira = {
+      enable = mkEnableOption "Atlassian JIRA service";
+
+      user = mkOption {
+        type = types.str;
+        default = "jira";
+        description = "User which runs JIRA.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "jira";
+        description = "Group which runs JIRA.";
+      };
+
+      home = mkOption {
+        type = types.str;
+        default = "/var/lib/jira";
+        description = "Home directory of the JIRA instance.";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "Address to listen on.";
+      };
+
+      listenPort = mkOption {
+        type = types.int;
+        default = 8091;
+        description = "Port to listen on.";
+      };
+
+      catalinaOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "-Xms1024m" "-Xmx2048m" ];
+        description = "Java options to pass to catalina/tomcat.";
+      };
+
+      proxy = {
+        enable = mkEnableOption "reverse proxy support";
+
+        name = mkOption {
+          type = types.str;
+          example = "jira.example.com";
+          description = "Virtual hostname at the proxy";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 443;
+          example = 80;
+          description = "Port used at the proxy";
+        };
+
+        scheme = mkOption {
+          type = types.str;
+          default = "https";
+          example = "http";
+          description = "Protocol used at the proxy.";
+        };
+
+        secure = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether the connections to the proxy should be considered secure.";
+        };
+      };
+
+      sso = {
+        enable = mkEnableOption "SSO with Atlassian Crowd";
+
+        crowd = mkOption {
+          type = types.str;
+          example = "http://localhost:8095/crowd";
+          description = "Crowd Base URL without trailing slash";
+        };
+
+        applicationName = mkOption {
+          type = types.str;
+          example = "jira";
+          description = "Exact name of this JIRA instance in Crowd";
+        };
+
+        applicationPassword = mkOption {
+          type = types.str;
+          description = "Application password of this JIRA instance in Crowd";
+        };
+
+        validationInterval = mkOption {
+          type = types.int;
+          default = 2;
+          example = 0;
+          description = ''
+            Set to 0, if you want authentication checks to occur on each
+            request. Otherwise set to the number of minutes between request
+            to validate if the user is logged in or out of the Crowd SSO
+            server. Setting this value to 1 or higher will increase the
+            performance of Crowd's integration.
+          '';
+        };
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.atlassian-jira;
+        defaultText = "pkgs.atlassian-jira";
+        description = "Atlassian JIRA package to use.";
+      };
+
+      jrePackage = mkOption {
+        type = types.package;
+        default = pkgs.oraclejre8;
+        defaultText = "pkgs.oraclejre8";
+        description = "Note that Atlassian only support the Oracle JRE (JRASERVER-46152).";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.${cfg.user} = {
+      isSystemUser = true;
+      group = cfg.group;
+    };
+
+    users.groups.${cfg.group} = {};
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.home}' - ${cfg.user} - - -"
+      "d /run/atlassian-jira - - - - -"
+
+      "L+ /run/atlassian-jira/home - - - - ${cfg.home}"
+      "L+ /run/atlassian-jira/logs - - - - ${cfg.home}/logs"
+      "L+ /run/atlassian-jira/work - - - - ${cfg.home}/work"
+      "L+ /run/atlassian-jira/temp - - - - ${cfg.home}/temp"
+      "L+ /run/atlassian-jira/server.xml - - - - ${cfg.home}/server.xml"
+    ];
+
+    systemd.services.atlassian-jira = {
+      description = "Atlassian JIRA";
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "postgresql.service" ];
+      after = [ "postgresql.service" ];
+
+      path = [ cfg.jrePackage pkgs.bash ];
+
+      environment = {
+        JIRA_USER = cfg.user;
+        JIRA_HOME = cfg.home;
+        JAVA_HOME = "${cfg.jrePackage}";
+        CATALINA_OPTS = concatStringsSep " " cfg.catalinaOptions;
+      };
+
+      preStart = ''
+        mkdir -p ${cfg.home}/{logs,work,temp,deploy}
+
+        sed -e 's,port="8080",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
+        '' + (lib.optionalString cfg.proxy.enable ''
+          -e 's,protocol="HTTP/1.1",protocol="HTTP/1.1" proxyName="${cfg.proxy.name}" proxyPort="${toString cfg.proxy.port}" scheme="${cfg.proxy.scheme}" secure="${toString cfg.proxy.secure}",' \
+        '') + ''
+          ${pkg}/conf/server.xml.dist > ${cfg.home}/server.xml
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        PrivateTmp = true;
+        ExecStart = "${pkg}/bin/start-jira.sh -fg";
+        ExecStop = "${pkg}/bin/stop-jira.sh";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/codimd.nix b/nixpkgs/nixos/modules/services/web-apps/codimd.nix
new file mode 100644
index 000000000000..751f81649ddb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/codimd.nix
@@ -0,0 +1,916 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.codimd;
+
+  prettyJSON = conf:
+    pkgs.runCommand "codimd-config.json" { preferLocalBuild = true; } ''
+      echo '${builtins.toJSON conf}' | ${pkgs.jq}/bin/jq \
+        '{production:del(.[]|nulls)|del(.[][]?|nulls)}' > $out
+    '';
+in
+{
+  options.services.codimd = {
+    enable = mkEnableOption "the CodiMD Markdown Editor";
+
+    groups = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Groups to which the codimd user should be added.
+      '';
+    };
+
+    workDir = mkOption {
+      type = types.path;
+      default = "/var/lib/codimd";
+      description = ''
+        Working directory for the CodiMD service.
+      '';
+    };
+
+    configuration = {
+      debug = mkEnableOption "debug mode";
+      domain = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "codimd.org";
+        description = ''
+          Domain name for the CodiMD instance.
+        '';
+      };
+      urlPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/url/path/to/codimd";
+        description = ''
+          Path under which CodiMD is accessible.
+        '';
+      };
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          Address to listen on.
+        '';
+      };
+      port = mkOption {
+        type = types.int;
+        default = 3000;
+        example = "80";
+        description = ''
+          Port to listen on.
+        '';
+      };
+      path = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/run/codimd.sock";
+        description = ''
+          Specify where a UNIX domain socket should be placed.
+        '';
+      };
+      allowOrigin = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "localhost" "codimd.org" ];
+        description = ''
+          List of domains to whitelist.
+        '';
+      };
+      useSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable to use SSL server. This will also enable
+          <option>protocolUseSSL</option>.
+        '';
+      };
+      hsts = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Wheter to enable HSTS if HTTPS is also enabled.
+          '';
+        };
+        maxAgeSeconds = mkOption {
+          type = types.int;
+          default = 31536000;
+          description = ''
+            Max duration for clients to keep the HSTS status.
+          '';
+        };
+        includeSubdomains = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to include subdomains in HSTS.
+          '';
+        };
+        preload = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to allow preloading of the site's HSTS status.
+          '';
+        };
+      };
+      csp = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        example = literalExample ''
+          {
+            enable = true;
+            directives = {
+              scriptSrc = "trustworthy.scripts.example.com";
+            };
+            upgradeInsecureRequest = "auto";
+            addDefaults = true;
+          }
+        '';
+        description = ''
+          Specify the Content Security Policy which is passed to Helmet.
+          For configuration details see <link xlink:href="https://helmetjs.github.io/docs/csp/"
+          >https://helmetjs.github.io/docs/csp/</link>.
+        '';
+      };
+      protocolUseSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable to use TLS for resource paths.
+          This only applies when <option>domain</option> is set.
+        '';
+      };
+      urlAddPort = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable to add the port to callback URLs.
+          This only applies when <option>domain</option> is set
+          and only for ports other than 80 and 443.
+        '';
+      };
+      useCDN = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use CDN resources or not.
+        '';
+      };
+      allowAnonymous = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to allow anonymous usage.
+        '';
+      };
+      allowAnonymousEdits = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow guests to edit existing notes with the `freely' permission,
+          when <option>allowAnonymous</option> is enabled.
+        '';
+      };
+      allowFreeURL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow note creation by accessing a nonexistent note URL.
+        '';
+      };
+      defaultPermission = mkOption {
+        type = types.enum [ "freely" "editable" "limited" "locked" "private" ];
+        default = "editable";
+        description = ''
+          Default permissions for notes.
+          This only applies for signed-in users.
+        '';
+      };
+      dbURL = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = ''
+          postgres://user:pass@host:5432/dbname
+        '';
+        description = ''
+          Specify which database to use.
+          CodiMD supports mysql, postgres, sqlite and mssql.
+          See <link xlink:href="https://sequelize.readthedocs.io/en/v3/">
+          https://sequelize.readthedocs.io/en/v3/</link> for more information.
+          Note: This option overrides <option>db</option>.
+        '';
+      };
+      db = mkOption {
+        type = types.attrs;
+        default = {};
+        example = literalExample ''
+          {
+            dialect = "sqlite";
+            storage = "/var/lib/codimd/db.codimd.sqlite";
+          }
+        '';
+        description = ''
+          Specify the configuration for sequelize.
+          CodiMD supports mysql, postgres, sqlite and mssql.
+          See <link xlink:href="https://sequelize.readthedocs.io/en/v3/">
+          https://sequelize.readthedocs.io/en/v3/</link> for more information.
+          Note: This option overrides <option>db</option>.
+        '';
+      };
+      sslKeyPath= mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/codimd/codimd.key";
+        description = ''
+          Path to the SSL key. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      sslCertPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/codimd/codimd.crt";
+        description = ''
+          Path to the SSL cert. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      sslCAPath = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "/var/lib/codimd/ca.crt" ];
+        description = ''
+          SSL ca chain. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      dhParamPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/codimd/dhparam.pem";
+        description = ''
+          Path to the SSL dh params. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      tmpPath = mkOption {
+        type = types.str;
+        default = "/tmp";
+        description = ''
+          Path to the temp directory CodiMD should use.
+          Note that <option>serviceConfig.PrivateTmp</option> is enabled for
+          the CodiMD systemd service by default.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      defaultNotePath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/default.md";
+        description = ''
+          Path to the default Note file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      docsPath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/docs";
+        description = ''
+          Path to the docs directory.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      indexPath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/views/index.ejs";
+        description = ''
+          Path to the index template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      hackmdPath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/views/hackmd.ejs";
+        description = ''
+          Path to the hackmd template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      errorPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        defaultText = "./public/views/error.ejs";
+        description = ''
+          Path to the error template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      prettyPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        defaultText = "./public/views/pretty.ejs";
+        description = ''
+          Path to the pretty template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      slidePath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        defaultText = "./public/views/slide.hbs";
+        description = ''
+          Path to the slide template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      uploadsPath = mkOption {
+        type = types.str;
+        default = "${cfg.workDir}/uploads";
+        defaultText = "/var/lib/codimd/uploads";
+        description = ''
+          Path under which uploaded files are saved.
+        '';
+      };
+      sessionName = mkOption {
+        type = types.str;
+        default = "connect.sid";
+        description = ''
+          Specify the name of the session cookie.
+        '';
+      };
+      sessionSecret = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Specify the secret used to sign the session cookie.
+          If unset, one will be generated on startup.
+        '';
+      };
+      sessionLife = mkOption {
+        type = types.int;
+        default = 1209600000;
+        description = ''
+          Session life time in milliseconds.
+        '';
+      };
+      heartbeatInterval = mkOption {
+        type = types.int;
+        default = 5000;
+        description = ''
+          Specify the socket.io heartbeat interval.
+        '';
+      };
+      heartbeatTimeout = mkOption {
+        type = types.int;
+        default = 10000;
+        description = ''
+          Specify the socket.io heartbeat timeout.
+        '';
+      };
+      documentMaxLength = mkOption {
+        type = types.int;
+        default = 100000;
+        description = ''
+          Specify the maximum document length.
+        '';
+      };
+      email = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable email sign-in.
+        '';
+      };
+      allowEmailRegister = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Wether to enable email registration.
+        '';
+      };
+      allowGravatar = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to use gravatar as profile picture source.
+        '';
+      };
+      imageUploadType = mkOption {
+        type = types.enum [ "imgur" "s3" "minio" "filesystem" ];
+        default = "filesystem";
+        description = ''
+          Specify where to upload images.
+        '';
+      };
+      minio = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            accessKey = mkOption {
+              type = types.str;
+              description = ''
+                Minio access key.
+              '';
+            };
+            secretKey = mkOption {
+              type = types.str;
+              description = ''
+                Minio secret key.
+              '';
+            };
+            endpoint = mkOption {
+              type = types.str;
+              description = ''
+                Minio endpoint.
+              '';
+            };
+            port = mkOption {
+              type = types.int;
+              default = 9000;
+              description = ''
+                Minio listen port.
+              '';
+            };
+            secure = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether to use HTTPS for Minio.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the minio third-party integration.";
+      };
+      s3 = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            accessKeyId = mkOption {
+              type = types.str;
+              description = ''
+                AWS access key id.
+              '';
+            };
+            secretAccessKey = mkOption {
+              type = types.str;
+              description = ''
+                AWS access key.
+              '';
+            };
+            region = mkOption {
+              type = types.str;
+              description = ''
+                AWS S3 region.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the s3 third-party integration.";
+      };
+      s3bucket = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Specify the bucket name for upload types <literal>s3</literal> and <literal>minio</literal>.
+        '';
+      };
+      allowPDFExport = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable PDF exports.
+        '';
+      };
+      imgur.clientId = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Imgur API client ID.
+        '';
+      };
+      azure = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            connectionString = mkOption {
+              type = types.str;
+              description = ''
+                Azure Blob Storage connection string.
+              '';
+            };
+            container = mkOption {
+              type = types.str;
+              description = ''
+                Azure Blob Storage container name.
+                It will be created if non-existent.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the azure third-party integration.";
+      };
+      oauth2 = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            authorizationURL = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth authorization URL.
+              '';
+            };
+            tokenURL = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth token URL.
+              '';
+            };
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the OAuth integration.";
+      };
+      facebook = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Facebook API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Facebook API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the facebook third-party integration";
+      };
+      twitter = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            consumerKey = mkOption {
+              type = types.str;
+              description = ''
+                Twitter API consumer key.
+              '';
+            };
+            consumerSecret = mkOption {
+              type = types.str;
+              description = ''
+                Twitter API consumer secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Twitter third-party integration.";
+      };
+      github = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                GitHub API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Github API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the GitHub third-party integration.";
+      };
+      gitlab = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            baseURL = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                GitLab API authentication endpoint.
+                Only needed for other endpoints than gitlab.com.
+              '';
+            };
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                GitLab API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                GitLab API client secret.
+              '';
+            };
+            scope = mkOption {
+              type = types.enum [ "api" "read_user" ];
+              default = "api";
+              description = ''
+                GitLab API requested scope.
+                GitLab snippet import/export requires api scope.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the GitLab third-party integration.";
+      };
+      mattermost = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            baseURL = mkOption {
+              type = types.str;
+              description = ''
+                Mattermost authentication endpoint.
+              '';
+            };
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Mattermost API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Mattermost API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Mattermost third-party integration.";
+      };
+      dropbox = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Dropbox API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Dropbox API client secret.
+              '';
+            };
+            appKey = mkOption {
+              type = types.str;
+              description = ''
+                Dropbox app key.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Dropbox third-party integration.";
+      };
+      google = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Google API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Google API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Google third-party integration.";
+      };
+      ldap = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            providerName = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                Optional name to be displayed at login form, indicating the LDAP provider.
+              '';
+            };
+            url = mkOption {
+              type = types.str;
+              example = "ldap://localhost";
+              description = ''
+                URL of LDAP server.
+              '';
+            };
+            bindDn = mkOption {
+              type = types.str;
+              description = ''
+                Bind DN for LDAP access.
+              '';
+            };
+            bindCredentials = mkOption {
+              type = types.str;
+              description = ''
+                Bind credentials for LDAP access.
+              '';
+            };
+            searchBase = mkOption {
+              type = types.str;
+              example = "o=users,dc=example,dc=com";
+              description = ''
+                LDAP directory to begin search from.
+              '';
+            };
+            searchFilter = mkOption {
+              type = types.str;
+              example = "(uid={{username}})";
+              description = ''
+                LDAP filter to search with.
+              '';
+            };
+            searchAttributes = mkOption {
+              type = types.listOf types.str;
+              example = [ "displayName" "mail" ];
+              description = ''
+                LDAP attributes to search with.
+              '';
+            };
+            userNameField = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                LDAP field which is used as the username on CodiMD.
+                By default <option>useridField</option> is used.
+              '';
+            };
+            useridField = mkOption {
+              type = types.str;
+              example = "uid";
+              description = ''
+                LDAP field which is a unique identifier for users on CodiMD.
+              '';
+            };
+            tlsca = mkOption {
+              type = types.str;
+              example = "server-cert.pem,root.pem";
+              description = ''
+                Root CA for LDAP TLS in PEM format.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the LDAP integration.";
+      };
+      saml = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            idpSsoUrl = mkOption {
+              type = types.str;
+              example = "https://idp.example.com/sso";
+              description = ''
+                IdP authentication endpoint.
+              '';
+            };
+            idpCert = mkOption {
+              type = types.path;
+              example = "/path/to/cert.pem";
+              description = ''
+                Path to IdP certificate file in PEM format.
+              '';
+            };
+            issuer = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                Optional identity of the service provider.
+                This defaults to the server URL.
+              '';
+            };
+            identifierFormat = mkOption {
+              type = types.str;
+              default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
+              description = ''
+                Optional name identifier format.
+              '';
+            };
+            groupAttribute = mkOption {
+              type = types.str;
+              default = "";
+              example = "memberOf";
+              description = ''
+                Optional attribute name for group list.
+              '';
+            };
+            externalGroups = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "Temporary-staff" "External-users" ];
+              description = ''
+                Excluded group names.
+              '';
+            };
+            requiredGroups = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "Hackmd-users" "Codimd-users" ];
+              description = ''
+                Required group names.
+              '';
+            };
+            attribute = {
+              id = mkOption {
+                type = types.str;
+                default = "";
+                description = ''
+                  Attribute map for `id'.
+                  Defaults to `NameID' of SAML response.
+                '';
+              };
+              username = mkOption {
+                type = types.str;
+                default = "";
+                description = ''
+                  Attribute map for `username'.
+                  Defaults to `NameID' of SAML response.
+                '';
+              };
+              email = mkOption {
+                type = types.str;
+                default = "";
+                description = ''
+                  Attribute map for `email'.
+                  Defaults to `NameID' of SAML response if
+                  <option>identifierFormat</option> has
+                  the default value.
+                '';
+              };
+            };
+          };
+        });
+        default = null;
+        description = "Configure the SAML integration.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.configuration.db == {} -> (
+          cfg.configuration.dbURL != "" && cfg.configuration.dbURL != null
+        );
+        message = "Database configuration for CodiMD missing."; }
+    ];
+    users.groups.codimd = {};
+    users.users.codimd = {
+      description = "CodiMD service user";
+      group = "codimd";
+      extraGroups = cfg.groups;
+      home = cfg.workDir;
+      createHome = true;
+      isSystemUser = true;
+    };
+
+    systemd.services.codimd = {
+      description = "CodiMD Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      serviceConfig = {
+        WorkingDirectory = cfg.workDir;
+        ExecStart = "${pkgs.codimd}/bin/codimd";
+        Environment = [
+          "CMD_CONFIG_FILE=${prettyJSON cfg.configuration}"
+          "NODE_ENV=production"
+        ];
+        Restart = "always";
+        User = "codimd";
+        PrivateTmp = true;
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/cryptpad.nix b/nixpkgs/nixos/modules/services/web-apps/cryptpad.nix
new file mode 100644
index 000000000000..69a89107d310
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/cryptpad.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cryptpad;
+in
+{
+  options.services.cryptpad = {
+    enable = mkEnableOption "the Cryptpad service";
+
+    package = mkOption {
+      default = pkgs.cryptpad;
+      defaultText = "pkgs.cryptpad";
+      type = types.package;
+      description = "
+        Cryptpad package to use.
+      ";
+    };
+
+    configFile = mkOption {
+      type = types.path;
+      default = "${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
+      defaultText = "\${cfg.package}/lib/node_modules/cryptpad/config/config.example.js";
+      description = ''
+        Path to the JavaScript configuration file.
+
+        See <link
+        xlink:href="https://github.com/xwiki-labs/cryptpad/blob/master/config/config.example.js"/>
+        for a configuration example.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.cryptpad = {
+      description = "Cryptpad Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        Environment = [
+          "CRYPTPAD_CONFIG=${cfg.configFile}"
+          "HOME=%S/cryptpad"
+        ];
+        ExecStart = "${cfg.package}/bin/cryptpad";
+        PrivateTmp = true;
+        Restart = "always";
+        StateDirectory = "cryptpad";
+        WorkingDirectory = "%S/cryptpad";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/documize.nix b/nixpkgs/nixos/modules/services/web-apps/documize.nix
new file mode 100644
index 000000000000..a5f48e744fdc
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/documize.nix
@@ -0,0 +1,149 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.documize;
+
+  mkParams = optional: concatMapStrings (name: let
+    predicate = optional -> cfg.${name} != null;
+    template = " -${name} '${toString cfg.${name}}'";
+  in optionalString predicate template);
+
+in {
+  options.services.documize = {
+    enable = mkEnableOption "Documize Wiki";
+
+    stateDirectoryName = mkOption {
+      type = types.str;
+      default = "documize";
+      description = ''
+        The name of the directory below <filename>/var/lib/private</filename>
+        where documize runs in and stores, for example, backups.
+      '';
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.documize-community;
+      description = ''
+        Which package to use for documize.
+      '';
+    };
+
+    salt = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "3edIYV6c8B28b19fh";
+      description = ''
+        The salt string used to encode JWT tokens, if not set a random value will be generated.
+      '';
+    };
+
+    cert = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The <filename>cert.pem</filename> file used for https.
+      '';
+    };
+
+    key = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The <filename>key.pem</filename> file used for https.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 5001;
+      description = ''
+        The http/https port number.
+      '';
+    };
+
+    forcesslport = mkOption {
+      type = types.nullOr types.port;
+      default = null;
+      description = ''
+        Redirect given http port number to TLS.
+      '';
+    };
+
+    offline = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Set <literal>true</literal> for offline mode.
+      '';
+      apply = v: if true == v then 1 else 0;
+    };
+
+    dbtype = mkOption {
+      type = types.enum [ "mysql" "percona" "mariadb" "postgresql" "sqlserver" ];
+      default = "postgresql";
+      description = ''
+        Specify the database provider:
+        <simplelist type='inline'>
+          <member><literal>mysql</literal></member>
+          <member><literal>percona</literal></member>
+          <member><literal>mariadb</literal></member>
+          <member><literal>postgresql</literal></member>
+          <member><literal>sqlserver</literal></member>
+        </simplelist>
+      '';
+    };
+
+    db = mkOption {
+      type = types.str;
+      description = ''
+        Database specific connection string for example:
+        <itemizedlist>
+        <listitem><para>MySQL/Percona/MariaDB:
+          <literal>user:password@tcp(host:3306)/documize</literal>
+        </para></listitem>
+        <listitem><para>MySQLv8+:
+          <literal>user:password@tcp(host:3306)/documize?allowNativePasswords=true</literal>
+        </para></listitem>
+        <listitem><para>PostgreSQL:
+          <literal>host=localhost port=5432 dbname=documize user=admin password=secret sslmode=disable</literal>
+        </para></listitem>
+        <listitem><para>MSSQL:
+          <literal>sqlserver://username:password@localhost:1433?database=Documize</literal> or
+          <literal>sqlserver://sa@localhost/SQLExpress?database=Documize</literal>
+        </para></listitem>
+        </itemizedlist>
+      '';
+    };
+
+    location = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        reserved
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.documize-server = {
+      description = "Documize Wiki";
+      documentation = [ "https://documize.com/" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = concatStringsSep " " [
+          "${cfg.package}/bin/documize"
+          (mkParams false [ "db" "dbtype" "port" ])
+          (mkParams true [ "offline" "location" "forcesslport" "key" "cert" "salt" ])
+        ];
+        Restart = "always";
+        DynamicUser = "yes";
+        StateDirectory = cfg.stateDirectoryName;
+        WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix b/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix
new file mode 100644
index 000000000000..33a828fa2cb3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/dokuwiki.nix
@@ -0,0 +1,388 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib) mkEnableOption mkForce mkIf mkMerge mkOption optionalAttrs recursiveUpdate types;
+  inherit (lib) concatMapStringsSep flatten mapAttrs mapAttrs' mapAttrsToList nameValuePair concatMapStringSep;
+
+  eachSite = config.services.dokuwiki;
+
+  user = "dokuwiki";
+  group = config.services.nginx.group;
+
+  dokuwikiAclAuthConfig = cfg: pkgs.writeText "acl.auth.php" ''
+    # acl.auth.php
+    # <?php exit()?>
+    #
+    # Access Control Lists
+    #
+    ${toString cfg.acl}
+  '';
+
+  dokuwikiLocalConfig = cfg: pkgs.writeText "local.php" ''
+    <?php
+    $conf['savedir'] = '${cfg.stateDir}';
+    $conf['superuser'] = '${toString cfg.superUser}';
+    $conf['useacl'] = '${toString cfg.aclUse}';
+    $conf['disableactions'] = '${cfg.disableActions}';
+    ${toString cfg.extraConfig}
+  '';
+
+  dokuwikiPluginsLocalConfig = cfg: pkgs.writeText "plugins.local.php" ''
+    <?php
+    ${cfg.pluginsConfig}
+  '';
+
+  pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
+    pname = "dokuwiki-${hostName}";
+    version = src.version;
+    src = cfg.package;
+
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+
+      # symlink the dokuwiki config
+      ln -s ${dokuwikiLocalConfig cfg} $out/share/dokuwiki/local.php
+
+      # symlink plugins config
+      ln -s ${dokuwikiPluginsLocalConfig cfg} $out/share/dokuwiki/plugins.local.php
+
+      # symlink acl
+      ln -s ${dokuwikiAclAuthConfig cfg} $out/share/dokuwiki/acl.auth.php
+
+      # symlink additional plugin(s) and templates(s)
+      ${concatMapStringsSep "\n" (template: "ln -s ${template} $out/share/dokuwiki/lib/tpl/${template.name}") cfg.templates}
+      ${concatMapStringsSep "\n" (plugin: "ln -s ${plugin} $out/share/dokuwiki/lib/plugins/${plugin.name}") cfg.plugins}
+    '';
+  };
+
+  siteOpts = { config, lib, name, ...}: {
+    options = {
+      enable = mkEnableOption "DokuWiki web application.";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.dokuwiki;
+        description = "Which dokuwiki package to use.";
+      };
+
+      hostName = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "FQDN for the instance.";
+      };
+
+      stateDir = mkOption {
+        type = types.path;
+        default = "/var/lib/dokuwiki/${name}/data";
+        description = "Location of the dokuwiki state directory.";
+      };
+
+      acl = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        example = "*               @ALL               8";
+        description = ''
+          Access Control Lists: see <link xlink:href="https://www.dokuwiki.org/acl"/>
+          Mutually exclusive with services.dokuwiki.aclFile
+          Set this to a value other than null to take precedence over aclFile option.
+
+          Warning: Consider using aclFile instead if you do not
+          want to store the ACL in the world-readable Nix store.
+        '';
+      };
+
+      aclFile = mkOption {
+        type = with types; nullOr str;
+        default = if (config.aclUse && config.acl == null) then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
+        description = ''
+          Location of the dokuwiki acl rules. Mutually exclusive with services.dokuwiki.acl
+          Mutually exclusive with services.dokuwiki.acl which is preferred.
+          Consult documentation <link xlink:href="https://www.dokuwiki.org/acl"/> for further instructions.
+          Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/acl.auth.php.dist"/>
+        '';
+        example = "/var/lib/dokuwiki/${name}/acl.auth.php";
+      };
+
+      aclUse = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Necessary for users to log in into the system.
+          Also limits anonymous users. When disabled,
+          everyone is able to create and edit content.
+        '';
+      };
+
+      pluginsConfig = mkOption {
+        type = types.lines;
+        default = ''
+          $plugins['authad'] = 0;
+          $plugins['authldap'] = 0;
+          $plugins['authmysql'] = 0;
+          $plugins['authpgsql'] = 0;
+        '';
+        description = ''
+          List of the dokuwiki (un)loaded plugins.
+        '';
+      };
+
+      superUser = mkOption {
+        type = types.nullOr types.str;
+        default = "@admin";
+        description = ''
+          You can set either a username, a list of usernames (“admin1,admin2”),
+          or the name of a group by prepending an @ char to the groupname
+          Consult documentation <link xlink:href="https://www.dokuwiki.org/config:superuser"/> for further instructions.
+        '';
+      };
+
+      usersFile = mkOption {
+        type = with types; nullOr str;
+        default = if config.aclUse then "/var/lib/dokuwiki/${name}/users.auth.php" else null;
+        description = ''
+          Location of the dokuwiki users file. List of users. Format:
+          login:passwordhash:Real Name:email:groups,comma,separated
+          Create passwordHash easily by using:$ mkpasswd -5 password `pwgen 8 1`
+          Example: <link xlink:href="https://github.com/splitbrain/dokuwiki/blob/master/conf/users.auth.php.dist"/>
+          '';
+        example = "/var/lib/dokuwiki/${name}/users.auth.php";
+      };
+
+      disableActions = mkOption {
+        type = types.nullOr types.str;
+        default = "";
+        example = "search,register";
+        description = ''
+          Disable individual action modes. Refer to
+          <link xlink:href="https://www.dokuwiki.org/config:action_modes"/>
+          for details on supported values.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.nullOr types.lines;
+        default = null;
+        example = ''
+          $conf['title'] = 'My Wiki';
+          $conf['userewrite'] = 1;
+        '';
+        description = ''
+          DokuWiki configuration. Refer to
+          <link xlink:href="https://www.dokuwiki.org/config"/>
+          for details on supported values.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+              List of path(s) to respective plugin(s) which are copied from the 'plugin' directory.
+              <note><para>These plugins need to be packaged before use, see example.</para></note>
+        '';
+        example = ''
+              # Let's package the icalevents plugin
+              plugin-icalevents = pkgs.stdenv.mkDerivation {
+                name = "icalevents";
+                # Download the plugin from the dokuwiki site
+                src = pkgs.fetchurl {
+                  url = "https://github.com/real-or-random/dokuwiki-plugin-icalevents/releases/download/2017-06-16/dokuwiki-plugin-icalevents-2017-06-16.zip";
+                  sha256 = "e40ed7dd6bbe7fe3363bbbecb4de481d5e42385b5a0f62f6a6ce6bf3a1f9dfa8";
+                };
+                sourceRoot = ".";
+                # We need unzip to build this package
+                buildInputs = [ pkgs.unzip ];
+                # Installing simply means copying all files to the output directory
+                installPhase = "mkdir -p $out; cp -R * $out/";
+              };
+
+              # And then pass this theme to the plugin list like this:
+              plugins = [ plugin-icalevents ];
+        '';
+      };
+
+      templates = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = ''
+              List of path(s) to respective template(s) which are copied from the 'tpl' directory.
+              <note><para>These templates need to be packaged before use, see example.</para></note>
+        '';
+        example = ''
+              # Let's package the bootstrap3 theme
+              template-bootstrap3 = pkgs.stdenv.mkDerivation {
+                name = "bootstrap3";
+                # Download the theme from the dokuwiki site
+                src = pkgs.fetchurl {
+                  url = "https://github.com/giterlizzi/dokuwiki-template-bootstrap3/archive/v2019-05-22.zip";
+                  sha256 = "4de5ff31d54dd61bbccaf092c9e74c1af3a4c53e07aa59f60457a8f00cfb23a6";
+                };
+                # We need unzip to build this package
+                buildInputs = [ pkgs.unzip ];
+                # Installing simply means copying all files to the output directory
+                installPhase = "mkdir -p $out; cp -R * $out/";
+              };
+
+              # And then pass this theme to the template list like this:
+              templates = [ template-bootstrap3 ];
+        '';
+      };
+
+      poolConfig = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool ]);
+        default = {
+          "pm" = "dynamic";
+          "pm.max_children" = 32;
+          "pm.start_servers" = 2;
+          "pm.min_spare_servers" = 2;
+          "pm.max_spare_servers" = 4;
+          "pm.max_requests" = 500;
+        };
+        description = ''
+          Options for the dokuwiki PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+          for details on configuration directives.
+        '';
+      };
+
+      nginx = mkOption {
+        type = types.submodule (
+          recursiveUpdate
+            (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
+            {
+              # Enable encryption by default,
+              options.forceSSL.default = true;
+              options.enableACME.default = true;
+            }
+        );
+        default = {forceSSL = true; enableACME = true;};
+        example = {
+          serverAliases = [
+            "wiki.\${config.networking.domain}"
+          ];
+          enableACME = false;
+        };
+        description = ''
+          With this option, you can customize the nginx virtualHost which already has sensible defaults for DokuWiki.
+        '';
+      };
+    };
+  };
+in
+{
+  # interface
+  options = {
+    services.dokuwiki = mkOption {
+      type = types.attrsOf (types.submodule siteOpts);
+      default = {};
+      description = "Sepcification of one or more dokuwiki sites to service.";
+    };
+  };
+
+  # implementation
+
+  config = mkIf (eachSite != {}) {
+
+    warnings = mapAttrsToList (hostName: cfg: mkIf (cfg.superUser == null) "Not setting services.dokuwiki.${hostName} superUser will impair your ability to administer DokuWiki") eachSite;
+
+    assertions = flatten (mapAttrsToList (hostName: cfg:
+    [{
+      assertion = cfg.aclUse -> (cfg.acl != null || cfg.aclFile != null);
+      message = "Either services.dokuwiki.${hostName}.acl or services.dokuwiki.${hostName}.aclFile is mandatory if aclUse true";
+    }
+    {
+      assertion = cfg.usersFile != null -> cfg.aclUse != false;
+      message = "services.dokuwiki.${hostName}.aclUse must must be true if usersFile is not null";
+    }
+    ]) eachSite);
+
+    services.phpfpm.pools = mapAttrs' (hostName: cfg: (
+      nameValuePair "dokuwiki-${hostName}" {
+        inherit user;
+        inherit group;
+        phpEnv = {
+          DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig cfg}";
+          DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig cfg}";
+        } // optionalAttrs (cfg.usersFile != null) {
+          DOKUWIKI_USERS_AUTH_CONFIG = "${cfg.usersFile}";
+        } //optionalAttrs (cfg.aclUse) {
+          DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig cfg}" else "${toString cfg.aclFile}";
+        };
+
+        settings = {
+          "listen.mode" = "0660";
+          "listen.owner" = user;
+          "listen.group" = group;
+        } // cfg.poolConfig;
+      })) eachSite;
+
+    services.nginx = {
+      enable = true;
+      virtualHosts = mapAttrs (hostName: cfg:  mkMerge [ cfg.nginx {
+        root = mkForce "${pkg hostName cfg}/share/dokuwiki";
+        extraConfig = "fastcgi_param HTTPS on;";
+
+        locations."~ /(conf/|bin/|inc/|install.php)" = {
+          extraConfig = "deny all;";
+        };
+
+        locations."~ ^/data/" = {
+          root = "${cfg.stateDir}";
+          extraConfig = "internal;";
+        };
+
+        locations."~ ^/lib.*\.(js|css|gif|png|ico|jpg|jpeg)$" = {
+          extraConfig = "expires 365d;";
+        };
+
+        locations."/" = {
+          priority = 1;
+          index = "doku.php";
+          extraConfig = ''try_files $uri $uri/ @dokuwiki;'';
+        };
+
+        locations."@dokuwiki" = {
+          extraConfig = ''
+              # rewrites "doku.php/" out of the URLs if you set the userwrite setting to .htaccess in dokuwiki config page
+              rewrite ^/_media/(.*) /lib/exe/fetch.php?media=$1 last;
+              rewrite ^/_detail/(.*) /lib/exe/detail.php?media=$1 last;
+              rewrite ^/_export/([^/]+)/(.*) /doku.php?do=export_$1&id=$2 last;
+              rewrite ^/(.*) /doku.php?id=$1&$args last;
+          '';
+        };
+
+        locations."~ \.php$" = {
+          extraConfig = ''
+              try_files $uri $uri/ /doku.php;
+              include ${pkgs.nginx}/conf/fastcgi_params;
+              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+              fastcgi_param REDIRECT_STATUS 200;
+              fastcgi_pass unix:${config.services.phpfpm.pools."dokuwiki-${hostName}".socket};
+              fastcgi_param HTTPS on;
+          '';
+        };
+      }]) eachSite;
+    };
+
+    systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
+      "d ${cfg.stateDir}/attic 0750 ${user} ${group} - -"
+      "d ${cfg.stateDir}/cache 0750 ${user} ${group} - -"
+      "d ${cfg.stateDir}/index 0750 ${user} ${group} - -"
+      "d ${cfg.stateDir}/locks 0750 ${user} ${group} - -"
+      "d ${cfg.stateDir}/media 0750 ${user} ${group} - -"
+      "d ${cfg.stateDir}/media_attic 0750 ${user} ${group} - -"
+      "d ${cfg.stateDir}/media_meta 0750 ${user} ${group} - -"
+      "d ${cfg.stateDir}/meta 0750 ${user} ${group} - -"
+      "d ${cfg.stateDir}/pages 0750 ${user} ${group} - -"
+      "d ${cfg.stateDir}/tmp 0750 ${user} ${group} - -"
+    ] ++ lib.optional (cfg.aclFile != null) "C ${cfg.aclFile} 0640 ${user} ${group} - ${pkg hostName cfg}/share/dokuwiki/conf/acl.auth.php.dist"
+    ++ lib.optional (cfg.usersFile != null) "C ${cfg.usersFile} 0640 ${user} ${group} - ${pkg hostName cfg}/share/dokuwiki/conf/users.auth.php.dist"
+    ) eachSite);
+
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/engelsystem.nix b/nixpkgs/nixos/modules/services/web-apps/engelsystem.nix
new file mode 100644
index 000000000000..899582a20304
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/engelsystem.nix
@@ -0,0 +1,186 @@
+{ config, lib, pkgs, utils, ... }:
+
+let
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption types literalExample;
+  cfg = config.services.engelsystem;
+in {
+  options = {
+    services.engelsystem = {
+      enable = mkOption {
+        default = false;
+        example = true;
+        description = ''
+          Whether to enable engelsystem, an online tool for coordinating helpers
+          and shifts on large events.
+        '';
+        type = lib.types.bool;
+      };
+
+      domain = mkOption {
+        type = types.str;
+        example = "engelsystem.example.com";
+        description = "Domain to serve on.";
+      };
+
+      package = mkOption {
+        type = types.package;
+        example = literalExample "pkgs.engelsystem";
+        description = "Engelsystem package used for the service.";
+        default = pkgs.engelsystem;
+      };
+
+      createDatabase = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to create a local database automatically.
+          This will override every database setting in <option>services.engelsystem.config</option>.
+        '';
+      };
+    };
+
+    services.engelsystem.config = mkOption {
+      type = types.attrs;
+      default = {
+        database = {
+          host = "localhost";
+          database = "engelsystem";
+          username = "engelsystem";
+        };
+      };
+      example = {
+        maintenance = false;
+        database = {
+          host = "database.example.com";
+          database = "engelsystem";
+          username = "engelsystem";
+          password._secret = "/var/keys/engelsystem/database";
+        };
+        email = {
+          driver = "smtp";
+          host = "smtp.example.com";
+          port = 587;
+          from.address = "engelsystem@example.com";
+          from.name = "example engelsystem";
+          encryption = "tls";
+          username = "engelsystem@example.com";
+          password._secret = "/var/keys/engelsystem/mail";
+        };
+        autoarrive = true;
+        min_password_length = 6;
+        default_locale = "de_DE";
+      };
+      description = ''
+        Options to be added to config.php, as a nix attribute set. Options containing secret data
+        should be set to an attribute set containing the attribute _secret - a string pointing to a
+        file containing the value the option should be set to. See the example to get a better
+        picture of this: in the resulting config.php file, the email.password key will be set to
+        the contents of the /var/keys/engelsystem/mail file.
+
+        See https://engelsystem.de/doc/admin/configuration/ for available options.
+
+        Note that the admin user login credentials cannot be set here - they always default to
+        admin:asdfasdf. Log in and change them immediately.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # create database
+    services.mysql = mkIf cfg.createDatabase {
+      enable = true;
+      package = mkDefault pkgs.mysql;
+      ensureUsers = [{
+        name = "engelsystem";
+        ensurePermissions = { "engelsystem.*" = "ALL PRIVILEGES"; };
+      }];
+      ensureDatabases = [ "engelsystem" ];
+    };
+
+    environment.etc."engelsystem/config.php".source =
+      pkgs.writeText "config.php" ''
+        <?php
+        return json_decode(file_get_contents("/var/lib/engelsystem/config.json"), true);
+      '';
+
+    services.phpfpm.pools.engelsystem = {
+      user = "engelsystem";
+      settings = {
+        "listen.owner" = config.services.nginx.user;
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.max_requests" = 500;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 5;
+        "php_admin_value[error_log]" = "stderr";
+        "php_admin_flag[log_errors]" = true;
+        "catch_workers_output" = true;
+      };
+    };
+
+    services.nginx = {
+      enable = true;
+      virtualHosts."${cfg.domain}".locations = {
+        "/" = {
+          root = "${cfg.package}/share/engelsystem/public";
+          extraConfig = ''
+            index index.php;
+            try_files $uri $uri/ /index.php?$args;
+            autoindex off;
+          '';
+        };
+        "~ \\.php$" = {
+          root = "${cfg.package}/share/engelsystem/public";
+          extraConfig = ''
+            fastcgi_pass unix:${config.services.phpfpm.pools.engelsystem.socket};
+            fastcgi_index index.php;
+            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+            include ${config.services.nginx.package}/conf/fastcgi_params;
+            include ${config.services.nginx.package}/conf/fastcgi.conf;
+          '';
+        };
+      };
+    };
+
+    systemd.services."engelsystem-init" = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = { Type = "oneshot"; };
+      script =
+        let
+          genConfigScript = pkgs.writeScript "engelsystem-gen-config.sh"
+            (utils.genJqSecretsReplacementSnippet cfg.config "config.json");
+        in ''
+          umask 077
+          mkdir -p /var/lib/engelsystem/storage/app
+          mkdir -p /var/lib/engelsystem/storage/cache/views
+          cd /var/lib/engelsystem
+          ${genConfigScript}
+          chmod 400 config.json
+          chown -R engelsystem .
+      '';
+    };
+    systemd.services."engelsystem-migrate" = {
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        Type = "oneshot";
+        User = "engelsystem";
+        Group = "engelsystem";
+      };
+      script = ''
+        ${cfg.package}/bin/migrate
+      '';
+      after = [ "engelsystem-init.service" "mysql.service" ];
+    };
+    systemd.services."phpfpm-engelsystem".after =
+      [ "engelsystem-migrate.service" ];
+
+    users.users.engelsystem = {
+      isSystemUser = true;
+      createHome = true;
+      home = "/var/lib/engelsystem/storage";
+      group = "engelsystem";
+    };
+    users.groups.engelsystem = { };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/frab.nix b/nixpkgs/nixos/modules/services/web-apps/frab.nix
new file mode 100644
index 000000000000..1b5890d6b0c7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/frab.nix
@@ -0,0 +1,222 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.frab;
+
+  package = pkgs.frab;
+
+  databaseConfig = builtins.toJSON { production = cfg.database; };
+
+  frabEnv = {
+    RAILS_ENV = "production";
+    RACK_ENV = "production";
+    SECRET_KEY_BASE = cfg.secretKeyBase;
+    FRAB_HOST = cfg.host;
+    FRAB_PROTOCOL = cfg.protocol;
+    FROM_EMAIL = cfg.fromEmail;
+    RAILS_SERVE_STATIC_FILES = "1";
+  } // cfg.extraEnvironment;
+
+  frab-rake = pkgs.stdenv.mkDerivation {
+    name = "frab-rake";
+    buildInputs = [ package.env pkgs.makeWrapper ];
+    phases = "installPhase fixupPhase";
+    installPhase = ''
+      mkdir -p $out/bin
+      makeWrapper ${package.env}/bin/bundle $out/bin/frab-bundle \
+          ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") frabEnv)} \
+          --set PATH '${lib.makeBinPath (with pkgs; [ nodejs file imagemagick ])}:$PATH' \
+          --set RAKEOPT '-f ${package}/share/frab/Rakefile' \
+          --run 'cd ${package}/share/frab'
+      makeWrapper $out/bin/frab-bundle $out/bin/frab-rake \
+          --add-flags "exec rake"
+     '';
+  };
+
+in
+
+{
+  options = {
+    services.frab = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable the frab service.
+        '';
+      };
+
+      host = mkOption {
+        type = types.str;
+        example = "frab.example.com";
+        description = ''
+          Hostname under which this frab instance can be reached.
+        '';
+      };
+
+      protocol = mkOption {
+        type = types.str;
+        default = "https";
+        example = "http";
+        description = ''
+          Either http or https, depending on how your Frab instance
+          will be exposed to the public.
+        '';
+      };
+
+      fromEmail = mkOption {
+        type = types.str;
+        default = "frab@localhost";
+        description = ''
+          Email address used by frab.
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          Address or hostname frab should listen on.
+        '';
+      };
+
+      listenPort = mkOption {
+        type = types.int;
+        default = 3000;
+        description = ''
+          Port frab should listen on.
+        '';
+      };
+
+      statePath = mkOption {
+        type = types.str;
+        default = "/var/lib/frab";
+        description = ''
+          Directory where frab keeps its state.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "frab";
+        description = ''
+          User to run frab.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "frab";
+        description = ''
+          Group to run frab.
+        '';
+      };
+
+      secretKeyBase = mkOption {
+        type = types.str;
+        description = ''
+          Your secret key is used for verifying the integrity of signed cookies.
+          If you change this key, all old signed cookies will become invalid!
+
+          Make sure the secret is at least 30 characters and all random,
+          no regular words or you'll be exposed to dictionary attacks.
+        '';
+      };
+
+      database = mkOption {
+        type = types.attrs;
+        default = {
+          adapter = "sqlite3";
+          database = "/var/lib/frab/db.sqlite3";
+          pool = 5;
+          timeout = 5000;
+        };
+        example = {
+          adapter = "postgresql";
+          database = "frab";
+          host = "localhost";
+          username = "frabuser";
+          password = "supersecret";
+          encoding = "utf8";
+          pool = 5;
+        };
+        description = ''
+          Rails database configuration for Frab as Nix attribute set.
+        '';
+      };
+
+      extraEnvironment = mkOption {
+        type = types.attrs;
+        default = {};
+        example = {
+          FRAB_CURRENCY_UNIT = "€";
+          FRAB_CURRENCY_FORMAT = "%n%u";
+          EXCEPTION_EMAIL = "frab-owner@example.com";
+          SMTP_ADDRESS = "localhost";
+          SMTP_PORT = "587";
+          SMTP_DOMAIN = "localdomain";
+          SMTP_USER_NAME = "root";
+          SMTP_PASSWORD = "toor";
+          SMTP_AUTHENTICATION = "1";
+          SMTP_NOTLS = "1";
+        };
+        description = ''
+          Additional environment variables to set for frab for further
+          configuration. See the frab documentation for more information.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ frab-rake ];
+
+    users.users.${cfg.user} =
+      { group = cfg.group;
+        home = "${cfg.statePath}";
+        isSystemUser = true;
+      };
+
+    users.groups.${cfg.group} = { };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.statePath}/system/attachments' - ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.frab = {
+      after = [ "network.target" "gitlab.service" ];
+      wantedBy = [ "multi-user.target" ];
+      environment = frabEnv;
+
+      preStart = ''
+        ln -sf ${pkgs.writeText "frab-database.yml" databaseConfig} /run/frab/database.yml
+        ln -sf ${cfg.statePath}/system /run/frab/system
+
+        if ! test -e "${cfg.statePath}/db-setup-done"; then
+          ${frab-rake}/bin/frab-rake db:setup
+          touch ${cfg.statePath}/db-setup-done
+        else
+          ${frab-rake}/bin/frab-rake db:migrate
+        fi
+      '';
+
+      serviceConfig = {
+        PrivateTmp = true;
+        PrivateDevices = true;
+        Type = "simple";
+        User = cfg.user;
+        Group = cfg.group;
+        TimeoutSec = "300s";
+        Restart = "on-failure";
+        RestartSec = "10s";
+        RuntimeDirectory = "frab";
+        WorkingDirectory = "${package}/share/frab";
+        ExecStart = "${frab-rake}/bin/frab-bundle exec rails server " +
+          "--binding=${cfg.listenAddress} --port=${toString cfg.listenPort}";
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/gerrit.nix b/nixpkgs/nixos/modules/services/web-apps/gerrit.nix
new file mode 100644
index 000000000000..b184c0754d45
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/gerrit.nix
@@ -0,0 +1,218 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.gerrit;
+
+  # NixOS option type for git-like configs
+  gitIniType = with types;
+    let
+      primitiveType = either str (either bool int);
+      multipleType = either primitiveType (listOf primitiveType);
+      sectionType = lazyAttrsOf multipleType;
+      supersectionType = lazyAttrsOf (either multipleType sectionType);
+    in lazyAttrsOf supersectionType;
+
+  gerritConfig = pkgs.writeText "gerrit.conf" (
+    lib.generators.toGitINI cfg.settings
+  );
+
+  # Wrap the gerrit java with all the java options so it can be called
+  # like a normal CLI app
+  gerrit-cli = pkgs.writeShellScriptBin "gerrit" ''
+    set -euo pipefail
+    jvmOpts=(
+      ${lib.escapeShellArgs cfg.jvmOpts}
+      -Xmx${cfg.jvmHeapLimit}
+    )
+    exec ${cfg.jvmPackage}/bin/java \
+      "''${jvmOpts[@]}" \
+      -jar ${cfg.package}/webapps/${cfg.package.name}.war \
+      "$@"
+  '';
+
+  gerrit-plugins = pkgs.runCommand
+    "gerrit-plugins"
+    {
+      buildInputs = [ gerrit-cli ];
+    }
+    ''
+      shopt -s nullglob
+      mkdir $out
+
+      for name in ${toString cfg.builtinPlugins}; do
+        echo "Installing builtin plugin $name.jar"
+        gerrit cat plugins/$name.jar > $out/$name.jar
+      done
+
+      for file in ${toString cfg.plugins}; do
+        name=$(echo "$file" | cut -d - -f 2-)
+        echo "Installing plugin $name"
+        ln -sf "$file" $out/$name
+      done
+    '';
+in
+{
+  options = {
+    services.gerrit = {
+      enable = mkEnableOption "Gerrit service";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.gerrit;
+        description = "Gerrit package to use";
+      };
+
+      jvmPackage = mkOption {
+        type = types.package;
+        default = pkgs.jre_headless;
+        defaultText = "pkgs.jre_headless";
+        description = "Java Runtime Environment package to use";
+      };
+
+      jvmOpts = mkOption {
+        type = types.listOf types.str;
+        default = [
+          "-Dflogger.backend_factory=com.google.common.flogger.backend.log4j.Log4jBackendFactory#getInstance"
+          "-Dflogger.logging_context=com.google.gerrit.server.logging.LoggingContext#getInstance"
+        ];
+        description = "A list of JVM options to start gerrit with.";
+      };
+
+      jvmHeapLimit = mkOption {
+        type = types.str;
+        default = "1024m";
+        description = ''
+          How much memory to allocate to the JVM heap
+        '';
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "[::]:8080";
+        description = ''
+          <literal>hostname:port</literal> to listen for HTTP traffic.
+
+          This is bound using the systemd socket activation.
+        '';
+      };
+
+      settings = mkOption {
+        type = gitIniType;
+        default = {};
+        description = ''
+          Gerrit configuration. This will be generated to the
+          <literal>etc/gerrit.config</literal> file.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = ''
+          List of plugins to add to Gerrit. Each derivation is a jar file
+          itself where the name of the derivation is the name of plugin.
+        '';
+      };
+
+      builtinPlugins = mkOption {
+        type = types.listOf (types.enum cfg.package.passthru.plugins);
+        default = [];
+        description = ''
+          List of builtins plugins to install. Those are shipped in the
+          <literal>gerrit.war</literal> file.
+        '';
+      };
+
+      serverId = mkOption {
+        type = types.str;
+        description = ''
+          Set a UUID that uniquely identifies the server.
+
+          This can be generated with
+          <literal>nix-shell -p utillinux --run uuidgen</literal>.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.gerrit.settings = {
+      cache.directory = "/var/cache/gerrit";
+      container.heapLimit = cfg.jvmHeapLimit;
+      gerrit.basePath = lib.mkDefault "git";
+      gerrit.serverId = cfg.serverId;
+      httpd.inheritChannel = "true";
+      httpd.listenUrl = lib.mkDefault "http://${cfg.listenAddress}";
+      index.type = lib.mkDefault "lucene";
+    };
+
+    # Add the gerrit CLI to the system to run `gerrit init` and friends.
+    environment.systemPackages = [ gerrit-cli ];
+
+    systemd.sockets.gerrit = {
+      unitConfig.Description = "Gerrit HTTP socket";
+      wantedBy = [ "sockets.target" ];
+      listenStreams = [ cfg.listenAddress ];
+    };
+
+    systemd.services.gerrit = {
+      description = "Gerrit";
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "gerrit.socket" ];
+      after = [ "gerrit.socket" "network.target" ];
+
+      path = [
+        gerrit-cli
+        pkgs.bash
+        pkgs.coreutils
+        pkgs.git
+        pkgs.openssh
+      ];
+
+      environment = {
+        GERRIT_HOME = "%S/gerrit";
+        GERRIT_TMP = "%T";
+        HOME = "%S/gerrit";
+        XDG_CONFIG_HOME = "%S/gerrit/.config";
+      };
+
+      preStart = ''
+        set -euo pipefail
+
+        # bootstrap if nothing exists
+        if [[ ! -d git ]]; then
+          gerrit init --batch --no-auto-start
+        fi
+
+        # install gerrit.war for the plugin manager
+        rm -rf bin
+        mkdir bin
+        ln -sfv ${cfg.package}/webapps/${cfg.package.name}.war bin/gerrit.war
+
+        # copy the config, keep it mutable because Gerrit
+        ln -sfv ${gerritConfig} etc/gerrit.config
+
+        # install the plugins
+        rm -rf plugins
+        ln -sv ${gerrit-plugins} plugins
+      ''
+      ;
+
+      serviceConfig = {
+        CacheDirectory = "gerrit";
+        DynamicUser = true;
+        ExecStart = "${gerrit-cli}/bin/gerrit daemon --console-log";
+        LimitNOFILE = 4096;
+        StandardInput = "socket";
+        StandardOutput = "journal";
+        StateDirectory = "gerrit";
+        WorkingDirectory = "%S/gerrit";
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ edef zimbatm ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/gotify-server.nix b/nixpkgs/nixos/modules/services/web-apps/gotify-server.nix
new file mode 100644
index 000000000000..03e01f46a944
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/gotify-server.nix
@@ -0,0 +1,49 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gotify;
+in {
+  options = {
+    services.gotify = {
+      enable = mkEnableOption "Gotify webserver";
+
+      port = mkOption {
+        type = types.port;
+        description = ''
+          Port the server listens to.
+        '';
+      };
+
+      stateDirectoryName = mkOption {
+        type = types.str;
+        default = "gotify-server";
+        description = ''
+          The name of the directory below <filename>/var/lib</filename> where
+          gotify stores its runtime data.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.gotify-server = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      description = "Simple server for sending and receiving messages";
+
+      environment = {
+        GOTIFY_SERVER_PORT = toString cfg.port;
+      };
+
+      serviceConfig = {
+        WorkingDirectory = "/var/lib/${cfg.stateDirectoryName}";
+        StateDirectory = cfg.stateDirectoryName;
+        Restart = "always";
+        DynamicUser = "yes";
+        ExecStart = "${pkgs.gotify-server}/bin/server";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/grocy.nix b/nixpkgs/nixos/modules/services/web-apps/grocy.nix
new file mode 100644
index 000000000000..568bdfd0c429
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/grocy.nix
@@ -0,0 +1,172 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.grocy;
+in {
+  options.services.grocy = {
+    enable = mkEnableOption "grocy";
+
+    hostName = mkOption {
+      type = types.str;
+      description = ''
+        FQDN for the grocy instance.
+      '';
+    };
+
+    nginx.enableSSL = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether or not to enable SSL (with ACME and let's encrypt)
+        for the grocy vhost.
+      '';
+    };
+
+    phpfpm.settings = mkOption {
+      type = with types; attrsOf (oneOf [ int str bool ]);
+      default = {
+        "pm" = "dynamic";
+        "php_admin_value[error_log]" = "stderr";
+        "php_admin_flag[log_errors]" = true;
+        "listen.owner" = "nginx";
+        "catch_workers_output" = true;
+        "pm.max_children" = "32";
+        "pm.start_servers" = "2";
+        "pm.min_spare_servers" = "2";
+        "pm.max_spare_servers" = "4";
+        "pm.max_requests" = "500";
+      };
+
+      description = ''
+        Options for grocy's PHPFPM pool.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/grocy";
+      description = ''
+        Home directory of the <literal>grocy</literal> user which contains
+        the application's state.
+      '';
+    };
+
+    settings = {
+      currency = mkOption {
+        type = types.str;
+        default = "USD";
+        example = "EUR";
+        description = ''
+          ISO 4217 code for the currency to display.
+        '';
+      };
+
+      culture = mkOption {
+        type = types.enum [ "de" "en" "da" "en_GB" "es" "fr" "hu" "it" "nl" "no" "pl" "pt_BR" "ru" "sk_SK" "sv_SE" "tr" ];
+        default = "en";
+        description = ''
+          Display language of the frontend.
+        '';
+      };
+
+      calendar = {
+        showWeekNumber = mkOption {
+          default = true;
+          type = types.bool;
+          description = ''
+            Show the number of the weeks in the calendar views.
+          '';
+        };
+        firstDayOfWeek = mkOption {
+          default = null;
+          type = types.nullOr (types.enum (range 0 6));
+          description = ''
+            Which day of the week (0=Sunday, 1=Monday etc.) should be the
+            first day.
+          '';
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."grocy/config.php".text = ''
+      <?php
+      Setting('CULTURE', '${cfg.settings.culture}');
+      Setting('CURRENCY', '${cfg.settings.currency}');
+      Setting('CALENDAR_FIRST_DAY_OF_WEEK', '${toString cfg.settings.calendar.firstDayOfWeek}');
+      Setting('CALENDAR_SHOW_WEEK_OF_YEAR', ${boolToString cfg.settings.calendar.showWeekNumber});
+    '';
+
+    users.users.grocy = {
+      isSystemUser = true;
+      createHome = true;
+      home = cfg.dataDir;
+      group = "nginx";
+    };
+
+    systemd.tmpfiles.rules = map (
+      dirName: "d '${cfg.dataDir}/${dirName}' - grocy nginx - -"
+    ) [ "viewcache" "plugins" "settingoverrides" "storage" ];
+
+    services.phpfpm.pools.grocy = {
+      user = "grocy";
+      group = "nginx";
+
+      # PHP 7.3 is the only version which is supported/tested by upstream:
+      # https://github.com/grocy/grocy/blob/v2.6.0/README.md#how-to-install
+      phpPackage = pkgs.php73;
+
+      inherit (cfg.phpfpm) settings;
+
+      phpEnv = {
+        GROCY_CONFIG_FILE = "/etc/grocy/config.php";
+        GROCY_DB_FILE = "${cfg.dataDir}/grocy.db";
+        GROCY_STORAGE_DIR = "${cfg.dataDir}/storage";
+        GROCY_PLUGIN_DIR = "${cfg.dataDir}/plugins";
+        GROCY_CACHE_DIR = "${cfg.dataDir}/viewcache";
+      };
+    };
+
+    services.nginx = {
+      enable = true;
+      virtualHosts."${cfg.hostName}" = mkMerge [
+        { root = "${pkgs.grocy}/public";
+          locations."/".extraConfig = ''
+            rewrite ^ /index.php;
+          '';
+          locations."~ \\.php$".extraConfig = ''
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+            fastcgi_pass unix:${config.services.phpfpm.pools.grocy.socket};
+            include ${config.services.nginx.package}/conf/fastcgi.conf;
+            include ${config.services.nginx.package}/conf/fastcgi_params;
+          '';
+          locations."~ \\.(js|css|ttf|woff2?|png|jpe?g|svg)$".extraConfig = ''
+            add_header Cache-Control "public, max-age=15778463";
+            add_header X-Content-Type-Options nosniff;
+            add_header X-XSS-Protection "1; mode=block";
+            add_header X-Robots-Tag none;
+            add_header X-Download-Options noopen;
+            add_header X-Permitted-Cross-Domain-Policies none;
+            add_header Referrer-Policy no-referrer;
+            access_log off;
+          '';
+          extraConfig = ''
+            try_files $uri /index.php;
+          '';
+        }
+        (mkIf cfg.nginx.enableSSL {
+          enableACME = true;
+          forceSSL = true;
+        })
+      ];
+    };
+  };
+
+  meta = {
+    maintainers = with maintainers; [ ma27 ];
+    doc = ./grocy.xml;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/grocy.xml b/nixpkgs/nixos/modules/services/web-apps/grocy.xml
new file mode 100644
index 000000000000..fdf6d00f4b12
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/grocy.xml
@@ -0,0 +1,77 @@
+<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-grocy">
+
+  <title>Grocy</title>
+  <para>
+    <link xlink:href="https://grocy.info/">Grocy</link> is a web-based self-hosted groceries
+    &amp; household management solution for your home.
+  </para>
+
+  <section xml:id="module-services-grocy-basic-usage">
+   <title>Basic usage</title>
+   <para>
+    A very basic configuration may look like this:
+<programlisting>{ pkgs, ... }:
+{
+  services.grocy = {
+    <link linkend="opt-services.grocy.enable">enable</link> = true;
+    <link linkend="opt-services.grocy.hostName">hostName</link> = "grocy.tld";
+  };
+}</programlisting>
+    This configures a simple vhost using <link linkend="opt-services.nginx.enable">nginx</link>
+    which listens to <literal>grocy.tld</literal> with fully configured ACME/LE (this can be
+    disabled by setting <link linkend="opt-services.grocy.nginx.enableSSL">services.grocy.nginx.enableSSL</link>
+    to <literal>false</literal>). After the initial setup the credentials <literal>admin:admin</literal>
+    can be used to login.
+   </para>
+   <para>
+    The application's state is persisted at <literal>/var/lib/grocy/grocy.db</literal> in a
+    <package>sqlite3</package> database. The migration is applied when requesting the <literal>/</literal>-route
+    of the application.
+   </para>
+  </section>
+
+  <section xml:id="module-services-grocy-settings">
+   <title>Settings</title>
+   <para>
+    The configuration for <literal>grocy</literal> is located at <literal>/etc/grocy/config.php</literal>.
+    By default, the following settings can be defined in the NixOS-configuration:
+<programlisting>{ pkgs, ... }:
+{
+  services.grocy.settings = {
+    # The default currency in the system for invoices etc.
+    # Please note that exchange rates aren't taken into account, this
+    # is just the setting for what's shown in the frontend.
+    <link linkend="opt-services.grocy.settings.currency">currency</link> = "EUR";
+
+    # The display language (and locale configuration) for grocy.
+    <link linkend="opt-services.grocy.settings.currency">culture</link> = "de";
+
+    calendar = {
+      # Whether or not to show the week-numbers
+      # in the calendar.
+      <link linkend="opt-services.grocy.settings.calendar.showWeekNumber">showWeekNumber</link> = true;
+
+      # Index of the first day to be shown in the calendar (0=Sunday, 1=Monday,
+      # 2=Tuesday and so on).
+      <link linkend="opt-services.grocy.settings.calendar.firstDayOfWeek">firstDayOfWeek</link> = 2;
+    };
+  };
+}</programlisting>
+   </para>
+   <para>
+    If you want to alter the configuration file on your own, you can do this manually with
+    an expression like this:
+<programlisting>{ lib, ... }:
+{
+  environment.etc."grocy/config.php".text = lib.mkAfter ''
+    // Arbitrary PHP code in grocy's configuration file
+  '';
+}</programlisting>
+   </para>
+  </section>
+
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
new file mode 100644
index 000000000000..d9ad7e9e3d39
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
@@ -0,0 +1,244 @@
+{ config, lib, pkgs, ... }: with lib; let
+  cfg = config.services.icingaweb2;
+  fpm = config.services.phpfpm.pools.${poolName};
+  poolName = "icingaweb2";
+
+  defaultConfig = {
+    global = {
+      module_path = "${pkgs.icingaweb2}/modules";
+    };
+  };
+in {
+  meta.maintainers = with maintainers; [ das_j ];
+
+  options.services.icingaweb2 = with types; {
+    enable = mkEnableOption "the icingaweb2 web interface";
+
+    pool = mkOption {
+      type = str;
+      default = poolName;
+      description = ''
+         Name of existing PHP-FPM pool that is used to run Icingaweb2.
+         If not specified, a pool will automatically created with default values.
+      '';
+    };
+
+    virtualHost = mkOption {
+      type = nullOr str;
+      default = "icingaweb2";
+      description = ''
+        Name of the nginx virtualhost to use and setup. If null, no virtualhost is set up.
+      '';
+    };
+
+    timezone = mkOption {
+      type = str;
+      default = "UTC";
+      example = "Europe/Berlin";
+      description = "PHP-compliant timezone specification";
+    };
+
+    modules = {
+      doc.enable = mkEnableOption "the icingaweb2 doc module";
+      migrate.enable = mkEnableOption "the icingaweb2 migrate module";
+      setup.enable = mkEnableOption "the icingaweb2 setup module";
+      test.enable = mkEnableOption "the icingaweb2 test module";
+      translation.enable = mkEnableOption "the icingaweb2 translation module";
+    };
+
+    modulePackages = mkOption {
+      type = attrsOf package;
+      default = {};
+      example = literalExample ''
+        {
+          "snow" = icingaweb2Modules.theme-snow;
+        }
+      '';
+      description = ''
+        Name-package attrset of Icingaweb 2 modules packages to enable.
+
+        If you enable modules manually (e.g. via the web ui), they will not be touched.
+      '';
+    };
+
+    generalConfig = mkOption {
+      type = nullOr attrs;
+      default = null;
+      example = {
+        general = {
+          showStacktraces = 1;
+          config_resource = "icingaweb_db";
+        };
+        logging = {
+          log = "syslog";
+          level = "CRITICAL";
+        };
+      };
+      description = ''
+        config.ini contents.
+        Will automatically be converted to a .ini file.
+        If you don't set global.module_path, the module will take care of it.
+
+        If the value is null, no config.ini is created and you can
+        modify it manually (e.g. via the web interface).
+        Note that you need to update module_path manually.
+      '';
+    };
+
+    resources = mkOption {
+      type = nullOr attrs;
+      default = null;
+      example = {
+        icingaweb_db = {
+          type = "db";
+          db = "mysql";
+          host = "localhost";
+          username = "icingaweb2";
+          password = "icingaweb2";
+          dbname = "icingaweb2";
+        };
+      };
+      description = ''
+        resources.ini contents.
+        Will automatically be converted to a .ini file.
+
+        If the value is null, no resources.ini is created and you can
+        modify it manually (e.g. via the web interface).
+        Note that if you set passwords here, they will go into the nix store.
+      '';
+    };
+
+    authentications = mkOption {
+      type = nullOr attrs;
+      default = null;
+      example = {
+        icingaweb = {
+          backend = "db";
+          resource = "icingaweb_db";
+        };
+      };
+      description = ''
+        authentication.ini contents.
+        Will automatically be converted to a .ini file.
+
+        If the value is null, no authentication.ini is created and you can
+        modify it manually (e.g. via the web interface).
+      '';
+    };
+
+    groupBackends = mkOption {
+      type = nullOr attrs;
+      default = null;
+      example = {
+        icingaweb = {
+          backend = "db";
+          resource = "icingaweb_db";
+        };
+      };
+      description = ''
+        groups.ini contents.
+        Will automatically be converted to a .ini file.
+
+        If the value is null, no groups.ini is created and you can
+        modify it manually (e.g. via the web interface).
+      '';
+    };
+
+    roles = mkOption {
+      type = nullOr attrs;
+      default = null;
+      example = {
+        Administrators = {
+          users = "admin";
+          permissions = "*";
+        };
+      };
+      description = ''
+        roles.ini contents.
+        Will automatically be converted to a .ini file.
+
+        If the value is null, no roles.ini is created and you can
+        modify it manually (e.g. via the web interface).
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
+      ${poolName} = {
+        user = "icingaweb2";
+        phpOptions = ''
+          extension = ${pkgs.phpPackages.imagick}/lib/php/extensions/imagick.so
+          date.timezone = "${cfg.timezone}"
+        '';
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = "nginx";
+          "listen.group" = "nginx";
+          "listen.mode" = "0600";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 2;
+          "pm.min_spare_servers" = 2;
+          "pm.max_spare_servers" = 10;
+        };
+      };
+    };
+
+    systemd.services."phpfpm-${poolName}".serviceConfig.ReadWritePaths = [ "/etc/icingaweb2" ];
+
+    services.nginx = {
+      enable = true;
+      virtualHosts = mkIf (cfg.virtualHost != null) {
+        ${cfg.virtualHost} = {
+          root = "${pkgs.icingaweb2}/public";
+
+          extraConfig = ''
+            index index.php;
+            try_files $1 $uri $uri/ /index.php$is_args$args;
+          '';
+
+          locations."~ ..*/.*.php$".extraConfig = ''
+            return 403;
+          '';
+
+          locations."~ ^/index.php(.*)$".extraConfig = ''
+            fastcgi_intercept_errors on;
+            fastcgi_index index.php;
+            include ${config.services.nginx.package}/conf/fastcgi.conf;
+            try_files $uri =404;
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+            fastcgi_pass unix:${fpm.socket};
+            fastcgi_param SCRIPT_FILENAME ${pkgs.icingaweb2}/public/index.php;
+          '';
+        };
+      };
+    };
+
+    # /etc/icingaweb2
+    environment.etc = let
+      doModule = name: optionalAttrs (cfg.modules.${name}.enable) { "icingaweb2/enabledModules/${name}".source = "${pkgs.icingaweb2}/modules/${name}"; };
+    in {}
+      # Module packages
+      // (mapAttrs' (k: v: nameValuePair "icingaweb2/enabledModules/${k}" { source = v; }) cfg.modulePackages)
+      # Built-in modules
+      // doModule "doc"
+      // doModule "migrate"
+      // doModule "setup"
+      // doModule "test"
+      // doModule "translation"
+      # Configs
+      // optionalAttrs (cfg.generalConfig != null) { "icingaweb2/config.ini".text = generators.toINI {} (defaultConfig // cfg.generalConfig); }
+      // optionalAttrs (cfg.resources != null) { "icingaweb2/resources.ini".text = generators.toINI {} cfg.resources; }
+      // optionalAttrs (cfg.authentications != null) { "icingaweb2/authentication.ini".text = generators.toINI {} cfg.authentications; }
+      // optionalAttrs (cfg.groupBackends != null) { "icingaweb2/groups.ini".text = generators.toINI {} cfg.groupBackends; }
+      // optionalAttrs (cfg.roles != null) { "icingaweb2/roles.ini".text = generators.toINI {} cfg.roles; };
+
+    # User and group
+    users.groups.icingaweb2 = {};
+    users.users.icingaweb2 = {
+      description = "Icingaweb2 service user";
+      group = "icingaweb2";
+      isSystemUser = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
new file mode 100644
index 000000000000..e9c1d4ffe5ea
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, ... }: with lib; let
+  cfg = config.services.icingaweb2.modules.monitoring;
+
+  configIni = ''
+    [security]
+    protected_customvars = "${concatStringsSep "," cfg.generalConfig.protectedVars}"
+  '';
+
+  backendsIni = let
+    formatBool = b: if b then "1" else "0";
+  in concatStringsSep "\n" (mapAttrsToList (name: config: ''
+    [${name}]
+    type = "ido"
+    resource = "${config.resource}"
+    disabled = "${formatBool config.disabled}"
+  '') cfg.backends);
+
+  transportsIni = concatStringsSep "\n" (mapAttrsToList (name: config: ''
+    [${name}]
+    type = "${config.type}"
+    ${optionalString (config.instance != null) ''instance = "${config.instance}"''}
+    ${optionalString (config.type == "local" || config.type == "remote") ''path = "${config.path}"''}
+    ${optionalString (config.type != "local") ''
+      host = "${config.host}"
+      ${optionalString (config.port != null) ''port = "${toString config.port}"''}
+      user${optionalString (config.type == "api") "name"} = "${config.username}"
+    ''}
+    ${optionalString (config.type == "api") ''password = "${config.password}"''}
+    ${optionalString (config.type == "remote") ''resource = "${config.resource}"''}
+  '') cfg.transports);
+
+in {
+  options.services.icingaweb2.modules.monitoring = with types; {
+    enable = mkOption {
+      type = bool;
+      default = true;
+      description = "Whether to enable the icingaweb2 monitoring module.";
+    };
+
+    generalConfig = {
+      mutable = mkOption {
+        type = bool;
+        default = false;
+        description = "Make config.ini of the monitoring module mutable (e.g. via the web interface).";
+      };
+
+      protectedVars = mkOption {
+        type = listOf str;
+        default = [ "*pw*" "*pass*" "community" ];
+        description = "List of string patterns for custom variables which should be excluded from user’s view.";
+      };
+    };
+
+    mutableBackends = mkOption {
+      type = bool;
+      default = false;
+      description = "Make backends.ini of the monitoring module mutable (e.g. via the web interface).";
+    };
+
+    backends = mkOption {
+      default = { icinga = { resource = "icinga_ido"; }; };
+      description = "Monitoring backends to define";
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          name = mkOption {
+            visible = false;
+            default = name;
+            type = str;
+            description = "Name of this backend";
+          };
+
+          resource = mkOption {
+            type = str;
+            description = "Name of the IDO resource";
+          };
+
+          disabled = mkOption {
+            type = bool;
+            default = false;
+            description = "Disable this backend";
+          };
+        };
+      }));
+    };
+
+    mutableTransports = mkOption {
+      type = bool;
+      default = true;
+      description = "Make commandtransports.ini of the monitoring module mutable (e.g. via the web interface).";
+    };
+
+    transports = mkOption {
+      default = {};
+      description = "Command transports to define";
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          name = mkOption {
+            visible = false;
+            default = name;
+            type = str;
+            description = "Name of this transport";
+          };
+
+          type = mkOption {
+            type = enum [ "api" "local" "remote" ];
+            default = "api";
+            description = "Type of  this transport";
+          };
+
+          instance = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Assign a icinga instance to this transport";
+          };
+
+          path = mkOption {
+            type = str;
+            description = "Path to the socket for local or remote transports";
+          };
+
+          host = mkOption {
+            type = str;
+            description = "Host for the api or remote transport";
+          };
+
+          port = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Port to connect to for the api or remote transport";
+          };
+
+          username = mkOption {
+            type = str;
+            description = "Username for the api or remote transport";
+          };
+
+          password = mkOption {
+            type = str;
+            description = "Password for the api transport";
+          };
+
+          resource = mkOption {
+            type = str;
+            description = "SSH identity resource for the remote transport";
+          };
+        };
+      }));
+    };
+  };
+
+  config = mkIf (config.services.icingaweb2.enable && cfg.enable) {
+    environment.etc = { "icingaweb2/enabledModules/monitoring" = { source = "${pkgs.icingaweb2}/modules/monitoring"; }; }
+      // optionalAttrs (!cfg.generalConfig.mutable) { "icingaweb2/modules/monitoring/config.ini".text = configIni; }
+      // optionalAttrs (!cfg.mutableBackends) { "icingaweb2/modules/monitoring/backends.ini".text = backendsIni; }
+      // optionalAttrs (!cfg.mutableTransports) { "icingaweb2/modules/monitoring/commandtransports.ini".text = transportsIni; };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix b/nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix
new file mode 100644
index 000000000000..68769ac8c031
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/ihatemoney/default.nix
@@ -0,0 +1,141 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+  cfg = config.services.ihatemoney;
+  user = "ihatemoney";
+  group = "ihatemoney";
+  db = "ihatemoney";
+  python3 = config.services.uwsgi.package.python3;
+  pkg = python3.pkgs.ihatemoney;
+  toBool = x: if x then "True" else "False";
+  configFile = pkgs.writeText "ihatemoney.cfg" ''
+        from secrets import token_hex
+        # load a persistent secret key
+        SECRET_KEY_FILE = "/var/lib/ihatemoney/secret_key"
+        SECRET_KEY = ""
+        try:
+          with open(SECRET_KEY_FILE) as f:
+            SECRET_KEY = f.read()
+        except FileNotFoundError:
+          pass
+        if not SECRET_KEY:
+          print("ihatemoney: generating a new secret key")
+          SECRET_KEY = token_hex(50)
+          with open(SECRET_KEY_FILE, "w") as f:
+            f.write(SECRET_KEY)
+        del token_hex
+        del SECRET_KEY_FILE
+
+        # "normal" configuration
+        DEBUG = False
+        SQLALCHEMY_DATABASE_URI = '${
+          if cfg.backend == "sqlite"
+          then "sqlite:////var/lib/ihatemoney/ihatemoney.sqlite"
+          else "postgresql:///${db}"}'
+        SQLALCHEMY_TRACK_MODIFICATIONS = False
+        MAIL_DEFAULT_SENDER = ("${cfg.defaultSender.name}", "${cfg.defaultSender.email}")
+        ACTIVATE_DEMO_PROJECT = ${toBool cfg.enableDemoProject}
+        ADMIN_PASSWORD = "${toString cfg.adminHashedPassword /*toString null == ""*/}"
+        ALLOW_PUBLIC_PROJECT_CREATION = ${toBool cfg.enablePublicProjectCreation}
+        ACTIVATE_ADMIN_DASHBOARD = ${toBool cfg.enableAdminDashboard}
+
+        ${cfg.extraConfig}
+  '';
+in
+  {
+    options.services.ihatemoney = {
+      enable = mkEnableOption "ihatemoney webapp. Note that this will set uwsgi to emperor mode running as root";
+      backend = mkOption {
+        type = types.enum [ "sqlite" "postgresql" ];
+        default = "sqlite";
+        description = ''
+          The database engine to use for ihatemoney.
+          If <literal>postgresql</literal> is selected, then a database called
+          <literal>${db}</literal> will be created. If you disable this option,
+          it will however not be removed.
+        '';
+      };
+      adminHashedPassword = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "The hashed password of the administrator. To obtain it, run <literal>ihatemoney generate_password_hash</literal>";
+      };
+      uwsgiConfig = mkOption {
+        type = types.attrs;
+        example = {
+          http = ":8000";
+        };
+        description = "Additionnal configuration of the UWSGI vassal running ihatemoney. It should notably specify on which interfaces and ports the vassal should listen.";
+      };
+      defaultSender = {
+        name = mkOption {
+          type = types.str;
+          default = "Budget manager";
+          description = "The display name of the sender of ihatemoney emails";
+        };
+        email = mkOption {
+          type = types.str;
+          default = "ihatemoney@${config.networking.hostName}";
+          description = "The email of the sender of ihatemoney emails";
+        };
+      };
+      enableDemoProject = mkEnableOption "access to the demo project in ihatemoney";
+      enablePublicProjectCreation = mkEnableOption "permission to create projects in ihatemoney by anyone";
+      enableAdminDashboard = mkEnableOption "ihatemoney admin dashboard";
+      extraConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = "Extra configuration appended to ihatemoney's configuration file. It is a python file, so pay attention to indentation.";
+      };
+    };
+    config = mkIf cfg.enable {
+      services.postgresql = mkIf (cfg.backend == "postgresql") {
+        enable = true;
+        ensureDatabases = [ db ];
+        ensureUsers = [ {
+          name = user;
+          ensurePermissions = {
+            "DATABASE ${db}" = "ALL PRIVILEGES";
+          };
+        } ];
+      };
+      systemd.services.postgresql = mkIf (cfg.backend == "postgresql") {
+        wantedBy = [ "uwsgi.service" ];
+        before = [ "uwsgi.service" ];
+      };
+      systemd.tmpfiles.rules = [
+        "d /var/lib/ihatemoney 770 ${user} ${group}"
+      ];
+      users = {
+        users.${user} = {
+          isSystemUser = true;
+          inherit group;
+        };
+        groups.${group} = {};
+      };
+      services.uwsgi = {
+        enable = true;
+        plugins = [ "python3" ];
+        # the vassal needs to be able to setuid
+        user = "root";
+        group = "root";
+        instance = {
+          type = "emperor";
+          vassals.ihatemoney = {
+            type = "normal";
+            strict = true;
+            uid = user;
+            gid = group;
+            # apparently flask uses threads: https://github.com/spiral-project/ihatemoney/commit/c7815e48781b6d3a457eaff1808d179402558f8c
+            enable-threads = true;
+            module = "wsgi:application";
+            chdir = "${pkg}/${pkg.pythonModule.sitePackages}/ihatemoney";
+            env = [ "IHATEMONEY_SETTINGS_FILE_PATH=${configFile}" ];
+            pythonPackages = self: [ self.ihatemoney ];
+          } // cfg.uwsgiConfig;
+        };
+      };
+    };
+  }
+
+
diff --git a/nixpkgs/nixos/modules/services/web-apps/jirafeau.nix b/nixpkgs/nixos/modules/services/web-apps/jirafeau.nix
new file mode 100644
index 000000000000..4f181257ef7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/jirafeau.nix
@@ -0,0 +1,169 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.jirafeau;
+
+  group = config.services.nginx.group;
+  user = config.services.nginx.user;
+
+  withTrailingSlash = str: if hasSuffix "/" str then str else "${str}/";
+
+  localConfig = pkgs.writeText "config.local.php" ''
+    <?php
+      $cfg['admin_password'] = '${cfg.adminPasswordSha256}';
+      $cfg['web_root'] = 'http://${withTrailingSlash cfg.hostName}';
+      $cfg['var_root'] = '${withTrailingSlash cfg.dataDir}';
+      $cfg['maximal_upload_size'] = ${builtins.toString cfg.maxUploadSizeMegabytes};
+      $cfg['installation_done'] = true;
+
+      ${cfg.extraConfig}
+  '';
+in
+{
+  options.services.jirafeau = {
+    adminPasswordSha256 = mkOption {
+      type = types.str;
+      default = "";
+      description = ''
+        SHA-256 of the desired administration password. Leave blank/unset for no password.
+      '';
+    };
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/jirafeau/data/";
+      description = "Location of Jirafeau storage directory.";
+    };
+
+    enable = mkEnableOption "Jirafeau file upload application.";
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        $cfg['style'] = 'courgette';
+        $cfg['organisation'] = 'ACME';
+      '';
+      description = let
+        documentationLink =
+          "https://gitlab.com/mojo42/Jirafeau/-/blob/${cfg.package.version}/lib/config.original.php";
+      in
+        ''
+          Jirefeau configuration. Refer to <link xlink:href="${documentationLink}"/> for supported
+          values.
+        '';
+    };
+
+    hostName = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = "URL of instance. Must have trailing slash.";
+    };
+
+    maxUploadSizeMegabytes = mkOption {
+      type = types.int;
+      default = 0;
+      description = "Maximum upload size of accepted files.";
+    };
+
+    maxUploadTimeout = mkOption {
+      type = types.str;
+      default = "30m";
+      description = let
+        nginxCoreDocumentation = "http://nginx.org/en/docs/http/ngx_http_core_module.html";
+      in
+        ''
+          Timeout for reading client request bodies and headers. Refer to
+          <link xlink:href="${nginxCoreDocumentation}#client_body_timeout"/> and
+          <link xlink:href="${nginxCoreDocumentation}#client_header_timeout"/> for accepted values.
+        '';
+    };
+
+    nginxConfig = mkOption {
+      type = types.submodule
+        (import ../web-servers/nginx/vhost-options.nix { inherit config lib; });
+      default = {};
+      example = {
+        serverAliases = [ "wiki.\${config.networking.domain}" ];
+      };
+      description = "Extra configuration for the nginx virtual host of Jirafeau.";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.jirafeau;
+      defaultText = "pkgs.jirafeau";
+      description = "Jirafeau package to use";
+      example = "pkgs.jirafeau";
+    };
+
+    poolConfig = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = ''
+        Options for Jirafeau PHP pool. See documentation on <literal>php-fpm.conf</literal> for
+        details on configuration directives.
+      '';
+    };
+  };
+
+
+  config = mkIf cfg.enable {
+    services = {
+      nginx = {
+        enable = true;
+        virtualHosts."${cfg.hostName}" = mkMerge [
+          cfg.nginxConfig
+          {
+            extraConfig = let
+              clientMaxBodySize =
+                if cfg.maxUploadSizeMegabytes == 0 then "0" else "${cfg.maxUploadSizeMegabytes}m";
+            in
+              ''
+                index index.php;
+                client_max_body_size ${clientMaxBodySize};
+                client_body_timeout ${cfg.maxUploadTimeout};
+                client_header_timeout ${cfg.maxUploadTimeout};
+              '';
+            locations = {
+              "~ \\.php$".extraConfig = ''
+                include ${pkgs.nginx}/conf/fastcgi_params;
+                fastcgi_split_path_info ^(.+\.php)(/.+)$;
+                fastcgi_index index.php;
+                fastcgi_pass unix:${config.services.phpfpm.pools.jirafeau.socket};
+                fastcgi_param PATH_INFO $fastcgi_path_info;
+                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+              '';
+            };
+            root = mkForce "${cfg.package}";
+          }
+        ];
+      };
+
+      phpfpm.pools.jirafeau = {
+        inherit group user;
+        phpEnv."JIRAFEAU_CONFIG" = "${localConfig}";
+        settings = {
+          "listen.mode" = "0660";
+          "listen.owner" = user;
+          "listen.group" = group;
+        } // cfg.poolConfig;
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d ${cfg.dataDir} 0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/files/ 0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/links/ 0750 ${user} ${group} - -"
+      "d ${cfg.dataDir}/async/ 0750 ${user} ${group} - -"
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix b/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix
new file mode 100644
index 000000000000..56265e80957e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/limesurvey.nix
@@ -0,0 +1,280 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
+  inherit (lib) literalExample mapAttrs optional optionalString types;
+
+  cfg = config.services.limesurvey;
+  fpm = config.services.phpfpm.pools.limesurvey;
+
+  user = "limesurvey";
+  group = config.services.httpd.group;
+  stateDir = "/var/lib/limesurvey";
+
+  pkg = pkgs.limesurvey;
+
+  configType = with types; oneOf [ (attrsOf configType) str int bool ] // {
+    description = "limesurvey config type (str, int, bool or attribute set thereof)";
+  };
+
+  limesurveyConfig = pkgs.writeText "config.php" ''
+    <?php
+      return json_decode('${builtins.toJSON cfg.config}', true);
+    ?>
+  '';
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+in
+{
+  # interface
+
+  options.services.limesurvey = {
+    enable = mkEnableOption "Limesurvey web application.";
+
+    database = {
+      type = mkOption {
+        type = types.enum [ "mysql" "pgsql" "odbc" "mssql" ];
+        example = "pgsql";
+        default = "mysql";
+        description = "Database engine to use.";
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Database host address.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = if cfg.database.type == "pgsql" then 5442 else 3306;
+        defaultText = "3306";
+        description = "Database host port.";
+      };
+
+      name = mkOption {
+        type = types.str;
+        default = "limesurvey";
+        description = "Database name.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "limesurvey";
+        description = "Database user.";
+      };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/limesurvey-dbpassword";
+        description = ''
+          A file containing the password corresponding to
+          <option>database.user</option>.
+        '';
+      };
+
+      socket = mkOption {
+        type = types.nullOr types.path;
+        default =
+          if mysqlLocal then "/run/mysqld/mysqld.sock"
+          else if pgsqlLocal then "/run/postgresql"
+          else null
+        ;
+        defaultText = "/run/mysqld/mysqld.sock";
+        description = "Path to the unix socket file to use for authentication.";
+      };
+
+      createLocally = mkOption {
+        type = types.bool;
+        default = cfg.database.type == "mysql";
+        defaultText = "true";
+        description = ''
+          Create the database and database user locally.
+          This currently only applies if database type "mysql" is selected.
+        '';
+      };
+    };
+
+    virtualHost = mkOption {
+      type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
+      example = literalExample ''
+        {
+          hostName = "survey.example.org";
+          adminAddr = "webmaster@example.org";
+          forceSSL = true;
+          enableACME = true;
+        }
+      '';
+      description = ''
+        Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
+        See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+      '';
+    };
+
+    poolConfig = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = ''
+        Options for the LimeSurvey PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+        for details on configuration directives.
+      '';
+    };
+
+    config = mkOption {
+      type = configType;
+      default = {};
+      description = ''
+        LimeSurvey configuration. Refer to
+        <link xlink:href="https://manual.limesurvey.org/Optional_settings"/>
+        for details on supported values.
+      '';
+    };
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
+        message = "services.limesurvey.createLocally is currently only supported for database type 'mysql'";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.limesurvey.database.user must be set to ${user} if services.limesurvey.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+        message = "services.limesurvey.database.socket must be set if services.limesurvey.database.createLocally is set to true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.limesurvey.database.createLocally is set to true";
+      }
+    ];
+
+    services.limesurvey.config = mapAttrs (name: mkDefault) {
+      runtimePath = "${stateDir}/tmp/runtime";
+      components = {
+        db = {
+          connectionString = "${cfg.database.type}:dbname=${cfg.database.name};host=${if pgsqlLocal then cfg.database.socket else cfg.database.host};port=${toString cfg.database.port}" +
+            optionalString mysqlLocal ";socket=${cfg.database.socket}";
+          username = cfg.database.user;
+          password = mkIf (cfg.database.passwordFile != null) "file_get_contents(\"${toString cfg.database.passwordFile}\");";
+          tablePrefix = "limesurvey_";
+        };
+        assetManager.basePath = "${stateDir}/tmp/assets";
+        urlManager = {
+          urlFormat = "path";
+          showScriptName = false;
+        };
+      };
+      config = {
+        tempdir = "${stateDir}/tmp";
+        uploaddir = "${stateDir}/upload";
+        force_ssl = mkIf (cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL) "on";
+        config.defaultlang = "en";
+      };
+    };
+
+    services.mysql = mkIf mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = {
+            "${cfg.database.name}.*" = "SELECT, CREATE, INSERT, UPDATE, DELETE, ALTER, DROP, INDEX";
+          };
+        }
+      ];
+    };
+
+    services.phpfpm.pools.limesurvey = {
+      inherit user group;
+      phpEnv.LIMESURVEY_CONFIG = "${limesurveyConfig}";
+      settings = {
+        "listen.owner" = config.services.httpd.user;
+        "listen.group" = config.services.httpd.group;
+      } // cfg.poolConfig;
+    };
+
+    services.httpd = {
+      enable = true;
+      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
+        documentRoot = mkForce "${pkg}/share/limesurvey";
+        extraConfig = ''
+          Alias "/tmp" "${stateDir}/tmp"
+          <Directory "${stateDir}">
+            AllowOverride all
+            Require all granted
+            Options -Indexes +FollowSymlinks
+          </Directory>
+
+          Alias "/upload" "${stateDir}/upload"
+          <Directory "${stateDir}/upload">
+            AllowOverride all
+            Require all granted
+            Options -Indexes
+          </Directory>
+
+          <Directory "${pkg}/share/limesurvey">
+            <FilesMatch "\.php$">
+              <If "-f %{REQUEST_FILENAME}">
+                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+              </If>
+            </FilesMatch>
+
+            AllowOverride all
+            Options -Indexes
+            DirectoryIndex index.php
+          </Directory>
+        '';
+      } ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d ${stateDir} 0750 ${user} ${group} - -"
+      "d ${stateDir}/tmp 0750 ${user} ${group} - -"
+      "d ${stateDir}/tmp/assets 0750 ${user} ${group} - -"
+      "d ${stateDir}/tmp/runtime 0750 ${user} ${group} - -"
+      "d ${stateDir}/tmp/upload 0750 ${user} ${group} - -"
+      "C ${stateDir}/upload 0750 ${user} ${group} - ${pkg}/share/limesurvey/upload"
+    ];
+
+    systemd.services.limesurvey-init = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "phpfpm-limesurvey.service" ];
+      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+      environment.LIMESURVEY_CONFIG = limesurveyConfig;
+      script = ''
+        # update or install the database as required
+        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
+        ${pkgs.php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
+      '';
+      serviceConfig = {
+        User = user;
+        Group = group;
+        Type = "oneshot";
+      };
+    };
+
+    systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml b/nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml
new file mode 100644
index 000000000000..69d1170e4523
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/matomo-doc.xml
@@ -0,0 +1,107 @@
+<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-matomo">
+ <title>Matomo</title>
+ <para>
+  Matomo is a real-time web analytics application. This module configures
+  php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
+ </para>
+ <para>
+  An automatic setup is not suported by Matomo, so you need to configure Matomo
+  itself in the browser-based Matomo setup.
+ </para>
+ <section xml:id="module-services-matomo-database-setup">
+  <title>Database Setup</title>
+
+  <para>
+   You also need to configure a MariaDB or MySQL database and -user for Matomo
+   yourself, and enter those credentials in your browser. You can use
+   passwordless database authentication via the UNIX_SOCKET authentication
+   plugin with the following SQL commands:
+<programlisting>
+# For MariaDB
+INSTALL PLUGIN unix_socket SONAME 'auth_socket';
+CREATE DATABASE matomo;
+CREATE USER 'matomo'@'localhost' IDENTIFIED WITH unix_socket;
+GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
+
+# For MySQL
+INSTALL PLUGIN auth_socket SONAME 'auth_socket.so';
+CREATE DATABASE matomo;
+CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
+GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
+</programlisting>
+   Then fill in <literal>matomo</literal> as database user and database name,
+   and leave the password field blank. This authentication works by allowing
+   only the <literal>matomo</literal> unix user to authenticate as the
+   <literal>matomo</literal> database user (without needing a password), but no
+   other users. For more information on passwordless login, see
+   <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />.
+  </para>
+
+  <para>
+   Of course, you can use password based authentication as well, e.g. when the
+   database is not on the same host.
+  </para>
+ </section>
+ <section xml:id="module-services-matomo-archive-processing">
+  <title>Archive Processing</title>
+
+  <para>
+   This module comes with the systemd service
+   <literal>matomo-archive-processing.service</literal> and a timer that
+   automatically triggers archive processing every hour. This means that you
+   can safely
+   <link xlink:href="https://matomo.org/docs/setup-auto-archiving/#disable-browser-triggers-for-matomo-archiving-and-limit-matomo-reports-to-updating-every-hour">
+   disable browser triggers for Matomo archiving </link> at
+   <literal>Administration > System > General Settings</literal>.
+  </para>
+
+  <para>
+   With automatic archive processing, you can now also enable to
+   <link xlink:href="https://matomo.org/docs/privacy/#step-2-delete-old-visitors-logs">
+   delete old visitor logs </link> at <literal>Administration > System >
+   Privacy</literal>, but make sure that you run <literal>systemctl start
+   matomo-archive-processing.service</literal> at least once without errors if
+   you have already collected data before, so that the reports get archived
+   before the source data gets deleted.
+  </para>
+ </section>
+ <section xml:id="module-services-matomo-backups">
+  <title>Backup</title>
+
+  <para>
+   You only need to take backups of your MySQL database and the
+   <filename>/var/lib/matomo/config/config.ini.php</filename> file. Use a user
+   in the <literal>matomo</literal> group or root to access the file. For more
+   information, see
+   <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/" />.
+  </para>
+ </section>
+ <section xml:id="module-services-matomo-issues">
+  <title>Issues</title>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Matomo will warn you that the JavaScript tracker is not writable. This is
+     because it's located in the read-only nix store. You can safely ignore
+     this, unless you need a plugin that needs JavaScript tracker access.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+ <section xml:id="module-services-matomo-other-web-servers">
+  <title>Using other Web Servers than nginx</title>
+
+  <para>
+   You can use other web servers by forwarding calls for
+   <filename>index.php</filename> and <filename>piwik.php</filename> to the
+   <literal><link linkend="opt-services.phpfpm.pools._name_.socket">services.phpfpm.pools.&lt;name&gt;.socket</link></literal> fastcgi unix socket. You can use
+   the nginx configuration in the module code as a reference to what else
+   should be configured.
+  </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/matomo.nix b/nixpkgs/nixos/modules/services/web-apps/matomo.nix
new file mode 100644
index 000000000000..75da474dc446
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/matomo.nix
@@ -0,0 +1,306 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.matomo;
+  fpm = config.services.phpfpm.pools.${pool};
+
+  user = "matomo";
+  dataDir = "/var/lib/${user}";
+  deprecatedDataDir = "/var/lib/piwik";
+
+  pool = user;
+  phpExecutionUnit = "phpfpm-${pool}";
+  databaseService = "mysql.service";
+
+  fqdn =
+    let
+      join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
+     in join config.networking.hostName config.networking.domain;
+
+in {
+  imports = [
+    (mkRenamedOptionModule [ "services" "piwik" "enable" ] [ "services" "matomo" "enable" ])
+    (mkRenamedOptionModule [ "services" "piwik" "webServerUser" ] [ "services" "matomo" "webServerUser" ])
+    (mkRemovedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings")
+    (mkRemovedOptionModule [ "services" "matomo" "phpfpmProcessManagerConfig" ] "Use services.phpfpm.pools.<name>.settings")
+    (mkRenamedOptionModule [ "services" "piwik" "nginx" ] [ "services" "matomo" "nginx" ])
+  ];
+
+  options = {
+    services.matomo = {
+      # NixOS PR for database setup: https://github.com/NixOS/nixpkgs/pull/6963
+      # Matomo issue for automatic Matomo setup: https://github.com/matomo-org/matomo/issues/10257
+      # TODO: find a nice way to do this when more NixOS MySQL and / or Matomo automatic setup stuff is implemented.
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable Matomo web analytics with php-fpm backend.
+          Either the nginx option or the webServerUser option is mandatory.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        description = ''
+          Matomo package for the service to use.
+          This can be used to point to newer releases from nixos-unstable,
+          as they don't get backported if they are not security-relevant.
+        '';
+        default = pkgs.matomo;
+        defaultText = "pkgs.matomo";
+      };
+
+      webServerUser = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "lighttpd";
+        description = ''
+          Name of the web server user that forwards requests to <option>services.phpfpm.pools.&lt;name&gt;.socket</option> the fastcgi socket for Matomo if the nginx
+          option is not used. Either this option or the nginx option is mandatory.
+          If you want to use another webserver than nginx, you need to set this to that server's user
+          and pass fastcgi requests to `index.php`, `matomo.php` and `piwik.php` (legacy name) to this socket.
+        '';
+      };
+
+      periodicArchiveProcessing = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enable periodic archive processing, which generates aggregated reports from the visits.
+
+          This means that you can safely disable browser triggers for Matomo archiving,
+          and safely enable to delete old visitor logs.
+          Before deleting visitor logs,
+          make sure though that you run <literal>systemctl start matomo-archive-processing.service</literal>
+          at least once without errors if you have already collected data before.
+        '';
+      };
+
+      nginx = mkOption {
+        type = types.nullOr (types.submodule (
+          recursiveUpdate
+            (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
+            {
+              # enable encryption by default,
+              # as sensitive login and Matomo data should not be transmitted in clear text.
+              options.forceSSL.default = true;
+              options.enableACME.default = true;
+            }
+        )
+        );
+        default = null;
+        example = {
+          serverAliases = [
+            "matomo.\${config.networking.domain}"
+            "stats.\${config.networking.domain}"
+          ];
+          enableACME = false;
+        };
+        description = ''
+            With this option, you can customize an nginx virtualHost which already has sensible defaults for Matomo.
+            Either this option or the webServerUser option is mandatory.
+            Set this to {} to just enable the virtualHost if you don't need any customization.
+            If enabled, then by default, the <option>serverName</option> is
+            <literal>''${user}.''${config.networking.hostName}.''${config.networking.domain}</literal>,
+            SSL is active, and certificates are acquired via ACME.
+            If this is set to null (the default), no nginx virtualHost will be configured.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    warnings = mkIf (cfg.nginx != null && cfg.webServerUser != null) [
+      "If services.matomo.nginx is set, services.matomo.nginx.webServerUser is ignored and should be removed."
+    ];
+
+    assertions = [ {
+        assertion = cfg.nginx != null || cfg.webServerUser != null;
+        message = "Either services.matomo.nginx or services.matomo.nginx.webServerUser is mandatory";
+    }];
+
+    users.users.${user} = {
+      isSystemUser = true;
+      createHome = true;
+      home = dataDir;
+      group  = user;
+    };
+    users.groups.${user} = {};
+
+    systemd.services.matomo-setup-update = {
+      # everything needs to set up and up to date before Matomo php files are executed
+      requiredBy = [ "${phpExecutionUnit}.service" ];
+      before = [ "${phpExecutionUnit}.service" ];
+      # the update part of the script can only work if the database is already up and running
+      requires = [ databaseService ];
+      after = [ databaseService ];
+      path = [ cfg.package ];
+      environment.PIWIK_USER_PATH = dataDir;
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        # hide especially config.ini.php from other
+        UMask = "0007";
+        # TODO: might get renamed to MATOMO_USER_PATH in future versions
+        # chown + chmod in preStart needs root
+        PermissionsStartOnly = true;
+      };
+
+      # correct ownership and permissions in case they're not correct anymore,
+      # e.g. after restoring from backup or moving from another system.
+      # Note that ${dataDir}/config/config.ini.php might contain the MySQL password.
+      preStart = ''
+        # migrate data from piwik to Matomo folder
+        if [ -d ${deprecatedDataDir} ]; then
+          echo "Migrating from ${deprecatedDataDir} to ${dataDir}"
+          mv -T ${deprecatedDataDir} ${dataDir}
+        fi
+        chown -R ${user}:${user} ${dataDir}
+        chmod -R ug+rwX,o-rwx ${dataDir}
+        '';
+      script = ''
+            # Use User-Private Group scheme to protect Matomo data, but allow administration / backup via 'matomo' group
+            # Copy config folder
+            chmod g+s "${dataDir}"
+            cp -r "${cfg.package}/share/config" "${dataDir}/"
+            chmod -R u+rwX,g+rwX,o-rwx "${dataDir}"
+
+            # check whether user setup has already been done
+            if test -f "${dataDir}/config/config.ini.php"; then
+              # then execute possibly pending database upgrade
+              matomo-console core:update --yes
+            fi
+      '';
+    };
+
+    # If this is run regularly via the timer,
+    # 'Browser trigger archiving' can be disabled in Matomo UI > Settings > General Settings.
+    systemd.services.matomo-archive-processing = {
+      description = "Archive Matomo reports";
+      # the archiving can only work if the database is already up and running
+      requires = [ databaseService ];
+      after = [ databaseService ];
+
+      # TODO: might get renamed to MATOMO_USER_PATH in future versions
+      environment.PIWIK_USER_PATH = dataDir;
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        UMask = "0007";
+        CPUSchedulingPolicy = "idle";
+        IOSchedulingClass = "idle";
+        ExecStart = "${cfg.package}/bin/matomo-console core:archive --url=https://${user}.${fqdn}";
+      };
+    };
+
+    systemd.timers.matomo-archive-processing = mkIf cfg.periodicArchiveProcessing {
+      description = "Automatically archive Matomo reports every hour";
+
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = "hourly";
+        Persistent = "yes";
+        AccuracySec = "10m";
+      };
+    };
+
+    systemd.services.${phpExecutionUnit} = {
+      # stop phpfpm on package upgrade, do database upgrade via matomo-setup-update, and then restart
+      restartTriggers = [ cfg.package ];
+      # stop config.ini.php from getting written with read permission for others
+      serviceConfig.UMask = "0007";
+    };
+
+    services.phpfpm.pools = let
+      # workaround for when both are null and need to generate a string,
+      # which is illegal, but as assertions apparently are being triggered *after* config generation,
+      # we have to avoid already throwing errors at this previous stage.
+      socketOwner = if (cfg.nginx != null) then config.services.nginx.user
+      else if (cfg.webServerUser != null) then cfg.webServerUser else "";
+    in {
+      ${pool} = {
+        inherit user;
+        phpOptions = ''
+          error_log = 'stderr'
+          log_errors = on
+        '';
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = socketOwner;
+          "listen.group" = "root";
+          "listen.mode" = "0660";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 10;
+          "pm.min_spare_servers" = 5;
+          "pm.max_spare_servers" = 20;
+          "pm.max_requests" = 500;
+          "catch_workers_output" = true;
+        };
+        phpEnv.PIWIK_USER_PATH = dataDir;
+      };
+    };
+
+
+    services.nginx.virtualHosts = mkIf (cfg.nginx != null) {
+      # References:
+      # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html
+      # https://github.com/perusio/piwik-nginx
+      "${user}.${fqdn}" = mkMerge [ cfg.nginx {
+        # don't allow to override the root easily, as it will almost certainly break Matomo.
+        # disadvantage: not shown as default in docs.
+        root = mkForce "${cfg.package}/share";
+
+        # define locations here instead of as the submodule option's default
+        # so that they can easily be extended with additional locations if required
+        # without needing to redefine the Matomo ones.
+        # disadvantage: not shown as default in docs.
+        locations."/" = {
+          index = "index.php";
+        };
+        # allow index.php for webinterface
+        locations."= /index.php".extraConfig = ''
+          fastcgi_pass unix:${fpm.socket};
+        '';
+        # allow matomo.php for tracking
+        locations."= /matomo.php".extraConfig = ''
+          fastcgi_pass unix:${fpm.socket};
+        '';
+        # allow piwik.php for tracking (deprecated name)
+        locations."= /piwik.php".extraConfig = ''
+          fastcgi_pass unix:${fpm.socket};
+        '';
+        # Any other attempt to access any php files is forbidden
+        locations."~* ^.+\\.php$".extraConfig = ''
+          return 403;
+        '';
+        # Disallow access to unneeded directories
+        # config and tmp are already removed
+        locations."~ ^/(?:core|lang|misc)/".extraConfig = ''
+          return 403;
+        '';
+        # Disallow access to several helper files
+        locations."~* \\.(?:bat|git|ini|sh|txt|tpl|xml|md)$".extraConfig = ''
+          return 403;
+        '';
+        # No crawling of this site for bots that obey robots.txt - no useful information here.
+        locations."= /robots.txt".extraConfig = ''
+          return 200 "User-agent: *\nDisallow: /\n";
+        '';
+        # let browsers cache matomo.js
+        locations."= /matomo.js".extraConfig = ''
+          expires 1M;
+        '';
+        # let browsers cache piwik.js (deprecated name)
+        locations."= /piwik.js".extraConfig = ''
+          expires 1M;
+        '';
+      }];
+    };
+  };
+
+  meta = {
+    doc = ./matomo-doc.xml;
+    maintainers = with lib.maintainers; [ florianjacob ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/mattermost.nix b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
new file mode 100644
index 000000000000..f5c2c356afce
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/mattermost.nix
@@ -0,0 +1,236 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.mattermost;
+
+  defaultConfig = builtins.fromJSON (builtins.replaceStrings [ "\\u0026" ] [ "&" ]
+    (readFile "${pkgs.mattermost}/config/config.json")
+  );
+
+  database = "postgres://${cfg.localDatabaseUser}:${cfg.localDatabasePassword}@localhost:5432/${cfg.localDatabaseName}?sslmode=disable&connect_timeout=10";
+
+  mattermostConf = foldl recursiveUpdate defaultConfig
+    [ { ServiceSettings.SiteURL = cfg.siteUrl;
+        ServiceSettings.ListenAddress = cfg.listenAddress;
+        TeamSettings.SiteName = cfg.siteName;
+        SqlSettings.DriverName = "postgres";
+        SqlSettings.DataSource = database;
+      }
+      cfg.extraConfig
+    ];
+
+  mattermostConfJSON = pkgs.writeText "mattermost-config-raw.json" (builtins.toJSON mattermostConf);
+
+in
+
+{
+  options = {
+    services.mattermost = {
+      enable = mkEnableOption "Mattermost chat server";
+
+      statePath = mkOption {
+        type = types.str;
+        default = "/var/lib/mattermost";
+        description = "Mattermost working directory";
+      };
+
+      siteUrl = mkOption {
+        type = types.str;
+        example = "https://chat.example.com";
+        description = ''
+          URL this Mattermost instance is reachable under, without trailing slash.
+        '';
+      };
+
+      siteName = mkOption {
+        type = types.str;
+        default = "Mattermost";
+        description = "Name of this Mattermost site.";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = ":8065";
+        example = "[::1]:8065";
+        description = ''
+          Address and port this Mattermost instance listens to.
+        '';
+      };
+
+      mutableConfig = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether the Mattermost config.json is writeable by Mattermost.
+
+          Most of the settings can be edited in the system console of
+          Mattermost if this option is enabled. A template config using
+          the options specified in services.mattermost will be generated
+          but won't be overwritten on changes or rebuilds.
+
+          If this option is disabled, changes in the system console won't
+          be possible (default). If an config.json is present, it will be
+          overwritten!
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = { };
+        description = ''
+          Addtional configuration options as Nix attribute set in config.json schema.
+        '';
+      };
+
+      localDatabaseCreate = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Create a local PostgreSQL database for Mattermost automatically.
+        '';
+      };
+
+      localDatabaseName = mkOption {
+        type = types.str;
+        default = "mattermost";
+        description = ''
+          Local Mattermost database name.
+        '';
+      };
+
+      localDatabaseUser = mkOption {
+        type = types.str;
+        default = "mattermost";
+        description = ''
+          Local Mattermost database username.
+        '';
+      };
+
+      localDatabasePassword = mkOption {
+        type = types.str;
+        default = "mmpgsecret";
+        description = ''
+          Password for local Mattermost database user.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "mattermost";
+        description = ''
+          User which runs the Mattermost service.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "mattermost";
+        description = ''
+          Group which runs the Mattermost service.
+        '';
+      };
+
+      matterircd = {
+        enable = mkEnableOption "Mattermost IRC bridge";
+        parameters = mkOption {
+          type = types.listOf types.str;
+          default = [ ];
+          example = [ "-mmserver chat.example.com" "-bind [::]:6667" ];
+          description = ''
+            Set commandline parameters to pass to matterircd. See
+            https://github.com/42wim/matterircd#usage for more information.
+          '';
+        };
+      };
+    };
+  };
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+      users.users = optionalAttrs (cfg.user == "mattermost") {
+        mattermost = {
+          group = cfg.group;
+          uid = config.ids.uids.mattermost;
+          home = cfg.statePath;
+        };
+      };
+
+      users.groups = optionalAttrs (cfg.group == "mattermost") {
+        mattermost.gid = config.ids.gids.mattermost;
+      };
+
+      services.postgresql.enable = cfg.localDatabaseCreate;
+
+      # The systemd service will fail to execute the preStart hook
+      # if the WorkingDirectory does not exist
+      system.activationScripts.mattermost = ''
+        mkdir -p ${cfg.statePath}
+      '';
+
+      systemd.services.mattermost = {
+        description = "Mattermost chat service";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "network.target" "postgresql.service" ];
+
+        preStart = ''
+          mkdir -p ${cfg.statePath}/{data,config,logs}
+          ln -sf ${pkgs.mattermost}/{bin,fonts,i18n,templates,client} ${cfg.statePath}
+        '' + lib.optionalString (!cfg.mutableConfig) ''
+          rm -f ${cfg.statePath}/config/config.json
+          cp ${mattermostConfJSON} ${cfg.statePath}/config/config.json
+          ${pkgs.mattermost}/bin/mattermost config migrate ${cfg.statePath}/config/config.json ${database}
+        '' + lib.optionalString cfg.mutableConfig ''
+          if ! test -e "${cfg.statePath}/config/.initial-created"; then
+            rm -f ${cfg.statePath}/config/config.json
+            cp ${mattermostConfJSON} ${cfg.statePath}/config/config.json
+            touch ${cfg.statePath}/config/.initial-created
+          fi
+        '' + lib.optionalString cfg.localDatabaseCreate ''
+          if ! test -e "${cfg.statePath}/.db-created"; then
+            ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
+              ${config.services.postgresql.package}/bin/psql postgres -c \
+                "CREATE ROLE ${cfg.localDatabaseUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.localDatabasePassword}'"
+            ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
+              ${config.services.postgresql.package}/bin/createdb \
+                --owner ${cfg.localDatabaseUser} ${cfg.localDatabaseName}
+            touch ${cfg.statePath}/.db-created
+          fi
+        '' + ''
+          chown ${cfg.user}:${cfg.group} -R ${cfg.statePath}
+          chmod u+rw,g+r,o-rwx -R ${cfg.statePath}
+        '';
+
+        serviceConfig = {
+          PermissionsStartOnly = true;
+          User = cfg.user;
+          Group = cfg.group;
+          ExecStart = "${pkgs.mattermost}/bin/mattermost" +
+            (lib.optionalString (!cfg.mutableConfig) " -c ${database}");
+          WorkingDirectory = "${cfg.statePath}";
+          Restart = "always";
+          RestartSec = "10";
+          LimitNOFILE = "49152";
+        };
+        unitConfig.JoinsNamespaceOf = mkIf cfg.localDatabaseCreate "postgresql.service";
+      };
+    })
+    (mkIf cfg.matterircd.enable {
+      systemd.services.matterircd = {
+        description = "Mattermost IRC bridge service";
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          User = "nobody";
+          Group = "nogroup";
+          ExecStart = "${pkgs.matterircd}/bin/matterircd ${concatStringsSep " " cfg.matterircd.parameters}";
+          WorkingDirectory = "/tmp";
+          PrivateTmp = true;
+          Restart = "always";
+          RestartSec = "5";
+        };
+      };
+    })
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix b/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix
new file mode 100644
index 000000000000..0a5b6047bb58
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/mediawiki.nix
@@ -0,0 +1,473 @@
+{ config, pkgs, lib, ... }:
+
+let
+
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
+  inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionals optionalString types;
+
+  cfg = config.services.mediawiki;
+  fpm = config.services.phpfpm.pools.mediawiki;
+  user = "mediawiki";
+  group = config.services.httpd.group;
+  cacheDir = "/var/cache/mediawiki";
+  stateDir = "/var/lib/mediawiki";
+
+  pkg = pkgs.stdenv.mkDerivation rec {
+    pname = "mediawiki-full";
+    version = src.version;
+    src = cfg.package;
+
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+
+      rm -rf $out/share/mediawiki/skins/*
+      rm -rf $out/share/mediawiki/extensions/*
+
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+        ln -s ${v} $out/share/mediawiki/skins/${k}
+      '') cfg.skins)}
+
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+        ln -s ${if v != null then v else "$src/share/mediawiki/extensions/${k}"} $out/share/mediawiki/extensions/${k}
+      '') cfg.extensions)}
+    '';
+  };
+
+  mediawikiScripts = pkgs.runCommand "mediawiki-scripts" {
+    buildInputs = [ pkgs.makeWrapper ];
+    preferLocalBuild = true;
+  } ''
+    mkdir -p $out/bin
+    for i in changePassword.php createAndPromote.php userOptions.php edit.php nukePage.php update.php; do
+      makeWrapper ${pkgs.php}/bin/php $out/bin/mediawiki-$(basename $i .php) \
+        --set MEDIAWIKI_CONFIG ${mediawikiConfig} \
+        --add-flags ${pkg}/share/mediawiki/maintenance/$i
+    done
+  '';
+
+  mediawikiConfig = pkgs.writeText "LocalSettings.php" ''
+    <?php
+      # Protect against web entry
+      if ( !defined( 'MEDIAWIKI' ) ) {
+        exit;
+      }
+
+      $wgSitename = "${cfg.name}";
+      $wgMetaNamespace = false;
+
+      ## The URL base path to the directory containing the wiki;
+      ## defaults for all runtime URL paths are based off of this.
+      ## For more information on customizing the URLs
+      ## (like /w/index.php/Page_title to /wiki/Page_title) please see:
+      ## https://www.mediawiki.org/wiki/Manual:Short_URL
+      $wgScriptPath = "";
+
+      ## The protocol and server name to use in fully-qualified URLs
+      $wgServer = "${if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then "https" else "http"}://${cfg.virtualHost.hostName}";
+
+      ## The URL path to static resources (images, scripts, etc.)
+      $wgResourceBasePath = $wgScriptPath;
+
+      ## The URL path to the logo.  Make sure you change this from the default,
+      ## or else you'll overwrite your logo when you upgrade!
+      $wgLogo = "$wgResourceBasePath/resources/assets/wiki.png";
+
+      ## UPO means: this is also a user preference option
+
+      $wgEnableEmail = true;
+      $wgEnableUserEmail = true; # UPO
+
+      $wgEmergencyContact = "${if cfg.virtualHost.adminAddr != null then cfg.virtualHost.adminAddr else config.services.httpd.adminAddr}";
+      $wgPasswordSender = $wgEmergencyContact;
+
+      $wgEnotifUserTalk = false; # UPO
+      $wgEnotifWatchlist = false; # UPO
+      $wgEmailAuthentication = true;
+
+      ## Database settings
+      $wgDBtype = "${cfg.database.type}";
+      $wgDBserver = "${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}";
+      $wgDBname = "${cfg.database.name}";
+      $wgDBuser = "${cfg.database.user}";
+      ${optionalString (cfg.database.passwordFile != null) "$wgDBpassword = file_get_contents(\"${cfg.database.passwordFile}\");"}
+
+      ${optionalString (cfg.database.type == "mysql" && cfg.database.tablePrefix != null) ''
+        # MySQL specific settings
+        $wgDBprefix = "${cfg.database.tablePrefix}";
+      ''}
+
+      ${optionalString (cfg.database.type == "mysql") ''
+        # MySQL table options to use during installation or update
+        $wgDBTableOptions = "ENGINE=InnoDB, DEFAULT CHARSET=binary";
+      ''}
+
+      ## Shared memory settings
+      $wgMainCacheType = CACHE_NONE;
+      $wgMemCachedServers = [];
+
+      ${optionalString (cfg.uploadsDir != null) ''
+        $wgEnableUploads = true;
+        $wgUploadDirectory = "${cfg.uploadsDir}";
+      ''}
+
+      $wgUseImageMagick = true;
+      $wgImageMagickConvertCommand = "${pkgs.imagemagick}/bin/convert";
+
+      # InstantCommons allows wiki to use images from https://commons.wikimedia.org
+      $wgUseInstantCommons = false;
+
+      # Periodically send a pingback to https://www.mediawiki.org/ with basic data
+      # about this MediaWiki instance. The Wikimedia Foundation shares this data
+      # with MediaWiki developers to help guide future development efforts.
+      $wgPingback = true;
+
+      ## If you use ImageMagick (or any other shell command) on a
+      ## Linux server, this will need to be set to the name of an
+      ## available UTF-8 locale
+      $wgShellLocale = "C.UTF-8";
+
+      ## Set $wgCacheDirectory to a writable directory on the web server
+      ## to make your wiki go slightly faster. The directory should not
+      ## be publically accessible from the web.
+      $wgCacheDirectory = "${cacheDir}";
+
+      # Site language code, should be one of the list in ./languages/data/Names.php
+      $wgLanguageCode = "en";
+
+      $wgSecretKey = file_get_contents("${stateDir}/secret.key");
+
+      # Changing this will log out all existing sessions.
+      $wgAuthenticationTokenVersion = "";
+
+      ## For attaching licensing metadata to pages, and displaying an
+      ## appropriate copyright notice / icon. GNU Free Documentation
+      ## License and Creative Commons licenses are supported so far.
+      $wgRightsPage = ""; # Set to the title of a wiki page that describes your license/copyright
+      $wgRightsUrl = "";
+      $wgRightsText = "";
+      $wgRightsIcon = "";
+
+      # Path to the GNU diff3 utility. Used for conflict resolution.
+      $wgDiff = "${pkgs.diffutils}/bin/diff";
+      $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
+
+      # Enabled skins.
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadSkin('${k}');") cfg.skins)}
+
+      # Enabled extensions.
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: "wfLoadExtension('${k}');") cfg.extensions)}
+
+
+      # End of automatically generated settings.
+      # Add more configuration options below.
+
+      ${cfg.extraConfig}
+  '';
+
+in
+{
+  # interface
+  options = {
+    services.mediawiki = {
+
+      enable = mkEnableOption "MediaWiki";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.mediawiki;
+        description = "Which MediaWiki package to use.";
+      };
+
+      name = mkOption {
+        default = "MediaWiki";
+        example = "Foobar Wiki";
+        description = "Name of the wiki.";
+      };
+
+      uploadsDir = mkOption {
+        type = types.nullOr types.path;
+        default = "${stateDir}/uploads";
+        description = ''
+          This directory is used for uploads of pictures. The directory passed here is automatically
+          created and permissions adjusted as required.
+        '';
+      };
+
+      passwordFile = mkOption {
+        type = types.path;
+        description = "A file containing the initial password for the admin user.";
+        example = "/run/keys/mediawiki-password";
+      };
+
+      skins = mkOption {
+        default = {};
+        type = types.attrsOf types.path;
+        description = ''
+          Attribute set of paths whose content is copied to the <filename>skins</filename>
+          subdirectory of the MediaWiki installation in addition to the default skins.
+        '';
+      };
+
+      extensions = mkOption {
+        default = {};
+        type = types.attrsOf (types.nullOr types.path);
+        description = ''
+          Attribute set of paths whose content is copied to the <filename>extensions</filename>
+          subdirectory of the MediaWiki installation and enabled in configuration.
+
+          Use <literal>null</literal> instead of path to enable extensions that are part of MediaWiki.
+        '';
+        example = literalExample ''
+          {
+            Matomo = pkgs.fetchzip {
+              url = "https://github.com/DaSchTour/matomo-mediawiki-extension/archive/v4.0.1.tar.gz";
+              sha256 = "0g5rd3zp0avwlmqagc59cg9bbkn3r7wx7p6yr80s644mj6dlvs1b";
+            };
+            ParserFunctions = null;
+          }
+        '';
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql" "postgres" "sqlite" "mssql" "oracle" ];
+          default = "mysql";
+          description = "Database engine to use. MySQL/MariaDB is the database of choice by MediaWiki developers.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 3306;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "mediawiki";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "mediawiki";
+          description = "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/mediawiki-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        tablePrefix = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            If you only have access to a single database and wish to install more than
+            one version of MediaWiki, or have other applications that also use the
+            database, you can give the table names a unique prefix to stop any naming
+            conflicts or confusion.
+            See <link xlink:href='https://www.mediawiki.org/wiki/Manual:$wgDBprefix'/>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = if cfg.database.createLocally then "/run/mysqld/mysqld.sock" else null;
+          defaultText = "/run/mysqld/mysqld.sock";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = cfg.database.type == "mysql";
+          defaultText = "true";
+          description = ''
+            Create the database and database user locally.
+            This currently only applies if database type "mysql" is selected.
+          '';
+        };
+      };
+
+      virtualHost = mkOption {
+        type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
+        example = literalExample ''
+          {
+            hostName = "mediawiki.example.org";
+            adminAddr = "webmaster@example.org";
+            forceSSL = true;
+            enableACME = true;
+          }
+        '';
+        description = ''
+          Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+          See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+        '';
+      };
+
+      poolConfig = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool ]);
+        default = {
+          "pm" = "dynamic";
+          "pm.max_children" = 32;
+          "pm.start_servers" = 2;
+          "pm.min_spare_servers" = 2;
+          "pm.max_spare_servers" = 4;
+          "pm.max_requests" = 500;
+        };
+        description = ''
+          Options for the MediaWiki PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+          for details on configuration directives.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        description = ''
+          Any additional text to be appended to MediaWiki's
+          LocalSettings.php configuration file. For configuration
+          settings, see <link xlink:href="https://www.mediawiki.org/wiki/Manual:Configuration_settings"/>.
+        '';
+        default = "";
+        example = ''
+          $wgEnableEmail = false;
+        '';
+      };
+
+    };
+  };
+
+  # implementation
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.type == "mysql";
+        message = "services.mediawiki.createLocally is currently only supported for database type 'mysql'";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.mediawiki.database.user must be set to ${user} if services.mediawiki.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.socket != null;
+        message = "services.mediawiki.database.socket must be set if services.mediawiki.database.createLocally is set to true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.mediawiki.database.createLocally is set to true";
+      }
+    ];
+
+    services.mediawiki.skins = {
+      MonoBook = "${cfg.package}/share/mediawiki/skins/MonoBook";
+      Timeless = "${cfg.package}/share/mediawiki/skins/Timeless";
+      Vector = "${cfg.package}/share/mediawiki/skins/Vector";
+    };
+
+    services.mysql = mkIf cfg.database.createLocally {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.phpfpm.pools.mediawiki = {
+      inherit user group;
+      phpEnv.MEDIAWIKI_CONFIG = "${mediawikiConfig}";
+      settings = {
+        "listen.owner" = config.services.httpd.user;
+        "listen.group" = config.services.httpd.group;
+      } // cfg.poolConfig;
+    };
+
+    services.httpd = {
+      enable = true;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
+        documentRoot = mkForce "${pkg}/share/mediawiki";
+        extraConfig = ''
+          <Directory "${pkg}/share/mediawiki">
+            <FilesMatch "\.php$">
+              <If "-f %{REQUEST_FILENAME}">
+                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+              </If>
+            </FilesMatch>
+
+            Require all granted
+            DirectoryIndex index.php
+            AllowOverride All
+          </Directory>
+        '' + optionalString (cfg.uploadsDir != null) ''
+          Alias "/images" "${cfg.uploadsDir}"
+          <Directory "${cfg.uploadsDir}">
+            Require all granted
+          </Directory>
+        '';
+      } ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${stateDir}' 0750 ${user} ${group} - -"
+      "d '${cacheDir}' 0750 ${user} ${group} - -"
+    ] ++ optionals (cfg.uploadsDir != null) [
+      "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+      "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+    ];
+
+    systemd.services.mediawiki-init = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "phpfpm-mediawiki.service" ];
+      after = optional cfg.database.createLocally "mysql.service";
+      script = ''
+        if ! test -e "${stateDir}/secret.key"; then
+          tr -dc A-Za-z0-9 </dev/urandom 2>/dev/null | head -c 64 > ${stateDir}/secret.key
+        fi
+
+        echo "exit( wfGetDB( DB_MASTER )->tableExists( 'user' ) ? 1 : 0 );" | \
+        ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/eval.php --conf ${mediawikiConfig} && \
+        ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/install.php \
+          --confpath /tmp \
+          --scriptpath / \
+          --dbserver ${cfg.database.host}${optionalString (cfg.database.socket != null) ":${cfg.database.socket}"} \
+          --dbport ${toString cfg.database.port} \
+          --dbname ${cfg.database.name} \
+          ${optionalString (cfg.database.tablePrefix != null) "--dbprefix ${cfg.database.tablePrefix}"} \
+          --dbuser ${cfg.database.user} \
+          ${optionalString (cfg.database.passwordFile != null) "--dbpassfile ${cfg.database.passwordFile}"} \
+          --passfile ${cfg.passwordFile} \
+          ${cfg.name} \
+          admin
+
+        ${pkgs.php}/bin/php ${pkg}/share/mediawiki/maintenance/update.php --conf ${mediawikiConfig} --quick
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        Group = group;
+        PrivateTmp = true;
+      };
+    };
+
+    systemd.services.httpd.after = optional (cfg.database.createLocally && cfg.database.type == "mysql") "mysql.service";
+
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
+
+    environment.systemPackages = [ mediawikiScripts ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/miniflux.nix b/nixpkgs/nixos/modules/services/web-apps/miniflux.nix
new file mode 100644
index 000000000000..304712d0efc3
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/miniflux.nix
@@ -0,0 +1,97 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.miniflux;
+
+  dbUser = "miniflux";
+  dbPassword = "miniflux";
+  dbHost = "localhost";
+  dbName = "miniflux";
+
+  defaultCredentials = pkgs.writeText "miniflux-admin-credentials" ''
+    ADMIN_USERNAME=admin
+    ADMIN_PASSWORD=password
+  '';
+
+  pgsu = "${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser}";
+  pgbin = "${config.services.postgresql.package}/bin";
+  preStart = pkgs.writeScript "miniflux-pre-start" ''
+    #!${pkgs.runtimeShell}
+    db_exists() {
+      [ "$(${pgsu} ${pgbin}/psql -Atc "select 1 from pg_database where datname='$1'")" == "1" ]
+    }
+    if ! db_exists "${dbName}"; then
+      ${pgsu} ${pgbin}/psql postgres -c "CREATE ROLE ${dbUser} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${dbPassword}'"
+      ${pgsu} ${pgbin}/createdb --owner "${dbUser}" "${dbName}"
+      ${pgsu} ${pgbin}/psql "${dbName}" -c "CREATE EXTENSION IF NOT EXISTS hstore"
+    fi
+  '';
+in
+
+{
+  options = {
+    services.miniflux = {
+      enable = mkEnableOption "miniflux";
+
+      config = mkOption {
+        type = types.attrsOf types.str;
+        example = literalExample ''
+          {
+            CLEANUP_FREQUENCY = "48";
+            LISTEN_ADDR = "localhost:8080";
+          }
+        '';
+        description = ''
+          Configuration for Miniflux, refer to
+          <link xlink:href="http://docs.miniflux.app/en/latest/configuration.html"/>
+          for documentation on the supported values.
+        '';
+      };
+
+      adminCredentialsFile = mkOption  {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          File containing the ADMIN_USERNAME, default is "admin", and
+          ADMIN_PASSWORD (length >= 6), default is "password"; in the format of
+          an EnvironmentFile=, as described by systemd.exec(5).
+        '';
+        example = "/etc/nixos/miniflux-admin-credentials";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.miniflux.config =  {
+      LISTEN_ADDR = mkDefault "localhost:8080";
+      DATABASE_URL = "postgresql://${dbUser}:${dbPassword}@${dbHost}/${dbName}?sslmode=disable";
+      RUN_MIGRATIONS = "1";
+      CREATE_ADMIN = "1";
+    };
+
+    services.postgresql.enable = true;
+
+    systemd.services.miniflux = {
+      description = "Miniflux service";
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "postgresql.service" ];
+      after = [ "network.target" "postgresql.service" ];
+
+      serviceConfig = {
+        ExecStart = "${pkgs.miniflux}/bin/miniflux";
+        ExecStartPre = "+${preStart}";
+        DynamicUser = true;
+        RuntimeDirectory = "miniflux";
+        RuntimeDirectoryMode = "0700";
+        EnvironmentFile = if cfg.adminCredentialsFile == null
+        then defaultCredentials
+        else cfg.adminCredentialsFile;
+      };
+
+      environment = cfg.config;
+    };
+    environment.systemPackages = [ pkgs.miniflux ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix b/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix
new file mode 100644
index 000000000000..dc7abce2a5cb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/moinmoin.nix
@@ -0,0 +1,303 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.services.moinmoin;
+  python = pkgs.python27;
+  pkg = python.pkgs.moinmoin;
+  dataDir = "/var/lib/moin";
+  usingGunicorn = cfg.webServer == "nginx-gunicorn" || cfg.webServer == "gunicorn";
+  usingNginx = cfg.webServer == "nginx-gunicorn";
+  user = "moin";
+  group = "moin";
+
+  uLit = s: ''u"${s}"'';
+  indentLines = n: str: concatMapStrings (line: "${fixedWidthString n " " " "}${line}\n") (splitString "\n" str);
+
+  moinCliWrapper = wikiIdent: pkgs.writeShellScriptBin "moin-${wikiIdent}" ''
+    ${pkgs.su}/bin/su -s ${pkgs.runtimeShell} -c "${pkg}/bin/moin --config-dir=/var/lib/moin/${wikiIdent}/config $*" ${user}
+  '';
+
+  wikiConfig = wikiIdent: w: ''
+    # -*- coding: utf-8 -*-
+
+    from MoinMoin.config import multiconfig, url_prefix_static
+
+    class Config(multiconfig.DefaultConfig):
+        ${optionalString (w.webLocation != "/") ''
+          url_prefix_static = '${w.webLocation}' + url_prefix_static
+        ''}
+
+        sitename = u'${w.siteName}'
+        page_front_page = u'${w.frontPage}'
+
+        data_dir = '${dataDir}/${wikiIdent}/data'
+        data_underlay_dir = '${dataDir}/${wikiIdent}/underlay'
+
+        language_default = u'${w.languageDefault}'
+        ${optionalString (w.superUsers != []) ''
+          superuser = [${concatMapStringsSep ", " uLit w.superUsers}]
+        ''}
+
+    ${indentLines 4 w.extraConfig}
+  '';
+  wikiConfigFile = name: wiki: pkgs.writeText "${name}.py" (wikiConfig name wiki);
+
+in
+{
+  options.services.moinmoin = with types; {
+    enable = mkEnableOption "MoinMoin Wiki Engine";
+
+    webServer = mkOption {
+      type = enum [ "nginx-gunicorn" "gunicorn" "none" ];
+      default = "nginx-gunicorn";
+      example = "none";
+      description = ''
+        Which web server to use to serve the wiki.
+        Use <literal>none</literal> if you want to configure this yourself.
+      '';
+    };
+
+    gunicorn.workers = mkOption {
+      type = ints.positive;
+      default = 3;
+      example = 10;
+      description = ''
+        The number of worker processes for handling requests.
+      '';
+    };
+
+    wikis = mkOption {
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          siteName = mkOption {
+            type = str;
+            default = "Untitled Wiki";
+            example = "ExampleWiki";
+            description = ''
+              Short description of your wiki site, displayed below the logo on each page, and
+              used in RSS documents as the channel title.
+            '';
+          };
+
+          webHost = mkOption {
+            type = str;
+            description = "Host part of the wiki URL. If undefined, the name of the attribute set will be used.";
+            example = "wiki.example.org";
+          };
+
+          webLocation = mkOption {
+            type = str;
+            default = "/";
+            example = "/moin";
+            description = "Location part of the wiki URL.";
+          };
+
+          frontPage = mkOption {
+            type = str;
+            default = "LanguageSetup";
+            example = "FrontPage";
+            description = ''
+              Front page name. Set this to something like <literal>FrontPage</literal> once languages are
+              configured.
+            '';
+          };
+
+          superUsers = mkOption {
+            type = listOf str;
+            default = [];
+            example = [ "elvis" ];
+            description = ''
+              List of trusted user names with wiki system administration super powers.
+
+              Please note that accounts for these users need to be created using the <command>moin</command> command-line utility, e.g.:
+              <command>moin-<replaceable>WIKINAME</replaceable> account create --name=<replaceable>NAME</replaceable> --email=<replaceable>EMAIL</replaceable> --password=<replaceable>PASSWORD</replaceable></command>.
+            '';
+          };
+
+          languageDefault = mkOption {
+            type = str;
+            default = "en";
+            example = "de";
+            description = "The ISO-639-1 name of the main wiki language. Languages that MoinMoin does not support are ignored.";
+          };
+
+          extraConfig = mkOption {
+            type = lines;
+            default = "";
+            example = ''
+              show_hosts = True
+              search_results_per_page = 100
+              acl_rights_default = u"Known:read,write,delete,revert All:read"
+              logo_string = u"<h2>\U0001f639</h2>"
+              theme_default = u"modernized"
+
+              user_checkbox_defaults = {'show_page_trail': 0, 'edit_on_doubleclick': 0}
+              navi_bar = [u'SomePage'] + multiconfig.DefaultConfig.navi_bar
+              actions_excluded = multiconfig.DefaultConfig.actions_excluded + ['newaccount']
+
+              mail_smarthost = "mail.example.org"
+              mail_from = u"Example.Org Wiki <wiki@example.org>"
+            '';
+            description = ''
+              Additional configuration to be appended verbatim to this wiki's config.
+
+              See <link xlink:href='http://moinmo.in/HelpOnConfiguration' /> for documentation.
+            '';
+          };
+
+        };
+        config = {
+          webHost = mkDefault name;
+        };
+      }));
+      example = literalExample ''
+        {
+          "mywiki" = {
+            siteName = "Example Wiki";
+            webHost = "wiki.example.org";
+            superUsers = [ "admin" ];
+            frontPage = "Index";
+            extraConfig = "page_category_regex = ur'(?P<all>(Category|Kategorie)(?P<key>(?!Template)\S+))'"
+          };
+        }
+      '';
+      description = ''
+        Configurations of the individual wikis. Attribute names must be valid Python
+        identifiers of the form <literal>[A-Za-z_][A-Za-z0-9_]*</literal>.
+
+        For every attribute <replaceable>WIKINAME</replaceable>, a helper script
+        moin-<replaceable>WIKINAME</replaceable> is created which runs the
+        <command>moin</command> command under the <literal>moin</literal> user (to avoid
+        file ownership issues) and with the right configuration directory passed to it.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = forEach (attrNames cfg.wikis) (wname:
+      { assertion = builtins.match "[A-Za-z_][A-Za-z0-9_]*" wname != null;
+        message = "${wname} is not valid Python identifier";
+      }
+    );
+
+    users.users = {
+      moin = {
+        description = "MoinMoin wiki";
+        home = dataDir;
+        group = group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = {
+      moin = {
+        members = mkIf usingNginx [ config.services.nginx.user ];
+      };
+    };
+
+    environment.systemPackages = [ pkg ] ++ map moinCliWrapper (attrNames cfg.wikis);
+
+    systemd.services = mkIf usingGunicorn
+      (flip mapAttrs' cfg.wikis (wikiIdent: wiki:
+        nameValuePair "moin-${wikiIdent}"
+          {
+            description = "MoinMoin wiki ${wikiIdent} - gunicorn process";
+            wantedBy = [ "multi-user.target" ];
+            after = [ "network.target" ];
+            restartIfChanged = true;
+            restartTriggers = [ (wikiConfigFile wikiIdent wiki) ];
+
+            environment = let
+              penv = python.buildEnv.override {
+                # setuptools: https://github.com/benoitc/gunicorn/issues/1716
+                extraLibs = [ python.pkgs.gevent python.pkgs.setuptools pkg ];
+              };
+            in {
+              PYTHONPATH = "${dataDir}/${wikiIdent}/config:${penv}/${python.sitePackages}";
+            };
+
+            preStart = ''
+              umask 0007
+              rm -rf ${dataDir}/${wikiIdent}/underlay
+              cp -r ${pkg}/share/moin/underlay ${dataDir}/${wikiIdent}/
+              chmod -R u+w ${dataDir}/${wikiIdent}/underlay
+            '';
+
+            serviceConfig = {
+              User = user;
+              Group = group;
+              WorkingDirectory = "${dataDir}/${wikiIdent}";
+              ExecStart = ''${python.pkgs.gunicorn}/bin/gunicorn moin_wsgi \
+                --name gunicorn-${wikiIdent} \
+                --workers ${toString cfg.gunicorn.workers} \
+                --worker-class gevent \
+                --bind unix:/run/moin/${wikiIdent}/gunicorn.sock
+              '';
+
+              Restart = "on-failure";
+              RestartSec = "2s";
+              StartLimitIntervalSec = "30s";
+
+              StateDirectory = "moin/${wikiIdent}";
+              StateDirectoryMode = "0750";
+              RuntimeDirectory = "moin/${wikiIdent}";
+              RuntimeDirectoryMode = "0750";
+
+              NoNewPrivileges = true;
+              ProtectSystem = "strict";
+              ProtectHome = true;
+              PrivateTmp = true;
+              PrivateDevices = true;
+              PrivateNetwork = true;
+              ProtectKernelTunables = true;
+              ProtectKernelModules = true;
+              ProtectControlGroups = true;
+              RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+              RestrictNamespaces = true;
+              LockPersonality = true;
+              MemoryDenyWriteExecute = true;
+              RestrictRealtime = true;
+            };
+          }
+      ));
+
+    services.nginx = mkIf usingNginx {
+      enable = true;
+      virtualHosts = flip mapAttrs' cfg.wikis (name: w: nameValuePair w.webHost {
+        forceSSL = mkDefault true;
+        enableACME = mkDefault true;
+        locations."${w.webLocation}" = {
+          extraConfig = ''
+            proxy_set_header Host $host;
+            proxy_set_header X-Real-IP $remote_addr;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+            proxy_set_header X-Forwarded-Host $host;
+            proxy_set_header X-Forwarded-Server $host;
+
+            proxy_pass http://unix:/run/moin/${name}/gunicorn.sock;
+          '';
+        };
+      });
+    };
+
+    systemd.tmpfiles.rules = [
+      "d  /run/moin            0750 ${user} ${group} - -"
+      "d  ${dataDir}           0550 ${user} ${group} - -"
+    ]
+    ++ (concatLists (flip mapAttrsToList cfg.wikis (wikiIdent: wiki: [
+      "d  ${dataDir}/${wikiIdent}                      0750 ${user} ${group} - -"
+      "d  ${dataDir}/${wikiIdent}/config               0550 ${user} ${group} - -"
+      "L+ ${dataDir}/${wikiIdent}/config/wikiconfig.py -    -       -        - ${wikiConfigFile wikiIdent wiki}"
+      # needed in order to pass module name to gunicorn
+      "L+ ${dataDir}/${wikiIdent}/config/moin_wsgi.py  -    -       -        - ${pkg}/share/moin/server/moin.wsgi"
+      # seed data files
+      "C  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - ${pkg}/share/moin/data"
+      # fix nix store permissions
+      "Z  ${dataDir}/${wikiIdent}/data                 0770 ${user} ${group} - -"
+    ])));
+  };
+
+  meta.maintainers = with lib.maintainers; [ mmilata ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/moodle.nix b/nixpkgs/nixos/modules/services/web-apps/moodle.nix
new file mode 100644
index 000000000000..1196780cf6ef
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/moodle.nix
@@ -0,0 +1,311 @@
+{ config, lib, pkgs, ... }:
+
+let
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+  inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionalString;
+
+  cfg = config.services.moodle;
+  fpm = config.services.phpfpm.pools.moodle;
+
+  user = "moodle";
+  group = config.services.httpd.group;
+  stateDir = "/var/lib/moodle";
+
+  moodleConfig = pkgs.writeText "config.php" ''
+  <?php  // Moodle configuration file
+
+  unset($CFG);
+  global $CFG;
+  $CFG = new stdClass();
+
+  $CFG->dbtype    = '${ { mysql = "mariadb"; pgsql = "pgsql"; }.${cfg.database.type} }';
+  $CFG->dblibrary = 'native';
+  $CFG->dbhost    = '${cfg.database.host}';
+  $CFG->dbname    = '${cfg.database.name}';
+  $CFG->dbuser    = '${cfg.database.user}';
+  ${optionalString (cfg.database.passwordFile != null) "$CFG->dbpass = file_get_contents('${cfg.database.passwordFile}');"}
+  $CFG->prefix    = 'mdl_';
+  $CFG->dboptions = array (
+    'dbpersist' => 0,
+    'dbport' => '${toString cfg.database.port}',
+    ${optionalString (cfg.database.socket != null) "'dbsocket' => '${cfg.database.socket}',"}
+    'dbcollation' => 'utf8mb4_unicode_ci',
+  );
+
+  $CFG->wwwroot   = '${if cfg.virtualHost.addSSL || cfg.virtualHost.forceSSL || cfg.virtualHost.onlySSL then "https" else "http"}://${cfg.virtualHost.hostName}';
+  $CFG->dataroot  = '${stateDir}';
+  $CFG->admin     = 'admin';
+
+  $CFG->directorypermissions = 02777;
+  $CFG->disableupdateautodeploy = true;
+
+  $CFG->pathtogs = '${pkgs.ghostscript}/bin/gs';
+  $CFG->pathtophp = '${pkgs.php}/bin/php';
+  $CFG->pathtodu = '${pkgs.coreutils}/bin/du';
+  $CFG->aspellpath = '${pkgs.aspell}/bin/aspell';
+  $CFG->pathtodot = '${pkgs.graphviz}/bin/dot';
+
+  ${cfg.extraConfig}
+
+  require_once('${cfg.package}/share/moodle/lib/setup.php');
+
+  // There is no php closing tag in this file,
+  // it is intentional because it prevents trailing whitespace problems!
+  '';
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+in
+{
+  # interface
+  options.services.moodle = {
+    enable = mkEnableOption "Moodle web application";
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.moodle;
+      defaultText = "pkgs.moodle";
+      description = "The Moodle package to use.";
+    };
+
+    initialPassword = mkOption {
+      type = types.str;
+      example = "correcthorsebatterystaple";
+      description = ''
+        Specifies the initial password for the admin, i.e. the password assigned if the user does not already exist.
+        The password specified here is world-readable in the Nix store, so it should be changed promptly.
+      '';
+    };
+
+    database = {
+      type = mkOption {
+        type = types.enum [ "mysql" "pgsql" ];
+        default = "mysql";
+        description = ''Database engine to use.'';
+      };
+
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Database host address.";
+      };
+
+      port = mkOption {
+        type = types.int;
+        description = "Database host port.";
+        default = {
+          mysql = 3306;
+          pgsql = 5432;
+        }.${cfg.database.type};
+        defaultText = "3306";
+      };
+
+      name = mkOption {
+        type = types.str;
+        default = "moodle";
+        description = "Database name.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "moodle";
+        description = "Database user.";
+      };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/run/keys/moodle-dbpassword";
+        description = ''
+          A file containing the password corresponding to
+          <option>database.user</option>.
+        '';
+      };
+
+      socket = mkOption {
+        type = types.nullOr types.path;
+        default =
+          if mysqlLocal then "/run/mysqld/mysqld.sock"
+          else if pgsqlLocal then "/run/postgresql"
+          else null;
+        defaultText = "/run/mysqld/mysqld.sock";
+        description = "Path to the unix socket file to use for authentication.";
+      };
+
+      createLocally = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Create the database and database user locally.";
+      };
+    };
+
+    virtualHost = mkOption {
+      type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
+      example = literalExample ''
+        {
+          hostName = "moodle.example.org";
+          adminAddr = "webmaster@example.org";
+          forceSSL = true;
+          enableACME = true;
+        }
+      '';
+      description = ''
+        Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+        See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+      '';
+    };
+
+    poolConfig = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = 32;
+        "pm.start_servers" = 2;
+        "pm.min_spare_servers" = 2;
+        "pm.max_spare_servers" = 4;
+        "pm.max_requests" = 500;
+      };
+      description = ''
+        Options for the Moodle PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+        for details on configuration directives.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Any additional text to be appended to the config.php
+        configuration file. This is a PHP script. For configuration
+        details, see <link xlink:href="https://docs.moodle.org/37/en/Configuration_file"/>.
+      '';
+      example = ''
+        $CFG->disableupdatenotifications = true;
+      '';
+    };
+  };
+
+  # implementation
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.moodle.database.user must be set to ${user} if services.moodle.database.createLocally is set true";
+      }
+      { assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
+        message = "a password cannot be specified if services.moodle.database.createLocally is set to true";
+      }
+    ];
+
+    services.mysql = mkIf mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = {
+            "${cfg.database.name}.*" = "SELECT, INSERT, UPDATE, DELETE, CREATE, CREATE TEMPORARY TABLES, DROP, INDEX, ALTER";
+          };
+        }
+      ];
+    };
+
+    services.postgresql = mkIf pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    services.phpfpm.pools.moodle = {
+      inherit user group;
+      phpEnv.MOODLE_CONFIG = "${moodleConfig}";
+      phpOptions = ''
+        zend_extension = opcache.so
+        opcache.enable = 1
+      '';
+      settings = {
+        "listen.owner" = config.services.httpd.user;
+        "listen.group" = config.services.httpd.group;
+      } // cfg.poolConfig;
+    };
+
+    services.httpd = {
+      enable = true;
+      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
+        documentRoot = mkForce "${cfg.package}/share/moodle";
+        extraConfig = ''
+          <Directory "${cfg.package}/share/moodle">
+            <FilesMatch "\.php$">
+              <If "-f %{REQUEST_FILENAME}">
+                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+              </If>
+            </FilesMatch>
+            Options -Indexes
+            DirectoryIndex index.php
+          </Directory>
+        '';
+      } ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${stateDir}' 0750 ${user} ${group} - -"
+    ];
+
+    systemd.services.moodle-init = {
+      wantedBy = [ "multi-user.target" ];
+      before = [ "phpfpm-moodle.service" ];
+      after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+      environment.MOODLE_CONFIG = moodleConfig;
+      script = ''
+        ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/check_database_schema.php && rc=$? || rc=$?
+
+        [ "$rc" == 1 ] && ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/upgrade.php \
+          --non-interactive \
+          --allow-unstable
+
+        [ "$rc" == 2 ] && ${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/install_database.php \
+          --agree-license \
+          --adminpass=${cfg.initialPassword}
+
+        true
+      '';
+      serviceConfig = {
+        User = user;
+        Group = group;
+        Type = "oneshot";
+      };
+    };
+
+    systemd.services.moodle-cron = {
+      description = "Moodle cron service";
+      after = [ "moodle-init.service" ];
+      environment.MOODLE_CONFIG = moodleConfig;
+      serviceConfig = {
+        User = user;
+        Group = group;
+        ExecStart = "${pkgs.php}/bin/php ${cfg.package}/share/moodle/admin/cli/cron.php";
+      };
+    };
+
+    systemd.timers.moodle-cron = {
+      description = "Moodle cron timer";
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = "minutely";
+      };
+    };
+
+    systemd.services.httpd.after = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
new file mode 100644
index 000000000000..5b9065dec3c5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.nix
@@ -0,0 +1,615 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nextcloud;
+  fpm = config.services.phpfpm.pools.nextcloud;
+
+  phpPackage =
+    let
+      base = pkgs.php74;
+    in
+      base.buildEnv {
+        extensions = { enabled, all }: with all;
+          enabled ++ [
+            apcu redis memcached imagick
+          ];
+        extraConfig = phpOptionsStr;
+      };
+
+  toKeyValue = generators.toKeyValue {
+    mkKeyValue = generators.mkKeyValueDefault {} " = ";
+  };
+
+  phpOptions = {
+    upload_max_filesize = cfg.maxUploadSize;
+    post_max_size = cfg.maxUploadSize;
+    memory_limit = cfg.maxUploadSize;
+  } // cfg.phpOptions;
+  phpOptionsStr = toKeyValue phpOptions;
+
+  occ = pkgs.writeScriptBin "nextcloud-occ" ''
+    #! ${pkgs.runtimeShell}
+    cd ${cfg.package}
+    sudo=exec
+    if [[ "$USER" != nextcloud ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR --preserve-env=OC_PASS'
+    fi
+    export NEXTCLOUD_CONFIG_DIR="${cfg.home}/config"
+    $sudo \
+      ${phpPackage}/bin/php \
+      occ $*
+  '';
+
+  inherit (config.system) stateVersion;
+
+in {
+  options.services.nextcloud = {
+    enable = mkEnableOption "nextcloud";
+    hostName = mkOption {
+      type = types.str;
+      description = "FQDN for the nextcloud instance.";
+    };
+    home = mkOption {
+      type = types.str;
+      default = "/var/lib/nextcloud";
+      description = "Storage path of nextcloud.";
+    };
+    logLevel = mkOption {
+      type = types.ints.between 0 4;
+      default = 2;
+      description = "Log level value between 0 (DEBUG) and 4 (FATAL).";
+    };
+    https = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Use https for generated links.";
+    };
+    package = mkOption {
+      type = types.package;
+      description = "Which package to use for the Nextcloud instance.";
+      relatedPackages = [ "nextcloud17" "nextcloud18" ];
+    };
+
+    maxUploadSize = mkOption {
+      default = "512M";
+      type = types.str;
+      description = ''
+        Defines the upload limit for files. This changes the relevant options
+        in php.ini and nginx if enabled.
+      '';
+    };
+
+    skeletonDirectory = mkOption {
+      default = "";
+      type = types.str;
+      description = ''
+        The directory where the skeleton files are located. These files will be
+        copied to the data directory of new users. Leave empty to not copy any
+        skeleton files.
+      '';
+    };
+
+    nginx.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable nginx virtual host management.
+        Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
+        See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+      '';
+    };
+
+    webfinger = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable this option if you plan on using the webfinger plugin.
+        The appropriate nginx rewrite rules will be added to your configuration.
+      '';
+    };
+
+    phpOptions = mkOption {
+      type = types.attrsOf types.str;
+      default = {
+        short_open_tag = "Off";
+        expose_php = "Off";
+        error_reporting = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
+        display_errors = "stderr";
+        "opcache.enable_cli" = "1";
+        "opcache.interned_strings_buffer" = "8";
+        "opcache.max_accelerated_files" = "10000";
+        "opcache.memory_consumption" = "128";
+        "opcache.revalidate_freq" = "1";
+        "opcache.fast_shutdown" = "1";
+        "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
+        catch_workers_output = "yes";
+      };
+      description = ''
+        Options for PHP's php.ini file for nextcloud.
+      '';
+    };
+
+    poolSettings = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool ]);
+      default = {
+        "pm" = "dynamic";
+        "pm.max_children" = "32";
+        "pm.start_servers" = "2";
+        "pm.min_spare_servers" = "2";
+        "pm.max_spare_servers" = "4";
+        "pm.max_requests" = "500";
+      };
+      description = ''
+        Options for nextcloud's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+      '';
+    };
+
+    poolConfig = mkOption {
+      type = types.nullOr types.lines;
+      default = null;
+      description = ''
+        Options for nextcloud's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+      '';
+    };
+
+    config = {
+      dbtype = mkOption {
+        type = types.enum [ "sqlite" "pgsql" "mysql" ];
+        default = "sqlite";
+        description = "Database type.";
+      };
+      dbname = mkOption {
+        type = types.nullOr types.str;
+        default = "nextcloud";
+        description = "Database name.";
+      };
+      dbuser = mkOption {
+        type = types.nullOr types.str;
+        default = "nextcloud";
+        description = "Database user.";
+      };
+      dbpass = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Database password.  Use <literal>dbpassFile</literal> to avoid this
+          being world-readable in the <literal>/nix/store</literal>.
+        '';
+      };
+      dbpassFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The full path to a file that contains the database password.
+        '';
+      };
+      dbhost = mkOption {
+        type = types.nullOr types.str;
+        default = "localhost";
+        description = ''
+          Database host.
+
+          Note: for using Unix authentication with PostgreSQL, this should be
+          set to <literal>/run/postgresql</literal>.
+        '';
+      };
+      dbport = mkOption {
+        type = with types; nullOr (either int str);
+        default = null;
+        description = "Database port.";
+      };
+      dbtableprefix = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Table prefix in Nextcloud database.";
+      };
+      adminuser = mkOption {
+        type = types.str;
+        default = "root";
+        description = "Admin username.";
+      };
+      adminpass = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Admin password.  Use <literal>adminpassFile</literal> to avoid this
+          being world-readable in the <literal>/nix/store</literal>.
+        '';
+      };
+      adminpassFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The full path to a file that contains the admin's password.
+        '';
+      };
+
+      extraTrustedDomains = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Trusted domains, from which the nextcloud installation will be
+          acessible.  You don't need to add
+          <literal>services.nextcloud.hostname</literal> here.
+        '';
+      };
+
+      trustedProxies = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Trusted proxies, to provide if the nextcloud installation is being
+          proxied to secure against e.g. spoofing.
+        '';
+      };
+
+      overwriteProtocol = mkOption {
+        type = types.nullOr (types.enum [ "http" "https" ]);
+        default = null;
+        example = "https";
+
+        description = ''
+          Force Nextcloud to always use HTTPS i.e. for link generation. Nextcloud
+          uses the currently used protocol by default, but when behind a reverse-proxy,
+          it may use <literal>http</literal> for everything although Nextcloud
+          may be served via HTTPS.
+        '';
+      };
+    };
+
+    caching = {
+      apcu = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to load the APCu module into PHP.
+        '';
+      };
+      redis = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to load the Redis module into PHP.
+          You still need to enable Redis in your config.php.
+          See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+        '';
+      };
+      memcached = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to load the Memcached module into PHP.
+          You still need to enable Memcached in your config.php.
+          See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+        '';
+      };
+    };
+    autoUpdateApps = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Run regular auto update of all apps installed from the nextcloud app store.
+        '';
+      };
+      startAt = mkOption {
+        type = with types; either str (listOf str);
+        default = "05:00:00";
+        example = "Sun 14:00:00";
+        description = ''
+          When to run the update. See `systemd.services.&lt;name&gt;.startAt`.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    { assertions = let acfg = cfg.config; in [
+        { assertion = !(acfg.dbpass != null && acfg.dbpassFile != null);
+          message = "Please specify no more than one of dbpass or dbpassFile";
+        }
+        { assertion = ((acfg.adminpass != null || acfg.adminpassFile != null)
+            && !(acfg.adminpass != null && acfg.adminpassFile != null));
+          message = "Please specify exactly one of adminpass or adminpassFile";
+        }
+      ];
+
+      warnings = []
+        ++ (optional (cfg.poolConfig != null) ''
+          Using config.services.nextcloud.poolConfig is deprecated and will become unsupported in a future release.
+          Please migrate your configuration to config.services.nextcloud.poolSettings.
+        '')
+        ++ (optional (versionOlder cfg.package.version "18") ''
+          A legacy Nextcloud install (from before NixOS 20.03) may be installed.
+
+          You're currently deploying an older version of Nextcloud. This may be needed
+          since Nextcloud doesn't allow major version upgrades that skip multiple
+          versions (i.e. an upgrade from 16 is possible to 17, but not 16 to 18).
+
+          It is assumed that Nextcloud will be upgraded from version 16 to 17.
+
+           * If this is a fresh install, there will be no upgrade to do now.
+
+           * If this server already had Nextcloud installed, first deploy this to your
+             server, and wait until the upgrade to 17 is finished.
+
+          Then, set `services.nextcloud.package` to `pkgs.nextcloud18` to upgrade to
+          Nextcloud version 18.
+        '');
+
+      services.nextcloud.package = with pkgs;
+        mkDefault (
+          if pkgs ? nextcloud
+            then throw ''
+              The `pkgs.nextcloud`-attribute has been removed. If it's supposed to be the default
+              nextcloud defined in an overlay, please set `services.nextcloud.package` to
+              `pkgs.nextcloud`.
+            ''
+          else if versionOlder stateVersion "20.03" then nextcloud17
+          else nextcloud18
+        );
+    }
+
+    { systemd.timers.nextcloud-cron = {
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnBootSec = "5m";
+        timerConfig.OnUnitActiveSec = "15m";
+        timerConfig.Unit = "nextcloud-cron.service";
+      };
+
+      systemd.services = {
+        nextcloud-setup = let
+          c = cfg.config;
+          writePhpArrary = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]";
+          overrideConfig = pkgs.writeText "nextcloud-config.php" ''
+            <?php
+            ${optionalString (c.dbpassFile != null) ''
+              function nix_read_pwd() {
+                $file = "${c.dbpassFile}";
+                if (!file_exists($file)) {
+                  throw new \RuntimeException(sprintf(
+                    "Cannot start Nextcloud, dbpass file %s set by NixOS doesn't exist!",
+                    $file
+                  ));
+                }
+
+                return trim(file_get_contents($file));
+              }
+            ''}
+            $CONFIG = [
+              'apps_paths' => [
+                [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
+                [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ],
+              ],
+              'datadirectory' => '${cfg.home}/data',
+              'skeletondirectory' => '${cfg.skeletonDirectory}',
+              ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
+              'log_type' => 'syslog',
+              'log_level' => '${builtins.toString cfg.logLevel}',
+              ${optionalString (c.overwriteProtocol != null) "'overwriteprotocol' => '${c.overwriteProtocol}',"}
+              ${optionalString (c.dbname != null) "'dbname' => '${c.dbname}',"}
+              ${optionalString (c.dbhost != null) "'dbhost' => '${c.dbhost}',"}
+              ${optionalString (c.dbport != null) "'dbport' => '${toString c.dbport}',"}
+              ${optionalString (c.dbuser != null) "'dbuser' => '${c.dbuser}',"}
+              ${optionalString (c.dbtableprefix != null) "'dbtableprefix' => '${toString c.dbtableprefix}',"}
+              ${optionalString (c.dbpass != null) "'dbpassword' => '${c.dbpass}',"}
+              ${optionalString (c.dbpassFile != null) "'dbpassword' => nix_read_pwd(),"}
+              'dbtype' => '${c.dbtype}',
+              'trusted_domains' => ${writePhpArrary ([ cfg.hostName ] ++ c.extraTrustedDomains)},
+              'trusted_proxies' => ${writePhpArrary (c.trustedProxies)},
+            ];
+          '';
+          occInstallCmd = let
+            dbpass = if c.dbpassFile != null
+              then ''"$(<"${toString c.dbpassFile}")"''
+              else if c.dbpass != null
+              then ''"${toString c.dbpass}"''
+              else null;
+            adminpass = if c.adminpassFile != null
+              then ''"$(<"${toString c.adminpassFile}")"''
+              else ''"${toString c.adminpass}"'';
+            installFlags = concatStringsSep " \\\n    "
+              (mapAttrsToList (k: v: "${k} ${toString v}") {
+              "--database" = ''"${c.dbtype}"'';
+              # The following attributes are optional depending on the type of
+              # database.  Those that evaluate to null on the left hand side
+              # will be omitted.
+              ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
+              ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
+              ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"'';
+              ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
+              ${if (any (x: x != null) [c.dbpass c.dbpassFile])
+                 then "--database-pass" else null} = dbpass;
+              ${if c.dbtableprefix != null
+                then "--database-table-prefix" else null} = ''"${toString c.dbtableprefix}"'';
+              "--admin-user" = ''"${c.adminuser}"'';
+              "--admin-pass" = adminpass;
+              "--data-dir" = ''"${cfg.home}/data"'';
+            });
+          in ''
+            ${occ}/bin/nextcloud-occ maintenance:install \
+                ${installFlags}
+          '';
+          occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
+            (i: v: ''
+              ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
+                ${toString i} --value="${toString v}"
+            '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains));
+
+        in {
+          wantedBy = [ "multi-user.target" ];
+          before = [ "phpfpm-nextcloud.service" ];
+          path = [ occ ];
+          script = ''
+            chmod og+x ${cfg.home}
+            ln -sf ${cfg.package}/apps ${cfg.home}/
+            mkdir -p ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps
+            ln -sf ${overrideConfig} ${cfg.home}/config/override.config.php
+
+            chown -R nextcloud:nginx ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps
+
+            # Do not install if already installed
+            if [[ ! -e ${cfg.home}/config/config.php ]]; then
+              ${occInstallCmd}
+            fi
+
+            ${occ}/bin/nextcloud-occ upgrade
+
+            ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
+            ${occSetTrustedDomainsCmd}
+          '';
+          serviceConfig.Type = "oneshot";
+        };
+        nextcloud-cron = {
+          environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+          serviceConfig.Type = "oneshot";
+          serviceConfig.User = "nextcloud";
+          serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${cfg.package}/cron.php";
+        };
+        nextcloud-update-plugins = mkIf cfg.autoUpdateApps.enable {
+          serviceConfig.Type = "oneshot";
+          serviceConfig.ExecStart = "${occ}/bin/nextcloud-occ app:update --all";
+          serviceConfig.User = "nextcloud";
+          startAt = cfg.autoUpdateApps.startAt;
+        };
+      };
+
+      services.phpfpm = {
+        pools.nextcloud = {
+          user = "nextcloud";
+          group = "nginx";
+          phpOptions = phpOptionsStr;
+          phpPackage = phpPackage;
+          phpEnv = {
+            NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+            PATH = "/run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin";
+          };
+          settings = mapAttrs (name: mkDefault) {
+            "listen.owner" = "nginx";
+            "listen.group" = "nginx";
+          } // cfg.poolSettings;
+          extraConfig = cfg.poolConfig;
+        };
+      };
+
+      users.extraUsers.nextcloud = {
+        home = "${cfg.home}";
+        group = "nginx";
+        createHome = true;
+      };
+
+      environment.systemPackages = [ occ ];
+    }
+
+    (mkIf cfg.nginx.enable {
+      services.nginx = {
+        enable = true;
+        virtualHosts = {
+          ${cfg.hostName} = {
+            root = cfg.package;
+            locations = {
+              "= /robots.txt" = {
+                priority = 100;
+                extraConfig = ''
+                  allow all;
+                  log_not_found off;
+                  access_log off;
+                '';
+              };
+              "/" = {
+                priority = 200;
+                extraConfig = "rewrite ^ /index.php;";
+              };
+              "~ ^/store-apps" = {
+                priority = 201;
+                extraConfig = "root ${cfg.home};";
+              };
+              "= /.well-known/carddav" = {
+                priority = 210;
+                extraConfig = "return 301 $scheme://$host/remote.php/dav;";
+              };
+              "= /.well-known/caldav" = {
+                priority = 210;
+                extraConfig = "return 301 $scheme://$host/remote.php/dav;";
+              };
+              "~ ^\\/(?:build|tests|config|lib|3rdparty|templates|data)\\/" = {
+                priority = 300;
+                extraConfig = "deny all;";
+              };
+              "~ ^\\/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
+                priority = 300;
+                extraConfig = "deny all;";
+              };
+              "~ ^\\/(?:index|remote|public|cron|core/ajax\\/update|status|ocs\\/v[12]|updater\\/.+|ocs-provider\\/.+|ocm-provider\\/.+)\\.php(?:$|\\/)" = {
+                priority = 500;
+                extraConfig = ''
+                  include ${config.services.nginx.package}/conf/fastcgi.conf;
+                  fastcgi_split_path_info ^(.+\.php)(\\/.*)$;
+                  try_files $fastcgi_script_name =404;
+                  fastcgi_param PATH_INFO $fastcgi_path_info;
+                  fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
+                  fastcgi_param modHeadersAvailable true;
+                  fastcgi_param front_controller_active true;
+                  fastcgi_pass unix:${fpm.socket};
+                  fastcgi_intercept_errors on;
+                  fastcgi_request_buffering off;
+                  fastcgi_read_timeout 120s;
+                '';
+              };
+              "~ ^\\/(?:updater|ocs-provider|ocm-provider)(?:$|\\/)".extraConfig = ''
+                try_files $uri/ =404;
+                index index.php;
+              '';
+              "~ \\.(?:css|js|woff2?|svg|gif)$".extraConfig = ''
+                try_files $uri /index.php$request_uri;
+                add_header Cache-Control "public, max-age=15778463";
+                add_header X-Content-Type-Options nosniff;
+                add_header X-XSS-Protection "1; mode=block";
+                add_header X-Robots-Tag none;
+                add_header X-Download-Options noopen;
+                add_header X-Permitted-Cross-Domain-Policies none;
+                add_header X-Frame-Options sameorigin;
+                add_header Referrer-Policy no-referrer;
+                access_log off;
+              '';
+              "~ \\.(?:png|html|ttf|ico|jpg|jpeg|bcmap|mp4|webm)$".extraConfig = ''
+                try_files $uri /index.php$request_uri;
+                access_log off;
+              '';
+            };
+            extraConfig = ''
+              add_header X-Content-Type-Options nosniff;
+              add_header X-XSS-Protection "1; mode=block";
+              add_header X-Robots-Tag none;
+              add_header X-Download-Options noopen;
+              add_header X-Permitted-Cross-Domain-Policies none;
+              add_header X-Frame-Options sameorigin;
+              add_header Referrer-Policy no-referrer;
+              add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
+              error_page 403 /core/templates/403.php;
+              error_page 404 /core/templates/404.php;
+              client_max_body_size ${cfg.maxUploadSize};
+              fastcgi_buffers 64 4K;
+              fastcgi_hide_header X-Powered-By;
+              gzip on;
+              gzip_vary on;
+              gzip_comp_level 4;
+              gzip_min_length 256;
+              gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
+              gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
+
+              ${optionalString cfg.webfinger ''
+                rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
+                rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
+              ''}
+            '';
+          };
+        };
+      };
+    })
+  ]);
+
+  meta.doc = ./nextcloud.xml;
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml b/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml
new file mode 100644
index 000000000000..fc454f8ba254
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/nextcloud.xml
@@ -0,0 +1,165 @@
+<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-nextcloud">
+ <title>Nextcloud</title>
+ <para>
+  <link xlink:href="https://nextcloud.com/">Nextcloud</link> is an open-source,
+  self-hostable cloud platform. The server setup can be automated using
+  <link linkend="opt-services.nextcloud.enable">services.nextcloud</link>. A
+  desktop client is packaged at <literal>pkgs.nextcloud-client</literal>.
+ </para>
+ <section xml:id="module-services-nextcloud-basic-usage">
+  <title>Basic usage</title>
+
+  <para>
+   Nextcloud is a PHP-based application which requires an HTTP server
+   (<literal><link linkend="opt-services.nextcloud.enable">services.nextcloud</link></literal>
+   optionally supports
+   <literal><link linkend="opt-services.nginx.enable">services.nginx</link></literal>)
+   and a database (it's recommended to use
+   <literal><link linkend="opt-services.postgresql.enable">services.postgresql</link></literal>).
+  </para>
+
+  <para>
+   A very basic configuration may look like this:
+<programlisting>{ pkgs, ... }:
+{
+  services.nextcloud = {
+    <link linkend="opt-services.nextcloud.enable">enable</link> = true;
+    <link linkend="opt-services.nextcloud.hostName">hostName</link> = "nextcloud.tld";
+    <link linkend="opt-services.nextcloud.nginx.enable">nginx.enable</link> = true;
+    config = {
+      <link linkend="opt-services.nextcloud.config.dbtype">dbtype</link> = "pgsql";
+      <link linkend="opt-services.nextcloud.config.dbuser">dbuser</link> = "nextcloud";
+      <link linkend="opt-services.nextcloud.config.dbhost">dbhost</link> = "/run/postgresql"; # nextcloud will add /.s.PGSQL.5432 by itself
+      <link linkend="opt-services.nextcloud.config.dbname">dbname</link> = "nextcloud";
+      <link linkend="opt-services.nextcloud.config.adminpassFile">adminpassFile</link> = "/path/to/admin-pass-file";
+      <link linkend="opt-services.nextcloud.config.adminuser">adminuser</link> = "root";
+    };
+  };
+
+  services.postgresql = {
+    <link linkend="opt-services.postgresql.enable">enable</link> = true;
+    <link linkend="opt-services.postgresql.ensureDatabases">ensureDatabases</link> = [ "nextcloud" ];
+    <link linkend="opt-services.postgresql.ensureUsers">ensureUsers</link> = [
+     { name = "nextcloud";
+       ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
+     }
+    ];
+  };
+
+  # ensure that postgres is running *before* running the setup
+  systemd.services."nextcloud-setup" = {
+    requires = ["postgresql.service"];
+    after = ["postgresql.service"];
+  };
+
+  <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
+}</programlisting>
+  </para>
+
+  <para>
+   The options <literal>hostName</literal> and <literal>nginx.enable</literal>
+   are used internally to configure an HTTP server using
+   <literal><link xlink:href="https://php-fpm.org/">PHP-FPM</link></literal>
+   and <literal>nginx</literal>. The <literal>config</literal> attribute set is
+   used by the imperative installer and all values are written to an additional file
+   to ensure that changes can be applied by changing the module's options.
+  </para>
+
+  <para>
+   In case the application serves multiple domains (those are checked with
+   <literal><link xlink:href="http://php.net/manual/en/reserved.variables.server.php">$_SERVER['HTTP_HOST']</link></literal>)
+   it's needed to add them to
+   <literal><link linkend="opt-services.nextcloud.config.extraTrustedDomains">services.nextcloud.config.extraTrustedDomains</link></literal>.
+  </para>
+
+  <para>
+   Auto updates for Nextcloud apps can be enabled using
+   <literal><link linkend="opt-services.nextcloud.autoUpdateApps.enable">services.nextcloud.autoUpdateApps</link></literal>.
+</para>
+
+ </section>
+ <section xml:id="module-services-nextcloud-pitfalls-during-upgrade">
+  <title>Pitfalls</title>
+
+  <para>
+   Unfortunately Nextcloud appears to be very stateful when it comes to
+   managing its own configuration. The config file lives in the home directory
+   of the <literal>nextcloud</literal> user (by default
+   <literal>/var/lib/nextcloud/config/config.php</literal>) and is also used to
+   track several states of the application (e.g. whether installed or not).
+  </para>
+
+  <para>
+   All configuration parameters are also stored in
+   <literal>/var/lib/nextcloud/config/override.config.php</literal> which is generated by
+   the module and linked from the store to ensure that all values from <literal>config.php</literal>
+   can be modified by the module.
+   However <literal>config.php</literal> manages the application's state and shouldn't be touched
+   manually because of that.
+  </para>
+
+  <warning>
+   <para>Don't delete <literal>config.php</literal>! This file
+   tracks the application's state and a deletion can cause unwanted
+   side-effects!</para>
+  </warning>
+
+  <warning>
+   <para>Don't rerun <literal>nextcloud-occ
+   maintenance:install</literal>! This command tries to install the application
+   and can cause unwanted side-effects!</para>
+  </warning>
+
+  <para>
+   Nextcloud doesn't allow to move more than one major-version forward. If you're e.g. on
+   <literal>v16</literal>, you cannot upgrade to <literal>v18</literal>, you need to upgrade to
+   <literal>v17</literal> first. This is ensured automatically as long as the
+   <link linkend="opt-system.stateVersion">stateVersion</link> is declared properly. In that case
+   the oldest version available (one major behind the one from the previous NixOS
+   release) will be selected by default and the module will generate a warning that reminds
+   the user to upgrade to latest Nextcloud <emphasis>after</emphasis> that deploy.
+  </para>
+ </section>
+
+ <section xml:id="module-services-nextcloud-maintainer-info">
+  <title>Maintainer information</title>
+
+  <para>
+   As stated in the previous paragraph, we must provide a clean upgrade-path for Nextcloud
+   since it cannot move more than one major version forward on a single upgrade. This chapter
+   adds some notes how Nextcloud updates should be rolled out in the future.
+  </para>
+
+  <para>
+   While minor and patch-level updates are no problem and can be done directly in the
+   package-expression (and should be backported to supported stable branches after that),
+   major-releases should be added in a new attribute (e.g. Nextcloud <literal>v19.0.0</literal>
+   should be available in <literal>nixpkgs</literal> as <literal>pkgs.nextcloud19</literal>).
+   To provide simple upgrade paths it's generally useful to backport those as well to stable
+   branches. As long as the package-default isn't altered, this won't break existing setups.
+   After that, the versioning-warning in the <literal>nextcloud</literal>-module should be
+   updated to make sure that the
+   <link linkend="opt-services.nextcloud.package">package</link>-option selects the latest version
+   on fresh setups.
+  </para>
+
+  <para>
+   If major-releases will be abandoned by upstream, we should check first if those are needed
+   in NixOS for a safe upgrade-path before removing those. In that case we shold keep those
+   packages, but mark them as insecure in an expression like this (in
+   <literal>&lt;nixpkgs/pkgs/servers/nextcloud/default.nix&gt;</literal>):
+<programlisting>/* ... */
+{
+  nextcloud17 = generic {
+    version = "17.0.x";
+    sha256 = "0000000000000000000000000000000000000000000000000000";
+    insecure = true;
+  };
+}</programlisting>
+  </para>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/web-apps/nexus.nix b/nixpkgs/nixos/modules/services/web-apps/nexus.nix
new file mode 100644
index 000000000000..d4d507362c97
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/nexus.nix
@@ -0,0 +1,134 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.nexus;
+
+in
+
+{
+  options = {
+    services.nexus = {
+      enable = mkEnableOption "Sonatype Nexus3 OSS service";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.nexus;
+        description = "Package which runs Nexus3";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "nexus";
+        description = "User which runs Nexus3.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nexus";
+        description = "Group which runs Nexus3.";
+      };
+
+      home = mkOption {
+        type = types.str;
+        default = "/var/lib/sonatype-work";
+        description = "Home directory of the Nexus3 instance.";
+      };
+
+      listenAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1";
+        description = "Address to listen on.";
+      };
+
+      listenPort = mkOption {
+        type = types.int;
+        default = 8081;
+        description = "Port to listen on.";
+      };
+
+      jvmOpts = mkOption {
+        type = types.lines;
+        default = ''
+          -Xms1200M
+          -Xmx1200M
+          -XX:MaxDirectMemorySize=2G
+          -XX:+UnlockDiagnosticVMOptions
+          -XX:+UnsyncloadClass
+          -XX:+LogVMOutput
+          -XX:LogFile=${cfg.home}/nexus3/log/jvm.log
+          -XX:-OmitStackTraceInFastThrow
+          -Djava.net.preferIPv4Stack=true
+          -Dkaraf.home=${cfg.package}
+          -Dkaraf.base=${cfg.package}
+          -Dkaraf.etc=${cfg.package}/etc/karaf
+          -Djava.util.logging.config.file=${cfg.package}/etc/karaf/java.util.logging.properties
+          -Dkaraf.data=${cfg.home}/nexus3
+          -Djava.io.tmpdir=${cfg.home}/nexus3/tmp
+          -Dkaraf.startLocalConsole=false
+          -Djava.endorsed.dirs=${cfg.package}/lib/endorsed
+        '';
+
+        description = ''
+          Options for the JVM written to `nexus.jvmopts`.
+          Please refer to the docs (https://help.sonatype.com/repomanager3/installation/configuring-the-runtime-environment)
+          for further information.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.${cfg.user} = {
+      isSystemUser = true;
+      group = cfg.group;
+      home = cfg.home;
+      createHome = true;
+    };
+
+    users.groups.${cfg.group} = {};
+
+    systemd.services.nexus = {
+      description = "Sonatype Nexus3";
+
+      wantedBy = [ "multi-user.target" ];
+
+      path = [ cfg.home ];
+
+      environment = {
+        NEXUS_USER = cfg.user;
+        NEXUS_HOME = cfg.home;
+
+        VM_OPTS_FILE = pkgs.writeText "nexus.vmoptions" cfg.jvmOpts;
+      };
+
+      preStart = ''
+        mkdir -p ${cfg.home}/nexus3/etc
+
+        if [ ! -f ${cfg.home}/nexus3/etc/nexus.properties ]; then
+          echo "# Jetty section" > ${cfg.home}/nexus3/etc/nexus.properties
+          echo "application-port=${toString cfg.listenPort}" >> ${cfg.home}/nexus3/etc/nexus.properties
+          echo "application-host=${toString cfg.listenAddress}" >> ${cfg.home}/nexus3/etc/nexus.properties
+        else
+          sed 's/^application-port=.*/application-port=${toString cfg.listenPort}/' -i ${cfg.home}/nexus3/etc/nexus.properties
+          sed 's/^# application-port=.*/application-port=${toString cfg.listenPort}/' -i ${cfg.home}/nexus3/etc/nexus.properties
+          sed 's/^application-host=.*/application-host=${toString cfg.listenAddress}/' -i ${cfg.home}/nexus3/etc/nexus.properties
+          sed 's/^# application-host=.*/application-host=${toString cfg.listenAddress}/' -i ${cfg.home}/nexus3/etc/nexus.properties
+        fi
+      '';
+
+      script = "${cfg.package}/bin/nexus run";
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        PrivateTmp = true;
+        LimitNOFILE = 102642;
+      };
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ ironpinguin ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
new file mode 100644
index 000000000000..ad70ba70bbef
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
@@ -0,0 +1,75 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.pgpkeyserver-lite;
+  sksCfg = config.services.sks;
+
+  webPkg = cfg.package;
+
+in
+
+{
+
+  options = {
+
+    services.pgpkeyserver-lite = {
+
+      enable = mkEnableOption "pgpkeyserver-lite on a nginx vHost proxying to a gpg keyserver";
+
+      package = mkOption {
+        default = pkgs.pgpkeyserver-lite;
+        defaultText = "pkgs.pgpkeyserver-lite";
+        type = types.package;
+        description = "
+          Which webgui derivation to use.
+        ";
+      };
+
+      hostname = mkOption {
+        type = types.str;
+        description = "
+          Which hostname to set the vHost to that is proxying to sks.
+        ";
+      };     
+
+      hkpAddress = mkOption {
+        default = builtins.head sksCfg.hkpAddress;
+        type = types.str;
+        description = "
+          Wich ip address the sks-keyserver is listening on.
+        ";
+      };
+
+      hkpPort = mkOption {
+        default = sksCfg.hkpPort;
+        type = types.int;
+        description = "
+          Which port the sks-keyserver is listening on.
+        ";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    services.nginx.enable = true;
+
+    services.nginx.virtualHosts = let
+      hkpPort = builtins.toString cfg.hkpPort;
+    in {
+      ${cfg.hostname} = {
+        root = webPkg;
+        locations = {
+          "/pks".extraConfig = ''
+            proxy_pass         http://${cfg.hkpAddress}:${hkpPort};
+            proxy_pass_header  Server;
+            add_header         Via "1.1 ${cfg.hostname}";
+          '';
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/restya-board.nix b/nixpkgs/nixos/modules/services/web-apps/restya-board.nix
new file mode 100644
index 000000000000..9d0a3f65253e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/restya-board.nix
@@ -0,0 +1,383 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+# TODO: are these php-packages needed?
+#imagick
+#php-geoip -> php.ini: extension = geoip.so
+#expat
+
+let
+  cfg = config.services.restya-board;
+  fpm = config.services.phpfpm.pools.${poolName};
+
+  runDir = "/run/restya-board";
+
+  poolName = "restya-board";
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.restya-board = {
+
+      enable = mkEnableOption "restya-board";
+
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/restya-board";
+        example = "/var/lib/restya-board";
+        description = ''
+          Data of the application.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "restya-board";
+        example = "restya-board";
+        description = ''
+          User account under which the web-application runs.
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nginx";
+        example = "nginx";
+        description = ''
+          Group account under which the web-application runs.
+        '';
+      };
+
+      virtualHost = {
+        serverName = mkOption {
+          type = types.str;
+          default = "restya.board";
+          description = ''
+            Name of the nginx virtualhost to use.
+          '';
+        };
+
+        listenHost = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = ''
+            Listen address for the virtualhost to use.
+          '';
+        };
+
+        listenPort = mkOption {
+          type = types.int;
+          default = 3000;
+          description = ''
+            Listen port for the virtualhost to use.
+          '';
+        };
+      };
+
+      database = {
+        host = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Host of the database. Leave 'null' to use a local PostgreSQL database.
+            A local PostgreSQL database is initialized automatically.
+          '';
+        };
+
+        port = mkOption {
+          type = types.nullOr types.int;
+          default = 5432;
+          description = ''
+            The database's port.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "restya_board";
+          description = ''
+            Name of the database. The database must exist.
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "restya_board";
+          description = ''
+            The database user. The user must exist and have access to
+            the specified database.
+          '';
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          description = ''
+            The database user's password. 'null' if no password is set.
+          '';
+        };
+      };
+
+      email = {
+        server = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "localhost";
+          description = ''
+            Hostname to send outgoing mail. Null to use the system MTA.
+          '';
+        };
+
+        port = mkOption {
+          type = types.int;
+          default = 25;
+          description = ''
+            Port used to connect to SMTP server.
+          '';
+        };
+
+        login = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            SMTP authentication login used when sending outgoing mail.
+          '';
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            SMTP authentication password used when sending outgoing mail.
+
+            ATTENTION: The password is stored world-readable in the nix-store!
+          '';
+        };
+      };
+
+      timezone = mkOption {
+        type = types.lines;
+        default = "GMT";
+        description = ''
+          Timezone the web-app runs in.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.phpfpm.pools = {
+      ${poolName} = {
+        inherit (cfg) user group;
+
+        phpOptions = ''
+          date.timezone = "CET"
+
+          ${optionalString (cfg.email.server != null) ''
+            SMTP = ${cfg.email.server}
+            smtp_port = ${toString cfg.email.port}
+            auth_username = ${cfg.email.login}
+            auth_password = ${cfg.email.password}
+          ''}
+        '';
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = "nginx";
+          "listen.group" = "nginx";
+          "listen.mode" = "0600";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 10;
+          "pm.min_spare_servers" = 5;
+          "pm.max_spare_servers" = 20;
+          "pm.max_requests" = 500;
+          "catch_workers_output" = 1;
+        };
+      };
+    };
+
+    services.nginx.enable = true;
+    services.nginx.virtualHosts.${cfg.virtualHost.serverName} = {
+      listen = [ { addr = cfg.virtualHost.listenHost; port = cfg.virtualHost.listenPort; } ];
+      serverName = cfg.virtualHost.serverName;
+      root = runDir;
+      extraConfig = ''
+        index index.html index.php;
+
+        gzip on;
+
+        gzip_comp_level 6;
+        gzip_min_length  1100;
+        gzip_buffers 16 8k;
+        gzip_proxied any;
+        gzip_types text/plain application/xml text/css text/js text/xml application/x-javascript text/javascript application/json application/xml+rss;
+
+        client_max_body_size 300M;
+
+        rewrite ^/oauth/authorize$ /server/php/authorize.php last;
+        rewrite ^/oauth_callback/([a-zA-Z0-9_\.]*)/([a-zA-Z0-9_\.]*)$ /server/php/oauth_callback.php?plugin=$1&code=$2 last;
+        rewrite ^/download/([0-9]*)/([a-zA-Z0-9_\.]*)$ /server/php/download.php?id=$1&hash=$2 last;
+        rewrite ^/ical/([0-9]*)/([0-9]*)/([a-z0-9]*).ics$ /server/php/ical.php?board_id=$1&user_id=$2&hash=$3 last;
+        rewrite ^/api/(.*)$ /server/php/R/r.php?_url=$1&$args last;
+        rewrite ^/api_explorer/api-docs/$ /client/api_explorer/api-docs/index.php last;
+      '';
+
+      locations."/".root = "${runDir}/client";
+
+      locations."~ \\.php$" = {
+        tryFiles = "$uri =404";
+        extraConfig = ''
+          include ${pkgs.nginx}/conf/fastcgi_params;
+          fastcgi_pass    unix:${fpm.socket};
+          fastcgi_index   index.php;
+          fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
+          fastcgi_param   PHP_VALUE "upload_max_filesize=9G \n post_max_size=9G \n max_execution_time=200 \n max_input_time=200 \n memory_limit=256M";
+        '';
+      };
+
+      locations."~* \\.(css|js|less|html|ttf|woff|jpg|jpeg|gif|png|bmp|ico)" = {
+        root = "${runDir}/client";
+        extraConfig = ''
+          if (-f $request_filename) {
+                  break;
+          }
+          rewrite ^/img/([a-zA-Z_]*)/([a-zA-Z_]*)/([a-zA-Z0-9_\.]*)$ /server/php/image.php?size=$1&model=$2&filename=$3 last;
+          add_header        Cache-Control public;
+          add_header        Cache-Control must-revalidate;
+          expires           7d;
+        '';
+      };
+    };
+
+    systemd.services.restya-board-init = {
+      description = "Restya board initialization";
+      serviceConfig.Type = "oneshot";
+      serviceConfig.RemainAfterExit = true;
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "postgresql.service" ];
+      after = [ "network.target" "postgresql.service" ];
+
+      script = ''
+        rm -rf "${runDir}"
+        mkdir -m 750 -p "${runDir}"
+        cp -r "${pkgs.restya-board}/"* "${runDir}"
+        sed -i "s/@restya.com/@${cfg.virtualHost.serverName}/g" "${runDir}/sql/restyaboard_with_empty_data.sql"
+        rm -rf "${runDir}/media"
+        rm -rf "${runDir}/client/img"
+        chmod -R 0750 "${runDir}"
+
+        sed -i "s@^php@${config.services.phpfpm.phpPackage}/bin/php@" "${runDir}/server/php/shell/"*.sh
+
+        ${if (cfg.database.host == null) then ''
+          sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', 'localhost');/g" "${runDir}/server/php/config.inc.php"
+          sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', 'restya');/g" "${runDir}/server/php/config.inc.php"
+        '' else ''
+          sed -i "s/^.*'R_DB_HOST'.*$/define('R_DB_HOST', '${cfg.database.host}');/g" "${runDir}/server/php/config.inc.php"
+          sed -i "s/^.*'R_DB_PASSWORD'.*$/define('R_DB_PASSWORD', ${if cfg.database.passwordFile == null then "''" else "'file_get_contents(${cfg.database.passwordFile})'"});/g" "${runDir}/server/php/config.inc.php
+        ''}
+        sed -i "s/^.*'R_DB_PORT'.*$/define('R_DB_PORT', '${toString cfg.database.port}');/g" "${runDir}/server/php/config.inc.php"
+        sed -i "s/^.*'R_DB_NAME'.*$/define('R_DB_NAME', '${cfg.database.name}');/g" "${runDir}/server/php/config.inc.php"
+        sed -i "s/^.*'R_DB_USER'.*$/define('R_DB_USER', '${cfg.database.user}');/g" "${runDir}/server/php/config.inc.php"
+
+        chmod 0400 "${runDir}/server/php/config.inc.php"
+
+        ln -sf "${cfg.dataDir}/media" "${runDir}/media"
+        ln -sf "${cfg.dataDir}/client/img" "${runDir}/client/img"
+
+        chmod g+w "${runDir}/tmp/cache"
+        chown -R "${cfg.user}"."${cfg.group}" "${runDir}"
+
+
+        mkdir -m 0750 -p "${cfg.dataDir}"
+        mkdir -m 0750 -p "${cfg.dataDir}/media"
+        mkdir -m 0750 -p "${cfg.dataDir}/client/img"
+        cp -r "${pkgs.restya-board}/media/"* "${cfg.dataDir}/media"
+        cp -r "${pkgs.restya-board}/client/img/"* "${cfg.dataDir}/client/img"
+        chown "${cfg.user}"."${cfg.group}" "${cfg.dataDir}"
+        chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/media"
+        chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/client/img"
+
+        ${optionalString (cfg.database.host == null) ''
+          if ! [ -e "${cfg.dataDir}/.db-initialized" ]; then
+            ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
+              ${config.services.postgresql.package}/bin/psql -U ${config.services.postgresql.superUser} \
+              -c "CREATE USER ${cfg.database.user} WITH ENCRYPTED PASSWORD 'restya'"
+
+            ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} \
+              ${config.services.postgresql.package}/bin/psql -U ${config.services.postgresql.superUser} \
+              -c "CREATE DATABASE ${cfg.database.name} OWNER ${cfg.database.user} ENCODING 'UTF8' TEMPLATE template0"
+
+            ${pkgs.sudo}/bin/sudo -u ${cfg.user} \
+              ${config.services.postgresql.package}/bin/psql -U ${cfg.database.user} \
+              -d ${cfg.database.name} -f "${runDir}/sql/restyaboard_with_empty_data.sql"
+
+            touch "${cfg.dataDir}/.db-initialized"
+          fi
+        ''}
+      '';
+    };
+
+    systemd.timers.restya-board = {
+      description = "restya-board scripts for e.g. email notification";
+      wantedBy = [ "timers.target" ];
+      after = [ "restya-board-init.service" ];
+      requires = [ "restya-board-init.service" ];
+      timerConfig = {
+        OnUnitInactiveSec = "60s";
+        Unit = "restya-board-timers.service";
+      };
+    };
+
+    systemd.services.restya-board-timers = {
+      description = "restya-board scripts for e.g. email notification";
+      serviceConfig.Type = "oneshot";
+      serviceConfig.User = cfg.user;
+
+      after = [ "restya-board-init.service" ];
+      requires = [ "restya-board-init.service" ];
+
+      script = ''
+        /bin/sh ${runDir}/server/php/shell/instant_email_notification.sh 2> /dev/null || true
+        /bin/sh ${runDir}/server/php/shell/periodic_email_notification.sh 2> /dev/null || true
+        /bin/sh ${runDir}/server/php/shell/imap.sh 2> /dev/null || true
+        /bin/sh ${runDir}/server/php/shell/webhook.sh 2> /dev/null || true
+        /bin/sh ${runDir}/server/php/shell/card_due_notification.sh 2> /dev/null || true
+      '';
+    };
+
+    users.users.restya-board = {
+      isSystemUser = true;
+      createHome = false;
+      home = runDir;
+      group  = "restya-board";
+    };
+    users.groups.restya-board = {};
+
+    services.postgresql.enable = mkIf (cfg.database.host == null) true;
+
+    services.postgresql.identMap = optionalString (cfg.database.host == null)
+      ''
+        restya-board-users restya-board restya_board
+      '';
+
+    services.postgresql.authentication = optionalString (cfg.database.host == null)
+      ''
+        local restya_board all ident map=restya-board-users
+      '';
+
+  };
+
+}
+
diff --git a/nixpkgs/nixos/modules/services/web-apps/selfoss.nix b/nixpkgs/nixos/modules/services/web-apps/selfoss.nix
new file mode 100644
index 000000000000..d5a660ebf289
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/selfoss.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.selfoss;
+
+  poolName = "selfoss_pool";
+
+  dataDir = "/var/lib/selfoss";
+
+  selfoss-config =
+  let
+    db_type = cfg.database.type;
+    default_port = if (db_type == "mysql") then 3306 else 5342;
+  in
+  pkgs.writeText "selfoss-config.ini" ''
+    [globals]
+    ${lib.optionalString (db_type != "sqlite") ''
+      db_type=${db_type}
+      db_host=${cfg.database.host}
+      db_database=${cfg.database.name}
+      db_username=${cfg.database.user}
+      db_password=${cfg.database.password}
+      db_port=${toString (if (cfg.database.port != null) then cfg.database.port
+                    else default_port)}
+    ''
+    }
+    ${cfg.extraConfig}
+  '';
+in
+  {
+    options = {
+      services.selfoss = {
+        enable = mkEnableOption "selfoss";
+
+        user = mkOption {
+          type = types.str;
+          default = "nginx";
+          example = "nginx";
+          description = ''
+            User account under which both the service and the web-application run.
+          '';
+        };
+
+        pool = mkOption {
+          type = types.str;
+          default = "${poolName}";
+          description = ''
+            Name of existing phpfpm pool that is used to run web-application.
+            If not specified a pool will be created automatically with
+            default values.
+          '';
+        };
+
+      database = {
+        type = mkOption {
+          type = types.enum ["pgsql" "mysql" "sqlite"];
+          default = "sqlite";
+          description = ''
+            Database to store feeds. Supported are sqlite, pgsql and mysql.
+          '';
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = ''
+            Host of the database (has no effect if type is "sqlite").
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "tt_rss";
+          description = ''
+            Name of the existing database (has no effect if type is "sqlite").
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "tt_rss";
+          description = ''
+            The database user. The user must exist and has access to
+            the specified database (has no effect if type is "sqlite").
+          '';
+        };
+
+        password = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            The database user's password (has no effect if type is "sqlite").
+          '';
+        };
+
+        port = mkOption {
+          type = types.nullOr types.int;
+          default = null;
+          description = ''
+            The database's port. If not set, the default ports will be
+            provided (5432 and 3306 for pgsql and mysql respectively)
+            (has no effect if type is "sqlite").
+          '';
+        };
+      };
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration added to config.ini
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
+      ${poolName} = {
+        user = "nginx";
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = "nginx";
+          "listen.group" = "nginx";
+          "listen.mode" = "0600";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 10;
+          "pm.min_spare_servers" = 5;
+          "pm.max_spare_servers" = 20;
+          "pm.max_requests" = 500;
+          "catch_workers_output" = 1;
+        };
+      };
+    };
+
+    systemd.services.selfoss-config = {
+      serviceConfig.Type = "oneshot";
+      script = ''
+        mkdir -m 755 -p ${dataDir}
+        cd ${dataDir}
+
+        # Delete all but the "data" folder
+        ls | grep -v data | while read line; do rm -rf $line; done || true
+
+        # Create the files
+        cp -r "${pkgs.selfoss}/"* "${dataDir}"
+        ln -sf "${selfoss-config}" "${dataDir}/config.ini"
+        chown -R "${cfg.user}" "${dataDir}"
+        chmod -R 755 "${dataDir}"
+      '';
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services.selfoss-update = {
+      serviceConfig = {
+        ExecStart = "${pkgs.php}/bin/php ${dataDir}/cliupdate.php";
+        User = "${cfg.user}";
+      };
+      startAt = "hourly";
+      after = [ "selfoss-config.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/shiori.nix b/nixpkgs/nixos/modules/services/web-apps/shiori.nix
new file mode 100644
index 000000000000..1817a2039352
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/shiori.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.shiori;
+in {
+  options = {
+    services.shiori = {
+      enable = mkEnableOption "Shiori simple bookmarks manager";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.shiori;
+        defaultText = "pkgs.shiori";
+        description = "The Shiori package to use.";
+      };
+
+      address = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          The IP address on which Shiori will listen.
+          If empty, listens on all interfaces.
+        '';
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = 8080;
+        description = "The port of the Shiori web application";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.shiori = with cfg; {
+      description = "Shiori simple bookmarks manager";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = "${package}/bin/shiori serve --address '${address}' --port '${toString port}'";
+        DynamicUser = true;
+        Environment = "SHIORI_DIR=/var/lib/shiori";
+        StateDirectory = "shiori";
+      };
+    };
+  };
+
+  meta.maintainers = with maintainers; [ minijackson ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/sogo.nix b/nixpkgs/nixos/modules/services/web-apps/sogo.nix
new file mode 100644
index 000000000000..5f30124dd68a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/sogo.nix
@@ -0,0 +1,272 @@
+{ config, pkgs, lib, ... }: with lib; let
+  cfg = config.services.sogo;
+
+  preStart = pkgs.writeShellScriptBin "sogo-prestart" ''
+    touch /etc/sogo/sogo.conf
+    chown sogo:sogo /etc/sogo/sogo.conf
+    chmod 640 /etc/sogo/sogo.conf
+
+    ${if (cfg.configReplaces != {}) then ''
+      # Insert secrets
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''export ${k}="$(cat "${v}" | tr -d '\n')"'') cfg.configReplaces)}
+
+      ${pkgs.perl}/bin/perl -p ${concatStringsSep " " (mapAttrsToList (k: v: '' -e 's/${k}/''${ENV{"${k}"}}/g;' '') cfg.configReplaces)} /etc/sogo/sogo.conf.raw > /etc/sogo/sogo.conf
+    '' else ''
+      cp /etc/sogo/sogo.conf.raw /etc/sogo/sogo.conf
+    ''}
+  '';
+
+in {
+  options.services.sogo = with types; {
+    enable = mkEnableOption "SOGo groupware";
+
+    vhostName = mkOption {
+      description = "Name of the nginx vhost";
+      type = str;
+      default = "sogo";
+    };
+
+    timezone = mkOption {
+      description = "Timezone of your SOGo instance";
+      type = str;
+      example = "America/Montreal";
+    };
+
+    language = mkOption {
+      description = "Language of SOGo";
+      type = str;
+      default = "English";
+    };
+
+    ealarmsCredFile = mkOption {
+      description = "Optional path to a credentials file for email alarms";
+      type = nullOr str;
+      default = null;
+    };
+
+    configReplaces = mkOption {
+      description = ''
+        Replacement-filepath mapping for sogo.conf.
+        Every key is replaced with the contents of the file specified as value.
+
+        In the example, every occurence of LDAP_BINDPW will be replaced with the text of the
+        specified file.
+      '';
+      type = attrsOf str;
+      default = {};
+      example = {
+        LDAP_BINDPW = "/var/lib/secrets/sogo/ldappw";
+      };
+    };
+
+    extraConfig = mkOption {
+      description = "Extra sogo.conf configuration lines";
+      type = lines;
+      default = "";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.sogo ];
+
+    environment.etc."sogo/sogo.conf.raw".text = ''
+      {
+        // Mandatory parameters
+        SOGoTimeZone = "${cfg.timezone}";
+        SOGoLanguage = "${cfg.language}";
+        // Paths
+        WOSendMail = "/run/wrappers/bin/sendmail";
+        SOGoMailSpoolPath = "/var/lib/sogo/spool";
+        SOGoZipPath = "${pkgs.zip}/bin/zip";
+        // Enable CSRF protection
+        SOGoXSRFValidationEnabled = YES;
+        // Remove dates from log (jornald does that)
+        NGLogDefaultLogEventFormatterClass = "NGLogEventFormatter";
+        // Extra config
+        ${cfg.extraConfig}
+      }
+    '';
+
+    systemd.services.sogo = {
+      description = "SOGo groupware";
+      after = [ "postgresql.service" "mysql.service" "memcached.service" "openldap.service" "dovecot2.service" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ config.environment.etc."sogo/sogo.conf.raw".source ];
+
+      environment.LDAPTLS_CACERT = "/etc/ssl/certs/ca-certificates.crt";
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStartPre = "+" + preStart + "/bin/sogo-prestart";
+        ExecStart = "${pkgs.sogo}/bin/sogod -WOLogFile - -WOPidFile /run/sogo/sogo.pid";
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RuntimeDirectory = "sogo";
+        StateDirectory = "sogo/spool";
+
+        User = "sogo";
+        Group = "sogo";
+
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+
+        LockPersonality = true;
+        RestrictRealtime = true;
+        PrivateMounts = true;
+        PrivateUsers = true;
+        MemoryDenyWriteExecute = true;
+        SystemCallFilter = "@basic-io @file-system @network-io @system-service @timer";
+        SystemCallArchitectures = "native";
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+      };
+    };
+
+    systemd.services.sogo-tmpwatch = {
+      description = "SOGo tmpwatch";
+
+      startAt = [ "hourly" ];
+      script = ''
+        SOGOSPOOL=/var/lib/sogo/spool
+
+        find "$SOGOSPOOL" -type f -user sogo -atime +23 -delete > /dev/null
+        find "$SOGOSPOOL" -mindepth 1 -type d -user sogo -empty -delete > /dev/null
+      '';
+
+      serviceConfig = {
+        Type = "oneshot";
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        StateDirectory = "sogo/spool";
+
+        User = "sogo";
+        Group = "sogo";
+
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+
+        LockPersonality = true;
+        RestrictRealtime = true;
+        PrivateMounts = true;
+        PrivateUsers = true;
+        PrivateNetwork = true;
+        SystemCallFilter = "@basic-io @file-system @system-service";
+        SystemCallArchitectures = "native";
+        RestrictAddressFamilies = "";
+      };
+    };
+
+    systemd.services.sogo-ealarms = {
+      description = "SOGo email alarms";
+
+      after = [ "postgresql.service" "mysqld.service" "memcached.service" "openldap.service" "dovecot2.service" "sogo.service" ];
+      restartTriggers = [ config.environment.etc."sogo/sogo.conf.raw".source ];
+
+      startAt = [ "minutely" ];
+
+      serviceConfig = {
+        Type = "oneshot";
+        ExecStart = "${pkgs.sogo}/bin/sogo-ealarms-notify${optionalString (cfg.ealarmsCredFile != null) " -p ${cfg.ealarmsCredFile}"}";
+
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        StateDirectory = "sogo/spool";
+
+        User = "sogo";
+        Group = "sogo";
+
+        CapabilityBoundingSet = "";
+        NoNewPrivileges = true;
+
+        LockPersonality = true;
+        RestrictRealtime = true;
+        PrivateMounts = true;
+        PrivateUsers = true;
+        MemoryDenyWriteExecute = true;
+        SystemCallFilter = "@basic-io @file-system @network-io @system-service";
+        SystemCallArchitectures = "native";
+        RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
+      };
+    };
+
+    # nginx vhost
+    services.nginx.virtualHosts."${cfg.vhostName}" = {
+      locations."/".extraConfig = ''
+        rewrite ^ https://$server_name/SOGo;
+        allow all;
+      '';
+
+      # For iOS 7
+      locations."/principals/".extraConfig = ''
+        rewrite ^ https://$server_name/SOGo/dav;
+        allow all;
+      '';
+
+      locations."^~/SOGo".extraConfig = ''
+        proxy_pass http://127.0.0.1:20000;
+        proxy_redirect http://127.0.0.1:20000 default;
+
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header Host $host;
+        proxy_set_header x-webobjects-server-protocol HTTP/1.0;
+        proxy_set_header x-webobjects-remote-host 127.0.0.1;
+        proxy_set_header x-webobjects-server-port $server_port;
+        proxy_set_header x-webobjects-server-name $server_name;
+        proxy_set_header x-webobjects-server-url $scheme://$host;
+        proxy_connect_timeout 90;
+        proxy_send_timeout 90;
+        proxy_read_timeout 90;
+        proxy_buffer_size 4k;
+        proxy_buffers 4 32k;
+        proxy_busy_buffers_size 64k;
+        proxy_temp_file_write_size 64k;
+        client_max_body_size 50m;
+        client_body_buffer_size 128k;
+        break;
+      '';
+
+      locations."/SOGo.woa/WebServerResources/".extraConfig = ''
+        alias ${pkgs.sogo}/lib/GNUstep/SOGo/WebServerResources/;
+        allow all;
+      '';
+
+      locations."/SOGo/WebServerResources/".extraConfig = ''
+        alias ${pkgs.sogo}/lib/GNUstep/SOGo/WebServerResources/;
+        allow all;
+      '';
+
+      locations."~ ^/SOGo/so/ControlPanel/Products/([^/]*)/Resources/(.*)$".extraConfig = ''
+        alias ${pkgs.sogo}/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
+      '';
+
+      locations."~ ^/SOGo/so/ControlPanel/Products/[^/]*UI/Resources/.*\\.(jpg|png|gif|css|js)$".extraConfig = ''
+        alias ${pkgs.sogo}/lib/GNUstep/SOGo/$1.SOGo/Resources/$2;
+      '';
+    };
+
+    # User and group
+    users.groups.sogo = {};
+    users.users.sogo = {
+      group = "sogo";
+      isSystemUser = true;
+      description = "SOGo service user";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/trac.nix b/nixpkgs/nixos/modules/services/web-apps/trac.nix
new file mode 100644
index 000000000000..207fb857438a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/trac.nix
@@ -0,0 +1,79 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.trac;
+
+  inherit (lib) mkEnableOption mkIf mkOption types;
+
+in {
+
+  options = {
+
+    services.trac = {
+      enable = mkEnableOption "Trac service";
+
+      listen = {
+        ip = mkOption {
+          type = types.str;
+          default = "0.0.0.0";
+          description = ''
+            IP address that Trac should listen on.
+          '';
+        };
+
+        port = mkOption {
+          type = types.port;
+          default = 8000;
+          description = ''
+            Listen port for Trac.
+          '';
+        };
+      };
+
+      dataDir = mkOption {
+        default = "/var/lib/trac";
+        type = types.path;
+        description = ''
+            The directory for storing the Trac data.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for Trac.
+        '';
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.trac = {
+      description = "Trac server";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = baseNameOf cfg.dataDir;
+        ExecStart = ''
+          ${pkgs.trac}/bin/tracd -s \
+            -b ${toString cfg.listen.ip} \
+            -p ${toString cfg.listen.port} \
+            ${cfg.dataDir}
+        '';
+      };
+      preStart = ''
+        if [ ! -e ${cfg.dataDir}/VERSION ]; then
+          ${pkgs.trac}/bin/trac-admin ${cfg.dataDir} initenv Trac "sqlite:db/trac.db"
+        fi
+      '';
+    };
+
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/trilium.nix b/nixpkgs/nixos/modules/services/web-apps/trilium.nix
new file mode 100644
index 000000000000..6f47193c62b9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/trilium.nix
@@ -0,0 +1,137 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.trilium-server;
+  configIni = pkgs.writeText "trilium-config.ini" ''
+    [General]
+    # Instance name can be used to distinguish between different instances
+    instanceName=${cfg.instanceName}
+
+    # Disable automatically generating desktop icon
+    noDesktopIcon=true
+
+    [Network]
+    # host setting is relevant only for web deployments - set the host on which the server will listen
+    host=${cfg.host}
+    # port setting is relevant only for web deployments, desktop builds run on random free port
+    port=${toString cfg.port}
+    # true for TLS/SSL/HTTPS (secure), false for HTTP (unsecure).
+    https=false
+  '';
+in
+{
+
+  options.services.trilium-server = with lib; {
+    enable = mkEnableOption "trilium-server";
+
+    dataDir = mkOption {
+      type = types.str;
+      default = "/var/lib/trilium";
+      description = ''
+        The directory storing the nodes database and the configuration.
+      '';
+    };
+
+    instanceName = mkOption {
+      type = types.str;
+      default = "Trilium";
+      description = ''
+        Instance name used to distinguish between different instances
+      '';
+    };
+
+    host = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = ''
+        The host address to bind to (defaults to localhost).
+      '';
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 8080;
+      description = ''
+        The port number to bind to.
+      '';
+    };
+
+    nginx = mkOption {
+      default = {};
+      description = ''
+        Configuration for nginx reverse proxy.
+      '';
+
+      type = types.submodule {
+        options = {
+          enable = mkOption {
+            type = types.bool;
+            default = false;
+            description = ''
+              Configure the nginx reverse proxy settings.
+            '';
+          };
+
+          hostName = mkOption {
+            type = types.str;
+            description = ''
+              The hostname use to setup the virtualhost configuration
+            '';
+          };
+        };
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable (lib.mkMerge [ 
+  {
+    meta.maintainers = with lib.maintainers; [ kampka ];
+
+    users.groups.trilium = {};
+    users.users.trilium = {
+      description = "Trilium User";
+      group = "trilium";
+      home = cfg.dataDir;
+      isSystemUser = true;
+    };
+
+    systemd.services.trilium-server = {
+      wantedBy = [ "multi-user.target" ];
+      environment.TRILIUM_DATA_DIR = cfg.dataDir;
+      serviceConfig = {
+        ExecStart = "${pkgs.trilium-server}/bin/trilium-server";
+        User = "trilium";
+        Group = "trilium";
+        PrivateTmp = "true";
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d  ${cfg.dataDir}            0750 trilium trilium - -"
+      "L+ ${cfg.dataDir}/config.ini -    -       -       - ${configIni}"
+    ];
+
+  }
+
+  (lib.mkIf cfg.nginx.enable {
+    services.nginx = {
+      enable = true;
+      virtualHosts."${cfg.nginx.hostName}" = {
+        locations."/" = {
+          proxyPass = "http://${cfg.host}:${toString cfg.port}/";
+          extraConfig = ''
+            proxy_http_version 1.1;
+            proxy_set_header Upgrade $http_upgrade;
+            proxy_set_header Connection 'upgrade';
+            proxy_set_header Host $host;
+            proxy_cache_bypass $http_upgrade;
+          '';
+        };
+        extraConfig = ''
+          client_max_body_size 0;
+        '';
+      };
+    };
+  })
+  ]);
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix b/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
new file mode 100644
index 000000000000..2ea9537b93de
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/tt-rss.nix
@@ -0,0 +1,679 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.tt-rss;
+
+  configVersion = 26;
+
+  cacheDir = "cache";
+  lockDir = "lock";
+  feedIconsDir = "feed-icons";
+
+  dbPort = if cfg.database.port == null
+    then (if cfg.database.type == "pgsql" then 5432 else 3306)
+    else cfg.database.port;
+
+  poolName = "tt-rss";
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
+
+  tt-rss-config = pkgs.writeText "config.php" ''
+    <?php
+
+      define('PHP_EXECUTABLE', '${pkgs.php}/bin/php');
+
+      define('LOCK_DIRECTORY', '${lockDir}');
+      define('CACHE_DIR', '${cacheDir}');
+      define('ICONS_DIR', '${feedIconsDir}');
+      define('ICONS_URL', '${feedIconsDir}');
+      define('SELF_URL_PATH', '${cfg.selfUrlPath}');
+
+      define('MYSQL_CHARSET', 'UTF8');
+
+      define('DB_TYPE', '${cfg.database.type}');
+      define('DB_HOST', '${optionalString (cfg.database.host != null) cfg.database.host}');
+      define('DB_USER', '${cfg.database.user}');
+      define('DB_NAME', '${cfg.database.name}');
+      define('DB_PASS', ${
+        if (cfg.database.password != null) then
+          "'${(escape ["'" "\\"] cfg.database.password)}'"
+        else if (cfg.database.passwordFile != null) then
+          "file_get_contents('${cfg.database.passwordFile}')"
+        else
+          "''"
+      });
+      define('DB_PORT', '${toString dbPort}');
+
+      define('AUTH_AUTO_CREATE', ${boolToString cfg.auth.autoCreate});
+      define('AUTH_AUTO_LOGIN', ${boolToString cfg.auth.autoLogin});
+
+      define('FEED_CRYPT_KEY', '${escape ["'" "\\"] cfg.feedCryptKey}');
+
+
+      define('SINGLE_USER_MODE', ${boolToString cfg.singleUserMode});
+
+      define('SIMPLE_UPDATE_MODE', ${boolToString cfg.simpleUpdateMode});
+
+      // Never check for updates - the running version of the code should be
+      // controlled entirely by the version of TT-RSS active in the current Nix
+      // profile. If TT-RSS updates itself to a version requiring a database
+      // schema upgrade, and then the SystemD tt-rss.service is restarted, the
+      // old code copied from the Nix store will overwrite the updated version,
+      // causing the code to detect the need for a schema "upgrade" (since the
+      // schema version in the database is different than in the code), but the
+      // update schema operation in TT-RSS will do nothing because the schema
+      // version in the database is newer than that in the code.
+      define('CHECK_FOR_UPDATES', false);
+
+      define('FORCE_ARTICLE_PURGE', ${toString cfg.forceArticlePurge});
+      define('SESSION_COOKIE_LIFETIME', ${toString cfg.sessionCookieLifetime});
+      define('ENABLE_GZIP_OUTPUT', ${boolToString cfg.enableGZipOutput});
+
+      define('PLUGINS', '${builtins.concatStringsSep "," cfg.plugins}');
+
+      define('LOG_DESTINATION', '${cfg.logDestination}');
+      define('CONFIG_VERSION', ${toString configVersion});
+
+
+      define('PUBSUBHUBBUB_ENABLED', ${boolToString cfg.pubSubHubbub.enable});
+      define('PUBSUBHUBBUB_HUB', '${cfg.pubSubHubbub.hub}');
+
+      define('SPHINX_SERVER', '${cfg.sphinx.server}');
+      define('SPHINX_INDEX', '${builtins.concatStringsSep "," cfg.sphinx.index}');
+
+      define('ENABLE_REGISTRATION', ${boolToString cfg.registration.enable});
+      define('REG_NOTIFY_ADDRESS', '${cfg.registration.notifyAddress}');
+      define('REG_MAX_USERS', ${toString cfg.registration.maxUsers});
+
+      define('SMTP_SERVER', '${cfg.email.server}');
+      define('SMTP_LOGIN', '${cfg.email.login}');
+      define('SMTP_PASSWORD', '${escape ["'" "\\"] cfg.email.password}');
+      define('SMTP_SECURE', '${cfg.email.security}');
+
+      define('SMTP_FROM_NAME', '${escape ["'" "\\"] cfg.email.fromName}');
+      define('SMTP_FROM_ADDRESS', '${escape ["'" "\\"] cfg.email.fromAddress}');
+      define('DIGEST_SUBJECT', '${escape ["'" "\\"] cfg.email.digestSubject}');
+
+      ${cfg.extraConfig}
+  '';
+
+ in {
+
+  ###### interface
+
+  options = {
+
+    services.tt-rss = {
+
+      enable = mkEnableOption "tt-rss";
+
+      root = mkOption {
+        type = types.path;
+        default = "/var/lib/tt-rss";
+        example = "/var/lib/tt-rss";
+        description = ''
+          Root of the application.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "tt_rss";
+        example = "tt_rss";
+        description = ''
+          User account under which both the update daemon and the web-application run.
+        '';
+      };
+
+      pool = mkOption {
+        type = types.str;
+        default = "${poolName}";
+        description = ''
+          Name of existing phpfpm pool that is used to run web-application.
+          If not specified a pool will be created automatically with
+          default values.
+        '';
+      };
+
+      virtualHost = mkOption {
+        type = types.nullOr types.str;
+        default = "tt-rss";
+        description = ''
+          Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
+        '';
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum ["pgsql" "mysql"];
+          default = "pgsql";
+          description = ''
+            Database to store feeds. Supported are pgsql and mysql.
+          '';
+        };
+
+        host = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Host of the database. Leave null to use Unix domain socket.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "tt_rss";
+          description = ''
+            Name of the existing database.
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "tt_rss";
+          description = ''
+            The database user. The user must exist and has access to
+            the specified database.
+          '';
+        };
+
+        password = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            The database user's password.
+          '';
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            The database user's password.
+          '';
+        };
+
+        port = mkOption {
+          type = types.nullOr types.int;
+          default = null;
+          description = ''
+            The database's port. If not set, the default ports will be provided (5432
+            and 3306 for pgsql and mysql respectively).
+          '';
+        };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Create the database and database user locally.";
+        };
+      };
+
+      auth = {
+        autoCreate = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Allow authentication modules to auto-create users in tt-rss internal
+            database when authenticated successfully.
+          '';
+        };
+
+        autoLogin = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Automatically login user on remote or other kind of externally supplied
+            authentication, otherwise redirect to login form as normal.
+            If set to true, users won't be able to set application language
+            and settings profile.
+          '';
+        };
+      };
+
+      pubSubHubbub = {
+        hub = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            URL to a PubSubHubbub-compatible hub server. If defined, "Published
+            articles" generated feed would automatically become PUSH-enabled.
+          '';
+        };
+
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Enable client PubSubHubbub support in tt-rss. When disabled, tt-rss
+            won't try to subscribe to PUSH feed updates.
+          '';
+        };
+      };
+
+      sphinx = {
+        server = mkOption {
+          type = types.str;
+          default = "localhost:9312";
+          description = ''
+            Hostname:port combination for the Sphinx server.
+          '';
+        };
+
+        index = mkOption {
+          type = types.listOf types.str;
+          default = ["ttrss" "delta"];
+          description = ''
+            Index names in Sphinx configuration. Example configuration
+            files are available on tt-rss wiki.
+          '';
+        };
+      };
+
+      registration = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Allow users to register themselves. Please be aware that allowing
+            random people to access your tt-rss installation is a security risk
+            and potentially might lead to data loss or server exploit. Disabled
+            by default.
+          '';
+        };
+
+        notifyAddress = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            Email address to send new user notifications to.
+          '';
+        };
+
+        maxUsers = mkOption {
+          type = types.int;
+          default = 0;
+          description = ''
+            Maximum amount of users which will be allowed to register on this
+            system. 0 - no limit.
+          '';
+        };
+      };
+
+      email = {
+        server = mkOption {
+          type = types.str;
+          default = "";
+          example = "localhost:25";
+          description = ''
+            Hostname:port combination to send outgoing mail. Blank - use system
+            MTA.
+          '';
+        };
+
+        login = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            SMTP authentication login used when sending outgoing mail.
+          '';
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            SMTP authentication password used when sending outgoing mail.
+          '';
+        };
+
+        security = mkOption {
+          type = types.enum ["" "ssl" "tls"];
+          default = "";
+          description = ''
+            Used to select a secure SMTP connection. Allowed values: ssl, tls,
+            or empty.
+          '';
+        };
+
+        fromName = mkOption {
+          type = types.str;
+          default = "Tiny Tiny RSS";
+          description = ''
+            Name for sending outgoing mail. This applies to password reset
+            notifications, digest emails and any other mail.
+          '';
+        };
+
+        fromAddress = mkOption {
+          type = types.str;
+          default = "";
+          description = ''
+            Address for sending outgoing mail. This applies to password reset
+            notifications, digest emails and any other mail.
+          '';
+        };
+
+        digestSubject = mkOption {
+          type = types.str;
+          default = "[tt-rss] New headlines for last 24 hours";
+          description = ''
+            Subject line for email digests.
+          '';
+        };
+      };
+
+      sessionCookieLifetime = mkOption {
+        type = types.int;
+        default = 86400;
+        description = ''
+          Default lifetime of a session (e.g. login) cookie. In seconds,
+          0 means cookie will be deleted when browser closes.
+        '';
+      };
+
+      selfUrlPath = mkOption {
+        type = types.str;
+        description = ''
+          Full URL of your tt-rss installation. This should be set to the
+          location of tt-rss directory, e.g. http://example.org/tt-rss/
+          You need to set this option correctly otherwise several features
+          including PUSH, bookmarklets and browser integration will not work properly.
+        '';
+        example = "http://localhost";
+      };
+
+      feedCryptKey = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Key used for encryption of passwords for password-protected feeds
+          in the database. A string of 24 random characters. If left blank, encryption
+          is not used. Requires mcrypt functions.
+          Warning: changing this key will make your stored feed passwords impossible
+          to decrypt.
+        '';
+      };
+
+      singleUserMode = mkOption {
+        type = types.bool;
+        default = false;
+
+        description = ''
+          Operate in single user mode, disables all functionality related to
+          multiple users and authentication. Enabling this assumes you have
+          your tt-rss directory protected by other means (e.g. http auth).
+        '';
+      };
+
+      simpleUpdateMode = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enables fallback update mode where tt-rss tries to update feeds in
+          background while tt-rss is open in your browser.
+          If you don't have a lot of feeds and don't want to or can't run
+          background processes while not running tt-rss, this method is generally
+          viable to keep your feeds up to date.
+          Still, there are more robust (and recommended) updating methods
+          available, you can read about them here: http://tt-rss.org/wiki/UpdatingFeeds
+        '';
+      };
+
+      forceArticlePurge = mkOption {
+        type = types.int;
+        default = 0;
+        description = ''
+          When this option is not 0, users ability to control feed purging
+          intervals is disabled and all articles (which are not starred)
+          older than this amount of days are purged.
+        '';
+      };
+
+      enableGZipOutput = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Selectively gzip output to improve wire performance. This requires
+          PHP Zlib extension on the server.
+          Enabling this can break tt-rss in several httpd/php configurations,
+          if you experience weird errors and tt-rss failing to start, blank pages
+          after login, or content encoding errors, disable it.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.str;
+        default = ["auth_internal" "note"];
+        description = ''
+          List of plugins to load automatically for all users.
+          System plugins have to be specified here. Please enable at least one
+          authentication plugin here (auth_*).
+          Users may enable other user plugins from Preferences/Plugins but may not
+          disable plugins specified in this list.
+          Disabling auth_internal in this list would automatically disable
+          reset password link on the login form.
+        '';
+      };
+
+      pluginPackages = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = ''
+          List of plugins to install. The list elements are expected to
+          be derivations. All elements in this derivation are automatically
+          copied to the <literal>plugins.local</literal> directory.
+        '';
+      };
+
+      themePackages = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = ''
+          List of themes to install. The list elements are expected to
+          be derivations. All elements in this derivation are automatically
+          copied to the <literal>themes.local</literal> directory.
+        '';
+      };
+
+      logDestination = mkOption {
+        type = types.enum ["" "sql" "syslog"];
+        default = "sql";
+        description = ''
+          Log destination to use. Possible values: sql (uses internal logging
+          you can read in Preferences -> System), syslog - logs to system log.
+          Setting this to blank uses PHP logging (usually to http server
+          error.log).
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional lines to append to <literal>config.php</literal>.
+        '';
+      };
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule ["services" "tt-rss" "checkForUpdates"] ''
+      This option was removed because setting this to true will cause TT-RSS
+      to be unable to start if an automatic update of the code in
+      services.tt-rss.root leads to a database schema upgrade that is not
+      supported by the code active in the Nix store.
+    '')
+  ];
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = cfg.database.password != null -> cfg.database.passwordFile == null;
+        message = "Cannot set both password and passwordFile";
+      }
+    ];
+
+    services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
+      ${poolName} = {
+        inherit (cfg) user;
+        settings = mapAttrs (name: mkDefault) {
+          "listen.owner" = "nginx";
+          "listen.group" = "nginx";
+          "listen.mode" = "0600";
+          "pm" = "dynamic";
+          "pm.max_children" = 75;
+          "pm.start_servers" = 10;
+          "pm.min_spare_servers" = 5;
+          "pm.max_spare_servers" = 20;
+          "pm.max_requests" = 500;
+          "catch_workers_output" = 1;
+        };
+      };
+    };
+
+    # NOTE: No configuration is done if not using virtual host
+    services.nginx = mkIf (cfg.virtualHost != null) {
+      enable = true;
+      virtualHosts = {
+        ${cfg.virtualHost} = {
+          root = "${cfg.root}";
+
+          locations."/" = {
+            index = "index.php";
+          };
+
+          locations."~ \\.php$" = {
+            extraConfig = ''
+              fastcgi_split_path_info ^(.+\.php)(/.+)$;
+              fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
+              fastcgi_index index.php;
+            '';
+          };
+        };
+      };
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.root}' 0755 ${cfg.user} tt_rss - -"
+      "Z '${cfg.root}' 0755 ${cfg.user} tt_rss - -"
+    ];
+
+    systemd.services.tt-rss =
+      {
+
+        description = "Tiny Tiny RSS feeds update daemon";
+
+        preStart = let
+          callSql = e:
+              if cfg.database.type == "pgsql" then ''
+                  ${optionalString (cfg.database.password != null) "PGPASSWORD=${cfg.database.password}"} \
+                  ${optionalString (cfg.database.passwordFile != null) "PGPASSWORD=$(cat ${cfg.database.passwordFile})"} \
+                  ${config.services.postgresql.package}/bin/psql \
+                    -U ${cfg.database.user} \
+                    ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} --port ${toString dbPort}"} \
+                    -c '${e}' \
+                    ${cfg.database.name}''
+
+              else if cfg.database.type == "mysql" then ''
+                  echo '${e}' | ${config.services.mysql.package}/bin/mysql \
+                    -u ${cfg.database.user} \
+                    ${optionalString (cfg.database.password != null) "-p${cfg.database.password}"} \
+                    ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} -P ${toString dbPort}"} \
+                    ${cfg.database.name}''
+
+              else "";
+
+        in ''
+          rm -rf "${cfg.root}/*"
+          cp -r "${pkgs.tt-rss}/"* "${cfg.root}"
+          ${optionalString (cfg.pluginPackages != []) ''
+            for plugin in ${concatStringsSep " " cfg.pluginPackages}; do
+              cp -r "$plugin"/* "${cfg.root}/plugins.local/"
+            done
+          ''}
+          ${optionalString (cfg.themePackages != []) ''
+            for theme in ${concatStringsSep " " cfg.themePackages}; do
+              cp -r "$theme"/* "${cfg.root}/themes.local/"
+            done
+          ''}
+          ln -sf "${tt-rss-config}" "${cfg.root}/config.php"
+          chmod -R 755 "${cfg.root}"
+        ''
+
+        + (optionalString (cfg.database.type == "pgsql") ''
+          exists=$(${callSql "select count(*) > 0 from pg_tables where tableowner = user"} \
+          | tail -n+3 | head -n-2 | sed -e 's/[ \n\t]*//')
+
+          if [ "$exists" == 'f' ]; then
+            ${callSql "\\i ${pkgs.tt-rss}/schema/ttrss_schema_${cfg.database.type}.sql"}
+          else
+            echo 'The database contains some data. Leaving it as it is.'
+          fi;
+        '')
+
+        + (optionalString (cfg.database.type == "mysql") ''
+          exists=$(${callSql "select count(*) > 0 from information_schema.tables where table_schema = schema()"} \
+          | tail -n+2 | sed -e 's/[ \n\t]*//')
+
+          if [ "$exists" == '0' ]; then
+            ${callSql "\\. ${pkgs.tt-rss}/schema/ttrss_schema_${cfg.database.type}.sql"}
+          else
+            echo 'The database contains some data. Leaving it as it is.'
+          fi;
+        '');
+
+        serviceConfig = {
+          User = "${cfg.user}";
+          Group = "tt_rss";
+          ExecStart = "${pkgs.php}/bin/php ${cfg.root}/update.php --daemon --quiet";
+          StandardOutput = "syslog";
+          StandardError = "syslog";
+          Restart = "on-failure";
+          RestartSec = "60";
+          SyslogIdentifier = "tt-rss";
+        };
+
+        wantedBy = [ "multi-user.target" ];
+        requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+        after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+    };
+
+    services.mysql = mkIf mysqlLocal {
+      enable = true;
+      package = mkDefault pkgs.mysql;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        {
+          name = cfg.user;
+          ensurePermissions = {
+            "${cfg.database.name}.*" = "ALL PRIVILEGES";
+          };
+        }
+      ];
+    };
+
+    services.postgresql = mkIf pgsqlLocal {
+      enable = mkDefault true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    users.users.tt_rss = optionalAttrs (cfg.user == "tt_rss") {
+      description = "tt-rss service user";
+      isSystemUser = true;
+      group = "tt_rss";
+    };
+
+    users.groups.tt_rss = {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/virtlyst.nix b/nixpkgs/nixos/modules/services/web-apps/virtlyst.nix
new file mode 100644
index 000000000000..37bdbb0e3b42
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/virtlyst.nix
@@ -0,0 +1,73 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.virtlyst;
+  stateDir = "/var/lib/virtlyst";
+
+  ini = pkgs.writeText "virtlyst-config.ini" ''
+    [wsgi]
+    master = true
+    threads = auto
+    http-socket = ${cfg.httpSocket}
+    application = ${pkgs.virtlyst}/lib/libVirtlyst.so
+    chdir2 = ${stateDir}
+    static-map = /static=${pkgs.virtlyst}/root/static
+
+    [Cutelyst]
+    production = true
+    DatabasePath = virtlyst.sqlite
+    TemplatePath = ${pkgs.virtlyst}/root/src
+
+    [Rules]
+    cutelyst.* = true
+    virtlyst.* = true
+  '';
+
+in
+
+{
+
+  options.services.virtlyst = {
+    enable = mkEnableOption "Virtlyst libvirt web interface";
+
+    adminPassword = mkOption {
+      type = types.str;
+      description = ''
+        Initial admin password with which the database will be seeded.
+      '';
+    };
+
+    httpSocket = mkOption {
+      type = types.str;
+      default = "localhost:3000";
+      description = ''
+        IP and/or port to which to bind the http socket.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.virtlyst = {
+      home = stateDir;
+      createHome = true;
+      group = mkIf config.virtualisation.libvirtd.enable "libvirtd";
+      isSystemUser = true;
+    };
+
+    systemd.services.virtlyst = {
+      wantedBy = [ "multi-user.target" ];
+      environment = {
+        VIRTLYST_ADMIN_PASSWORD = cfg.adminPassword;
+      };
+      serviceConfig = {
+        ExecStart = "${pkgs.cutelyst}/bin/cutelyst-wsgi2 --ini ${ini}";
+        User = "virtlyst";
+        WorkingDirectory = stateDir;
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/wordpress.nix b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix
new file mode 100644
index 000000000000..5fbe53221ae8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/wordpress.nix
@@ -0,0 +1,366 @@
+{ config, pkgs, lib, ... }:
+
+let
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+  inherit (lib) any attrValues concatMapStringsSep flatten literalExample;
+  inherit (lib) mapAttrs mapAttrs' mapAttrsToList nameValuePair optional optionalAttrs optionalString;
+
+  eachSite = config.services.wordpress;
+  user = "wordpress";
+  group = config.services.httpd.group;
+  stateDir = hostName: "/var/lib/wordpress/${hostName}";
+
+  pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
+    pname = "wordpress-${hostName}";
+    version = src.version;
+    src = cfg.package;
+
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+
+      # symlink the wordpress config
+      ln -s ${wpConfig hostName cfg} $out/share/wordpress/wp-config.php
+      # symlink uploads directory
+      ln -s ${cfg.uploadsDir} $out/share/wordpress/wp-content/uploads
+
+      # https://github.com/NixOS/nixpkgs/pull/53399
+      #
+      # Symlinking works for most plugins and themes, but Avada, for instance, fails to
+      # understand the symlink, causing its file path stripping to fail. This results in
+      # requests that look like: https://example.com/wp-content//nix/store/...plugin/path/some-file.js
+      # Since hard linking directories is not allowed, copying is the next best thing.
+
+      # copy additional plugin(s) and theme(s)
+      ${concatMapStringsSep "\n" (theme: "cp -r ${theme} $out/share/wordpress/wp-content/themes/${theme.name}") cfg.themes}
+      ${concatMapStringsSep "\n" (plugin: "cp -r ${plugin} $out/share/wordpress/wp-content/plugins/${plugin.name}") cfg.plugins}
+    '';
+  };
+
+  wpConfig = hostName: cfg: pkgs.writeText "wp-config-${hostName}.php" ''
+    <?php
+      define('DB_NAME', '${cfg.database.name}');
+      define('DB_HOST', '${cfg.database.host}:${if cfg.database.socket != null then cfg.database.socket else toString cfg.database.port}');
+      define('DB_USER', '${cfg.database.user}');
+      ${optionalString (cfg.database.passwordFile != null) "define('DB_PASSWORD', file_get_contents('${cfg.database.passwordFile}'));"}
+      define('DB_CHARSET', 'utf8');
+      $table_prefix  = '${cfg.database.tablePrefix}';
+
+      require_once('${stateDir hostName}/secret-keys.php');
+
+      # wordpress is installed onto a read-only file system
+      define('DISALLOW_FILE_EDIT', true);
+      define('AUTOMATIC_UPDATER_DISABLED', true);
+
+      ${cfg.extraConfig}
+
+      if ( !defined('ABSPATH') )
+        define('ABSPATH', dirname(__FILE__) . '/');
+
+      require_once(ABSPATH . 'wp-settings.php');
+    ?>
+  '';
+
+  secretsVars = [ "AUTH_KEY" "SECURE_AUTH_KEY" "LOOGGED_IN_KEY" "NONCE_KEY" "AUTH_SALT" "SECURE_AUTH_SALT" "LOGGED_IN_SALT" "NONCE_SALT" ];
+  secretsScript = hostStateDir: ''
+    if ! test -e "${hostStateDir}/secret-keys.php"; then
+      umask 0177
+      echo "<?php" >> "${hostStateDir}/secret-keys.php"
+      ${concatMapStringsSep "\n" (var: ''
+        echo "define('${var}', '`tr -dc a-zA-Z0-9 </dev/urandom | head -c 64`');" >> "${hostStateDir}/secret-keys.php"
+      '') secretsVars}
+      echo "?>" >> "${hostStateDir}/secret-keys.php"
+      chmod 440 "${hostStateDir}/secret-keys.php"
+    fi
+  '';
+
+  siteOpts = { lib, name, ... }:
+    {
+      options = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.wordpress;
+          description = "Which WordPress package to use.";
+        };
+
+        uploadsDir = mkOption {
+          type = types.path;
+          default = "/var/lib/wordpress/${name}/uploads";
+          description = ''
+            This directory is used for uploads of pictures. The directory passed here is automatically
+            created and permissions adjusted as required.
+          '';
+        };
+
+        plugins = mkOption {
+          type = types.listOf types.path;
+          default = [];
+          description = ''
+            List of path(s) to respective plugin(s) which are copied from the 'plugins' directory.
+            <note><para>These plugins need to be packaged before use, see example.</para></note>
+          '';
+          example = ''
+            # Wordpress plugin 'embed-pdf-viewer' installation example
+            embedPdfViewerPlugin = pkgs.stdenv.mkDerivation {
+              name = "embed-pdf-viewer-plugin";
+              # Download the theme from the wordpress site
+              src = pkgs.fetchurl {
+                url = "https://downloads.wordpress.org/plugin/embed-pdf-viewer.2.0.3.zip";
+                sha256 = "1rhba5h5fjlhy8p05zf0p14c9iagfh96y91r36ni0rmk6y891lyd";
+              };
+              # We need unzip to build this package
+              buildInputs = [ pkgs.unzip ];
+              # Installing simply means copying all files to the output directory
+              installPhase = "mkdir -p $out; cp -R * $out/";
+            };
+
+            And then pass this theme to the themes list like this:
+              plugins = [ embedPdfViewerPlugin ];
+          '';
+        };
+
+        themes = mkOption {
+          type = types.listOf types.path;
+          default = [];
+          description = ''
+            List of path(s) to respective theme(s) which are copied from the 'theme' directory.
+            <note><para>These themes need to be packaged before use, see example.</para></note>
+          '';
+          example = ''
+            # Let's package the responsive theme
+            responsiveTheme = pkgs.stdenv.mkDerivation {
+              name = "responsive-theme";
+              # Download the theme from the wordpress site
+              src = pkgs.fetchurl {
+                url = "https://downloads.wordpress.org/theme/responsive.3.14.zip";
+                sha256 = "0rjwm811f4aa4q43r77zxlpklyb85q08f9c8ns2akcarrvj5ydx3";
+              };
+              # We need unzip to build this package
+              buildInputs = [ pkgs.unzip ];
+              # Installing simply means copying all files to the output directory
+              installPhase = "mkdir -p $out; cp -R * $out/";
+            };
+
+            And then pass this theme to the themes list like this:
+              themes = [ responsiveTheme ];
+          '';
+        };
+
+        database = {
+          host = mkOption {
+            type = types.str;
+            default = "localhost";
+            description = "Database host address.";
+          };
+
+          port = mkOption {
+            type = types.port;
+            default = 3306;
+            description = "Database host port.";
+          };
+
+          name = mkOption {
+            type = types.str;
+            default = "wordpress";
+            description = "Database name.";
+          };
+
+          user = mkOption {
+            type = types.str;
+            default = "wordpress";
+            description = "Database user.";
+          };
+
+          passwordFile = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            example = "/run/keys/wordpress-dbpassword";
+            description = ''
+              A file containing the password corresponding to
+              <option>database.user</option>.
+            '';
+          };
+
+          tablePrefix = mkOption {
+            type = types.str;
+            default = "wp_";
+            description = ''
+              The $table_prefix is the value placed in the front of your database tables.
+              Change the value if you want to use something other than wp_ for your database
+              prefix. Typically this is changed if you are installing multiple WordPress blogs
+              in the same database.
+
+              See <link xlink:href='https://codex.wordpress.org/Editing_wp-config.php#table_prefix'/>.
+            '';
+          };
+
+          socket = mkOption {
+            type = types.nullOr types.path;
+            default = null;
+            defaultText = "/run/mysqld/mysqld.sock";
+            description = "Path to the unix socket file to use for authentication.";
+          };
+
+          createLocally = mkOption {
+            type = types.bool;
+            default = true;
+            description = "Create the database and database user locally.";
+          };
+        };
+
+        virtualHost = mkOption {
+          type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
+          example = literalExample ''
+            {
+              adminAddr = "webmaster@example.org";
+              forceSSL = true;
+              enableACME = true;
+            }
+          '';
+          description = ''
+            Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+          '';
+        };
+
+        poolConfig = mkOption {
+          type = with types; attrsOf (oneOf [ str int bool ]);
+          default = {
+            "pm" = "dynamic";
+            "pm.max_children" = 32;
+            "pm.start_servers" = 2;
+            "pm.min_spare_servers" = 2;
+            "pm.max_spare_servers" = 4;
+            "pm.max_requests" = 500;
+          };
+          description = ''
+            Options for the WordPress PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+            for details on configuration directives.
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Any additional text to be appended to the wp-config.php
+            configuration file. This is a PHP script. For configuration
+            settings, see <link xlink:href='https://codex.wordpress.org/Editing_wp-config.php'/>.
+          '';
+          example = ''
+            define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds
+          '';
+        };
+      };
+
+      config.virtualHost.hostName = mkDefault name;
+    };
+in
+{
+  # interface
+  options = {
+    services.wordpress = mkOption {
+      type = types.attrsOf (types.submodule siteOpts);
+      default = {};
+      description = "Specification of one or more WordPress sites to serve via Apache.";
+    };
+  };
+
+  # implementation
+  config = mkIf (eachSite != {}) {
+
+    assertions = mapAttrsToList (hostName: cfg:
+      { assertion = cfg.database.createLocally -> cfg.database.user == user;
+        message = "services.wordpress.${hostName}.database.user must be ${user} if the database is to be automatically provisioned";
+      }
+    ) eachSite;
+
+    services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
+      enable = true;
+      package = mkDefault pkgs.mariadb;
+      ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite;
+      ensureUsers = mapAttrsToList (hostName: cfg:
+        { name = cfg.database.user;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+        }
+      ) eachSite;
+    };
+
+    services.phpfpm.pools = mapAttrs' (hostName: cfg: (
+      nameValuePair "wordpress-${hostName}" {
+        inherit user group;
+        settings = {
+          "listen.owner" = config.services.httpd.user;
+          "listen.group" = config.services.httpd.group;
+        } // cfg.poolConfig;
+      }
+    )) eachSite;
+
+    services.httpd = {
+      enable = true;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts = mapAttrs (hostName: cfg: mkMerge [ cfg.virtualHost {
+        documentRoot = mkForce "${pkg hostName cfg}/share/wordpress";
+        extraConfig = ''
+          <Directory "${pkg hostName cfg}/share/wordpress">
+            <FilesMatch "\.php$">
+              <If "-f %{REQUEST_FILENAME}">
+                SetHandler "proxy:unix:${config.services.phpfpm.pools."wordpress-${hostName}".socket}|fcgi://localhost/"
+              </If>
+            </FilesMatch>
+
+            # standard wordpress .htaccess contents
+            <IfModule mod_rewrite.c>
+              RewriteEngine On
+              RewriteBase /
+              RewriteRule ^index\.php$ - [L]
+              RewriteCond %{REQUEST_FILENAME} !-f
+              RewriteCond %{REQUEST_FILENAME} !-d
+              RewriteRule . /index.php [L]
+            </IfModule>
+
+            DirectoryIndex index.php
+            Require all granted
+            Options +FollowSymLinks
+          </Directory>
+
+          # https://wordpress.org/support/article/hardening-wordpress/#securing-wp-config-php
+          <Files wp-config.php>
+            Require all denied
+          </Files>
+        '';
+      } ]) eachSite;
+    };
+
+    systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
+      "d '${stateDir hostName}' 0750 ${user} ${group} - -"
+      "d '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+      "Z '${cfg.uploadsDir}' 0750 ${user} ${group} - -"
+    ]) eachSite);
+
+    systemd.services = mkMerge [
+      (mapAttrs' (hostName: cfg: (
+        nameValuePair "wordpress-init-${hostName}" {
+          wantedBy = [ "multi-user.target" ];
+          before = [ "phpfpm-wordpress-${hostName}.service" ];
+          after = optional cfg.database.createLocally "mysql.service";
+          script = secretsScript (stateDir hostName);
+
+          serviceConfig = {
+            Type = "oneshot";
+            User = user;
+            Group = group;
+          };
+      })) eachSite)
+
+      (optionalAttrs (any (v: v.database.createLocally) (attrValues eachSite)) {
+        httpd.after = [ "mysql.service" ];
+      })
+    ];
+
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/youtrack.nix b/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
new file mode 100644
index 000000000000..b4d653d2d77e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/youtrack.nix
@@ -0,0 +1,180 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.youtrack;
+
+  extraAttr = concatStringsSep " " (mapAttrsToList (k: v: "-D${k}=${v}") (stdParams // cfg.extraParams));
+  mergeAttrList = lib.foldl' lib.mergeAttrs {};
+
+  stdParams = mergeAttrList [
+    (optionalAttrs (cfg.baseUrl != null) {
+      "jetbrains.youtrack.baseUrl" = cfg.baseUrl;
+    })
+    {
+    "java.aws.headless" = "true";
+    "jetbrains.youtrack.disableBrowser" = "true";
+    }
+  ];
+in
+{
+  options.services.youtrack = {
+
+    enable = mkEnableOption "YouTrack service";
+
+    address = mkOption {
+      description = ''
+        The interface youtrack will listen on.
+      '';
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    baseUrl = mkOption {
+      description = ''
+        Base URL for youtrack. Will be auto-detected and stored in database.
+      '';
+      type = types.nullOr types.str;
+      default = null;
+    };
+
+    extraParams = mkOption {
+      default = {};
+      description = ''
+        Extra parameters to pass to youtrack. See
+        https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Java-Start-Parameters.html
+        for more information.
+      '';
+      example = literalExample ''
+        {
+          "jetbrains.youtrack.overrideRootPassword" = "tortuga";
+        }
+      '';
+      type = types.attrsOf types.str;
+    };
+
+    package = mkOption {
+      description = ''
+        Package to use.
+      '';
+      type = types.package;
+      default = pkgs.youtrack;
+      defaultText = "pkgs.youtrack";
+    };
+
+    port = mkOption {
+      description = ''
+        The port youtrack will listen on.
+      '';
+      default = 8080;
+      type = types.int;
+    };
+
+    statePath = mkOption {
+      description = ''
+        Where to keep the youtrack database.
+      '';
+      type = types.path;
+      default = "/var/lib/youtrack";
+    };
+
+    virtualHost = mkOption {
+      description = ''
+        Name of the nginx virtual host to use and setup.
+        If null, do not setup anything.
+      '';
+      default = null;
+      type = types.nullOr types.str;
+    };
+
+    jvmOpts = mkOption {
+      description = ''
+        Extra options to pass to the JVM.
+        See https://www.jetbrains.com/help/youtrack/standalone/Configure-JVM-Options.html
+        for more information.
+      '';
+      type = types.separatedString " ";
+      example = "-XX:MetaspaceSize=250m";
+      default = "";
+    };
+
+    maxMemory = mkOption {
+      description = ''
+        Maximum Java heap size
+      '';
+      type = types.str;
+      default = "1g";
+    };
+
+    maxMetaspaceSize = mkOption {
+      description = ''
+        Maximum java Metaspace memory.
+      '';
+      type = types.str;
+      default = "350m";
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.youtrack = {
+      environment.HOME = cfg.statePath;
+      environment.YOUTRACK_JVM_OPTS = "${extraAttr}";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = with pkgs; [ unixtools.hostname ];
+      serviceConfig = {
+        Type = "simple";
+        User = "youtrack";
+        Group = "youtrack";
+        ExecStart = ''${cfg.package}/bin/youtrack --J-Xmx${cfg.maxMemory} --J-XX:MaxMetaspaceSize=${cfg.maxMetaspaceSize} ${cfg.jvmOpts} ${cfg.address}:${toString cfg.port}'';
+      };
+    };
+
+    users.users.youtrack = {
+      description = "Youtrack service user";
+      isSystemUser = true;
+      home = cfg.statePath;
+      createHome = true;
+      group = "youtrack";
+    };
+
+    users.groups.youtrack = {};
+
+    services.nginx = mkIf (cfg.virtualHost != null) {
+      upstreams.youtrack.servers."${cfg.address}:${toString cfg.port}" = {};
+      virtualHosts.${cfg.virtualHost}.locations = {
+        "/" = {
+          proxyPass = "http://youtrack";
+          extraConfig = ''
+            client_max_body_size 10m;
+            proxy_http_version 1.1;
+            proxy_set_header X-Forwarded-Host $http_host;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+          '';
+        };
+
+        "/api/eventSourceBus" = {
+          proxyPass = "http://youtrack";
+          extraConfig = ''
+            proxy_cache off;
+            proxy_buffering off;
+            proxy_read_timeout 86400s;
+            proxy_send_timeout 86400s;
+            proxy_set_header Connection "";
+            chunked_transfer_encoding off;
+            client_max_body_size 10m;
+            proxy_http_version 1.1;
+            proxy_set_header X-Forwarded-Host $http_host;
+            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto $scheme;
+          '';
+        };
+
+      };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-apps/zabbix.nix b/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
new file mode 100644
index 000000000000..007195128347
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-apps/zabbix.nix
@@ -0,0 +1,217 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+  inherit (lib) literalExample mapAttrs optionalString;
+
+  cfg = config.services.zabbixWeb;
+  fpm = config.services.phpfpm.pools.zabbix;
+
+  user = "zabbix";
+  group = "zabbix";
+  stateDir = "/var/lib/zabbix";
+
+  zabbixConfig = pkgs.writeText "zabbix.conf.php" ''
+    <?php
+    // Zabbix GUI configuration file.
+    global $DB;
+    $DB['TYPE'] = '${ { mysql = "MYSQL"; pgsql = "POSTGRESQL"; oracle = "ORACLE"; }.${cfg.database.type} }';
+    $DB['SERVER'] = '${cfg.database.host}';
+    $DB['PORT'] = '${toString cfg.database.port}';
+    $DB['DATABASE'] = '${cfg.database.name}';
+    $DB['USER'] = '${cfg.database.user}';
+    $DB['PASSWORD'] = ${if cfg.database.passwordFile != null then "file_get_contents('${cfg.database.passwordFile}')" else "''"};
+    // Schema name. Used for IBM DB2 and PostgreSQL.
+    $DB['SCHEMA'] = ''';
+    $ZBX_SERVER = '${cfg.server.address}';
+    $ZBX_SERVER_PORT = '${toString cfg.server.port}';
+    $ZBX_SERVER_NAME = ''';
+    $IMAGE_FORMAT_DEFAULT = IMAGE_FORMAT_PNG;
+  '';
+
+in
+{
+  # interface
+
+  options.services = {
+    zabbixWeb = {
+      enable = mkEnableOption "the Zabbix web interface";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.zabbix.web;
+        defaultText = "zabbix.web";
+        description = "Which Zabbix package to use.";
+      };
+
+      server = {
+        port = mkOption {
+          type = types.int;
+          description = "The port of the Zabbix server to connect to.";
+          default = 10051;
+        };
+
+        address = mkOption {
+          type = types.str;
+          description = "The IP address or hostname of the Zabbix server to connect to.";
+          default = "localhost";
+        };
+      };
+
+      database = {
+        type = mkOption {
+          type = types.enum [ "mysql" "pgsql" "oracle" ];
+          example = "mysql";
+          default = "pgsql";
+          description = "Database engine to use.";
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "";
+          description = "Database host address.";
+        };
+
+        port = mkOption {
+          type = types.int;
+          default =
+            if cfg.database.type == "mysql" then config.services.mysql.port
+            else if cfg.database.type == "pgsql" then config.services.postgresql.port
+            else 1521;
+          description = "Database host port.";
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database name.";
+        };
+
+        user = mkOption {
+          type = types.str;
+          default = "zabbix";
+          description = "Database user.";
+        };
+
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/keys/zabbix-dbpassword";
+          description = ''
+            A file containing the password corresponding to
+            <option>database.user</option>.
+          '';
+        };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/postgresql";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+      };
+
+      virtualHost = mkOption {
+        type = types.submodule (import ../web-servers/apache-httpd/vhost-options.nix);
+        example = literalExample ''
+          {
+            hostName = "zabbix.example.org";
+            adminAddr = "webmaster@example.org";
+            forceSSL = true;
+            enableACME = true;
+          }
+        '';
+        description = ''
+          Apache configuration can be done by adapting <literal>services.httpd.virtualHosts.&lt;name&gt;</literal>.
+          See <xref linkend="opt-services.httpd.virtualHosts"/> for further information.
+        '';
+      };
+
+      poolConfig = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool ]);
+        default = {
+          "pm" = "dynamic";
+          "pm.max_children" = 32;
+          "pm.start_servers" = 2;
+          "pm.min_spare_servers" = 2;
+          "pm.max_spare_servers" = 4;
+          "pm.max_requests" = 500;
+        };
+        description = ''
+          Options for the Zabbix PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+        '';
+      };
+
+    };
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.tmpfiles.rules = [
+      "d '${stateDir}' 0750 ${user} ${group} - -"
+      "d '${stateDir}/session' 0750 ${user} ${config.services.httpd.group} - -"
+    ];
+
+    services.phpfpm.pools.zabbix = {
+      inherit user;
+      group = config.services.httpd.group;
+      phpOptions = ''
+        # https://www.zabbix.com/documentation/current/manual/installation/install
+        memory_limit = 128M
+        post_max_size = 16M
+        upload_max_filesize = 2M
+        max_execution_time = 300
+        max_input_time = 300
+        session.auto_start = 0
+        mbstring.func_overload = 0
+        always_populate_raw_post_data = -1
+        # https://bbs.archlinux.org/viewtopic.php?pid=1745214#p1745214
+        session.save_path = ${stateDir}/session
+      '' + optionalString (config.time.timeZone != null) ''
+        date.timezone = "${config.time.timeZone}"
+      '' + optionalString (cfg.database.type == "oracle") ''
+        extension=${pkgs.phpPackages.oci8}/lib/php/extensions/oci8.so
+      '';
+      phpEnv.ZABBIX_CONFIG = "${zabbixConfig}";
+      settings = {
+        "listen.owner" = config.services.httpd.user;
+        "listen.group" = config.services.httpd.group;
+      } // cfg.poolConfig;
+    };
+
+    services.httpd = {
+      enable = true;
+      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts.${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost {
+        documentRoot = mkForce "${cfg.package}/share/zabbix";
+        extraConfig = ''
+          <Directory "${cfg.package}/share/zabbix">
+            <FilesMatch "\.php$">
+              <If "-f %{REQUEST_FILENAME}">
+                SetHandler "proxy:unix:${fpm.socket}|fcgi://localhost/"
+              </If>
+            </FilesMatch>
+            AllowOverride all
+            Options -Indexes
+            DirectoryIndex index.php
+          </Directory>
+        '';
+      } ];
+    };
+
+    users.users.${user} = mapAttrs (name: mkDefault) {
+      description = "Zabbix daemon user";
+      uid = config.ids.uids.zabbix;
+      inherit group;
+    };
+
+    users.groups.${group} = mapAttrs (name: mkDefault) {
+      gid = config.ids.gids.zabbix;
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
new file mode 100644
index 000000000000..8abee7130d7c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -0,0 +1,745 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.httpd;
+
+  runtimeDir = "/run/httpd";
+
+  pkg = cfg.package.out;
+
+  httpdConf = cfg.configFile;
+
+  php = cfg.phpPackage.override { apacheHttpd = pkg; };
+
+  phpMajorVersion = lib.versions.major (lib.getVersion php);
+
+  mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; };
+
+  vhosts = attrValues cfg.virtualHosts;
+
+  mkListenInfo = hostOpts:
+    if hostOpts.listen != [] then hostOpts.listen
+    else (
+      optional (hostOpts.onlySSL || hostOpts.addSSL || hostOpts.forceSSL) { ip = "*"; port = 443; ssl = true; } ++
+      optional (!hostOpts.onlySSL) { ip = "*"; port = 80; ssl = false; }
+    );
+
+  listenInfo = unique (concatMap mkListenInfo vhosts);
+
+  enableHttp2 = any (vhost: vhost.http2) vhosts;
+  enableSSL = any (listen: listen.ssl) listenInfo;
+  enableUserDir = any (vhost: vhost.enableUserDir) vhosts;
+
+  # NOTE: generally speaking order of modules is very important
+  modules =
+    [ # required apache modules our httpd service cannot run without
+      "authn_core" "authz_core"
+      "log_config"
+      "mime" "autoindex" "negotiation" "dir"
+      "alias" "rewrite"
+      "unixd" "slotmem_shm" "socache_shmcb"
+      "mpm_${cfg.mpm}"
+    ]
+    ++ (if cfg.mpm == "prefork" then [ "cgi" ] else [ "cgid" ])
+    ++ optional enableHttp2 "http2"
+    ++ optional enableSSL "ssl"
+    ++ optional enableUserDir "userdir"
+    ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+    ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+    ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+    ++ cfg.extraModules;
+
+  loggingConf = (if cfg.logFormat != "none" then ''
+    ErrorLog ${cfg.logDir}/error.log
+
+    LogLevel notice
+
+    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+    LogFormat "%h %l %u %t \"%r\" %>s %b" common
+    LogFormat "%{Referer}i -> %U" referer
+    LogFormat "%{User-agent}i" agent
+
+    CustomLog ${cfg.logDir}/access.log ${cfg.logFormat}
+  '' else ''
+    ErrorLog /dev/null
+  '');
+
+
+  browserHacks = ''
+    <IfModule mod_setenvif.c>
+        BrowserMatch "Mozilla/2" nokeepalive
+        BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0
+        BrowserMatch "RealPlayer 4\.0" force-response-1.0
+        BrowserMatch "Java/1\.0" force-response-1.0
+        BrowserMatch "JDK/1\.0" force-response-1.0
+        BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully
+        BrowserMatch "^WebDrive" redirect-carefully
+        BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully
+        BrowserMatch "^gnome-vfs" redirect-carefully
+    </IfModule>
+  '';
+
+
+  sslConf = ''
+    <IfModule mod_ssl.c>
+        SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
+
+        Mutex posixsem
+
+        SSLRandomSeed startup builtin
+        SSLRandomSeed connect builtin
+
+        SSLProtocol ${cfg.sslProtocols}
+        SSLCipherSuite ${cfg.sslCiphers}
+        SSLHonorCipherOrder on
+    </IfModule>
+  '';
+
+
+  mimeConf = ''
+    TypesConfig ${pkg}/conf/mime.types
+
+    AddType application/x-x509-ca-cert .crt
+    AddType application/x-pkcs7-crl    .crl
+    AddType application/x-httpd-php    .php .phtml
+
+    <IfModule mod_mime_magic.c>
+        MIMEMagicFile ${pkg}/conf/magic
+    </IfModule>
+  '';
+
+  mkVHostConf = hostOpts:
+    let
+      adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
+      listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts);
+      listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts);
+
+      useACME = hostOpts.enableACME || hostOpts.useACMEHost != null;
+      sslCertDir =
+        if hostOpts.enableACME then config.security.acme.certs.${hostOpts.hostName}.directory
+        else if hostOpts.useACMEHost != null then config.security.acme.certs.${hostOpts.useACMEHost}.directory
+        else abort "This case should never happen.";
+
+      sslServerCert = if useACME then "${sslCertDir}/full.pem" else hostOpts.sslServerCert;
+      sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey;
+      sslServerChain = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerChain;
+
+      acmeChallenge = optionalString useACME ''
+        Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/"
+        <Directory "${hostOpts.acmeRoot}">
+            AllowOverride None
+            Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+            Require method GET POST OPTIONS
+            Require all granted
+        </Directory>
+      '';
+    in
+      optionalString (listen != []) ''
+        <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}>
+            ServerName ${hostOpts.hostName}
+            ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
+            ServerAdmin ${adminAddr}
+            <IfModule mod_ssl.c>
+                SSLEngine off
+            </IfModule>
+            ${acmeChallenge}
+            ${if hostOpts.forceSSL then ''
+              <IfModule mod_rewrite.c>
+                  RewriteEngine on
+                  RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC]
+                  RewriteCond %{HTTPS} off
+                  RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}
+              </IfModule>
+            '' else mkVHostCommonConf hostOpts}
+        </VirtualHost>
+      '' +
+      optionalString (listenSSL != []) ''
+        <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}>
+            ServerName ${hostOpts.hostName}
+            ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases}
+            ServerAdmin ${adminAddr}
+            SSLEngine on
+            SSLCertificateFile ${sslServerCert}
+            SSLCertificateKeyFile ${sslServerKey}
+            ${optionalString (sslServerChain != null) "SSLCertificateChainFile ${sslServerChain}"}
+            ${optionalString hostOpts.http2 "Protocols h2 h2c http/1.1"}
+            ${acmeChallenge}
+            ${mkVHostCommonConf hostOpts}
+        </VirtualHost>
+      ''
+  ;
+
+  mkVHostCommonConf = hostOpts:
+    let
+      documentRoot = if hostOpts.documentRoot != null
+        then hostOpts.documentRoot
+        else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out"
+      ;
+
+      mkLocations = locations: concatStringsSep "\n" (map (config: ''
+        <Location ${config.location}>
+          ${optionalString (config.proxyPass != null) ''
+            <IfModule mod_proxy.c>
+                ProxyPass ${config.proxyPass}
+                ProxyPassReverse ${config.proxyPass}
+            </IfModule>
+          ''}
+          ${optionalString (config.index != null) ''
+            <IfModule mod_dir.c>
+                DirectoryIndex ${config.index}
+            </IfModule>
+          ''}
+          ${optionalString (config.alias != null) ''
+            <IfModule mod_alias.c>
+                Alias "${config.alias}"
+            </IfModule>
+          ''}
+          ${config.extraConfig}
+        </Location>
+      '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
+    in
+      ''
+        ${optionalString cfg.logPerVirtualHost ''
+          ErrorLog ${cfg.logDir}/error-${hostOpts.hostName}.log
+          CustomLog ${cfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat}
+        ''}
+
+        ${optionalString (hostOpts.robotsEntries != "") ''
+          Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries}
+        ''}
+
+        DocumentRoot "${documentRoot}"
+
+        <Directory "${documentRoot}">
+            Options Indexes FollowSymLinks
+            AllowOverride None
+            Require all granted
+        </Directory>
+
+        ${optionalString hostOpts.enableUserDir ''
+          UserDir public_html
+          UserDir disabled root
+          <Directory "/home/*/public_html">
+              AllowOverride FileInfo AuthConfig Limit Indexes
+              Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
+              <Limit GET POST OPTIONS>
+                  Require all granted
+              </Limit>
+              <LimitExcept GET POST OPTIONS>
+                  Require all denied
+              </LimitExcept>
+          </Directory>
+        ''}
+
+        ${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") ''
+          RedirectPermanent / ${hostOpts.globalRedirect}
+        ''}
+
+        ${
+          let makeDirConf = elem: ''
+                Alias ${elem.urlPath} ${elem.dir}/
+                <Directory ${elem.dir}>
+                    Options +Indexes
+                    Require all granted
+                    AllowOverride All
+                </Directory>
+              '';
+          in concatMapStrings makeDirConf hostOpts.servedDirs
+        }
+
+        ${mkLocations hostOpts.locations}
+        ${hostOpts.extraConfig}
+      ''
+  ;
+
+
+  confFile = pkgs.writeText "httpd.conf" ''
+
+    ServerRoot ${pkg}
+    ServerName ${config.networking.hostName}
+    DefaultRuntimeDir ${runtimeDir}/runtime
+
+    PidFile ${runtimeDir}/httpd.pid
+
+    ${optionalString (cfg.mpm != "prefork") ''
+      # mod_cgid requires this.
+      ScriptSock ${runtimeDir}/cgisock
+    ''}
+
+    <IfModule prefork.c>
+        MaxClients           ${toString cfg.maxClients}
+        MaxRequestsPerChild  ${toString cfg.maxRequestsPerChild}
+    </IfModule>
+
+    ${let
+        toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}";
+        uniqueListen = uniqList {inputList = map toStr listenInfo;};
+      in concatStringsSep "\n" uniqueListen
+    }
+
+    User ${cfg.user}
+    Group ${cfg.group}
+
+    ${let
+        mkModule = module:
+          if isString module then { name = module; path = "${pkg}/modules/mod_${module}.so"; }
+          else if isAttrs module then { inherit (module) name path; }
+          else throw "Expecting either a string or attribute set including a name and path.";
+      in
+        concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules))
+    }
+
+    AddHandler type-map var
+
+    <Files ~ "^\.ht">
+        Require all denied
+    </Files>
+
+    ${mimeConf}
+    ${loggingConf}
+    ${browserHacks}
+
+    Include ${pkg}/conf/extra/httpd-default.conf
+    Include ${pkg}/conf/extra/httpd-autoindex.conf
+    Include ${pkg}/conf/extra/httpd-multilang-errordoc.conf
+    Include ${pkg}/conf/extra/httpd-languages.conf
+
+    TraceEnable off
+
+    ${sslConf}
+
+    # Fascist default - deny access to everything.
+    <Directory />
+        Options FollowSymLinks
+        AllowOverride None
+        Require all denied
+    </Directory>
+
+    # But do allow access to files in the store so that we don't have
+    # to generate <Directory> clauses for every generated file that we
+    # want to serve.
+    <Directory /nix/store>
+        Require all granted
+    </Directory>
+
+    ${cfg.extraConfig}
+
+    ${concatMapStringsSep "\n" mkVHostConf vhosts}
+  '';
+
+  # Generate the PHP configuration file.  Should probably be factored
+  # out into a separate module.
+  phpIni = pkgs.runCommand "php.ini"
+    { options = cfg.phpOptions;
+      preferLocalBuild = true;
+    }
+    ''
+      cat ${php}/etc/php.ini > $out
+      cat ${php.phpIni} > $out
+      echo "$options" >> $out
+    '';
+
+in
+
+
+{
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.")
+    (mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.")
+    (mkRenamedOptionModule [ "services" "httpd" "multiProcessingModule" ] [ "services" "httpd" "mpm" ])
+
+    # virtualHosts options
+    (mkRemovedOptionModule [ "services" "httpd" "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+    (mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.")
+  ];
+
+  # interface
+
+  options = {
+
+    services.httpd = {
+
+      enable = mkEnableOption "the Apache HTTP Server";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.apacheHttpd;
+        defaultText = "pkgs.apacheHttpd";
+        description = ''
+          Overridable attribute of the Apache HTTP Server package to use.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        default = confFile;
+        defaultText = "confFile";
+        example = literalExample ''pkgs.writeText "httpd.conf" "# my custom config file ..."'';
+        description = ''
+          Override the configuration file used by Apache. By default,
+          NixOS generates one automatically.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Configuration lines appended to the generated Apache
+          configuration file. Note that this mechanism will not work
+          when <option>configFile</option> is overridden.
+        '';
+      };
+
+      extraModules = mkOption {
+        type = types.listOf types.unspecified;
+        default = [];
+        example = literalExample ''
+          [
+            "proxy_connect"
+            { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
+          ]
+        '';
+        description = ''
+          Additional Apache modules to be used. These can be
+          specified as a string in the case of modules distributed
+          with Apache, or as an attribute set specifying the
+          <varname>name</varname> and <varname>path</varname> of the
+          module.
+        '';
+      };
+
+      adminAddr = mkOption {
+        type = types.str;
+        example = "admin@example.org";
+        description = "E-mail address of the server administrator.";
+      };
+
+      logFormat = mkOption {
+        type = types.str;
+        default = "common";
+        example = "combined";
+        description = ''
+          Log format for log files. Possible values are: combined, common, referer, agent.
+          See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details.
+        '';
+      };
+
+      logPerVirtualHost = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          If enabled, each virtual host gets its own
+          <filename>access.log</filename> and
+          <filename>error.log</filename>, namely suffixed by the
+          <option>hostName</option> of the virtual host.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "wwwrun";
+        description = ''
+          User account under which httpd children processes run.
+
+          If you require the main httpd process to run as
+          <literal>root</literal> add the following configuration:
+          <programlisting>
+          systemd.services.httpd.serviceConfig.User = lib.mkForce "root";
+          </programlisting>
+        '';
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "wwwrun";
+        description = ''
+          Group under which httpd children processes run.
+        '';
+      };
+
+      logDir = mkOption {
+        type = types.path;
+        default = "/var/log/httpd";
+        description = ''
+          Directory for Apache's log files. It is created automatically.
+        '';
+      };
+
+      virtualHosts = mkOption {
+        type = with types; attrsOf (submodule (import ./vhost-options.nix));
+        default = {
+          localhost = {
+            documentRoot = "${pkg}/htdocs";
+          };
+        };
+        example = literalExample ''
+          {
+            "foo.example.com" = {
+              forceSSL = true;
+              documentRoot = "/var/www/foo.example.com"
+            };
+            "bar.example.com" = {
+              addSSL = true;
+              documentRoot = "/var/www/bar.example.com";
+            };
+          }
+        '';
+        description = ''
+          Specification of the virtual hosts served by Apache. Each
+          element should be an attribute set specifying the
+          configuration of the virtual host.
+        '';
+      };
+
+      enableMellon = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the mod_auth_mellon module.";
+      };
+
+      enablePHP = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the PHP module.";
+      };
+
+      phpPackage = mkOption {
+        type = types.package;
+        default = pkgs.php;
+        defaultText = "pkgs.php";
+        description = ''
+          Overridable attribute of the PHP package to use.
+        '';
+      };
+
+      enablePerl = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the Perl module (mod_perl).";
+      };
+
+      phpOptions = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            date.timezone = "CET"
+          '';
+        description = ''
+          Options appended to the PHP configuration file <filename>php.ini</filename>.
+        '';
+      };
+
+      mpm = mkOption {
+        type = types.enum [ "event" "prefork" "worker" ];
+        default = "event";
+        example = "worker";
+        description =
+          ''
+            Multi-processing module to be used by Apache. Available
+            modules are <literal>prefork</literal> (handles each
+            request in a separate child process), <literal>worker</literal>
+            (hybrid approach that starts a number of child processes
+            each running a number of threads) and <literal>event</literal>
+            (the default; a recent variant of <literal>worker</literal>
+            that handles persistent connections more efficiently).
+          '';
+      };
+
+      maxClients = mkOption {
+        type = types.int;
+        default = 150;
+        example = 8;
+        description = "Maximum number of httpd processes (prefork)";
+      };
+
+      maxRequestsPerChild = mkOption {
+        type = types.int;
+        default = 0;
+        example = 500;
+        description = ''
+          Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited.
+        '';
+      };
+
+      sslCiphers = mkOption {
+        type = types.str;
+        default = "HIGH:!aNULL:!MD5:!EXP";
+        description = "Cipher Suite available for negotiation in SSL proxy handshake.";
+      };
+
+      sslProtocols = mkOption {
+        type = types.str;
+        default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1";
+        example = "All -SSLv2 -SSLv3";
+        description = "Allowed SSL/TLS protocol versions.";
+      };
+    };
+
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      {
+        assertion = all (hostOpts: !hostOpts.enableSSL) vhosts;
+        message = ''
+          The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it.
+          Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`,
+          or `services.httpd.virtualHosts.<name>.onlySSL`.
+        '';
+      }
+      {
+        assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts;
+        message = ''
+          Options `services.httpd.virtualHosts.<name>.addSSL`,
+          `services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL`
+          are mutually exclusive.
+        '';
+      }
+      {
+        assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts;
+        message = ''
+          Options `services.httpd.virtualHosts.<name>.enableACME` and
+          `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive.
+        '';
+      }
+    ];
+
+    warnings =
+      mapAttrsToList (name: hostOpts: ''
+        Using config.services.httpd.virtualHosts."${name}".servedFiles is deprecated and will become unsupported in a future release. Your configuration will continue to work as is but please migrate your configuration to config.services.httpd.virtualHosts."${name}".locations before the 20.09 release of NixOS.
+      '') (filterAttrs (name: hostOpts: hostOpts.servedFiles != []) cfg.virtualHosts);
+
+    users.users = optionalAttrs (cfg.user == "wwwrun") {
+      wwwrun = {
+        group = cfg.group;
+        description = "Apache httpd user";
+        uid = config.ids.uids.wwwrun;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "wwwrun") {
+      wwwrun.gid = config.ids.gids.wwwrun;
+    };
+
+    security.acme.certs = mapAttrs (name: hostOpts: {
+      user = cfg.user;
+      group = mkDefault cfg.group;
+      email = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr;
+      webroot = hostOpts.acmeRoot;
+      extraDomains = genAttrs hostOpts.serverAliases (alias: null);
+      postRun = "systemctl reload httpd.service";
+    }) (filterAttrs (name: hostOpts: hostOpts.enableACME) cfg.virtualHosts);
+
+    environment.systemPackages = [ pkg ];
+
+    # required for "apachectl configtest"
+    environment.etc."httpd/httpd.conf".source = httpdConf;
+
+    services.httpd.phpOptions =
+      ''
+        ; Needed for PHP's mail() function.
+        sendmail_path = ${pkgs.system-sendmail}/bin/sendmail -t -i
+
+        ; Don't advertise PHP
+        expose_php = off
+      '' + optionalString (config.time.timeZone != null) ''
+
+        ; Apparently PHP doesn't use $TZ.
+        date.timezone = "${config.time.timeZone}"
+      '';
+
+    services.httpd.extraModules = mkBefore [
+      # HTTP authentication mechanisms: basic and digest.
+      "auth_basic" "auth_digest"
+
+      # Authentication: is the user who he claims to be?
+      "authn_file" "authn_dbm" "authn_anon"
+
+      # Authorization: is the user allowed access?
+      "authz_user" "authz_groupfile" "authz_host"
+
+      # Other modules.
+      "ext_filter" "include" "env" "mime_magic"
+      "cern_meta" "expires" "headers" "usertrack" "setenvif"
+      "dav" "status" "asis" "info" "dav_fs"
+      "vhost_alias" "imagemap" "actions" "speling"
+      "proxy" "proxy_http"
+      "cache" "cache_disk"
+
+      # For compatibility with old configurations, the new module mod_access_compat is provided.
+      "access_compat"
+    ];
+
+    systemd.tmpfiles.rules =
+      let
+        svc = config.systemd.services.httpd.serviceConfig;
+      in
+        [
+          "d '${cfg.logDir}' 0700 ${svc.User} ${svc.Group}"
+          "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}"
+        ];
+
+    systemd.services.httpd =
+      let
+        vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts;
+      in
+      { description = "Apache HTTPD";
+
+        wantedBy = [ "multi-user.target" ];
+        wants = concatLists (map (hostOpts: [ "acme-${hostOpts.hostName}.service" "acme-selfsigned-${hostOpts.hostName}.service" ]) vhostsACME);
+        after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME;
+
+        path = [ pkg pkgs.coreutils pkgs.gnugrep ];
+
+        environment =
+          optionalAttrs cfg.enablePHP { PHPRC = phpIni; }
+          // optionalAttrs cfg.enableMellon { LD_LIBRARY_PATH  = "${pkgs.xmlsec}/lib"; };
+
+        preStart =
+          ''
+            # Get rid of old semaphores.  These tend to accumulate across
+            # server restarts, eventually preventing it from restarting
+            # successfully.
+            for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do
+                ${pkgs.utillinux}/bin/ipcrm -s $i
+            done
+          '';
+
+        serviceConfig = {
+          ExecStart = "@${pkg}/bin/httpd httpd -f ${httpdConf}";
+          ExecStop = "${pkg}/bin/httpd -f ${httpdConf} -k graceful-stop";
+          ExecReload = "${pkg}/bin/httpd -f ${httpdConf} -k graceful";
+          User = cfg.user;
+          Group = cfg.group;
+          Type = "forking";
+          PIDFile = "${runtimeDir}/httpd.pid";
+          Restart = "always";
+          RestartSec = "5s";
+          RuntimeDirectory = "httpd httpd/runtime";
+          RuntimeDirectoryMode = "0750";
+          AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+        };
+      };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix
new file mode 100644
index 000000000000..8ea88f94f973
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/location-options.nix
@@ -0,0 +1,54 @@
+{ config, lib, name, ... }:
+let
+  inherit (lib) mkOption types;
+in
+{
+  options = {
+
+    proxyPass = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "http://www.example.org/";
+      description = ''
+        Sets up a simple reverse proxy as described by <link xlink:href="https://httpd.apache.org/docs/2.4/howto/reverse_proxy.html#simple" />.
+      '';
+    };
+
+    index = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      example = "index.php index.html";
+      description = ''
+        Adds DirectoryIndex directive. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_dir.html#directoryindex" />.
+      '';
+    };
+
+    alias = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      example = "/your/alias/directory";
+      description = ''
+        Alias directory for requests. See <link xlink:href="https://httpd.apache.org/docs/2.4/mod/mod_alias.html#alias" />.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        These lines go to the end of the location verbatim.
+      '';
+    };
+
+    priority = mkOption {
+      type = types.int;
+      default = 1000;
+      description = ''
+        Order of this location block in relation to the others in the vhost.
+        The semantics are the same as with `lib.mkOrder`. Smaller values have
+        a greater priority.
+      '';
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
new file mode 100644
index 000000000000..173c0f8561c0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/apache-httpd/vhost-options.nix
@@ -0,0 +1,275 @@
+{ config, lib, name, ... }:
+let
+  inherit (lib) literalExample mkOption nameValuePair types;
+in
+{
+  options = {
+
+    hostName = mkOption {
+      type = types.str;
+      default = name;
+      description = "Canonical hostname for the server.";
+    };
+
+    serverAliases = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = ["www.example.org" "www.example.org:8080" "example.org"];
+      description = ''
+        Additional names of virtual hosts served by this virtual host configuration.
+      '';
+    };
+
+    listen = mkOption {
+      type = with types; listOf (submodule ({
+        options = {
+          port = mkOption {
+            type = types.port;
+            description = "Port to listen on";
+          };
+          ip = mkOption {
+            type = types.str;
+            default = "*";
+            description = "IP to listen on. 0.0.0.0 for IPv4 only, * for all.";
+          };
+          ssl = mkOption {
+            type = types.bool;
+            default = false;
+            description = "Whether to enable SSL (https) support.";
+          };
+        };
+      }));
+      default = [];
+      example = [
+        { ip = "195.154.1.1"; port = 443; ssl = true;}
+        { ip = "192.154.1.1"; port = 80; }
+        { ip = "*"; port = 8080; }
+      ];
+      description = ''
+        Listen addresses and ports for this virtual host.
+        <note><para>
+          This option overrides <literal>addSSL</literal>, <literal>forceSSL</literal> and <literal>onlySSL</literal>.
+        </para></note>
+      '';
+    };
+
+    enableSSL = mkOption {
+      type = types.bool;
+      visible = false;
+      default = false;
+    };
+
+    addSSL = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
+        <literal>listen</literal> to listen on all interfaces on the respective default
+        ports (80, 443).
+      '';
+    };
+
+    onlySSL = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable HTTPS and reject plain HTTP connections. This will set
+        defaults for <literal>listen</literal> to listen on all interfaces on port 443.
+      '';
+    };
+
+    forceSSL = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to add a separate nginx server block that permanently redirects (301)
+        all plain HTTP traffic to HTTPS. This will set defaults for
+        <literal>listen</literal> to listen on all interfaces on the respective default
+        ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
+      '';
+    };
+
+    enableACME = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to ask Let's Encrypt to sign a certificate for this vhost.
+        Alternately, you can use an existing certificate through <option>useACMEHost</option>.
+      '';
+    };
+
+    useACMEHost = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        A host of an existing Let's Encrypt certificate to use.
+        This is useful if you have many subdomains and want to avoid hitting the
+        <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
+        Alternately, you can generate a certificate through <option>enableACME</option>.
+        <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using  <xref linkend="opt-security.acme.certs"/>.</emphasis>
+      '';
+    };
+
+    acmeRoot = mkOption {
+      type = types.str;
+      default = "/var/lib/acme/acme-challenges";
+      description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
+    };
+
+    sslServerCert = mkOption {
+      type = types.path;
+      example = "/var/host.cert";
+      description = "Path to server SSL certificate.";
+    };
+
+    sslServerKey = mkOption {
+      type = types.path;
+      example = "/var/host.key";
+      description = "Path to server SSL certificate key.";
+    };
+
+    sslServerChain = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/var/ca.pem";
+      description = "Path to server SSL chain file.";
+    };
+
+    http2 = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to enable HTTP 2. HTTP/2 is supported in all multi-processing modules that come with httpd. <emphasis>However, if you use the prefork mpm, there will
+        be severe restrictions.</emphasis> Refer to <link xlink:href="https://httpd.apache.org/docs/2.4/howto/http2.html#mpm-config"/> for details.
+      '';
+    };
+
+    adminAddr = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "admin@example.org";
+      description = "E-mail address of the server administrator.";
+    };
+
+    documentRoot = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/data/webserver/docs";
+      description = ''
+        The path of Apache's document root directory.  If left undefined,
+        an empty directory in the Nix store will be used as root.
+      '';
+    };
+
+    servedDirs = mkOption {
+      type = types.listOf types.attrs;
+      default = [];
+      example = [
+        { urlPath = "/nix";
+          dir = "/home/eelco/Dev/nix-homepage";
+        }
+      ];
+      description = ''
+        This option provides a simple way to serve static directories.
+      '';
+    };
+
+    servedFiles = mkOption {
+      type = types.listOf types.attrs;
+      default = [];
+      example = [
+        { urlPath = "/foo/bar.png";
+          file = "/home/eelco/some-file.png";
+        }
+      ];
+      description = ''
+        This option provides a simple way to serve individual, static files.
+
+        <note><para>
+          This option has been deprecated and will be removed in a future
+          version of NixOS. You can achieve the same result by making use of
+          the <literal>locations.&lt;name&gt;.alias</literal> option.
+        </para></note>
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        <Directory /home>
+          Options FollowSymlinks
+          AllowOverride All
+        </Directory>
+      '';
+      description = ''
+        These lines go to httpd.conf verbatim. They will go after
+        directories and directory aliases defined by default.
+      '';
+    };
+
+    enableUserDir = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable serving <filename>~/public_html</filename> as
+        <literal>/~<replaceable>username</replaceable></literal>.
+      '';
+    };
+
+    globalRedirect = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "http://newserver.example.org/";
+      description = ''
+        If set, all requests for this host are redirected permanently to
+        the given URL.
+      '';
+    };
+
+    logFormat = mkOption {
+      type = types.str;
+      default = "common";
+      example = "combined";
+      description = ''
+        Log format for Apache's log files. Possible values are: combined, common, referer, agent.
+      '';
+    };
+
+    robotsEntries = mkOption {
+      type = types.lines;
+      default = "";
+      example = "Disallow: /foo/";
+      description = ''
+        Specification of pages to be ignored by web crawlers. See <link
+        xlink:href='http://www.robotstxt.org/'/> for details.
+      '';
+    };
+
+    locations = mkOption {
+      type = with types; attrsOf (submodule (import ./location-options.nix));
+      default = {};
+      example = literalExample ''
+        {
+          "/" = {
+            proxyPass = "http://localhost:3000";
+          };
+          "/foo/bar.png" = {
+            alias = "/home/eelco/some-file.png";
+          };
+        };
+      '';
+      description = ''
+        Declarative location config. See <link
+        xlink:href="https://httpd.apache.org/docs/2.4/mod/core.html#location"/> for details.
+      '';
+    };
+
+  };
+
+  config = {
+
+    locations = builtins.listToAttrs (map (elem: nameValuePair elem.urlPath { alias = elem.file; }) config.servedFiles);
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/caddy.nix b/nixpkgs/nixos/modules/services/web-servers/caddy.nix
new file mode 100644
index 000000000000..0e6e10a5f47d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/caddy.nix
@@ -0,0 +1,111 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.caddy;
+  configFile = pkgs.writeText "Caddyfile" cfg.config;
+in {
+  options.services.caddy = {
+    enable = mkEnableOption "Caddy web server";
+
+    config = mkOption {
+      default = "";
+      example = ''
+        example.com {
+        gzip
+        minify
+        log syslog
+
+        root /srv/http
+        }
+      '';
+      type = types.lines;
+      description = "Verbatim Caddyfile to use";
+    };
+
+    ca = mkOption {
+      default = "https://acme-v02.api.letsencrypt.org/directory";
+      example = "https://acme-staging-v02.api.letsencrypt.org/directory";
+      type = types.str;
+      description = "Certificate authority ACME server. The default (Let's Encrypt production server) should be fine for most people.";
+    };
+
+    email = mkOption {
+      default = "";
+      type = types.str;
+      description = "Email address (for Let's Encrypt certificate)";
+    };
+
+    agree = mkOption {
+      default = false;
+      type = types.bool;
+      description = "Agree to Let's Encrypt Subscriber Agreement";
+    };
+
+    dataDir = mkOption {
+      default = "/var/lib/caddy";
+      type = types.path;
+      description = ''
+        The data directory, for storing certificates. Before 17.09, this
+        would create a .caddy directory. With 17.09 the contents of the
+        .caddy directory are in the specified data directory instead.
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.caddy;
+      defaultText = "pkgs.caddy";
+      type = types.package;
+      description = "Caddy package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.caddy = {
+      description = "Caddy web server";
+      # upstream unit: https://github.com/caddyserver/caddy/blob/master/dist/init/linux-systemd/caddy.service
+      after = [ "network-online.target" ];
+      wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
+      wantedBy = [ "multi-user.target" ];
+      environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
+        { CADDYPATH = cfg.dataDir; };
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/caddy -log stdout -log-timestamps=false \
+            -root=/var/tmp -conf=${configFile} \
+            -ca=${cfg.ca} -email=${cfg.email} ${optionalString cfg.agree "-agree"}
+        '';
+        ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID";
+        Type = "simple";
+        User = "caddy";
+        Group = "caddy";
+        Restart = "on-abnormal";
+        StartLimitIntervalSec = 14400;
+        StartLimitBurst = 10;
+        AmbientCapabilities = "cap_net_bind_service";
+        CapabilityBoundingSet = "cap_net_bind_service";
+        NoNewPrivileges = true;
+        LimitNPROC = 512;
+        LimitNOFILE = 1048576;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHome = true;
+        ProtectSystem = "full";
+        ReadWriteDirectories = cfg.dataDir;
+        KillMode = "mixed";
+        KillSignal = "SIGQUIT";
+        TimeoutStopSec = "5s";
+      };
+    };
+
+    users.users.caddy = {
+      group = "caddy";
+      uid = config.ids.uids.caddy;
+      home = cfg.dataDir;
+      createHome = true;
+    };
+
+    users.groups.caddy.gid = config.ids.uids.caddy;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix
new file mode 100644
index 000000000000..d6649fd472d9
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/darkhttpd.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.darkhttpd;
+
+  args = concatStringsSep " " ([
+    cfg.rootDir
+    "--port ${toString cfg.port}"
+    "--addr ${cfg.address}"
+  ] ++ cfg.extraArgs
+    ++ optional cfg.hideServerId             "--no-server-id"
+    ++ optional config.networking.enableIPv6 "--ipv6");
+
+in {
+  options.services.darkhttpd = with types; {
+    enable = mkEnableOption "DarkHTTPd web server";
+
+    port = mkOption {
+      default = 80;
+      type = ints.u16;
+      description = ''
+        Port to listen on.
+        Pass 0 to let the system choose any free port for you.
+      '';
+    };
+
+    address = mkOption {
+      default = "127.0.0.1";
+      type = str;
+      description = ''
+        Address to listen on.
+        Pass `all` to listen on all interfaces.
+      '';
+    };
+
+    rootDir = mkOption {
+      type = path;
+      description = ''
+        Path from which to serve files.
+      '';
+    };
+
+    hideServerId = mkOption {
+      type = bool;
+      default = true;
+      description = ''
+        Don't identify the server type in headers or directory listings.
+      '';
+    };
+
+    extraArgs = mkOption {
+      type = listOf str;
+      default = [];
+      description = ''
+        Additional configuration passed to the executable.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.darkhttpd = {
+      description = "Dark HTTPd";
+      wants = [ "network.target" ];
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.darkhttpd}/bin/darkhttpd ${args}";
+        AmbientCapabilities = lib.mkIf (cfg.port < 1024) [ "CAP_NET_BIND_SERVICE" ];
+        Restart = "on-failure";
+        RestartSec = "2s";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
new file mode 100644
index 000000000000..a64a187255a4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/fcgiwrap.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.fcgiwrap;
+in {
+
+  options = {
+    services.fcgiwrap = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable fcgiwrap, a server for running CGI applications over FastCGI.";
+      };
+
+      preforkProcesses = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Number of processes to prefork.";
+      };
+
+      socketType = mkOption {
+        type = types.enum [ "unix" "tcp" "tcp6" ];
+        default = "unix";
+        description = "Socket type: 'unix', 'tcp' or 'tcp6'.";
+      };
+
+      socketAddress = mkOption {
+        type = types.str;
+        default = "/run/fcgiwrap.sock";
+        example = "1.2.3.4:5678";
+        description = "Socket address. In case of a UNIX socket, this should be its filesystem path.";
+      };
+
+      user = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "User permissions for the socket.";
+      };
+
+      group = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Group permissions for the socket.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.fcgiwrap = {
+      after = [ "nss-user-lookup.target" ];
+      wantedBy = optional (cfg.socketType != "unix") "multi-user.target";
+
+      serviceConfig = {
+        ExecStart = "${pkgs.fcgiwrap}/sbin/fcgiwrap -c ${builtins.toString cfg.preforkProcesses} ${
+          if (cfg.socketType != "unix") then "-s ${cfg.socketType}:${cfg.socketAddress}" else ""
+        }";
+      } // (if cfg.user != null && cfg.group != null then {
+        User = cfg.user;
+        Group = cfg.group;
+      } else { } );
+    };
+
+    systemd.sockets = if (cfg.socketType == "unix") then {
+      fcgiwrap = {
+        wantedBy = [ "sockets.target" ];
+        socketConfig.ListenStream = cfg.socketAddress;
+      };
+    } else { };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
new file mode 100644
index 000000000000..1812f225b74d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/hitch/default.nix
@@ -0,0 +1,111 @@
+{ config, lib, pkgs, ...}:
+let
+  cfg = config.services.hitch;
+  ocspDir = lib.optionalString cfg.ocsp-stapling.enabled "/var/cache/hitch/ocsp";
+  hitchConfig = with lib; pkgs.writeText "hitch.conf" (concatStringsSep "\n" [
+    ("backend = \"${cfg.backend}\"")
+    (concatMapStrings (s: "frontend = \"${s}\"\n") cfg.frontend)
+    (concatMapStrings (s: "pem-file = \"${s}\"\n") cfg.pem-files)
+    ("ciphers = \"${cfg.ciphers}\"")
+    ("ocsp-dir = \"${ocspDir}\"")
+    "user = \"${cfg.user}\""
+    "group = \"${cfg.group}\""
+    cfg.extraConfig
+  ]);
+in
+with lib;
+{
+  options = {
+    services.hitch = {
+      enable = mkEnableOption "Hitch Server";
+
+      backend = mkOption {
+        type = types.str;
+        description = ''
+          The host and port Hitch connects to when receiving
+          a connection in the form [HOST]:PORT
+        '';
+      };
+
+      ciphers = mkOption {
+        type = types.str;
+        default = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
+        description = "The list of ciphers to use";
+      };
+
+      frontend = mkOption {
+        type = types.either types.str (types.listOf types.str);
+        default = "[127.0.0.1]:443";
+        description = ''
+          The port and interface of the listen endpoint in the
++         form [HOST]:PORT[+CERT].
+        '';
+        apply = toList;
+      };
+
+      pem-files = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        description = "PEM files to use";
+      };
+
+      ocsp-stapling = {
+        enabled = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Whether to enable OCSP Stapling";
+        };
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "hitch";
+        description = "The user to run as";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "hitch";
+        description = "The group to run as";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional configuration lines";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.hitch = {
+      description = "Hitch";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      preStart = ''
+        ${pkgs.hitch}/sbin/hitch -t --config ${hitchConfig}
+      '' + (optionalString cfg.ocsp-stapling.enabled ''
+        mkdir -p ${ocspDir}
+        chown -R hitch:hitch ${ocspDir}
+      '');
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.hitch}/sbin/hitch --daemon --config ${hitchConfig}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+        RestartSec = "5s";
+        LimitNOFILE = 131072;
+      };
+    };
+
+    environment.systemPackages = [ pkgs.hitch ];
+
+    users.users.hitch = {
+      group = "hitch";
+      isSystemUser = true;
+    };
+    users.groups.hitch = {};
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/hydron.nix b/nixpkgs/nixos/modules/services/web-servers/hydron.nix
new file mode 100644
index 000000000000..a4a5a435b2e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/hydron.nix
@@ -0,0 +1,165 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.hydron;
+in with lib; {
+  options.services.hydron = {
+    enable = mkEnableOption "hydron";
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/hydron";
+      example = "/home/okina/hydron";
+      description = "Location where hydron runs and stores data.";
+    };
+
+    interval = mkOption {
+      type = types.str;
+      default = "weekly";
+      example = "06:00";
+      description = ''
+        How often we run hydron import and possibly fetch tags. Runs by default every week.
+
+        The format is described in
+        <citerefentry><refentrytitle>systemd.time</refentrytitle>
+        <manvolnum>7</manvolnum></citerefentry>.
+      '';
+    };
+
+    password = mkOption {
+      type = types.str;
+      default = "hydron";
+      example = "dumbpass";
+      description = "Password for the hydron database.";
+    };
+
+    passwordFile = mkOption {
+      type = types.path;
+      default = "/run/keys/hydron-password-file";
+      example = "/home/okina/hydron/keys/pass";
+      description = "Password file for the hydron database.";
+    };
+
+    postgresArgs = mkOption {
+      type = types.str;
+      description = "Postgresql connection arguments.";
+      example = ''
+        {
+          "driver": "postgres",
+          "connection": "user=hydron password=dumbpass dbname=hydron sslmode=disable"
+        }
+      '';
+    };
+
+    postgresArgsFile = mkOption {
+      type = types.path;
+      default = "/run/keys/hydron-postgres-args";
+      example = "/home/okina/hydron/keys/postgres";
+      description = "Postgresql connection arguments file.";
+    };
+
+    listenAddress = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "127.0.0.1:8010";
+      description = "Listen on a specific IP address and port.";
+    };
+
+    importPaths = mkOption {
+      type = types.listOf types.path;
+      default = [];
+      example = [ "/home/okina/Pictures" ];
+      description = "Paths that hydron will recursively import.";
+    };
+
+    fetchTags = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Fetch tags for imported images and webm from gelbooru.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.hydron.passwordFile = mkDefault (pkgs.writeText "hydron-password-file" cfg.password);
+    services.hydron.postgresArgsFile = mkDefault (pkgs.writeText "hydron-postgres-args" cfg.postgresArgs);
+    services.hydron.postgresArgs = mkDefault ''
+      {
+        "driver": "postgres",
+        "connection": "user=hydron password=${cfg.password} host=/run/postgresql dbname=hydron sslmode=disable"
+      }
+    '';
+
+    services.postgresql = {
+      enable = true;
+      ensureDatabases = [ "hydron" ];
+      ensureUsers = [
+        { name = "hydron";
+          ensurePermissions = { "DATABASE hydron" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0750 hydron hydron - -"
+      "d '${cfg.dataDir}/.hydron' - hydron hydron - -"
+      "d '${cfg.dataDir}/images' - hydron hydron - -"
+      "Z '${cfg.dataDir}' - hydron hydron - -"
+
+      "L+ '${cfg.dataDir}/.hydron/db_conf.json' - - - - ${cfg.postgresArgsFile}"
+    ];
+
+    systemd.services.hydron = {
+      description = "hydron";
+      after = [ "network.target" "postgresql.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        User = "hydron";
+        Group = "hydron";
+        ExecStart = "${pkgs.hydron}/bin/hydron serve"
+        + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}";
+      };
+    };
+
+    systemd.services.hydron-fetch = {
+      description = "Import paths into hydron and possibly fetch tags";
+
+      serviceConfig = {
+        Type = "oneshot";
+        User = "hydron";
+        Group = "hydron";
+        ExecStart = "${pkgs.hydron}/bin/hydron import "
+        + optionalString cfg.fetchTags "-f "
+        + (escapeShellArg cfg.dataDir) + "/images " + (escapeShellArgs cfg.importPaths);
+      };
+    };
+
+    systemd.timers.hydron-fetch = {
+      description = "Automatically import paths into hydron and possibly fetch tags";
+      after = [ "network.target" "hydron.service" ];
+      wantedBy = [ "timers.target" ];
+
+      timerConfig = {
+        Persistent = true;
+        OnCalendar = cfg.interval;
+      };
+    };
+
+    users = {
+      groups.hydron.gid = config.ids.gids.hydron;
+
+      users.hydron = {
+        description = "hydron server service user";
+        home = cfg.dataDir;
+        group = "hydron";
+        uid = config.ids.uids.hydron;
+      };
+    };
+  };
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "hydron" "baseDir" ] [ "services" "hydron" "dataDir" ])
+  ];
+
+  meta.maintainers = with maintainers; [ chiiruno ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh
new file mode 100644
index 000000000000..2eb89a90f67d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/jboss/builder.sh
@@ -0,0 +1,72 @@
+set -e
+
+source $stdenv/setup
+
+mkdir -p $out/bin
+
+cat > $out/bin/control <<EOF
+mkdir -p $logDir
+chown -R $user $logDir
+export PATH=$PATH:$su/bin
+
+start()
+{
+  su $user -s /bin/sh -c "$jboss/bin/run.sh \
+      -Djboss.server.base.dir=$serverDir \
+      -Djboss.server.base.url=file://$serverDir \
+      -Djboss.server.temp.dir=$tempDir \
+      -Djboss.server.log.dir=$logDir \
+      -Djboss.server.lib.url=$libUrl \
+      -c default"
+}
+
+stop()
+{
+  su $user -s /bin/sh -c "$jboss/bin/shutdown.sh -S"
+}
+
+if test "\$1" = start
+then
+  trap stop 15
+  
+  start
+elif test "\$1" = stop
+then
+  stop  
+elif test "\$1" = init
+then
+  echo "Are you sure you want to create a new server instance (old server instance will be lost!)?"
+  read answer
+
+  if ! test \$answer = "yes"
+  then
+    exit 1
+  fi
+  
+  rm -rf $serverDir
+  mkdir -p $serverDir
+  cd $serverDir
+  cp -av $jboss/server/default .
+  sed -i -e "s|deploy/|$deployDir|" default/conf/jboss-service.xml
+  
+  if ! test "$useJK" = ""
+  then
+    sed -i -e 's|<attribute name="UseJK">false</attribute>|<attribute name="UseJK">true</attribute>|' default/deploy/jboss-web.deployer/META-INF/jboss-service.xml
+    sed -i -e 's|<Engine name="jboss.web" defaultHost="localhost">|<Engine name="jboss.web" defaultHost="localhost" jvmRoute="node1">|' default/deploy/jboss-web.deployer/server.xml
+  fi
+  
+  # Make files accessible for the server user
+  
+  chown -R $user $serverDir
+  for i in \`find $serverDir -type d\`
+  do
+    chmod 755 \$i
+  done
+  for i in \`find $serverDir -type f\`
+  do
+    chmod 644 \$i
+  done
+fi
+EOF
+
+chmod +x $out/bin/*
diff --git a/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix b/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix
new file mode 100644
index 000000000000..ca5b8635fc00
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/jboss/default.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.jboss;
+
+  jbossService = pkgs.stdenv.mkDerivation {
+    name = "jboss-server";
+    builder = ./builder.sh;
+    inherit (pkgs) jboss su;
+    inherit (cfg) tempDir logDir libUrl deployDir serverDir user useJK;
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.jboss = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable JBoss. WARNING : this package is outdated and is known to have vulnerabilities.";
+      };
+
+      tempDir = mkOption {
+        default = "/tmp";
+        description = "Location where JBoss stores its temp files";
+      };
+
+      logDir = mkOption {
+        default = "/var/log/jboss";
+        description = "Location of the logfile directory of JBoss";
+      };
+
+      serverDir = mkOption {
+        description = "Location of the server instance files";
+        default = "/var/jboss/server";
+      };
+
+      deployDir = mkOption {
+        description = "Location of the deployment files";
+        default = "/nix/var/nix/profiles/default/server/default/deploy/";
+      };
+
+      libUrl = mkOption {
+        default = "file:///nix/var/nix/profiles/default/server/default/lib";
+        description = "Location where the shared library JARs are stored";
+      };
+
+      user = mkOption {
+        default = "nobody";
+        description = "User account under which jboss runs.";
+      };
+
+      useJK = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to use to connector to the Apache HTTP server";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.jboss.enable {
+    systemd.services.jboss = {
+      description = "JBoss server";
+      script = "${jbossService}/bin/control start";
+      wantedBy = [ "multi-user.target" ];
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix
new file mode 100644
index 000000000000..9f25dc34f3f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lighttpd.cgit;
+  pathPrefix = if stringLength cfg.subdir == 0 then "" else "/" + cfg.subdir;
+  configFile = pkgs.writeText "cgitrc"
+    ''
+      # default paths to static assets
+      css=${pathPrefix}/cgit.css
+      logo=${pathPrefix}/cgit.png
+      favicon=${pathPrefix}/favicon.ico
+
+      # user configuration
+      ${cfg.configText}
+    '';
+in
+{
+
+  options.services.lighttpd.cgit = {
+
+    enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        If true, enable cgit (fast web interface for git repositories) as a
+        sub-service in lighttpd.
+      '';
+    };
+
+    subdir = mkOption {
+      default = "cgit";
+      example = "";
+      type = types.str;
+      description = ''
+        The subdirectory in which to serve cgit. The web application will be
+        accessible at http://yourserver/''${subdir}
+      '';
+    };
+
+    configText = mkOption {
+      default = "";
+      example = ''
+        source-filter=''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
+        about-filter=''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh
+        cache-size=1000
+        scan-path=/srv/git
+      '';
+      type = types.lines;
+      description = ''
+        Verbatim contents of the cgit runtime configuration file. Documentation
+        (with cgitrc example file) is available in "man cgitrc". Or online:
+        http://git.zx2c4.com/cgit/tree/cgitrc.5.txt
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    # make the cgitrc manpage available
+    environment.systemPackages = [ pkgs.cgit ];
+
+    # declare module dependencies
+    services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ];
+
+    services.lighttpd.extraConfig = ''
+      $HTTP["url"] =~ "^/${cfg.subdir}" {
+          cgi.assign = (
+              "cgit.cgi" => "${pkgs.cgit}/cgit/cgit.cgi"
+          )
+          alias.url = (
+              "${pathPrefix}/cgit.css" => "${pkgs.cgit}/cgit/cgit.css",
+              "${pathPrefix}/cgit.png" => "${pkgs.cgit}/cgit/cgit.png",
+              "${pathPrefix}"          => "${pkgs.cgit}/cgit/cgit.cgi"
+          )
+          setenv.add-environment = (
+              "CGIT_CONFIG" => "${configFile}"
+          )
+      }
+    '';
+
+    systemd.services.lighttpd.preStart = ''
+      mkdir -p /var/cache/cgit
+      chown lighttpd:lighttpd /var/cache/cgit
+    '';
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
new file mode 100644
index 000000000000..3f262451c2cb
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/collectd.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lighttpd.collectd;
+
+  collectionConf = pkgs.writeText "collection.conf" ''
+    datadir: "${config.services.collectd.dataDir}"
+    libdir: "${config.services.collectd.package}/lib/collectd"
+  '';
+
+  defaultCollectionCgi = config.services.collectd.package.overrideDerivation(old: {
+    name = "collection.cgi";
+    dontConfigure = true;
+    buildPhase = "true";
+    installPhase = ''
+      substituteInPlace contrib/collection.cgi --replace '"/etc/collection.conf"' '$ENV{COLLECTION_CONF}'
+      cp contrib/collection.cgi $out
+    '';
+  });
+in
+{
+
+  options.services.lighttpd.collectd = {
+
+    enable = mkEnableOption "collectd subservice accessible at http://yourserver/collectd";
+
+    collectionCgi = mkOption {
+      type = types.path;
+      default = defaultCollectionCgi;
+      description = ''
+        Path to collection.cgi script from (collectd sources)/contrib/collection.cgi
+        This option allows to use a customized version
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.lighttpd.enableModules = [ "mod_cgi" "mod_alias" "mod_setenv" ];
+
+    services.lighttpd.extraConfig = ''
+      $HTTP["url"] =~ "^/collectd" {
+        cgi.assign = (
+          ".cgi" => "${pkgs.perl}/bin/perl"
+        )
+        alias.url = (
+          "/collectd" => "${cfg.collectionCgi}"
+        )
+        setenv.add-environment = (
+          "PERL5LIB" => "${with pkgs.perlPackages; makePerlPath [ CGI HTMLParser URI pkgs.rrdtool ]}",
+          "COLLECTION_CONF" => "${collectionConf}"
+        )
+      }
+    '';
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix
new file mode 100644
index 000000000000..7a3df26e47a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/default.nix
@@ -0,0 +1,256 @@
+# NixOS module for lighttpd web server
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.lighttpd;
+
+  # List of known lighttpd modules, ordered by how the lighttpd documentation
+  # recommends them being imported:
+  # http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+  #
+  # Some modules are always imported and should not appear in the config:
+  # disallowedModules = [ "mod_indexfile" "mod_dirlisting" "mod_staticfile" ];
+  #
+  # For full module list, see the output of running ./configure in the lighttpd
+  # source.
+  allKnownModules = [
+    "mod_rewrite"
+    "mod_redirect"
+    "mod_alias"
+    "mod_access"
+    "mod_auth"
+    "mod_status"
+    "mod_simple_vhost"
+    "mod_evhost"
+    "mod_userdir"
+    "mod_secdownload"
+    "mod_fastcgi"
+    "mod_proxy"
+    "mod_cgi"
+    "mod_ssi"
+    "mod_compress"
+    "mod_usertrack"
+    "mod_expire"
+    "mod_rrdtool"
+    "mod_accesslog"
+    # Remaining list of modules, order assumed to be unimportant.
+    "mod_authn_file"
+    "mod_authn_gssapi"
+    "mod_authn_ldap"
+    "mod_authn_mysql"
+    "mod_cml"
+    "mod_deflate"
+    "mod_evasive"
+    "mod_extforward"
+    "mod_flv_streaming"
+    "mod_geoip"
+    "mod_magnet"
+    "mod_mysql_vhost"
+    "mod_openssl"  # since v1.4.46
+    "mod_scgi"
+    "mod_setenv"
+    "mod_trigger_b4_dl"
+    "mod_uploadprogress"
+    "mod_vhostdb"  # since v1.4.46
+    "mod_webdav"
+    "mod_wstunnel"  # since v1.4.46
+  ];
+
+  maybeModuleString = moduleName:
+    if elem moduleName cfg.enableModules then ''"${moduleName}"'' else "";
+
+  modulesIncludeString = concatStringsSep ",\n"
+    (filter (x: x != "") (map maybeModuleString allKnownModules));
+
+  configFile = if cfg.configText != "" then
+    pkgs.writeText "lighttpd.conf" ''
+      ${cfg.configText}
+    ''
+    else
+    pkgs.writeText "lighttpd.conf" ''
+      server.document-root = "${cfg.document-root}"
+      server.port = ${toString cfg.port}
+      server.username = "lighttpd"
+      server.groupname = "lighttpd"
+
+      # As for why all modules are loaded here, instead of having small
+      # server.modules += () entries in each sub-service extraConfig snippet,
+      # read this:
+      #
+      #   http://redmine.lighttpd.net/projects/1/wiki/Server_modulesDetails
+      #   http://redmine.lighttpd.net/issues/2337
+      #
+      # Basically, lighttpd doesn't want to load (or even silently ignore) a
+      # module for a second time, and there is no way to check if a module has
+      # been loaded already. So if two services were to put the same module in
+      # server.modules += (), that would break the lighttpd configuration.
+      server.modules = (
+          ${modulesIncludeString}
+      )
+
+      # Logging (logs end up in systemd journal)
+      accesslog.use-syslog = "enable"
+      server.errorlog-use-syslog = "enable"
+
+      ${lib.optionalString cfg.enableUpstreamMimeTypes ''
+      include "${pkgs.lighttpd}/share/lighttpd/doc/config/conf.d/mime.conf"
+      ''}
+
+      static-file.exclude-extensions = ( ".fcgi", ".php", ".rb", "~", ".inc" )
+      index-file.names = ( "index.html" )
+
+      ${if cfg.mod_userdir then ''
+        userdir.path = "public_html"
+      '' else ""}
+
+      ${if cfg.mod_status then ''
+        status.status-url = "/server-status"
+        status.statistics-url = "/server-statistics"
+        status.config-url = "/server-config"
+      '' else ""}
+
+      ${cfg.extraConfig}
+    '';
+
+in
+
+{
+
+  options = {
+
+    services.lighttpd = {
+
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enable the lighttpd web server.
+        '';
+      };
+
+      port = mkOption {
+        default = 80;
+        type = types.int;
+        description = ''
+          TCP port number for lighttpd to bind to.
+        '';
+      };
+
+      document-root = mkOption {
+        default = "/srv/www";
+        type = types.path;
+        description = ''
+          Document-root of the web server. Must be readable by the "lighttpd" user.
+        '';
+      };
+
+      mod_userdir = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If true, requests in the form /~user/page.html are rewritten to take
+          the file public_html/page.html from the home directory of the user.
+        '';
+      };
+
+      enableModules = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "mod_cgi" "mod_status" ];
+        description = ''
+          List of lighttpd modules to enable. Sub-services take care of
+          enabling modules as needed, so this option is mainly for when you
+          want to add custom stuff to
+          <option>services.lighttpd.extraConfig</option> that depends on a
+          certain module.
+        '';
+      };
+
+      enableUpstreamMimeTypes = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to include the list of mime types bundled with lighttpd
+          (upstream). If you disable this, no mime types will be added by
+          NixOS and you will have to add your own mime types in
+          <option>services.lighttpd.extraConfig</option>.
+        '';
+      };
+
+      mod_status = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Show server status overview at /server-status, statistics at
+          /server-statistics and list of loaded modules at /server-config.
+        '';
+      };
+
+      configText = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''...verbatim config file contents...'';
+        description = ''
+          Overridable config file contents to use for lighttpd. By default, use
+          the contents automatically generated by NixOS.
+        '';
+      };
+
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          These configuration lines will be appended to the generated lighttpd
+          config file. Note that this mechanism does not work when the manual
+          <option>configText</option> option is used.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = all (x: elem x allKnownModules) cfg.enableModules;
+        message = ''
+          One (or more) modules in services.lighttpd.enableModules are
+          unrecognized.
+
+          Known modules: ${toString allKnownModules}
+
+          services.lighttpd.enableModules: ${toString cfg.enableModules}
+        '';
+      }
+    ];
+
+    services.lighttpd.enableModules = mkMerge
+      [ (mkIf cfg.mod_status [ "mod_status" ])
+        (mkIf cfg.mod_userdir [ "mod_userdir" ])
+        # always load mod_accesslog so that we can log to the journal
+        [ "mod_accesslog" ]
+      ];
+
+    systemd.services.lighttpd = {
+      description = "Lighttpd Web Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${pkgs.lighttpd}/sbin/lighttpd -D -f ${configFile}";
+      # SIGINT => graceful shutdown
+      serviceConfig.KillSignal = "SIGINT";
+    };
+
+    users.users.lighttpd = {
+      group = "lighttpd";
+      description = "lighttpd web server privilege separation user";
+      uid = config.ids.uids.lighttpd;
+    };
+
+    users.groups.lighttpd.gid = config.ids.gids.lighttpd;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix b/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix
new file mode 100644
index 000000000000..c494d6966a7f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/lighttpd/gitweb.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.gitweb;
+  package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme {
+    gitwebTheme = true;
+  });
+
+in
+{
+
+  options.services.lighttpd.gitweb = {
+
+    enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        If true, enable gitweb in lighttpd. Access it at http://yourserver/gitweb
+      '';
+    };
+
+  };
+
+  config = mkIf config.services.lighttpd.gitweb.enable {
+
+    # declare module dependencies
+    services.lighttpd.enableModules = [ "mod_cgi" "mod_redirect" "mod_alias" "mod_setenv" ];
+
+    services.lighttpd.extraConfig = ''
+      $HTTP["url"] =~ "^/gitweb" {
+          cgi.assign = (
+              ".cgi" => "${pkgs.perl}/bin/perl"
+          )
+          url.redirect = (
+              "^/gitweb$" => "/gitweb/"
+          )
+          alias.url = (
+              "/gitweb/static/" => "${package}/static/",
+              "/gitweb/"        => "${package}/gitweb.cgi"
+          )
+          setenv.add-environment = (
+              "GITWEB_CONFIG" => "${cfg.gitwebConfigFile}",
+              "HOME" => "${cfg.projectroot}"
+          )
+      }
+    '';
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/meguca.nix b/nixpkgs/nixos/modules/services/web-servers/meguca.nix
new file mode 100644
index 000000000000..5a00070dc941
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/meguca.nix
@@ -0,0 +1,174 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.meguca;
+  postgres = config.services.postgresql;
+in with lib; {
+  options.services.meguca = {
+    enable = mkEnableOption "meguca";
+
+    dataDir = mkOption {
+      type = types.path;
+      default = "/var/lib/meguca";
+      example = "/home/okina/meguca";
+      description = "Location where meguca stores it's database and links.";
+    };
+
+    password = mkOption {
+      type = types.str;
+      default = "meguca";
+      example = "dumbpass";
+      description = "Password for the meguca database.";
+    };
+
+    passwordFile = mkOption {
+      type = types.path;
+      default = "/run/keys/meguca-password-file";
+      example = "/home/okina/meguca/keys/pass";
+      description = "Password file for the meguca database.";
+    };
+
+    reverseProxy = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "192.168.1.5";
+      description = "Reverse proxy IP.";
+    };
+
+    sslCertificate = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "/home/okina/meguca/ssl.cert";
+      description = "Path to the SSL certificate.";
+    };
+
+    listenAddress = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "127.0.0.1:8000";
+      description = "Listen on a specific IP address and port.";
+    };
+
+    cacheSize = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      example = 256;
+      description = "Cache size in MB.";
+    };
+
+    postgresArgs = mkOption {
+      type = types.str;
+      example = "user=meguca password=dumbpass dbname=meguca sslmode=disable";
+      description = "Postgresql connection arguments.";
+    };
+
+    postgresArgsFile = mkOption {
+      type = types.path;
+      default = "/run/keys/meguca-postgres-args";
+      example = "/home/okina/meguca/keys/postgres";
+      description = "Postgresql connection arguments file.";
+    };
+
+    compressTraffic = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Compress all traffic with gzip.";
+    };
+
+    assumeReverseProxy = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Assume the server is behind a reverse proxy, when resolving client IPs.";
+    };
+
+    httpsOnly = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Serve and listen only through HTTPS.";
+    };
+
+    videoPaths = mkOption {
+      type = types.listOf types.path;
+      default = [];
+      example = [ "/home/okina/Videos/tehe_pero.webm" ];
+      description = "Videos that will be symlinked into www/videos.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    security.sudo.enable = cfg.enable;
+    services.postgresql.enable = cfg.enable;
+    services.postgresql.package = pkgs.postgresql_11;
+    services.meguca.passwordFile = mkDefault (pkgs.writeText "meguca-password-file" cfg.password);
+    services.meguca.postgresArgsFile = mkDefault (pkgs.writeText "meguca-postgres-args" cfg.postgresArgs);
+    services.meguca.postgresArgs = mkDefault "user=meguca password=${cfg.password} dbname=meguca sslmode=disable";
+
+    systemd.services.meguca = {
+      description = "meguca";
+      after = [ "network.target" "postgresql.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        # Ensure folder exists or create it and links and permissions are correct
+        mkdir -p ${escapeShellArg cfg.dataDir}/www
+        rm -rf ${escapeShellArg cfg.dataDir}/www/videos
+        ln -sf ${pkgs.meguca}/share/meguca/www/* ${escapeShellArg cfg.dataDir}/www
+        unlink ${escapeShellArg cfg.dataDir}/www/videos
+        mkdir -p ${escapeShellArg cfg.dataDir}/www/videos
+
+        for vid in ${escapeShellArg cfg.videoPaths}; do
+          ln -sf $vid ${escapeShellArg cfg.dataDir}/www/videos
+        done
+
+        chmod 750 ${escapeShellArg cfg.dataDir}
+        chown -R meguca:meguca ${escapeShellArg cfg.dataDir}
+
+        # Ensure the database is correct or create it
+        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createuser \
+          -SDR meguca || true
+        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createdb \
+          -T template0 -E UTF8 -O meguca meguca || true
+        ${pkgs.sudo}/bin/sudo -u meguca ${postgres.package}/bin/psql \
+          -c "ALTER ROLE meguca WITH PASSWORD '$(cat ${escapeShellArg cfg.passwordFile})';" || true
+      '';
+
+    script = ''
+      cd ${escapeShellArg cfg.dataDir}
+
+      ${pkgs.meguca}/bin/meguca -d "$(cat ${escapeShellArg cfg.postgresArgsFile})"''
+      + optionalString (cfg.reverseProxy != null) " -R ${cfg.reverseProxy}"
+      + optionalString (cfg.sslCertificate != null) " -S ${cfg.sslCertificate}"
+      + optionalString (cfg.listenAddress != null) " -a ${cfg.listenAddress}"
+      + optionalString (cfg.cacheSize != null) " -c ${toString cfg.cacheSize}"
+      + optionalString (cfg.compressTraffic) " -g"
+      + optionalString (cfg.assumeReverseProxy) " -r"
+      + optionalString (cfg.httpsOnly) " -s" + " start";
+
+      serviceConfig = {
+        PermissionsStartOnly = true;
+        Type = "forking";
+        User = "meguca";
+        Group = "meguca";
+        ExecStop = "${pkgs.meguca}/bin/meguca stop";
+      };
+    };
+
+    users = {
+      groups.meguca.gid = config.ids.gids.meguca;
+
+      users.meguca = {
+        description = "meguca server service user";
+        home = cfg.dataDir;
+        createHome = true;
+        group = "meguca";
+        uid = config.ids.uids.meguca;
+      };
+    };
+  };
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "meguca" "baseDir" ] [ "services" "meguca" "dataDir" ])
+  ];
+
+  meta.maintainers = with maintainers; [ chiiruno ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix
new file mode 100644
index 000000000000..f9b1a8b6ccce
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/mighttpd2.nix
@@ -0,0 +1,132 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mighttpd2;
+  configFile = pkgs.writeText "mighty-config" cfg.config;
+  routingFile = pkgs.writeText "mighty-routing" cfg.routing;
+in {
+  options.services.mighttpd2 = {
+    enable = mkEnableOption "Mighttpd2 web server";
+
+    config = mkOption {
+      default = "";
+      example = ''
+        # Example configuration for Mighttpd 2
+        Port: 80
+        # IP address or "*"
+        Host: *
+        Debug_Mode: Yes # Yes or No
+        # If available, "nobody" is much more secure for User:.
+        User: root
+        # If available, "nobody" is much more secure for Group:.
+        Group: root
+        Pid_File: /run/mighty.pid
+        Logging: Yes # Yes or No
+        Log_File: /var/log/mighty # The directory must be writable by User:
+        Log_File_Size: 16777216 # bytes
+        Log_Backup_Number: 10
+        Index_File: index.html
+        Index_Cgi: index.cgi
+        Status_File_Dir: /usr/local/share/mighty/status
+        Connection_Timeout: 30 # seconds
+        Fd_Cache_Duration: 10 # seconds
+        # Server_Name: Mighttpd/3.x.y
+        Tls_Port: 443
+        Tls_Cert_File: cert.pem # should change this with an absolute path
+        # should change this with comma-separated absolute paths
+        Tls_Chain_Files: chain.pem
+        # Currently, Tls_Key_File must not be encrypted.
+        Tls_Key_File: privkey.pem # should change this with an absolute path
+        Service: 0 # 0 is HTTP only, 1 is HTTPS only, 2 is both
+      '';
+      type = types.lines;
+      description = ''
+        Verbatim config file to use
+        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+      '';
+    };
+
+    routing = mkOption {
+      default = "";
+      example = ''
+        # Example routing for Mighttpd 2
+
+        # Domain lists
+        [localhost www.example.com]
+
+        # Entries are looked up in the specified order
+        # All paths must end with "/"
+
+        # A path to CGI scripts should be specified with "=>"
+        /~alice/cgi-bin/ => /home/alice/public_html/cgi-bin/
+
+        # A path to static files should be specified with "->"
+        /~alice/         -> /home/alice/public_html/
+        /cgi-bin/        => /export/cgi-bin/
+
+        # Reverse proxy rules should be specified with ">>"
+        # /path >> host:port/path2
+        # Either "host" or ":port" can be committed, but not both.
+        /app/cal/        >> example.net/calendar/
+        # Yesod app in the same server
+        /app/wiki/       >> 127.0.0.1:3000/
+
+        /                -> /export/www/
+      '';
+      type = types.lines;
+      description = ''
+        Verbatim routing file to use
+        (see http://www.mew.org/~kazu/proj/mighttpd/en/config.html)
+      '';
+    };
+
+    cores = mkOption {
+      default = null;
+      type = types.nullOr types.int;
+      description = ''
+        How many cores to use.
+        If null it will be determined automatically
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    assertions =
+      [ { assertion = cfg.routing != "";
+          message = "You need at least one rule in mighttpd2.routing";
+        }
+      ];
+    systemd.services.mighttpd2 = {
+      description = "Mighttpd2 web server";
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.haskellPackages.mighttpd2}/bin/mighty \
+            ${configFile} \
+            ${routingFile} \
+            +RTS -N${optionalString (cfg.cores != null) "${cfg.cores}"}
+        '';
+        Type = "simple";
+        User = "mighttpd2";
+        Group = "mighttpd2";
+        Restart = "on-failure";
+        AmbientCapabilities = "cap_net_bind_service";
+        CapabilityBoundingSet = "cap_net_bind_service";
+      };
+    };
+
+    users.users.mighttpd2 = {
+      group = "mighttpd2";
+      uid = config.ids.uids.mighttpd2;
+      isSystemUser = true;
+    };
+
+    users.groups.mighttpd2.gid = config.ids.gids.mighttpd2;
+  };
+
+  meta.maintainers = with lib.maintainers; [ fgaz ];
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/minio.nix b/nixpkgs/nixos/modules/services/web-servers/minio.nix
new file mode 100644
index 000000000000..cd123000f009
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/minio.nix
@@ -0,0 +1,108 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.minio;
+in
+{
+  meta.maintainers = [ maintainers.bachp ];
+
+  options.services.minio = {
+    enable = mkEnableOption "Minio Object Storage";
+
+    listenAddress = mkOption {
+      default = ":9000";
+      type = types.str;
+      description = "Listen on a specific IP address and port.";
+    };
+
+    dataDir = mkOption {
+      default = "/var/lib/minio/data";
+      type = types.path;
+      description = "The data directory, for storing the objects.";
+    };
+
+    configDir = mkOption {
+      default = "/var/lib/minio/config";
+      type = types.path;
+      description = "The config directory, for the access keys and other settings.";
+    };
+
+    accessKey = mkOption {
+      default = "";
+      type = types.str;
+      description = ''
+        Access key of 5 to 20 characters in length that clients use to access the server.
+        This overrides the access key that is generated by minio on first startup and stored inside the
+        <literal>configDir</literal> directory.
+      '';
+    };
+
+    secretKey = mkOption {
+      default = "";
+      type = types.str;
+      description = ''
+        Specify the Secret key of 8 to 40 characters in length that clients use to access the server.
+        This overrides the secret key that is generated by minio on first startup and stored inside the
+        <literal>configDir</literal> directory.
+      '';
+    };
+
+    region = mkOption {
+      default = "us-east-1";
+      type = types.str;
+      description = ''
+        The physical location of the server. By default it is set to us-east-1, which is same as AWS S3's and Minio's default region.
+      '';
+    };
+
+    browser = mkOption {
+      default = true;
+      type = types.bool;
+      description = "Enable or disable access to web UI.";
+    };
+
+    package = mkOption {
+      default = pkgs.minio;
+      defaultText = "pkgs.minio";
+      type = types.package;
+      description = "Minio package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.configDir}' - minio minio - -"
+      "d '${cfg.dataDir}' - minio minio - -"
+    ];
+
+    systemd.services.minio = {
+      description = "Minio Object Storage";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/minio server --json --address ${cfg.listenAddress} --config-dir=${cfg.configDir} ${cfg.dataDir}";
+        Type = "simple";
+        User = "minio";
+        Group = "minio";
+        LimitNOFILE = 65536;
+      };
+      environment = {
+        MINIO_REGION = "${cfg.region}";
+        MINIO_BROWSER = "${if cfg.browser then "on" else "off"}";
+      } // optionalAttrs (cfg.accessKey != "") {
+        MINIO_ACCESS_KEY = "${cfg.accessKey}";
+      } // optionalAttrs (cfg.secretKey != "") {
+        MINIO_SECRET_KEY = "${cfg.secretKey}";
+      };
+    };
+
+    users.users.minio = {
+      group = "minio";
+      uid = config.ids.uids.minio;
+    };
+
+    users.groups.minio.gid = config.ids.uids.minio;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
new file mode 100644
index 000000000000..8a015bb3556c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/default.nix
@@ -0,0 +1,794 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nginx;
+  certs = config.security.acme.certs;
+  vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
+  acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME && vhostConfig.useACMEHost == null) vhostsConfigs;
+  virtualHosts = mapAttrs (vhostName: vhostConfig:
+    let
+      serverName = if vhostConfig.serverName != null
+        then vhostConfig.serverName
+        else vhostName;
+    in
+    vhostConfig // {
+      inherit serverName;
+    } // (optionalAttrs vhostConfig.enableACME {
+      sslCertificate = "${certs.${serverName}.directory}/fullchain.pem";
+      sslCertificateKey = "${certs.${serverName}.directory}/key.pem";
+      sslTrustedCertificate = "${certs.${serverName}.directory}/full.pem";
+    }) // (optionalAttrs (vhostConfig.useACMEHost != null) {
+      sslCertificate = "${certs.${vhostConfig.useACMEHost}.directory}/fullchain.pem";
+      sslCertificateKey = "${certs.${vhostConfig.useACMEHost}.directory}/key.pem";
+      sslTrustedCertificate = "${certs.${vhostConfig.useACMEHost}.directory}/fullchain.pem";
+    })
+  ) cfg.virtualHosts;
+  enableIPv6 = config.networking.enableIPv6;
+
+  recommendedProxyConfig = pkgs.writeText "nginx-recommended-proxy-headers.conf" ''
+    proxy_set_header        Host $host;
+    proxy_set_header        X-Real-IP $remote_addr;
+    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header        X-Forwarded-Proto $scheme;
+    proxy_set_header        X-Forwarded-Host $host;
+    proxy_set_header        X-Forwarded-Server $host;
+    proxy_set_header        Accept-Encoding "";
+  '';
+
+  upstreamConfig = toString (flip mapAttrsToList cfg.upstreams (name: upstream: ''
+    upstream ${name} {
+      ${toString (flip mapAttrsToList upstream.servers (name: server: ''
+        server ${name} ${optionalString server.backup "backup"};
+      ''))}
+      ${upstream.extraConfig}
+    }
+  ''));
+
+  commonHttpConfig = ''
+      # The mime type definitions included with nginx are very incomplete, so
+      # we use a list of mime types from the mailcap package, which is also
+      # used by most other Linux distributions by default.
+      include ${pkgs.mailcap}/etc/nginx/mime.types;
+      include ${cfg.package}/conf/fastcgi.conf;
+      include ${cfg.package}/conf/uwsgi_params;
+  '';
+
+  configFile = pkgs.writers.writeNginxConfig "nginx.conf" ''
+    pid /run/nginx/nginx.pid;
+    error_log ${cfg.logError};
+    daemon off;
+
+    ${cfg.config}
+
+    ${optionalString (cfg.eventsConfig != "" || cfg.config == "") ''
+    events {
+      ${cfg.eventsConfig}
+    }
+    ''}
+
+    ${optionalString (cfg.httpConfig == "" && cfg.config == "") ''
+    http {
+      ${commonHttpConfig}
+
+      ${optionalString (cfg.resolver.addresses != []) ''
+        resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"} ${optionalString (!cfg.resolver.ipv6) "ipv6=off"};
+      ''}
+      ${upstreamConfig}
+
+      ${optionalString (cfg.recommendedOptimisation) ''
+        # optimisation
+        sendfile on;
+        tcp_nopush on;
+        tcp_nodelay on;
+        keepalive_timeout 65;
+        types_hash_max_size 4096;
+      ''}
+
+      ssl_protocols ${cfg.sslProtocols};
+      ssl_ciphers ${cfg.sslCiphers};
+      ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"}
+
+      ${optionalString (cfg.recommendedTlsSettings) ''
+        # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
+
+        ssl_session_timeout 1d;
+        ssl_session_cache shared:SSL:10m;
+        # Breaks forward secrecy: https://github.com/mozilla/server-side-tls/issues/135
+        ssl_session_tickets off;
+        # We don't enable insecure ciphers by default, so this allows
+        # clients to pick the most performant, per https://github.com/mozilla/server-side-tls/issues/260
+        ssl_prefer_server_ciphers off;
+
+        # OCSP stapling
+        ssl_stapling on;
+        ssl_stapling_verify on;
+      ''}
+
+      ${optionalString (cfg.recommendedGzipSettings) ''
+        gzip on;
+        gzip_proxied any;
+        gzip_comp_level 5;
+        gzip_types
+          application/atom+xml
+          application/javascript
+          application/json
+          application/xml
+          application/xml+rss
+          image/svg+xml
+          text/css
+          text/javascript
+          text/plain
+          text/xml;
+        gzip_vary on;
+      ''}
+
+      ${optionalString (cfg.recommendedProxySettings) ''
+        proxy_redirect          off;
+        proxy_connect_timeout   90;
+        proxy_send_timeout      90;
+        proxy_read_timeout      90;
+        proxy_http_version      1.0;
+        include ${recommendedProxyConfig};
+      ''}
+
+      ${optionalString (cfg.mapHashBucketSize != null) ''
+        map_hash_bucket_size ${toString cfg.mapHashBucketSize};
+      ''}
+
+      ${optionalString (cfg.mapHashMaxSize != null) ''
+        map_hash_max_size ${toString cfg.mapHashMaxSize};
+      ''}
+
+      # $connection_upgrade is used for websocket proxying
+      map $http_upgrade $connection_upgrade {
+          default upgrade;
+          '''      close;
+      }
+      client_max_body_size ${cfg.clientMaxBodySize};
+
+      server_tokens ${if cfg.serverTokens then "on" else "off"};
+
+      ${cfg.commonHttpConfig}
+
+      ${vhosts}
+
+      ${optionalString cfg.statusPage ''
+        server {
+          listen 80;
+          ${optionalString enableIPv6 "listen [::]:80;" }
+
+          server_name localhost;
+
+          location /nginx_status {
+            stub_status on;
+            access_log off;
+            allow 127.0.0.1;
+            ${optionalString enableIPv6 "allow ::1;"}
+            deny all;
+          }
+        }
+      ''}
+
+      ${cfg.appendHttpConfig}
+    }''}
+
+    ${optionalString (cfg.httpConfig != "") ''
+    http {
+      ${commonHttpConfig}
+      ${cfg.httpConfig}
+    }''}
+
+    ${cfg.appendConfig}
+  '';
+
+  configPath = if cfg.enableReload
+    then "/etc/nginx/nginx.conf"
+    else configFile;
+
+  execCommand = "${cfg.package}/bin/nginx -c '${configPath}'";
+
+  vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost:
+    let
+        onlySSL = vhost.onlySSL || vhost.enableSSL;
+        hasSSL = onlySSL || vhost.addSSL || vhost.forceSSL;
+
+        defaultListen =
+          if vhost.listen != [] then vhost.listen
+          else ((optionals hasSSL (
+            singleton                    { addr = "0.0.0.0"; port = 443; ssl = true; }
+            ++ optional enableIPv6 { addr = "[::]";    port = 443; ssl = true; }
+          )) ++ optionals (!onlySSL) (
+            singleton                    { addr = "0.0.0.0"; port = 80;  ssl = false; }
+            ++ optional enableIPv6 { addr = "[::]";    port = 80;  ssl = false; }
+          ));
+
+        hostListen =
+          if vhost.forceSSL
+            then filter (x: x.ssl) defaultListen
+            else defaultListen;
+
+        listenString = { addr, port, ssl, extraParameters ? [], ... }:
+          "listen ${addr}:${toString port} "
+          + optionalString ssl "ssl "
+          + optionalString (ssl && vhost.http2) "http2 "
+          + optionalString vhost.default "default_server "
+          + optionalString (extraParameters != []) (concatStringsSep " " extraParameters)
+          + ";";
+
+        redirectListen = filter (x: !x.ssl) defaultListen;
+
+        acmeLocation = optionalString (vhost.enableACME || vhost.useACMEHost != null) ''
+          location /.well-known/acme-challenge {
+            ${optionalString (vhost.acmeFallbackHost != null) "try_files $uri @acme-fallback;"}
+            root ${vhost.acmeRoot};
+            auth_basic off;
+          }
+          ${optionalString (vhost.acmeFallbackHost != null) ''
+            location @acme-fallback {
+              auth_basic off;
+              proxy_pass http://${vhost.acmeFallbackHost};
+            }
+          ''}
+        '';
+
+      in ''
+        ${optionalString vhost.forceSSL ''
+          server {
+            ${concatMapStringsSep "\n" listenString redirectListen}
+
+            server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
+            ${acmeLocation}
+            location / {
+              return 301 https://$host$request_uri;
+            }
+          }
+        ''}
+
+        server {
+          ${concatMapStringsSep "\n" listenString hostListen}
+          server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases};
+          ${acmeLocation}
+          ${optionalString (vhost.root != null) "root ${vhost.root};"}
+          ${optionalString (vhost.globalRedirect != null) ''
+            return 301 http${optionalString hasSSL "s"}://${vhost.globalRedirect}$request_uri;
+          ''}
+          ${optionalString hasSSL ''
+            ssl_certificate ${vhost.sslCertificate};
+            ssl_certificate_key ${vhost.sslCertificateKey};
+          ''}
+          ${optionalString (hasSSL && vhost.sslTrustedCertificate != null) ''
+            ssl_trusted_certificate ${vhost.sslTrustedCertificate};
+          ''}
+
+          ${optionalString (vhost.basicAuthFile != null || vhost.basicAuth != {}) ''
+            auth_basic secured;
+            auth_basic_user_file ${if vhost.basicAuthFile != null then vhost.basicAuthFile else mkHtpasswd vhostName vhost.basicAuth};
+          ''}
+
+          ${mkLocations vhost.locations}
+
+          ${vhost.extraConfig}
+        }
+      ''
+  ) virtualHosts);
+  mkLocations = locations: concatStringsSep "\n" (map (config: ''
+    location ${config.location} {
+      ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning)
+        "proxy_pass ${config.proxyPass};"
+      }
+      ${optionalString (config.proxyPass != null && cfg.proxyResolveWhileRunning) ''
+        set $nix_proxy_target "${config.proxyPass}";
+        proxy_pass $nix_proxy_target;
+      ''}
+      ${optionalString config.proxyWebsockets ''
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection $connection_upgrade;
+      ''}
+      ${optionalString (config.index != null) "index ${config.index};"}
+      ${optionalString (config.tryFiles != null) "try_files ${config.tryFiles};"}
+      ${optionalString (config.root != null) "root ${config.root};"}
+      ${optionalString (config.alias != null) "alias ${config.alias};"}
+      ${optionalString (config.return != null) "return ${config.return};"}
+      ${config.extraConfig}
+      ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"}
+    }
+  '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
+  mkHtpasswd = vhostName: authDef: pkgs.writeText "${vhostName}.htpasswd" (
+    concatStringsSep "\n" (mapAttrsToList (user: password: ''
+      ${user}:{PLAIN}${password}
+    '') authDef)
+  );
+in
+
+{
+  options = {
+    services.nginx = {
+      enable = mkEnableOption "Nginx Web Server";
+
+      statusPage = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable status page reachable from localhost on http://127.0.0.1/nginx_status.
+        ";
+      };
+
+      recommendedTlsSettings = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable recommended TLS settings.
+        ";
+      };
+
+      recommendedOptimisation = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable recommended optimisation settings.
+        ";
+      };
+
+      recommendedGzipSettings = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable recommended gzip settings.
+        ";
+      };
+
+      recommendedProxySettings = mkOption {
+        default = false;
+        type = types.bool;
+        description = "
+          Enable recommended proxy settings.
+        ";
+      };
+
+      package = mkOption {
+        default = pkgs.nginxStable;
+        defaultText = "pkgs.nginxStable";
+        type = types.package;
+        description = "
+          Nginx package to use. This defaults to the stable version. Note
+          that the nginx team recommends to use the mainline version which
+          available in nixpkgs as <literal>nginxMainline</literal>.
+        ";
+      };
+
+      logError = mkOption {
+        default = "stderr";
+        description = "
+          Configures logging.
+          The first parameter defines a file that will store the log. The
+          special value stderr selects the standard error file. Logging to
+          syslog can be configured by specifying the “syslog:” prefix.
+          The second parameter determines the level of logging, and can be
+          one of the following: debug, info, notice, warn, error, crit,
+          alert, or emerg. Log levels above are listed in the order of
+          increasing severity. Setting a certain log level will cause all
+          messages of the specified and more severe log levels to be logged.
+          If this parameter is omitted then error is used.
+        ";
+      };
+
+      preStart =  mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Shell commands executed before the service's nginx is started.
+        ";
+      };
+
+      config = mkOption {
+        default = "";
+        description = "
+          Verbatim nginx.conf configuration.
+          This is mutually exclusive with the structured configuration
+          via virtualHosts and the recommendedXyzSettings configuration
+          options. See appendConfig for appending to the generated http block.
+        ";
+      };
+
+      appendConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Configuration lines appended to the generated Nginx
+          configuration file. Commonly used by different modules
+          providing http snippets. <option>appendConfig</option>
+          can be specified more than once and it's value will be
+          concatenated (contrary to <option>config</option> which
+          can be set only once).
+        '';
+      };
+
+      commonHttpConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          resolver 127.0.0.1 valid=5s;
+
+          log_format myformat '$remote_addr - $remote_user [$time_local] '
+                              '"$request" $status $body_bytes_sent '
+                              '"$http_referer" "$http_user_agent"';
+        '';
+        description = ''
+          With nginx you must provide common http context definitions before
+          they are used, e.g. log_format, resolver, etc. inside of server
+          or location contexts. Use this attribute to set these definitions
+          at the appropriate location.
+        '';
+      };
+
+      httpConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Configuration lines to be set inside the http block.
+          This is mutually exclusive with the structured configuration
+          via virtualHosts and the recommendedXyzSettings configuration
+          options. See appendHttpConfig for appending to the generated http block.
+        ";
+      };
+
+      eventsConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Configuration lines to be set inside the events block.
+        '';
+      };
+
+      appendHttpConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Configuration lines to be appended to the generated http block.
+          This is mutually exclusive with using config and httpConfig for
+          specifying the whole http block verbatim.
+        ";
+      };
+
+      enableReload = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Reload nginx when configuration file changes (instead of restart).
+          The configuration file is exposed at <filename>/etc/nginx/nginx.conf</filename>.
+          See also <literal>systemd.services.*.restartIfChanged</literal>.
+        '';
+      };
+
+      enableSandbox = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Starting Nginx web server with additional sandbox/hardening options.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "nginx";
+        description = "User account under which nginx runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "nginx";
+        description = "Group account under which nginx runs.";
+      };
+
+      serverTokens = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Show nginx version in headers and error pages.";
+      };
+
+      clientMaxBodySize = mkOption {
+        type = types.str;
+        default = "10m";
+        description = "Set nginx global client_max_body_size.";
+      };
+
+      sslCiphers = mkOption {
+        type = types.str;
+        # Keep in sync with https://ssl-config.mozilla.org/#server=nginx&config=intermediate
+        default = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";
+        description = "Ciphers to choose from when negotiating TLS handshakes.";
+      };
+
+      sslProtocols = mkOption {
+        type = types.str;
+        default = "TLSv1.2 TLSv1.3";
+        example = "TLSv1 TLSv1.1 TLSv1.2 TLSv1.3";
+        description = "Allowed TLS protocol versions.";
+      };
+
+      sslDhparam = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/path/to/dhparams.pem";
+        description = "Path to DH parameters file.";
+      };
+
+      proxyResolveWhileRunning = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Resolves domains of proxyPass targets at runtime
+          and not only at start, you have to set
+          services.nginx.resolver, too.
+        '';
+      };
+
+      mapHashBucketSize = mkOption {
+        type = types.nullOr (types.enum [ 32 64 128 ]);
+        default = null;
+        description = ''
+            Sets the bucket size for the map variables hash tables. Default
+            value depends on the processor’s cache line size.
+          '';
+      };
+
+      mapHashMaxSize = mkOption {
+        type = types.nullOr types.ints.positive;
+        default = null;
+        description = ''
+            Sets the maximum size of the map variables hash tables.
+          '';
+      };
+
+      resolver = mkOption {
+        type = types.submodule {
+          options = {
+            addresses = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = literalExample ''[ "[::1]" "127.0.0.1:5353" ]'';
+              description = "List of resolvers to use";
+            };
+            valid = mkOption {
+              type = types.str;
+              default = "";
+              example = "30s";
+              description = ''
+                By default, nginx caches answers using the TTL value of a response.
+                An optional valid parameter allows overriding it
+              '';
+            };
+            ipv6 = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                By default, nginx will look up both IPv4 and IPv6 addresses while resolving.
+                If looking up of IPv6 addresses is not desired, the ipv6=off parameter can be
+                specified.
+              '';
+            };
+          };
+        };
+        description = ''
+          Configures name servers used to resolve names of upstream servers into addresses
+        '';
+        default = {};
+      };
+
+      upstreams = mkOption {
+        type = types.attrsOf (types.submodule {
+          options = {
+            servers = mkOption {
+              type = types.attrsOf (types.submodule {
+                options = {
+                  backup = mkOption {
+                    type = types.bool;
+                    default = false;
+                    description = ''
+                      Marks the server as a backup server. It will be passed
+                      requests when the primary servers are unavailable.
+                    '';
+                  };
+                };
+              });
+              description = ''
+                Defines the address and other parameters of the upstream servers.
+              '';
+              default = {};
+            };
+            extraConfig = mkOption {
+              type = types.lines;
+              default = "";
+              description = ''
+                These lines go to the end of the upstream verbatim.
+              '';
+            };
+          };
+        });
+        description = ''
+          Defines a group of servers to use as proxy target.
+        '';
+        default = {};
+      };
+
+      virtualHosts = mkOption {
+        type = types.attrsOf (types.submodule (import ./vhost-options.nix {
+          inherit config lib;
+        }));
+        default = {
+          localhost = {};
+        };
+        example = literalExample ''
+          {
+            "hydra.example.com" = {
+              forceSSL = true;
+              enableACME = true;
+              locations."/" = {
+                proxyPass = "http://localhost:3000";
+              };
+            };
+          };
+        '';
+        description = "Declarative vhost config";
+      };
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "nginx" "stateDir" ] ''
+      The Nginx log directory has been moved to /var/log/nginx, the cache directory
+      to /var/cache/nginx. The option services.nginx.stateDir has been removed.
+    '')
+  ];
+
+  config = mkIf cfg.enable {
+    # TODO: test user supplied config file pases syntax test
+
+    warnings =
+    let
+      deprecatedSSL = name: config: optional config.enableSSL
+      ''
+        config.services.nginx.virtualHosts.<name>.enableSSL is deprecated,
+        use config.services.nginx.virtualHosts.<name>.onlySSL instead.
+      '';
+
+    in flatten (mapAttrsToList deprecatedSSL virtualHosts);
+
+    assertions =
+    let
+      hostOrAliasIsNull = l: l.root == null || l.alias == null;
+    in [
+      {
+        assertion = all (host: all hostOrAliasIsNull (attrValues host.locations)) (attrValues virtualHosts);
+        message = "Only one of nginx root or alias can be specified on a location.";
+      }
+
+      {
+        assertion = all (conf: with conf;
+          !(addSSL && (onlySSL || enableSSL)) &&
+          !(forceSSL && (onlySSL || enableSSL)) &&
+          !(addSSL && forceSSL)
+        ) (attrValues virtualHosts);
+        message = ''
+          Options services.nginx.service.virtualHosts.<name>.addSSL,
+          services.nginx.virtualHosts.<name>.onlySSL and services.nginx.virtualHosts.<name>.forceSSL
+          are mutually exclusive.
+        '';
+      }
+
+      {
+        assertion = all (conf: !(conf.enableACME && conf.useACMEHost != null)) (attrValues virtualHosts);
+        message = ''
+          Options services.nginx.service.virtualHosts.<name>.enableACME and
+          services.nginx.virtualHosts.<name>.useACMEHost are mutually exclusive.
+        '';
+      }
+    ];
+
+    systemd.services.nginx = {
+      description = "Nginx Web Server";
+      wantedBy = [ "multi-user.target" ];
+      wants = concatLists (map (vhostConfig: ["acme-${vhostConfig.serverName}.service" "acme-selfsigned-${vhostConfig.serverName}.service"]) acmeEnabledVhosts);
+      after = [ "network.target" ] ++ map (vhostConfig: "acme-selfsigned-${vhostConfig.serverName}.service") acmeEnabledVhosts;
+      stopIfChanged = false;
+      preStart = ''
+        ${cfg.preStart}
+        ${execCommand} -t
+      '';
+      serviceConfig = {
+        ExecStart = execCommand;
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+        RestartSec = "10s";
+        StartLimitInterval = "1min";
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+        # Runtime directory and mode
+        RuntimeDirectory = "nginx";
+        RuntimeDirectoryMode = "0750";
+        # Cache directory and mode
+        CacheDirectory = "nginx";
+        CacheDirectoryMode = "0750";
+        # Logs directory and mode
+        LogsDirectory = "nginx";
+        LogsDirectoryMode = "0750";
+        # Capabilities
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+        CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
+        # Security
+        NoNewPrivileges = true;
+      } // optionalAttrs cfg.enableSandbox {
+        # Sandboxing
+        ProtectSystem = "strict";
+        ProtectHome = mkDefault true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = !(builtins.any (mod: (mod.allowMemoryWriteExecute or false)) pkgs.nginx.modules);
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        PrivateMounts = true;
+        # System Call Filtering
+        SystemCallArchitectures = "native";
+      };
+    };
+
+    environment.etc."nginx/nginx.conf" = mkIf cfg.enableReload {
+      source = configFile;
+    };
+
+    systemd.services.nginx-config-reload = mkIf cfg.enableReload {
+      wants = [ "nginx.service" ];
+      wantedBy = [ "multi-user.target" ];
+      restartTriggers = [ configFile ];
+      # commented, because can cause extra delays during activate for this config:
+      #      services.nginx.virtualHosts."_".locations."/".proxyPass = "http://blabla:3000";
+      # stopIfChanged = false;
+      serviceConfig.Type = "oneshot";
+      serviceConfig.TimeoutSec = 60;
+      script = ''
+        if /run/current-system/systemd/bin/systemctl -q is-active nginx.service ; then
+          ${execCommand} -t && \
+            /run/current-system/systemd/bin/systemctl reload nginx.service
+        fi
+      '';
+      serviceConfig.RemainAfterExit = true;
+    };
+
+    security.acme.certs = filterAttrs (n: v: v != {}) (
+      let
+        acmePairs = map (vhostConfig: { name = vhostConfig.serverName; value = {
+            user = cfg.user;
+            group = lib.mkDefault cfg.group;
+            webroot = vhostConfig.acmeRoot;
+            extraDomains = genAttrs vhostConfig.serverAliases (alias: null);
+            postRun = ''
+              /run/current-system/systemd/bin/systemctl reload nginx
+            '';
+          }; }) acmeEnabledVhosts;
+      in
+        listToAttrs acmePairs
+    );
+
+    users.users = optionalAttrs (cfg.user == "nginx") {
+      nginx = {
+        group = cfg.group;
+        uid = config.ids.uids.nginx;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "nginx") {
+      nginx.gid = config.ids.gids.nginx;
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix
new file mode 100644
index 000000000000..f7fb07bb7975
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/gitweb.nix
@@ -0,0 +1,94 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nginx.gitweb;
+  gitwebConfig = config.services.gitweb;
+  package = pkgs.gitweb.override (optionalAttrs gitwebConfig.gitwebTheme {
+    gitwebTheme = true;
+  });
+
+in
+{
+
+  options.services.nginx.gitweb = {
+
+    enable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        If true, enable gitweb in nginx.
+      '';
+    };
+
+    location = mkOption {
+      default = "/gitweb";
+      type = types.str;
+      description = ''
+        Location to serve gitweb on.
+      '';
+    };
+
+    user = mkOption {
+      default = "nginx";
+      type = types.str;
+      description = ''
+        Existing user that the CGI process will belong to. (Default almost surely will do.)
+      '';
+    };
+
+    group = mkOption {
+      default = "nginx";
+      type = types.str;
+      description = ''
+        Group that the CGI process will belong to. (Set to <literal>config.services.gitolite.group</literal> if you are using gitolite.)
+      '';
+    };
+
+    virtualHost = mkOption {
+      default = "_";
+      type = types.str;
+      description = ''
+        VirtualHost to serve gitweb on. Default is catch-all.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.gitweb = {
+      description = "GitWeb service";
+      script = "${package}/gitweb.cgi --fastcgi --nproc=1";
+      environment  = {
+        FCGI_SOCKET_PATH = "/run/gitweb/gitweb.sock";
+      };
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        RuntimeDirectory = [ "gitweb" ];
+      };
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    services.nginx = {
+      virtualHosts.${cfg.virtualHost} = {
+        locations."${cfg.location}/static/" = {
+          alias = "${package}/static/";
+        };
+        locations."${cfg.location}/" = {
+          extraConfig = ''
+            include ${pkgs.nginx}/conf/fastcgi_params;
+            fastcgi_param GITWEB_CONFIG ${gitwebConfig.gitwebConfigFile};
+            fastcgi_pass unix:/run/gitweb/gitweb.sock;
+          '';
+        };
+      };
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ gnidorah ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
new file mode 100644
index 000000000000..3d9e391ecf20
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -0,0 +1,94 @@
+# This file defines the options that can be used both for the Nginx
+# main server configuration, and for the virtual hosts.  (The latter
+# has additional options that affect the web server as a whole, like
+# the user/group to run under.)
+
+{ lib }:
+
+with lib;
+
+{
+  options = {
+    proxyPass = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "http://www.example.org/";
+      description = ''
+        Adds proxy_pass directive and sets recommended proxy headers if
+        recommendedProxySettings is enabled.
+      '';
+    };
+
+    proxyWebsockets = mkOption {
+      type = types.bool;
+      default = false;
+      example = true;
+      description = ''
+        Whether to supporty proxying websocket connections with HTTP/1.1.
+      '';
+    };
+
+    index = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "index.php index.html";
+      description = ''
+        Adds index directive.
+      '';
+    };
+
+    tryFiles = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "$uri =404";
+      description = ''
+        Adds try_files directive.
+      '';
+    };
+
+    root = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/your/root/directory";
+      description = ''
+        Root directory for requests.
+      '';
+    };
+
+    alias = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/your/alias/directory";
+      description = ''
+        Alias directory for requests.
+      '';
+    };
+
+    return = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "301 http://example.com$request_uri";
+      description = ''
+        Adds a return directive, for e.g. redirections.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        These lines go to the end of the location verbatim.
+      '';
+    };
+
+    priority = mkOption {
+      type = types.int;
+      default = 1000;
+      description = ''
+        Order of this location block in relation to the others in the vhost.
+        The semantics are the same as with `lib.mkOrder`. Smaller values have
+        a greater priority.
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
new file mode 100644
index 000000000000..455854e2a965
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -0,0 +1,229 @@
+# This file defines the options that can be used both for the Nginx
+# main server configuration, and for the virtual hosts.  (The latter
+# has additional options that affect the web server as a whole, like
+# the user/group to run under.)
+
+{ lib, ... }:
+
+with lib;
+{
+  options = {
+    serverName = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Name of this virtual host. Defaults to attribute name in virtualHosts.
+      '';
+      example = "example.org";
+    };
+
+    serverAliases = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = ["www.example.org" "example.org"];
+      description = ''
+        Additional names of virtual hosts served by this virtual host configuration.
+      '';
+    };
+
+    listen = mkOption {
+      type = with types; listOf (submodule { options = {
+        addr = mkOption { type = str;  description = "IP address.";  };
+        port = mkOption { type = int;  description = "Port number."; default = 80; };
+        ssl  = mkOption { type = bool; description = "Enable SSL.";  default = false; };
+        extraParameters = mkOption { type = listOf str; description = "Extra parameters of this listen directive."; default = []; example = [ "reuseport" "deferred" ]; };
+      }; });
+      default = [];
+      example = [
+        { addr = "195.154.1.1"; port = 443; ssl = true;}
+        { addr = "192.154.1.1"; port = 80; }
+      ];
+      description = ''
+        Listen addresses and ports for this virtual host.
+        IPv6 addresses must be enclosed in square brackets.
+        Note: this option overrides <literal>addSSL</literal>
+        and <literal>onlySSL</literal>.
+      '';
+    };
+
+    enableACME = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to ask Let's Encrypt to sign a certificate for this vhost.
+        Alternately, you can use an existing certificate through <option>useACMEHost</option>.
+      '';
+    };
+
+    useACMEHost = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        A host of an existing Let's Encrypt certificate to use.
+        This is useful if you have many subdomains and want to avoid hitting the
+        <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>.
+        Alternately, you can generate a certificate through <option>enableACME</option>.
+        <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using  <xref linkend="opt-security.acme.certs"/>.</emphasis>
+      '';
+    };
+
+    acmeRoot = mkOption {
+      type = types.str;
+      default = "/var/lib/acme/acme-challenge";
+      description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
+    };
+
+    acmeFallbackHost = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        Host which to proxy requests to if acme challenge is not found. Useful
+        if you want multiple hosts to be able to verify the same domain name.
+      '';
+    };
+
+    addSSL = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable HTTPS in addition to plain HTTP. This will set defaults for
+        <literal>listen</literal> to listen on all interfaces on the respective default
+        ports (80, 443).
+      '';
+    };
+
+    onlySSL = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable HTTPS and reject plain HTTP connections. This will set
+        defaults for <literal>listen</literal> to listen on all interfaces on port 443.
+      '';
+    };
+
+    enableSSL = mkOption {
+      type = types.bool;
+      visible = false;
+      default = false;
+    };
+
+    forceSSL = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to add a separate nginx server block that permanently redirects (301)
+        all plain HTTP traffic to HTTPS. This will set defaults for
+        <literal>listen</literal> to listen on all interfaces on the respective default
+        ports (80, 443), where the non-SSL listens are used for the redirect vhosts.
+      '';
+    };
+
+    sslCertificate = mkOption {
+      type = types.path;
+      example = "/var/host.cert";
+      description = "Path to server SSL certificate.";
+    };
+
+    sslCertificateKey = mkOption {
+      type = types.path;
+      example = "/var/host.key";
+      description = "Path to server SSL certificate key.";
+    };
+
+    sslTrustedCertificate = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/var/root.cert";
+      description = "Path to root SSL certificate for stapling and client certificates.";
+    };
+
+    http2 = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to enable HTTP 2.
+        Note that (as of writing) due to nginx's implementation, to disable
+        HTTP 2 you have to disable it on all vhosts that use a given
+        IP address / port.
+        If there is one server block configured to enable http2,then it is
+        enabled for all server blocks on this IP.
+        See https://stackoverflow.com/a/39466948/263061.
+      '';
+    };
+
+    root = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      example = "/data/webserver/docs";
+      description = ''
+        The path of the web root directory.
+      '';
+    };
+
+    default = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Makes this vhost the default.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        These lines go to the end of the vhost verbatim.
+      '';
+    };
+
+    globalRedirect = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      example = "newserver.example.org";
+      description = ''
+        If set, all requests for this host are redirected permanently to
+        the given hostname.
+      '';
+    };
+
+    basicAuth = mkOption {
+      type = types.attrsOf types.str;
+      default = {};
+      example = literalExample ''
+        {
+          user = "password";
+        };
+      '';
+      description = ''
+        Basic Auth protection for a vhost.
+
+        WARNING: This is implemented to store the password in plain text in the
+        nix store.
+      '';
+    };
+
+    basicAuthFile = mkOption {
+      type = types.nullOr types.path;
+      default = null;
+      description = ''
+        Basic Auth password file for a vhost.
+        Can be created via: <command>htpasswd -c &lt;filename&gt; &lt;username&gt;</command>
+      '';
+    };
+
+    locations = mkOption {
+      type = types.attrsOf (types.submodule (import ./location-options.nix {
+        inherit lib;
+      }));
+      default = {};
+      example = literalExample ''
+        {
+          "/" = {
+            proxyPass = "http://localhost:3000";
+          };
+        };
+      '';
+      description = "Declarative location config";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
new file mode 100644
index 000000000000..d090885a8ca5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -0,0 +1,284 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.phpfpm;
+
+  runtimeDir = "/run/phpfpm";
+
+  toStr = value:
+    if true == value then "yes"
+    else if false == value then "no"
+    else toString value;
+
+  fpmCfgFile = pool: poolOpts: pkgs.writeText "phpfpm-${pool}.conf" ''
+    [global]
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)}
+    ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
+
+    [${pool}]
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") poolOpts.settings)}
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "env[${n}] = ${toStr v}") poolOpts.phpEnv)}
+    ${optionalString (poolOpts.extraConfig != null) poolOpts.extraConfig}
+  '';
+
+  phpIni = poolOpts: pkgs.runCommand "php.ini" {
+    inherit (poolOpts) phpPackage phpOptions;
+    preferLocalBuild = true;
+    nixDefaults = ''
+      sendmail_path = "/run/wrappers/bin/sendmail -t -i"
+    '';
+    passAsFile = [ "nixDefaults" "phpOptions" ];
+  } ''
+    cat ${poolOpts.phpPackage}/etc/php.ini $nixDefaultsPath $phpOptionsPath > $out
+  '';
+
+  poolOpts = { name, ... }:
+    let
+      poolOpts = cfg.pools.${name};
+    in
+    {
+      options = {
+        socket = mkOption {
+          type = types.str;
+          readOnly = true;
+          description = ''
+            Path to the unix socket file on which to accept FastCGI requests.
+            <note><para>This option is read-only and managed by NixOS.</para></note>
+          '';
+          example = "${runtimeDir}/<name>.sock";
+        };
+
+        listen = mkOption {
+          type = types.str;
+          default = "";
+          example = "/path/to/unix/socket";
+          description = ''
+            The address on which to accept FastCGI requests.
+          '';
+        };
+
+        phpPackage = mkOption {
+          type = types.package;
+          default = cfg.phpPackage;
+          defaultText = "config.services.phpfpm.phpPackage";
+          description = ''
+            The PHP package to use for running this PHP-FPM pool.
+          '';
+        };
+
+        phpOptions = mkOption {
+          type = types.lines;
+          description = ''
+            "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
+          '';
+        };
+
+        phpEnv = lib.mkOption {
+          type = with types; attrsOf str;
+          default = {};
+          description = ''
+            Environment variables used for this PHP-FPM pool.
+          '';
+          example = literalExample ''
+            {
+              HOSTNAME = "$HOSTNAME";
+              TMP = "/tmp";
+              TMPDIR = "/tmp";
+              TEMP = "/tmp";
+            }
+          '';
+        };
+
+        user = mkOption {
+          type = types.str;
+          description = "User account under which this pool runs.";
+        };
+
+        group = mkOption {
+          type = types.str;
+          description = "Group account under which this pool runs.";
+        };
+
+        settings = mkOption {
+          type = with types; attrsOf (oneOf [ str int bool ]);
+          default = {};
+          description = ''
+            PHP-FPM pool directives. Refer to the "List of pool directives" section of
+            <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+            for details. Note that settings names must be enclosed in quotes (e.g.
+            <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
+          '';
+          example = literalExample ''
+            {
+              "pm" = "dynamic";
+              "pm.max_children" = 75;
+              "pm.start_servers" = 10;
+              "pm.min_spare_servers" = 5;
+              "pm.max_spare_servers" = 20;
+              "pm.max_requests" = 500;
+            }
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = with types; nullOr lines;
+          default = null;
+          description = ''
+            Extra lines that go into the pool configuration.
+            See the documentation on <literal>php-fpm.conf</literal> for
+            details on configuration directives.
+          '';
+        };
+      };
+
+      config = {
+        socket = if poolOpts.listen == "" then "${runtimeDir}/${name}.sock" else poolOpts.listen;
+        group = mkDefault poolOpts.user;
+        phpOptions = mkBefore cfg.phpOptions;
+
+        settings = mapAttrs (name: mkDefault){
+          listen = poolOpts.socket;
+          user = poolOpts.user;
+          group = poolOpts.group;
+        };
+      };
+    };
+
+in {
+  imports = [
+    (mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.")
+    (mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "")
+  ];
+
+  options = {
+    services.phpfpm = {
+      settings = mkOption {
+        type = with types; attrsOf (oneOf [ str int bool ]);
+        default = {};
+        description = ''
+          PHP-FPM global directives. Refer to the "List of global php-fpm.conf directives" section of
+          <link xlink:href="https://www.php.net/manual/en/install.fpm.configuration.php"/>
+          for details. Note that settings names must be enclosed in quotes (e.g.
+          <literal>"pm.max_children"</literal> instead of <literal>pm.max_children</literal>).
+          You need not specify the options <literal>error_log</literal> or
+          <literal>daemonize</literal> here, since they are generated by NixOS.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = with types; nullOr lines;
+        default = null;
+        description = ''
+          Extra configuration that should be put in the global section of
+          the PHP-FPM configuration file. Do not specify the options
+          <literal>error_log</literal> or
+          <literal>daemonize</literal> here, since they are generated by
+          NixOS.
+        '';
+      };
+
+      phpPackage = mkOption {
+        type = types.package;
+        default = pkgs.php;
+        defaultText = "pkgs.php";
+        description = ''
+          The PHP package to use for running the PHP-FPM service.
+        '';
+      };
+
+      phpOptions = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            date.timezone = "CET"
+          '';
+        description = ''
+          Options appended to the PHP configuration file <filename>php.ini</filename>.
+        '';
+      };
+
+      pools = mkOption {
+        type = types.attrsOf (types.submodule poolOpts);
+        default = {};
+        example = literalExample ''
+         {
+           mypool = {
+             user = "php";
+             group = "php";
+             phpPackage = pkgs.php;
+             settings = {
+               "pm" = "dynamic";
+               "pm.max_children" = 75;
+               "pm.start_servers" = 10;
+               "pm.min_spare_servers" = 5;
+               "pm.max_spare_servers" = 20;
+               "pm.max_requests" = 500;
+             };
+           }
+         }'';
+        description = ''
+          PHP-FPM pools. If no pools are defined, the PHP-FPM
+          service is disabled.
+        '';
+      };
+    };
+  };
+
+  config = mkIf (cfg.pools != {}) {
+
+    warnings =
+      mapAttrsToList (pool: poolOpts: ''
+        Using config.services.phpfpm.pools.${pool}.listen is deprecated and will become unsupported in a future release. Please reference the read-only option config.services.phpfpm.pools.${pool}.socket to access the path of your socket.
+      '') (filterAttrs (pool: poolOpts: poolOpts.listen != "") cfg.pools) ++
+      mapAttrsToList (pool: poolOpts: ''
+        Using config.services.phpfpm.pools.${pool}.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.pools.${pool}.settings.
+      '') (filterAttrs (pool: poolOpts: poolOpts.extraConfig != null) cfg.pools) ++
+      optional (cfg.extraConfig != null) ''
+        Using config.services.phpfpm.extraConfig is deprecated and will become unsupported in a future release. Please migrate your configuration to config.services.phpfpm.settings.
+      ''
+    ;
+
+    services.phpfpm.settings = {
+      error_log = "syslog";
+      daemonize = false;
+    };
+
+    systemd.slices.phpfpm = {
+      description = "PHP FastCGI Process manager pools slice";
+    };
+
+    systemd.targets.phpfpm = {
+      description = "PHP FastCGI Process manager pools target";
+      wantedBy = [ "multi-user.target" ];
+    };
+
+    systemd.services = mapAttrs' (pool: poolOpts:
+      nameValuePair "phpfpm-${pool}" {
+        description = "PHP FastCGI Process Manager service for pool ${pool}";
+        after = [ "network.target" ];
+        wantedBy = [ "phpfpm.target" ];
+        partOf = [ "phpfpm.target" ];
+        serviceConfig = let
+          cfgFile = fpmCfgFile pool poolOpts;
+          iniFile = phpIni poolOpts;
+        in {
+          Slice = "phpfpm.slice";
+          PrivateDevices = true;
+          PrivateTmp = true;
+          ProtectSystem = "full";
+          ProtectHome = true;
+          # 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 = "${poolOpts.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
+          ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
+          RuntimeDirectory = "phpfpm";
+          RuntimeDirectoryPreserve = true; # Relevant when multiple processes are running
+        };
+      }
+    ) cfg.pools;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix b/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix
new file mode 100644
index 000000000000..58a02ac59c35
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/shellinabox.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.shellinabox;
+
+  # If a certificate file is specified, shellinaboxd requires
+  # a file descriptor to retrieve it
+  fd = "3";
+  createFd = optionalString (cfg.certFile != null) "${fd}<${cfg.certFile}";
+
+  # Command line arguments for the shellinabox daemon
+  args = [ "--background" ]
+   ++ optional (! cfg.enableSSL) "--disable-ssl"
+   ++ optional (cfg.certFile != null) "--cert-fd=${fd}"
+   ++ optional (cfg.certDirectory != null) "--cert=${cfg.certDirectory}"
+   ++ cfg.extraOptions;
+
+  # Command to start shellinaboxd
+  cmd = "${pkgs.shellinabox}/bin/shellinaboxd ${concatStringsSep " " args}";
+
+  # Command to start shellinaboxd if certFile is specified
+  wrappedCmd = "${pkgs.bash}/bin/bash -c 'exec ${createFd} && ${cmd}'";
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.shellinabox = {
+      enable = mkEnableOption "shellinabox daemon";
+
+      user = mkOption {
+        type = types.str;
+        default = "root";
+        description = ''
+          User to run shellinaboxd as. If started as root, the server drops
+          privileges by changing to nobody, unless overridden by the
+          <literal>--user</literal> option.
+        '';
+      };
+
+      enableSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not to enable SSL (https) support.
+        '';
+      };
+        
+      certDirectory = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/certs";
+        description = ''
+          The daemon will look in this directory far any certificates.
+          If the browser negotiated a Server Name Identification the daemon
+          will look for a matching certificate-SERVERNAME.pem file. If no SNI
+          handshake takes place, it will fall back on using the certificate in the
+          certificate.pem file.
+
+          If no suitable certificate is installed, shellinaboxd will attempt to
+          create a new self-signed certificate. This will only succeed if, after
+          dropping privileges, shellinaboxd has write permissions for this
+          directory.
+        '';
+      };
+
+      certFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/certificate.pem";
+        description = "Path to server SSL certificate.";
+      };
+
+      extraOptions = mkOption {
+        type = types.listOf types.str;
+        default = [ ];
+        example = [ "--port=443" "--service /:LOGIN" ];
+        description = ''
+          A list of strings to be appended to the command line arguments
+          for shellinaboxd. Please see the manual page
+          <link xlink:href="https://code.google.com/p/shellinabox/wiki/shellinaboxd_man"/>
+          for a full list of available arguments.
+        '';
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions =
+      [ { assertion = cfg.enableSSL == true
+            -> cfg.certDirectory != null || cfg.certFile != null;
+          message = "SSL is enabled for shellinabox, but no certDirectory or certFile has been specefied."; }
+        { assertion = ! (cfg.certDirectory != null && cfg.certFile != null);
+          message = "Cannot set both certDirectory and certFile for shellinabox."; }
+      ];
+
+    systemd.services.shellinaboxd = {
+      description = "Shellinabox Web Server Daemon";
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "sshd.service" ];
+      after = [ "sshd.service" ];
+
+      serviceConfig = {
+        Type = "forking";
+        User = "${cfg.user}";
+        ExecStart = "${if cfg.certFile == null then "${cmd}" else "${wrappedCmd}"}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/tomcat.nix b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
new file mode 100644
index 000000000000..6d12925829f7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/tomcat.nix
@@ -0,0 +1,421 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.tomcat;
+  tomcat = cfg.package;
+in
+
+{
+
+  meta = {
+    maintainers = with maintainers; [ danbst ];
+  };
+
+  ###### interface
+
+  options = {
+
+    services.tomcat = {
+      enable = mkEnableOption "Apache Tomcat";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.tomcat85;
+        defaultText = "pkgs.tomcat85";
+        example = lib.literalExample "pkgs.tomcat9";
+        description = ''
+          Which tomcat package to use.
+        '';
+      };
+
+      purifyOnStart = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          On startup, the `baseDir` directory is populated with various files,
+          subdirectories and symlinks. If this option is enabled, these items
+          (except for the `logs` and `work` subdirectories) are first removed.
+          This prevents interference from remainders of an old configuration
+          (libraries, webapps, etc.), so it's recommended to enable this option.
+        '';
+      };
+
+      baseDir = mkOption {
+        type = lib.types.path;
+        default = "/var/tomcat";
+        description = ''
+          Location where Tomcat stores configuration files, web applications
+          and logfiles. Note that it is partially cleared on each service startup
+          if `purifyOnStart` is enabled.
+        '';
+      };
+
+      logDirs = mkOption {
+        default = [];
+        type = types.listOf types.path;
+        description = "Directories to create in baseDir/logs/";
+      };
+
+      extraConfigFiles = mkOption {
+        default = [];
+        type = types.listOf types.path;
+        description = "Extra configuration files to pull into the tomcat conf directory";
+      };
+
+      extraEnvironment = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "ENVIRONMENT=production" ];
+        description = "Environment Variables to pass to the tomcat service";
+      };
+
+      extraGroups = mkOption {
+        default = [];
+        example = [ "users" ];
+        description = "Defines extra groups to which the tomcat user belongs.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "tomcat";
+        description = "User account under which Apache Tomcat runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "tomcat";
+        description = "Group account under which Apache Tomcat runs.";
+      };
+
+      javaOpts = mkOption {
+        type = types.either (types.listOf types.str) types.str;
+        default = "";
+        description = "Parameters to pass to the Java Virtual Machine which spawns Apache Tomcat";
+      };
+
+      catalinaOpts = mkOption {
+        type = types.either (types.listOf types.str) types.str;
+        default = "";
+        description = "Parameters to pass to the Java Virtual Machine which spawns the Catalina servlet container";
+      };
+
+      sharedLibs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications";
+      };
+
+      serverXml = mkOption {
+        type = types.lines;
+        default = "";
+        description = "
+          Verbatim server.xml configuration.
+          This is mutually exclusive with the virtualHosts options.
+        ";
+      };
+
+      commonLibs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "List containing JAR files or directories with JAR files which are libraries shared by the web applications and the servlet container";
+      };
+
+      webapps = mkOption {
+        type = types.listOf types.path;
+        default = [ tomcat.webapps ];
+        defaultText = "[ pkgs.tomcat85.webapps ]";
+        description = "List containing WAR files or directories with WAR files which are web applications to be deployed on Tomcat";
+      };
+
+      virtualHosts = mkOption {
+        type = types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = types.str;
+              description = "name of the virtualhost";
+            };
+            aliases = mkOption {
+              type = types.listOf types.str;
+              description = "aliases of the virtualhost";
+              default = [];
+            };
+            webapps = mkOption {
+              type = types.listOf types.path;
+              description = ''
+                List containing web application WAR files and/or directories containing
+                web applications and configuration files for the virtual host.
+              '';
+              default = [];
+            };
+          };
+        });
+        default = [];
+        description = "List consisting of a virtual host name and a list of web applications to deploy on each virtual host";
+      };
+
+      logPerVirtualHost = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable logging per virtual host.";
+      };
+
+      jdk = mkOption {
+        type = types.package;
+        default = pkgs.jdk;
+        defaultText = "pkgs.jdk";
+        description = "Which JDK to use.";
+      };
+
+      axis2 = {
+
+        enable = mkOption {
+          default = false;
+          type = types.bool;
+          description = "Whether to enable an Apache Axis2 container";
+        };
+
+        services = mkOption {
+          default = [];
+          type = types.listOf types.str;
+          description = "List containing AAR files or directories with AAR files which are web services to be deployed on Axis2";
+        };
+
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.tomcat.enable {
+
+    users.groups.tomcat.gid = config.ids.gids.tomcat;
+
+    users.users.tomcat =
+      { uid = config.ids.uids.tomcat;
+        description = "Tomcat user";
+        home = "/homeless-shelter";
+        extraGroups = cfg.extraGroups;
+      };
+
+    systemd.services.tomcat = {
+      description = "Apache Tomcat server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      preStart = ''
+        ${lib.optionalString cfg.purifyOnStart ''
+          # Delete most directories/symlinks we create from the existing base directory,
+          # to get rid of remainders of an old configuration.
+          # The list of directories to delete is taken from the "mkdir" command below,
+          # excluding "logs" (because logs are valuable) and "work" (because normally
+          # session files are there), and additionally including "bin".
+          rm -rf ${cfg.baseDir}/{conf,virtualhosts,temp,lib,shared/lib,webapps,bin}
+        ''}
+
+        # Create the base directory
+        mkdir -p \
+          ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work}
+        chown ${cfg.user}:${cfg.group} \
+          ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work}
+
+        # Create a symlink to the bin directory of the tomcat component
+        ln -sfn ${tomcat}/bin ${cfg.baseDir}/bin
+
+        # Symlink the config files in the conf/ directory (except for catalina.properties and server.xml)
+        for i in $(ls ${tomcat}/conf | grep -v catalina.properties | grep -v server.xml); do
+          ln -sfn ${tomcat}/conf/$i ${cfg.baseDir}/conf/`basename $i`
+        done
+
+        ${if cfg.extraConfigFiles != [] then ''
+          for i in ${toString cfg.extraConfigFiles}; do
+            ln -sfn $i ${cfg.baseDir}/conf/`basename $i`
+          done
+        '' else ""}
+
+        # Create a modified catalina.properties file
+        # Change all references from CATALINA_HOME to CATALINA_BASE and add support for shared libraries
+        sed -e 's|''${catalina.home}|''${catalina.base}|g' \
+          -e 's|shared.loader=|shared.loader=''${catalina.base}/shared/lib/*.jar|' \
+          ${tomcat}/conf/catalina.properties > ${cfg.baseDir}/conf/catalina.properties
+
+        ${if cfg.serverXml != "" then ''
+          cp -f ${pkgs.writeTextDir "server.xml" cfg.serverXml}/* ${cfg.baseDir}/conf/
+        '' else
+          let
+            hostElementForVirtualHost = virtualHost: ''
+              <Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps"
+                    unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
+            '' + concatStrings (innerElementsForVirtualHost virtualHost) + ''
+              </Host>
+            '';
+            innerElementsForVirtualHost = virtualHost:
+              (map (alias: ''
+                <Alias>${alias}</Alias>
+              '') virtualHost.aliases)
+              ++ (optional cfg.logPerVirtualHost ''
+                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}"
+                       prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/>
+              '');
+            hostElementsString = concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
+            hostElementsSedString = replaceStrings ["\n"] ["\\\n"] hostElementsString;
+          in ''
+            # Create a modified server.xml which also includes all virtual hosts
+            sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${escapeShellArg hostElementsSedString} \
+                  ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
+          ''
+        }
+        ${optionalString (cfg.logDirs != []) ''
+          for i in ${toString cfg.logDirs}; do
+            mkdir -p ${cfg.baseDir}/logs/$i
+            chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/$i
+          done
+        ''}
+        ${optionalString cfg.logPerVirtualHost (toString (map (h: ''
+          mkdir -p ${cfg.baseDir}/logs/${h.name}
+          chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/logs/${h.name}
+        '') cfg.virtualHosts))}
+
+        # Symlink all the given common libs files or paths into the lib/ directory
+        for i in ${tomcat} ${toString cfg.commonLibs}; do
+          if [ -f $i ]; then
+            # If the given web application is a file, symlink it into the common/lib/ directory
+            ln -sfn $i ${cfg.baseDir}/lib/`basename $i`
+          elif [ -d $i ]; then
+            # If the given web application is a directory, then iterate over the files
+            # in the special purpose directories and symlink them into the tomcat tree
+
+            for j in $i/lib/*; do
+              ln -sfn $j ${cfg.baseDir}/lib/`basename $j`
+            done
+          fi
+        done
+
+        # Symlink all the given shared libs files or paths into the shared/lib/ directory
+        for i in ${toString cfg.sharedLibs}; do
+          if [ -f $i ]; then
+            # If the given web application is a file, symlink it into the common/lib/ directory
+            ln -sfn $i ${cfg.baseDir}/shared/lib/`basename $i`
+          elif [ -d $i ]; then
+            # If the given web application is a directory, then iterate over the files
+            # in the special purpose directories and symlink them into the tomcat tree
+
+            for j in $i/shared/lib/*; do
+              ln -sfn $j ${cfg.baseDir}/shared/lib/`basename $j`
+            done
+          fi
+        done
+
+        # Symlink all the given web applications files or paths into the webapps/ directory
+        for i in ${toString cfg.webapps}; do
+          if [ -f $i ]; then
+            # If the given web application is a file, symlink it into the webapps/ directory
+            ln -sfn $i ${cfg.baseDir}/webapps/`basename $i`
+          elif [ -d $i ]; then
+            # If the given web application is a directory, then iterate over the files
+            # in the special purpose directories and symlink them into the tomcat tree
+
+            for j in $i/webapps/*; do
+              ln -sfn $j ${cfg.baseDir}/webapps/`basename $j`
+            done
+
+            # Also symlink the configuration files if they are included
+            if [ -d $i/conf/Catalina ]; then
+              for j in $i/conf/Catalina/*; do
+                mkdir -p ${cfg.baseDir}/conf/Catalina/localhost
+                ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
+              done
+            fi
+          fi
+        done
+
+        ${toString (map (virtualHost: ''
+          # Create webapps directory for the virtual host
+          mkdir -p ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
+
+          # Modify ownership
+          chown ${cfg.user}:${cfg.group} ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps
+
+          # Symlink all the given web applications files or paths into the webapps/ directory
+          # of this virtual host
+          for i in "${if virtualHost ? webapps then toString virtualHost.webapps else ""}"; do
+            if [ -f $i ]; then
+              # If the given web application is a file, symlink it into the webapps/ directory
+              ln -sfn $i ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $i`
+            elif [ -d $i ]; then
+              # If the given web application is a directory, then iterate over the files
+              # in the special purpose directories and symlink them into the tomcat tree
+
+              for j in $i/webapps/*; do
+                ln -sfn $j ${cfg.baseDir}/virtualhosts/${virtualHost.name}/webapps/`basename $j`
+              done
+
+              # Also symlink the configuration files if they are included
+              if [ -d $i/conf/Catalina ]; then
+                for j in $i/conf/Catalina/*; do
+                  mkdir -p ${cfg.baseDir}/conf/Catalina/${virtualHost.name}
+                  ln -sfn $j ${cfg.baseDir}/conf/Catalina/${virtualHost.name}/`basename $j`
+                done
+              fi
+            fi
+          done
+        '') cfg.virtualHosts)}
+
+        ${optionalString cfg.axis2.enable ''
+          # Copy the Axis2 web application
+          cp -av ${pkgs.axis2}/webapps/axis2 ${cfg.baseDir}/webapps
+
+          # Turn off addressing, which causes many errors
+          sed -i -e 's%<module ref="addressing"/>%<!-- <module ref="addressing"/> -->%' ${cfg.baseDir}/webapps/axis2/WEB-INF/conf/axis2.xml
+
+          # Modify permissions on the Axis2 application
+          chown -R ${cfg.user}:${cfg.group} ${cfg.baseDir}/webapps/axis2
+
+          # Symlink all the given web service files or paths into the webapps/axis2/WEB-INF/services directory
+          for i in ${toString cfg.axis2.services}; do
+            if [ -f $i ]; then
+              # If the given web service is a file, symlink it into the webapps/axis2/WEB-INF/services
+              ln -sfn $i ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $i`
+            elif [ -d $i ]; then
+              # If the given web application is a directory, then iterate over the files
+              # in the special purpose directories and symlink them into the tomcat tree
+
+              for j in $i/webapps/axis2/WEB-INF/services/*; do
+                ln -sfn $j ${cfg.baseDir}/webapps/axis2/WEB-INF/services/`basename $j`
+              done
+
+              # Also symlink the configuration files if they are included
+              if [ -d $i/conf/Catalina ]; then
+                for j in $i/conf/Catalina/*; do
+                  ln -sfn $j ${cfg.baseDir}/conf/Catalina/localhost/`basename $j`
+                done
+              fi
+            fi
+          done
+        ''}
+      '';
+
+      serviceConfig = {
+        Type = "forking";
+        PermissionsStartOnly = true;
+        PIDFile="/run/tomcat/tomcat.pid";
+        RuntimeDirectory = "tomcat";
+        User = cfg.user;
+        Environment=[
+          "CATALINA_BASE=${cfg.baseDir}"
+          "CATALINA_PID=/run/tomcat/tomcat.pid"
+          "JAVA_HOME='${cfg.jdk}'"
+          "JAVA_OPTS='${builtins.toString cfg.javaOpts}'"
+          "CATALINA_OPTS='${builtins.toString cfg.catalinaOpts}'"
+        ] ++ cfg.extraEnvironment;
+        ExecStart = "${tomcat}/bin/startup.sh";
+        ExecStop = "${tomcat}/bin/shutdown.sh";
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/traefik.nix b/nixpkgs/nixos/modules/services/web-servers/traefik.nix
new file mode 100644
index 000000000000..4ab7307c3b67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/traefik.nix
@@ -0,0 +1,170 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.traefik;
+  jsonValue = with types;
+    let
+      valueType = nullOr (oneOf [
+        bool
+        int
+        float
+        str
+        (lazyAttrsOf valueType)
+        (listOf valueType)
+      ]) // {
+        description = "JSON value";
+        emptyValue.value = { };
+      };
+    in valueType;
+  dynamicConfigFile = if cfg.dynamicConfigFile == null then
+    pkgs.runCommand "config.toml" {
+      buildInputs = [ pkgs.remarshal ];
+      preferLocalBuild = true;
+    } ''
+      remarshal -if json -of toml \
+        < ${
+          pkgs.writeText "dynamic_config.json"
+          (builtins.toJSON cfg.dynamicConfigOptions)
+        } \
+        > $out
+    ''
+  else
+    cfg.dynamicConfigFile;
+  staticConfigFile = if cfg.staticConfigFile == null then
+    pkgs.runCommand "config.toml" {
+      buildInputs = [ pkgs.yj ];
+      preferLocalBuild = true;
+    } ''
+      yj -jt -i \
+        < ${
+          pkgs.writeText "static_config.json" (builtins.toJSON
+            (recursiveUpdate cfg.staticConfigOptions {
+              providers.file.filename = "${dynamicConfigFile}";
+            }))
+        } \
+        > $out
+    ''
+  else
+    cfg.staticConfigFile;
+in {
+  options.services.traefik = {
+    enable = mkEnableOption "Traefik web server";
+
+    staticConfigFile = mkOption {
+      default = null;
+      example = literalExample "/path/to/static_config.toml";
+      type = types.nullOr types.path;
+      description = ''
+        Path to traefik's static configuration to use.
+        (Using that option has precedence over <literal>staticConfigOptions</literal> and <literal>dynamicConfigOptions</literal>)
+      '';
+    };
+
+    staticConfigOptions = mkOption {
+      description = ''
+        Static configuration for Traefik.
+      '';
+      type = jsonValue;
+      default = { entryPoints.http.address = ":80"; };
+      example = {
+        entryPoints.web.address = ":8080";
+        entryPoints.http.address = ":80";
+
+        api = { };
+      };
+    };
+
+    dynamicConfigFile = mkOption {
+      default = null;
+      example = literalExample "/path/to/dynamic_config.toml";
+      type = types.nullOr types.path;
+      description = ''
+        Path to traefik's dynamic configuration to use.
+        (Using that option has precedence over <literal>dynamicConfigOptions</literal>)
+      '';
+    };
+
+    dynamicConfigOptions = mkOption {
+      description = ''
+        Dynamic configuration for Traefik.
+      '';
+      type = jsonValue;
+      default = { };
+      example = {
+        http.routers.router1 = {
+          rule = "Host(`localhost`)";
+          service = "service1";
+        };
+
+        http.services.service1.loadBalancer.servers =
+          [{ url = "http://localhost:8080"; }];
+      };
+    };
+
+    dataDir = mkOption {
+      default = "/var/lib/traefik";
+      type = types.path;
+      description = ''
+        Location for any persistent data traefik creates, ie. acme
+      '';
+    };
+
+    group = mkOption {
+      default = "traefik";
+      type = types.str;
+      example = "docker";
+      description = ''
+        Set the group that traefik runs under.
+        For the docker backend this needs to be set to <literal>docker</literal> instead.
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.traefik;
+      defaultText = "pkgs.traefik";
+      type = types.package;
+      description = "Traefik package to use.";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [ "d '${cfg.dataDir}' 0700 traefik traefik - -" ];
+
+    systemd.services.traefik = {
+      description = "Traefik web server";
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart =
+          "${cfg.package}/bin/traefik --configfile=${staticConfigFile}";
+        Type = "simple";
+        User = "traefik";
+        Group = cfg.group;
+        Restart = "on-failure";
+        StartLimitInterval = 86400;
+        StartLimitBurst = 5;
+        AmbientCapabilities = "cap_net_bind_service";
+        CapabilityBoundingSet = "cap_net_bind_service";
+        NoNewPrivileges = true;
+        LimitNPROC = 64;
+        LimitNOFILE = 1048576;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHome = true;
+        ProtectSystem = "full";
+        ReadWriteDirectories = cfg.dataDir;
+      };
+    };
+
+    users.users.traefik = {
+      group = "traefik";
+      home = cfg.dataDir;
+      createHome = true;
+      isSystemUser = true;
+    };
+
+    users.groups.traefik = { };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/ttyd.nix b/nixpkgs/nixos/modules/services/web-servers/ttyd.nix
new file mode 100644
index 000000000000..01a01d97a234
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/ttyd.nix
@@ -0,0 +1,196 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.ttyd;
+
+  # Command line arguments for the ttyd daemon
+  args = [ "--port" (toString cfg.port) ]
+         ++ optionals (cfg.socket != null) [ "--interface" cfg.socket ]
+         ++ optionals (cfg.interface != null) [ "--interface" cfg.interface ]
+         ++ [ "--signal" (toString cfg.signal) ]
+         ++ (concatLists (mapAttrsToList (_k: _v: [ "--client-option" "${_k}=${_v}" ]) cfg.clientOptions))
+         ++ [ "--terminal-type" cfg.terminalType ]
+         ++ optionals cfg.checkOrigin [ "--check-origin" ]
+         ++ [ "--max-clients" (toString cfg.maxClients) ]
+         ++ optionals (cfg.indexFile != null) [ "--index" cfg.indexFile ]
+         ++ optionals cfg.enableIPv6 [ "--ipv6" ]
+         ++ optionals cfg.enableSSL [ "--ssl-cert" cfg.certFile
+                                      "--ssl-key" cfg.keyFile
+                                      "--ssl-ca" cfg.caFile ]
+         ++ [ "--debug" (toString cfg.logLevel) ];
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.ttyd = {
+      enable = mkEnableOption "ttyd daemon";
+
+      port = mkOption {
+        type = types.int;
+        default = 7681;
+        description = "Port to listen on (use 0 for random port)";
+      };
+
+      socket = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        example = "/var/run/ttyd.sock";
+        description = "UNIX domain socket path to bind.";
+      };
+
+      interface = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "eth0";
+        description = "Network interface to bind.";
+      };
+
+      username = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Username for basic authentication.";
+      };
+
+      passwordFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        apply = value: if value == null then null else toString value;
+        description = ''
+          File containing the password to use for basic authentication.
+          For insecurely putting the password in the globally readable store use
+          <literal>pkgs.writeText "ttydpw" "MyPassword"</literal>.
+        '';
+      };
+
+      signal = mkOption {
+        type = types.ints.u8;
+        default = 1;
+        description = "Signal to send to the command on session close.";
+      };
+
+      clientOptions = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        example = literalExample ''{
+          fontSize = "16";
+          fontFamily = "Fira Code";
+
+        }'';
+        description = ''
+          Attribute set of client options for xtermjs.
+          <link xlink:href="https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/"/>
+        '';
+      };
+
+      terminalType = mkOption {
+        type = types.str;
+        default = "xterm-256color";
+        description = "Terminal type to report.";
+      };
+
+      checkOrigin = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to allow a websocket connection from a different origin.";
+      };
+
+      maxClients = mkOption {
+        type = types.int;
+        default = 0;
+        description = "Maximum clients to support (0, no limit)";
+      };
+
+      indexFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "Custom index.html path";
+      };
+
+      enableIPv6 = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether or not to enable IPv6 support.";
+      };
+
+      enableSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether or not to enable SSL (https) support.";
+      };
+
+      certFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "SSL certificate file path.";
+      };
+
+      keyFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        apply = value: if value == null then null else toString value;
+        description = ''
+          SSL key file path.
+          For insecurely putting the keyFile in the globally readable store use
+          <literal>pkgs.writeText "ttydKeyFile" "SSLKEY"</literal>.
+        '';
+      };
+
+      caFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "SSL CA file path for client certificate verification.";
+      };
+
+      logLevel = mkOption {
+        type = types.int;
+        default = 7;
+        description = "Set log level.";
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions =
+      [ { assertion = cfg.enableSSL
+            -> cfg.certFile != null && cfg.keyFile != null && cfg.caFile != null;
+          message = "SSL is enabled for ttyd, but no certFile, keyFile or caFile has been specefied."; }
+        { assertion = ! (cfg.interface != null && cfg.socket != null);
+          message = "Cannot set both interface and socket for ttyd."; }
+        { assertion = (cfg.username != null) == (cfg.passwordFile != null);
+          message = "Need to set both username and passwordFile for ttyd"; }
+      ];
+
+    systemd.services.ttyd = {
+      description = "ttyd Web Server Daemon";
+
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        # Runs login which needs to be run as root
+        # login: Cannot possibly work without effective root
+        User = "root";
+      };
+
+      script = if cfg.passwordFile != null then ''
+        PASSWORD=$(cat ${escapeShellArg cfg.passwordFile})
+        ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
+          --credential ${escapeShellArg cfg.username}:"$PASSWORD" \
+          ${pkgs.shadow}/bin/login
+      ''
+      else ''
+        ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \
+          ${pkgs.shadow}/bin/login
+      '';
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/unit/default.nix b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
new file mode 100644
index 000000000000..989866144e1e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/unit/default.nix
@@ -0,0 +1,150 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.unit;
+
+  configFile = pkgs.writeText "unit.json" cfg.config;
+
+in {
+  options = {
+    services.unit = {
+      enable = mkEnableOption "Unit App Server";
+      package = mkOption {
+        type = types.package;
+        default = pkgs.unit;
+        defaultText = "pkgs.unit";
+        description = "Unit package to use.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = "unit";
+        description = "User account under which unit runs.";
+      };
+      group = mkOption {
+        type = types.str;
+        default = "unit";
+        description = "Group account under which unit runs.";
+      };
+      stateDir = mkOption {
+        default = "/var/spool/unit";
+        description = "Unit data directory.";
+      };
+      logDir = mkOption {
+        default = "/var/log/unit";
+        description = "Unit log directory.";
+      };
+      config = mkOption {
+        type = types.str;
+        default = ''
+          {
+            "listeners": {},
+            "applications": {}
+          }
+        '';
+        example = literalExample ''
+          {
+            "listeners": {
+              "*:8300": {
+                "application": "example-php-72"
+              }
+            },
+            "applications": {
+              "example-php-72": {
+                "type": "php 7.2",
+                "processes": 4,
+                "user": "nginx",
+                "group": "nginx",
+                "root": "/var/www",
+                "index": "index.php",
+                "options": {
+                  "file": "/etc/php.d/default.ini",
+                  "admin": {
+                    "max_execution_time": "30",
+                    "max_input_time": "30",
+                    "display_errors": "off",
+                    "display_startup_errors": "off",
+                    "open_basedir": "/dev/urandom:/proc/cpuinfo:/proc/meminfo:/etc/ssl/certs:/var/www",
+                    "disable_functions": "exec,passthru,shell_exec,system"
+                  }
+                }
+              }
+            }
+          }
+        '';
+        description = "Unit configuration in JSON format. More details here https://unit.nginx.org/configuration";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.logDir}' 0750 ${cfg.user} ${cfg.group} - -"
+    ];
+
+    systemd.services.unit = {
+      description = "Unit App Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        [ ! -e '${cfg.stateDir}/conf.json' ] || rm -f '${cfg.stateDir}/conf.json'
+      '';
+      postStart = ''
+        ${pkgs.curl}/bin/curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
+      '';
+      serviceConfig = {
+        Type = "forking";
+        PIDFile = "/run/unit/unit.pid";
+        ExecStart = ''
+          ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \
+                                   --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' \
+                                   --user ${cfg.user} --group ${cfg.group}
+        '';
+        ExecStop = ''
+          ${pkgs.curl}/bin/curl -X DELETE --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
+        '';
+        # Runtime directory and mode
+        RuntimeDirectory = "unit";
+        RuntimeDirectoryMode = "0750";
+        # Access write directories
+        ReadWritePaths = [ cfg.stateDir cfg.logDir ];
+        # Security
+        NoNewPrivileges = true;
+        # Sandboxing
+        ProtectSystem = "strict";
+        ProtectHome = true;
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
+        PrivateMounts = true;
+        # System Call Filtering
+        SystemCallArchitectures = "native";
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == "unit") {
+      unit = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "unit") {
+      unit = { };
+    };
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
new file mode 100644
index 000000000000..936e211ec713
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/uwsgi.nix
@@ -0,0 +1,183 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.uwsgi;
+
+  buildCfg = name: c:
+    let
+      plugins =
+        if any (n: !any (m: m == n) cfg.plugins) (c.plugins or [])
+        then throw "`plugins` attribute in UWSGI configuration contains plugins not in config.services.uwsgi.plugins"
+        else c.plugins or cfg.plugins;
+
+      hasPython = v: filter (n: n == "python${v}") plugins != [];
+      hasPython2 = hasPython "2";
+      hasPython3 = hasPython "3";
+
+      python =
+        if hasPython2 && hasPython3 then
+          throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3"
+        else if hasPython2 then cfg.package.python2
+        else if hasPython3 then cfg.package.python3
+        else null;
+
+      pythonEnv = python.withPackages (c.pythonPackages or (self: []));
+
+      uwsgiCfg = {
+        uwsgi =
+          if c.type == "normal"
+            then {
+              inherit plugins;
+            } // removeAttrs c [ "type" "pythonPackages" ]
+              // optionalAttrs (python != null) {
+                pyhome = "${pythonEnv}";
+                env =
+                  # Argh, uwsgi expects list of key-values there instead of a dictionary.
+                  let env' = c.env or [];
+                      getPath =
+                        x: if hasPrefix "PATH=" x
+                           then substring (stringLength "PATH=") (stringLength x) x
+                           else null;
+                      oldPaths = filter (x: x != null) (map getPath env');
+                  in env' ++ [ "PATH=${optionalString (oldPaths != []) "${last oldPaths}:"}${pythonEnv}/bin" ];
+              }
+          else if c.type == "emperor"
+            then {
+              emperor = if builtins.typeOf c.vassals != "set" then c.vassals
+                        else pkgs.buildEnv {
+                          name = "vassals";
+                          paths = mapAttrsToList buildCfg c.vassals;
+                        };
+            } // removeAttrs c [ "type" "vassals" ]
+          else throw "`type` attribute in UWSGI configuration should be either 'normal' or 'emperor'";
+      };
+
+    in pkgs.writeTextDir "${name}.json" (builtins.toJSON uwsgiCfg);
+
+in {
+
+  options = {
+    services.uwsgi = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable uWSGI";
+      };
+
+      runDir = mkOption {
+        type = types.path;
+        default = "/run/uwsgi";
+        description = "Where uWSGI communication sockets can live";
+      };
+
+      package = mkOption {
+        type = types.package;
+        internal = true;
+      };
+
+      instance = mkOption {
+        type =  with lib.types; let
+          valueType = nullOr (oneOf [
+            bool
+            int
+            float
+            str
+            (lazyAttrsOf valueType)
+            (listOf valueType)
+            (mkOptionType {
+              name = "function";
+              description = "function";
+              check = x: isFunction x;
+              merge = mergeOneOption;
+            })
+          ]) // {
+            description = "Json value or lambda";
+            emptyValue.value = {};
+          };
+        in valueType;
+        default = {
+          type = "normal";
+        };
+        example = literalExample ''
+          {
+            type = "emperor";
+            vassals = {
+              moin = {
+                type = "normal";
+                pythonPackages = self: with self; [ moinmoin ];
+                socket = "${config.services.uwsgi.runDir}/uwsgi.sock";
+              };
+            };
+          }
+        '';
+        description = ''
+          uWSGI configuration. It awaits an attribute <literal>type</literal> inside which can be either
+          <literal>normal</literal> or <literal>emperor</literal>.
+
+          For <literal>normal</literal> mode you can specify <literal>pythonPackages</literal> as a function
+          from libraries set into a list of libraries. <literal>pythonpath</literal> will be set accordingly.
+
+          For <literal>emperor</literal> mode, you should use <literal>vassals</literal> attribute
+          which should be either a set of names and configurations or a path to a directory.
+
+          Other attributes will be used in configuration file as-is. Notice that you can redefine
+          <literal>plugins</literal> setting here.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Plugins used with uWSGI";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "uwsgi";
+        description = "User account under which uwsgi runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "uwsgi";
+        description = "Group account under which uwsgi runs.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.uwsgi = {
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -p ${cfg.runDir}
+        chown ${cfg.user}:${cfg.group} ${cfg.runDir}
+      '';
+      serviceConfig = {
+        Type = "notify";
+        ExecStart = "${cfg.package}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+        NotifyAccess = "main";
+        KillSignal = "SIGQUIT";
+      };
+    };
+
+    users.users = optionalAttrs (cfg.user == "uwsgi") {
+      uwsgi = {
+        group = cfg.group;
+        uid = config.ids.uids.uwsgi;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "uwsgi") {
+      uwsgi.gid = config.ids.gids.uwsgi;
+    };
+
+    services.uwsgi.package = pkgs.uwsgi.override {
+      inherit (cfg) plugins;
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
new file mode 100644
index 000000000000..01fe3d12917a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/varnish/default.nix
@@ -0,0 +1,113 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+  cfg = config.services.varnish;
+
+  commandLine = "-f ${pkgs.writeText "default.vcl" cfg.config}" +
+      optionalString (cfg.extraModules != []) " -p vmod_path='${makeSearchPathOutput "lib" "lib/varnish/vmods" ([cfg.package] ++ cfg.extraModules)}' -r vmod_path";
+in
+{
+  options = {
+    services.varnish = {
+      enable = mkEnableOption "Varnish Server";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.varnish;
+        defaultText = "pkgs.varnish";
+        description = ''
+          The package to use
+        '';
+      };
+
+      http_address = mkOption {
+        type = types.str;
+        default = "*:6081";
+        description = "
+          HTTP listen address and port.
+        ";
+      };
+
+      config = mkOption {
+        type = types.lines;
+        description = "
+          Verbatim default.vcl configuration.
+        ";
+      };
+
+      stateDir = mkOption {
+        type = types.path;
+        default = "/var/spool/varnish/${config.networking.hostName}";
+        description = "
+          Directory holding all state for Varnish to run.
+        ";
+      };
+
+      extraModules = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = literalExample "[ pkgs.varnishPackages.geoip ]";
+        description = "
+          Varnish modules (except 'std').
+        ";
+      };
+
+      extraCommandLine = mkOption {
+        type = types.str;
+        default = "";
+        example = "-s malloc,256M";
+        description = "
+          Command line switches for varnishd (run 'varnishd -?' to get list of options)
+        ";
+      };
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    systemd.services.varnish = {
+      description = "Varnish";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      preStart = ''
+        mkdir -p ${cfg.stateDir}
+        chown -R varnish:varnish ${cfg.stateDir}
+      '';
+      postStop = ''
+        rm -rf ${cfg.stateDir}
+      '';
+      serviceConfig = {
+        Type = "simple";
+        PermissionsStartOnly = true;
+        ExecStart = "${cfg.package}/sbin/varnishd -a ${cfg.http_address} -n ${cfg.stateDir} -F ${cfg.extraCommandLine} ${commandLine}";
+        Restart = "always";
+        RestartSec = "5s";
+        User = "varnish";
+        Group = "varnish";
+        AmbientCapabilities = "cap_net_bind_service";
+        NoNewPrivileges = true;
+        LimitNOFILE = 131072;
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+
+    # check .vcl syntax at compile time (e.g. before nixops deployment)
+    system.extraDependencies = [
+      (pkgs.stdenv.mkDerivation {
+        name = "check-varnish-syntax";
+        buildCommand = "${cfg.package}/sbin/varnishd -C ${commandLine} 2> $out || (cat $out; exit 1)";
+      })
+    ];
+
+    users.users.varnish = {
+      group = "varnish";
+      uid = config.ids.uids.varnish;
+    };
+
+    users.groups.varnish.gid = config.ids.uids.varnish;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/web-servers/zope2.nix b/nixpkgs/nixos/modules/services/web-servers/zope2.nix
new file mode 100644
index 000000000000..3abd506827c0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/web-servers/zope2.nix
@@ -0,0 +1,258 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.zope2;
+
+  zope2Opts = { name, ... }: {
+    options = {
+
+      name = mkOption {
+        default = "${name}";
+        type = types.str;
+        description = "The name of the zope2 instance. If undefined, the name of the attribute set will be used.";
+      };
+
+      threads = mkOption {
+        default = 2;
+        type = types.int;
+        description = "Specify the number of threads that Zope's ZServer web server will use to service requests. ";
+      };
+
+      http_address = mkOption {
+        default = "localhost:8080";
+        type = types.str;
+        description = "Give a port and address for the HTTP server.";
+      };
+
+      user = mkOption {
+        default = "zope2";
+        type = types.str;
+        description = "The name of the effective user for the Zope process.";
+      };
+
+      clientHome = mkOption {
+        default = "/var/lib/zope2/${name}";
+        type = types.path;
+        description = "Home directory of zope2 instance.";
+      };
+      extra = mkOption {
+        default =
+          ''
+          <zodb_db main>
+            mount-point /
+            cache-size 30000
+            <blobstorage>
+                blob-dir /var/lib/zope2/${name}/blobstorage
+                <filestorage>
+                path /var/lib/zope2/${name}/filestorage/Data.fs
+                </filestorage>
+            </blobstorage>
+          </zodb_db>
+          '';
+        type = types.lines;
+        description = "Extra zope.conf";
+      };
+
+      packages = mkOption {
+        type = types.listOf types.package;
+        description = "The list of packages you want to make available to the zope2 instance.";
+      };
+
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.zope2.instances = mkOption {
+      default = {};
+      type = with types; attrsOf (submodule zope2Opts);
+      example = literalExample ''
+        {
+          plone01 = {
+            http_address = "127.0.0.1:8080";
+            extra =
+              '''
+              <zodb_db main>
+                mount-point /
+                cache-size 30000
+                <blobstorage>
+                    blob-dir /var/lib/zope2/plone01/blobstorage
+                    <filestorage>
+                    path /var/lib/zope2/plone01/filestorage/Data.fs
+                    </filestorage>
+                </blobstorage>
+              </zodb_db>
+              ''';
+          };
+        }
+      '';
+      description = "zope2 instances to be created automaticaly by the system.";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf (cfg.instances != {}) {
+
+    users.users.zope2.uid = config.ids.uids.zope2;
+
+    systemd.services =
+      let
+
+        createZope2Instance = opts: name:
+          let
+            interpreter = pkgs.writeScript "interpreter"
+              ''
+              import sys
+
+              _interactive = True
+              if len(sys.argv) > 1:
+                  _options, _args = __import__("getopt").getopt(sys.argv[1:], 'ic:m:')
+                  _interactive = False
+                  for (_opt, _val) in _options:
+                      if _opt == '-i':
+                          _interactive = True
+                      elif _opt == '-c':
+                          exec _val
+                      elif _opt == '-m':
+                          sys.argv[1:] = _args
+                          _args = []
+                          __import__("runpy").run_module(
+                              _val, {}, "__main__", alter_sys=True)
+
+                  if _args:
+                      sys.argv[:] = _args
+                      __file__ = _args[0]
+                      del _options, _args
+                      execfile(__file__)
+
+              if _interactive:
+                  del _interactive
+                  __import__("code").interact(banner="", local=globals())
+              '';
+            env = pkgs.buildEnv {
+              name = "zope2-${name}-env";
+              paths = [
+                pkgs.python27
+                pkgs.python27Packages.recursivePthLoader
+                pkgs.python27Packages."plone.recipe.zope2instance"
+              ] ++ attrValues pkgs.python27.modules
+                ++ opts.packages;
+              postBuild =
+                ''
+                echo "#!$out/bin/python" > $out/bin/interpreter
+                cat ${interpreter} >> $out/bin/interpreter
+                '';
+            };
+            conf = pkgs.writeText "zope2-${name}-conf"
+              ''
+              %define INSTANCEHOME ${env}
+              instancehome $INSTANCEHOME
+              %define CLIENTHOME ${opts.clientHome}/${opts.name}
+              clienthome $CLIENTHOME
+
+              debug-mode off
+              security-policy-implementation C
+              verbose-security off
+              default-zpublisher-encoding utf-8
+              zserver-threads ${toString opts.threads}
+              effective-user ${opts.user}
+
+              pid-filename ${opts.clientHome}/${opts.name}/pid
+              lock-filename ${opts.clientHome}/${opts.name}/lock
+              python-check-interval 1000
+              enable-product-installation off
+
+              <environment>
+                zope_i18n_compile_mo_files false
+              </environment>
+
+              <eventlog>
+              level INFO
+              <logfile>
+                  path /var/log/zope2/${name}.log
+                  level INFO
+              </logfile>
+              </eventlog>
+
+              <logger access>
+              level WARN
+              <logfile>
+                  path /var/log/zope2/${name}-Z2.log
+                  format %(message)s
+              </logfile>
+              </logger>
+
+              <http-server>
+              address ${opts.http_address}
+              </http-server>
+
+              <zodb_db temporary>
+              <temporarystorage>
+                  name temporary storage for sessioning
+              </temporarystorage>
+              mount-point /temp_folder
+              container-class Products.TemporaryFolder.TemporaryContainer
+              </zodb_db>
+
+              ${opts.extra}
+              '';
+            ctlScript = pkgs.writeScript "zope2-${name}-ctl-script"
+              ''
+              #!${env}/bin/python
+
+              import sys
+              import plone.recipe.zope2instance.ctl
+
+              if __name__ == '__main__':
+                  sys.exit(plone.recipe.zope2instance.ctl.main(
+                      ["-C", "${conf}"]
+                      + sys.argv[1:]))
+              '';
+
+            ctl = pkgs.writeScript "zope2-${name}-ctl"
+              ''
+              #!${pkgs.bash}/bin/bash -e
+              export PYTHONHOME=${env}
+              exec ${ctlScript} "$@"
+              '';
+          in {
+            #description = "${name} instance";
+            after = [ "network.target" ];  # with RelStorage also add "postgresql.service"
+            wantedBy = [ "multi-user.target" ];
+            path = opts.packages;
+            preStart =
+              ''
+              mkdir -p /var/log/zope2/
+              touch /var/log/zope2/${name}.log
+              touch /var/log/zope2/${name}-Z2.log
+              chown ${opts.user} /var/log/zope2/${name}.log
+              chown ${opts.user} /var/log/zope2/${name}-Z2.log
+
+              mkdir -p ${opts.clientHome}/filestorage ${opts.clientHome}/blobstorage
+              mkdir -p ${opts.clientHome}/${opts.name}
+              chown ${opts.user} ${opts.clientHome} -R
+
+              ${ctl} adduser admin admin
+              '';
+
+            serviceConfig.Type = "forking";
+            serviceConfig.ExecStart = "${ctl} start";
+            serviceConfig.ExecStop = "${ctl} stop";
+            serviceConfig.ExecReload = "${ctl} restart";
+          };
+
+      in listToAttrs (map (name: { name = "zope2-${name}"; value = createZope2Instance (builtins.getAttr name cfg.instances) name; }) (builtins.attrNames cfg.instances));
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/clight.nix b/nixpkgs/nixos/modules/services/x11/clight.nix
new file mode 100644
index 000000000000..4daf6d8d9db7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/clight.nix
@@ -0,0 +1,115 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.clight;
+
+  toConf = v:
+    if builtins.isFloat v then toString v
+    else if isInt v       then toString v
+    else if isBool v      then boolToString v
+    else if isString v    then ''"${escape [''"''] v}"''
+    else if isList v      then "[ " + concatMapStringsSep ", " toConf v + " ]"
+    else abort "clight.toConf: unexpected type (v = ${v})";
+
+  clightConf = pkgs.writeText "clight.conf"
+    (concatStringsSep "\n" (mapAttrsToList
+      (name: value: "${toString name} = ${toConf value};")
+      (filterAttrs
+        (_: value: value != null)
+        cfg.settings)));
+in {
+  options.services.clight = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable clight or not.
+      '';
+    };
+
+    temperature = {
+      day = mkOption {
+        type = types.int;
+        default = 5500;
+        description = ''
+          Colour temperature to use during the day, between
+          <literal>1000</literal> and <literal>25000</literal> K.
+        '';
+      };
+      night = mkOption {
+        type = types.int;
+        default = 3700;
+        description = ''
+          Colour temperature to use at night, between
+          <literal>1000</literal> and <literal>25000</literal> K.
+        '';
+      };
+    };
+
+    settings = let
+      validConfigTypes = with types; either int (either str (either bool float));
+    in mkOption {
+      type = with types; attrsOf (nullOr (either validConfigTypes (listOf validConfigTypes)));
+      default = {};
+      example = { captures = 20; gamma_long_transition = true; ac_capture_timeouts = [ 120 300 60 ]; };
+      description = ''
+        Additional configuration to extend clight.conf. See
+        <link xlink:href="https://github.com/FedeDP/Clight/blob/master/Extra/clight.conf"/> for a
+        sample configuration file.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.kernelModules = [ "i2c_dev" ];
+    environment.systemPackages = with pkgs; [ clight clightd ];
+    services.dbus.packages = with pkgs; [ clight clightd ];
+    services.upower.enable = true;
+
+    services.clight.settings = {
+      gamma_temp = with cfg.temperature; mkDefault [ day night ];
+    } // (optionalAttrs (config.location.provider == "manual") {
+      latitude = mkDefault config.location.latitude;
+      longitude = mkDefault config.location.longitude;
+    });
+
+    services.geoclue2.appConfig.clightc = {
+      isAllowed = true;
+      isSystem = true;
+    };
+
+    systemd.services.clightd = {
+      requires = [ "polkit.service" ];
+      wantedBy = [ "multi-user.target" ];
+
+      description = "Bus service to manage various screen related properties (gamma, dpms, backlight)";
+      serviceConfig = {
+        Type = "dbus";
+        BusName = "org.clightd.clightd";
+        Restart = "on-failure";
+        RestartSec = 5;
+        ExecStart = ''
+          ${pkgs.clightd}/bin/clightd
+        '';
+      };
+    };
+
+    systemd.user.services.clight = {
+      after = [ "upower.service" "clightd.service" ];
+      wants = [ "upower.service" "clightd.service" ];
+      partOf = [ "graphical-session.target" ];
+      wantedBy = [ "graphical-session.target" ];
+
+      description = "C daemon to adjust screen brightness to match ambient brightness, as computed capturing frames from webcam";
+      serviceConfig = {
+        Restart = "on-failure";
+        RestartSec = 5;
+        ExecStart = ''
+          ${pkgs.clight}/bin/clight --conf-file ${clightConf}
+        '';
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/colord.nix b/nixpkgs/nixos/modules/services/x11/colord.nix
new file mode 100644
index 000000000000..cf113ad2af8c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/colord.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.colord;
+
+in {
+
+  options = {
+
+    services.colord = {
+      enable = mkEnableOption "colord, the color management daemon";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.colord ];
+
+    services.dbus.packages = [ pkgs.colord ];
+
+    services.udev.packages = [ pkgs.colord ];
+
+    systemd.packages = [ pkgs.colord ];
+
+    environment.etc."tmpfiles.d/colord.conf".source = "${pkgs.colord}/lib/tmpfiles.d/colord.conf";
+
+    users.users.colord = {
+      isSystemUser = true;
+      home = "/var/lib/colord";
+      group = "colord";
+    };
+
+    users.groups.colord = {};
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/cde.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/cde.nix
new file mode 100644
index 000000000000..2d9504fb5f1e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/cde.nix
@@ -0,0 +1,72 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager.cde;
+in {
+  options.services.xserver.desktopManager.cde = {
+    enable = mkEnableOption "Common Desktop Environment";
+
+    extraPackages = mkOption {
+      type = with types; listOf package;
+      default = with pkgs.xorg; [
+        xclock bitmap xlsfonts xfd xrefresh xload xwininfo xdpyinfo xwd xwud
+      ];
+      example = literalExample ''
+        with pkgs.xorg; [
+          xclock bitmap xlsfonts xfd xrefresh xload xwininfo xdpyinfo xwd xwud
+        ]
+      '';
+      description = ''
+        Extra packages to be installed system wide.
+      '';
+    };
+  };
+
+  config = mkIf (xcfg.enable && cfg.enable) {
+    environment.systemPackages = cfg.extraPackages;
+
+    services.rpcbind.enable = true;
+
+    services.xinetd.enable = true;
+    services.xinetd.services = [
+      {
+        name = "cmsd";
+        protocol = "udp";
+        user = "root";
+        server = "${pkgs.cdesktopenv}/opt/dt/bin/rpc.cmsd";
+        extraConfig = ''
+          type  = RPC UNLISTED
+          rpc_number  = 100068
+          rpc_version = 2-5
+          only_from   = 127.0.0.1/0
+        '';
+      }
+    ];
+
+    users.groups.mail = {};
+    security.wrappers = {
+      dtmail = {
+        source = "${pkgs.cdesktopenv}/bin/dtmail";
+        group = "mail";
+        setgid = true;
+      };
+    };
+
+    system.activationScripts.setup-cde = ''
+      mkdir -p /var/dt/{tmp,appconfig/appmanager}
+      chmod a+w+t /var/dt/{tmp,appconfig/appmanager}
+    '';
+
+    services.xserver.desktopManager.session = [
+    { name = "CDE";
+      start = ''
+        exec ${pkgs.cdesktopenv}/opt/dt/bin/Xsession
+      '';
+    }];
+  };
+
+  meta.maintainers = [ maintainers.gnidorah ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix
new file mode 100644
index 000000000000..5d3a84d71399
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/default.nix
@@ -0,0 +1,98 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager;
+
+  # If desktop manager `d' isn't capable of setting a background and
+  # the xserver is enabled, `feh' or `xsetroot' are used as a fallback.
+  needBGCond = d: ! (d ? bgSupport && d.bgSupport) && xcfg.enable;
+
+in
+
+{
+  # Note: the order in which desktop manager modules are imported here
+  # determines the default: later modules (if enabled) are preferred.
+  # E.g., if Plasma 5 is enabled, it supersedes xterm.
+  imports = [
+    ./none.nix ./xterm.nix ./xfce.nix ./plasma5.nix ./lumina.nix
+    ./lxqt.nix ./enlightenment.nix ./gnome3.nix ./kodi.nix
+    ./mate.nix ./pantheon.nix ./surf-display.nix ./cde.nix
+  ];
+
+  options = {
+
+    services.xserver.desktopManager = {
+
+      wallpaper = {
+        mode = mkOption {
+          type = types.enum [ "center" "fill" "max" "scale" "tile" ];
+          default = "scale";
+          example = "fill";
+          description = ''
+            The file <filename>~/.background-image</filename> is used as a background image.
+            This option specifies the placement of this image onto your desktop.
+
+            Possible values:
+            <literal>center</literal>: Center the image on the background. If it is too small, it will be surrounded by a black border.
+            <literal>fill</literal>: Like <literal>scale</literal>, but preserves aspect ratio by zooming the image until it fits. Either a horizontal or a vertical part of the image will be cut off.
+            <literal>max</literal>: Like <literal>fill</literal>, but scale the image to the maximum size that fits the screen with black borders on one side.
+            <literal>scale</literal>: Fit the file into the background without repeating it, cutting off stuff or using borders. But the aspect ratio is not preserved either.
+            <literal>tile</literal>: Tile (repeat) the image in case it is too small for the screen.
+          '';
+        };
+
+        combineScreens = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            When set to <literal>true</literal> the wallpaper will stretch across all screens.
+            When set to <literal>false</literal> the wallpaper is duplicated to all screens.
+          '';
+        };
+      };
+
+      session = mkOption {
+        internal = true;
+        default = [];
+        example = singleton
+          { name = "kde";
+            bgSupport = true;
+            start = "...";
+          };
+        description = ''
+          Internal option used to add some common line to desktop manager
+          scripts before forwarding the value to the
+          <varname>displayManager</varname>.
+        '';
+        apply = map (d: d // {
+          manage = "desktop";
+          start = d.start
+          + optionalString (needBGCond d) ''
+            if [ -e $HOME/.background-image ]; then
+              ${pkgs.feh}/bin/feh --bg-${cfg.wallpaper.mode} ${optionalString cfg.wallpaper.combineScreens "--no-xinerama"} $HOME/.background-image
+            fi
+          '';
+        });
+      };
+
+      default = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "none";
+        description = ''
+          <emphasis role="strong">Deprecated</emphasis>, please use <xref linkend="opt-services.xserver.displayManager.defaultSession"/> instead.
+
+          Default desktop manager loaded if none have been chosen.
+        '';
+      };
+
+    };
+
+  };
+
+  config.services.xserver.displayManager.session = cfg.session;
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/enlightenment.nix
new file mode 100644
index 000000000000..3a7ab64510b5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -0,0 +1,103 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  e = pkgs.enlightenment;
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager.enlightenment;
+  GST_PLUGIN_PATH = lib.makeSearchPathOutput "lib" "lib/gstreamer-1.0" [
+    pkgs.gst_all_1.gst-plugins-base
+    pkgs.gst_all_1.gst-plugins-good
+    pkgs.gst_all_1.gst-plugins-bad
+    pkgs.gst_all_1.gst-libav ];
+
+in
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "e19" "enable" ] [ "services" "xserver" "desktopManager" "enlightenment" "enable" ])
+  ];
+
+  options = {
+
+    services.xserver.desktopManager.enlightenment.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable the Enlightenment desktop environment.";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = with pkgs; [
+      enlightenment.econnman
+      enlightenment.efl
+      enlightenment.enlightenment
+      enlightenment.ephoto
+      enlightenment.rage
+      enlightenment.terminology
+      xorg.xcursorthemes
+    ];
+
+    environment.pathsToLink = [
+      "/etc/enlightenment"
+      "/share/enlightenment"
+      "/share/elementary"
+      "/share/locale"
+    ];
+
+    services.xserver.displayManager.sessionPackages = [ pkgs.enlightenment.enlightenment ];
+
+    services.xserver.displayManager.sessionCommands = ''
+      if test "$XDG_CURRENT_DESKTOP" = "Enlightenment"; then
+        export GST_PLUGIN_PATH="${GST_PLUGIN_PATH}"
+
+        # make available for D-BUS user services
+        #export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}:${config.system.path}/share:${e.efl}/share
+
+        # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+        ${pkgs.xdg-user-dirs}/bin/xdg-user-dirs-update
+      fi
+    '';
+
+    # Wrappers for programs installed by enlightenment that should be setuid
+    security.wrappers = {
+      enlightenment_ckpasswd.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_ckpasswd";
+      enlightenment_sys.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_sys";
+      enlightenment_system.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_system";
+    };
+
+    environment.etc."X11/xkb".source = xcfg.xkbDir;
+
+    fonts.fonts = [ pkgs.dejavu_fonts pkgs.ubuntu_font_family ];
+
+    services.udisks2.enable = true;
+    services.upower.enable = config.powerManagement.enable;
+
+    services.dbus.packages = [ e.efl ];
+
+    systemd.user.services.efreet =
+      { enable = true;
+        description = "org.enlightenment.Efreet";
+        serviceConfig =
+          { ExecStart = "${e.efl}/bin/efreetd";
+            StandardOutput = "null";
+          };
+      };
+
+    systemd.user.services.ethumb =
+      { enable = true;
+        description = "org.enlightenment.Ethumb";
+        serviceConfig =
+          { ExecStart = "${e.efl}/bin/ethumbd";
+            StandardOutput = "null";
+          };
+      };
+
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix
new file mode 100644
index 000000000000..69cf98321720
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -0,0 +1,397 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.desktopManager.gnome3;
+  serviceCfg = config.services.gnome3;
+
+  # Prioritize nautilus by default when opening directories
+  mimeAppsList = pkgs.writeTextFile {
+    name = "gnome-mimeapps";
+    destination = "/share/applications/mimeapps.list";
+    text = ''
+      [Default Applications]
+      inode/directory=nautilus.desktop;org.gnome.Nautilus.desktop
+    '';
+  };
+
+  nixos-gsettings-desktop-schemas = let
+    defaultPackages = with pkgs; [ gsettings-desktop-schemas gnome3.gnome-shell ];
+  in
+  pkgs.runCommand "nixos-gsettings-desktop-schemas" { preferLocalBuild = true; }
+    ''
+     mkdir -p $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
+
+     ${concatMapStrings
+        (pkg: "cp -rf ${pkg}/share/gsettings-schemas/*/glib-2.0/schemas/*.xml $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas\n")
+        (defaultPackages ++ cfg.extraGSettingsOverridePackages)}
+
+     cp -f ${pkgs.gnome3.gnome-shell}/share/gsettings-schemas/*/glib-2.0/schemas/*.gschema.override $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
+
+     ${optionalString flashbackEnabled ''
+       cp -f ${pkgs.gnome3.gnome-flashback}/share/gsettings-schemas/*/glib-2.0/schemas/*.gschema.override $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas
+     ''}
+
+     chmod -R a+w $out/share/gsettings-schemas/nixos-gsettings-overrides
+     cat - > $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/nixos-defaults.gschema.override <<- EOF
+       [org.gnome.desktop.background]
+       picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray.gnomeFilePath}'
+
+       [org.gnome.desktop.screensaver]
+       picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath}'
+
+       [org.gnome.shell]
+       favorite-apps=[ 'org.gnome.Epiphany.desktop', 'org.gnome.Geary.desktop', 'org.gnome.Music.desktop', 'org.gnome.Photos.desktop', 'org.gnome.Nautilus.desktop', 'org.gnome.Software.desktop' ]
+
+       ${cfg.extraGSettingsOverrides}
+     EOF
+
+     ${pkgs.glib.dev}/bin/glib-compile-schemas $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/
+    '';
+
+  flashbackEnabled = cfg.flashback.enableMetacity || length cfg.flashback.customSessions > 0;
+
+in
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  options = {
+
+    services.gnome3 = {
+      core-os-services.enable = mkEnableOption "essential services for GNOME3";
+      core-shell.enable = mkEnableOption "GNOME Shell services";
+      core-utilities.enable = mkEnableOption "GNOME core utilities";
+      games.enable = mkEnableOption "GNOME games";
+    };
+
+    services.xserver.desktopManager.gnome3 = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable Gnome 3 desktop manager.";
+      };
+
+      sessionPath = mkOption {
+        default = [];
+        example = literalExample "[ pkgs.gnome3.gpaste ]";
+        description = ''
+          Additional list of packages to be added to the session search path.
+          Useful for GNOME Shell extensions or GSettings-conditional autostart.
+
+          Note that this should be a last resort; patching the package is preferred (see GPaste).
+        '';
+        apply = list: list ++ [ pkgs.gnome3.gnome-shell pkgs.gnome3.gnome-shell-extensions ];
+      };
+
+      extraGSettingsOverrides = mkOption {
+        default = "";
+        type = types.lines;
+        description = "Additional gsettings overrides.";
+      };
+
+      extraGSettingsOverridePackages = mkOption {
+        default = [];
+        type = types.listOf types.path;
+        description = "List of packages for which gsettings are overridden.";
+      };
+
+      debug = mkEnableOption "gnome-session debug messages";
+
+      flashback = {
+        enableMetacity = mkEnableOption "the standard GNOME Flashback session with Metacity";
+
+        customSessions = mkOption {
+          type = types.listOf (types.submodule {
+            options = {
+              wmName = mkOption {
+                type = types.str;
+                description = "The filename-compatible name of the window manager to use.";
+                example = "xmonad";
+              };
+
+              wmLabel = mkOption {
+                type = types.str;
+                description = "The pretty name of the window manager to use.";
+                example = "XMonad";
+              };
+
+              wmCommand = mkOption {
+                type = types.str;
+                description = "The executable of the window manager to use.";
+                example = "\${pkgs.haskellPackages.xmonad}/bin/xmonad";
+              };
+            };
+          });
+          default = [];
+          description = "Other GNOME Flashback sessions to enable.";
+        };
+      };
+    };
+
+    environment.gnome3.excludePackages = mkOption {
+      default = [];
+      example = literalExample "[ pkgs.gnome3.totem ]";
+      type = types.listOf types.package;
+      description = "Which packages gnome should exclude from the default environment";
+    };
+
+  };
+
+  config = mkMerge [
+    (mkIf (cfg.enable || flashbackEnabled) {
+      services.gnome3.core-os-services.enable = true;
+      services.gnome3.core-shell.enable = true;
+      services.gnome3.core-utilities.enable = mkDefault true;
+
+      services.xserver.displayManager.sessionPackages = [ pkgs.gnome3.gnome-session.sessions ];
+
+      environment.extraInit = ''
+        ${concatMapStrings (p: ''
+          if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+            export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+          fi
+
+          if [ -d "${p}/lib/girepository-1.0" ]; then
+            export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+            export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+          fi
+        '') cfg.sessionPath}
+      '';
+
+      environment.systemPackages = cfg.sessionPath;
+
+      environment.sessionVariables.GNOME_SESSION_DEBUG = mkIf cfg.debug "1";
+
+      # Override GSettings schemas
+      environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
+
+       # If gnome3 is installed, build vim for gtk3 too.
+      nixpkgs.config.vim.gui = "gtk3";
+    })
+
+    (mkIf flashbackEnabled {
+      services.xserver.displayManager.sessionPackages =  map
+        (wm: pkgs.gnome3.gnome-flashback.mkSessionForWm {
+          inherit (wm) wmName wmLabel wmCommand;
+        }) (optional cfg.flashback.enableMetacity {
+              wmName = "metacity";
+              wmLabel = "Metacity";
+              wmCommand = "${pkgs.gnome3.metacity}/bin/metacity";
+            } ++ cfg.flashback.customSessions);
+
+      security.pam.services.gnome-flashback = {
+        enableGnomeKeyring = true;
+      };
+
+      systemd.packages = with pkgs.gnome3; [
+        gnome-flashback
+      ] ++ (map
+        (wm: gnome-flashback.mkSystemdTargetForWm {
+          inherit (wm) wmName;
+        }) cfg.flashback.customSessions);
+
+        # gnome-panel needs these for menu applet
+        environment.sessionVariables.XDG_DATA_DIRS = [ "${pkgs.gnome3.gnome-flashback}/share" ];
+        # TODO: switch to sessionVariables (resolve conflict)
+        environment.variables.XDG_CONFIG_DIRS = [ "${pkgs.gnome3.gnome-flashback}/etc/xdg" ];
+    })
+
+    (mkIf serviceCfg.core-os-services.enable {
+      hardware.bluetooth.enable = mkDefault true;
+      hardware.pulseaudio.enable = mkDefault true;
+      programs.dconf.enable = true;
+      security.polkit.enable = true;
+      services.accounts-daemon.enable = true;
+      services.dleyna-renderer.enable = mkDefault true;
+      services.dleyna-server.enable = mkDefault true;
+      services.gnome3.at-spi2-core.enable = true;
+      services.gnome3.evolution-data-server.enable = true;
+      services.gnome3.gnome-keyring.enable = true;
+      services.gnome3.gnome-online-accounts.enable = mkDefault true;
+      services.gnome3.gnome-online-miners.enable = true;
+      services.gnome3.tracker-miners.enable = mkDefault true;
+      services.gnome3.tracker.enable = mkDefault true;
+      services.hardware.bolt.enable = mkDefault true;
+      services.packagekit.enable = mkDefault true;
+      services.udisks2.enable = true;
+      services.upower.enable = config.powerManagement.enable;
+      services.xserver.libinput.enable = mkDefault true; # for controlling touchpad settings via gnome control center
+
+      xdg.portal.enable = true;
+      xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
+
+      networking.networkmanager.enable = mkDefault true;
+
+      services.xserver.updateDbusEnvironment = true;
+
+      # gnome has a custom alert theme but it still
+      # inherits from the freedesktop theme.
+      environment.systemPackages = with pkgs; [
+        sound-theme-freedesktop
+      ];
+
+      # Needed for themes and backgrounds
+      environment.pathsToLink = [
+        "/share" # TODO: https://github.com/NixOS/nixpkgs/issues/47173
+      ];
+    })
+
+    (mkIf serviceCfg.core-shell.enable {
+      services.colord.enable = mkDefault true;
+      services.gnome3.chrome-gnome-shell.enable = mkDefault true;
+      services.gnome3.glib-networking.enable = true;
+      services.gnome3.gnome-initial-setup.enable = mkDefault true;
+      services.gnome3.gnome-remote-desktop.enable = mkDefault true;
+      services.gnome3.gnome-settings-daemon.enable = true;
+      services.gnome3.gnome-user-share.enable = mkDefault true;
+      services.gnome3.rygel.enable = mkDefault true;
+      services.gvfs.enable = true;
+      services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+      services.telepathy.enable = mkDefault true;
+
+      systemd.packages = with pkgs.gnome3; [
+        gnome-session
+        gnome-shell
+      ];
+
+      services.avahi.enable = mkDefault true;
+
+      xdg.portal.extraPortals = [
+        pkgs.gnome3.gnome-shell
+      ];
+
+      services.geoclue2.enable = mkDefault true;
+      services.geoclue2.enableDemoAgent = false; # GNOME has its own geoclue agent
+
+      services.geoclue2.appConfig.gnome-datetime-panel = {
+        isAllowed = true;
+        isSystem = true;
+      };
+      services.geoclue2.appConfig.gnome-color-panel = {
+        isAllowed = true;
+        isSystem = true;
+      };
+      services.geoclue2.appConfig."org.gnome.Shell" = {
+        isAllowed = true;
+        isSystem = true;
+      };
+
+      fonts.fonts = with pkgs; [
+        cantarell-fonts
+        dejavu_fonts
+        source-code-pro # Default monospace font in 3.32
+        source-sans-pro
+      ];
+
+      ## Enable soft realtime scheduling, only supported on wayland ##
+
+      security.wrappers.".gnome-shell-wrapped" = {
+        source = "${pkgs.gnome3.gnome-shell}/bin/.gnome-shell-wrapped";
+        capabilities = "cap_sys_nice=ep";
+      };
+
+      systemd.user.services.gnome-shell-wayland = let
+        gnomeShellRT = with pkgs.gnome3; pkgs.runCommand "gnome-shell-rt" {} ''
+          mkdir -p $out/bin/
+          cp ${gnome-shell}/bin/gnome-shell $out/bin
+          sed -i "s@${gnome-shell}/bin/@${config.security.wrapperDir}/@" $out/bin/gnome-shell
+        '';
+      in {
+        # Note we need to clear ExecStart before overriding it
+        serviceConfig.ExecStart = ["" "${gnomeShellRT}/bin/gnome-shell"];
+        # Do not use the default environment, it provides a broken PATH
+        environment = mkForce {};
+      };
+
+      # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-36/elements/core/meta-gnome-core-shell.bst
+      environment.systemPackages = with pkgs.gnome3; [
+        adwaita-icon-theme
+        gnome-backgrounds
+        gnome-bluetooth
+        gnome-color-manager
+        gnome-control-center
+        gnome-getting-started-docs
+        gnome-shell
+        gnome-shell-extensions
+        gnome-themes-extra
+        pkgs.nixos-artwork.wallpapers.simple-dark-gray
+        pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom
+        pkgs.gnome-user-docs
+        pkgs.orca
+        pkgs.glib # for gsettings
+        pkgs.gnome-menus
+        pkgs.gtk3.out # for gtk-launch
+        pkgs.hicolor-icon-theme
+        pkgs.shared-mime-info # for update-mime-database
+        pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
+      ];
+    })
+
+    # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-36/elements/core/meta-gnome-core-utilities.bst
+    (mkIf serviceCfg.core-utilities.enable {
+      environment.systemPackages = (with pkgs.gnome3; removePackagesByName [
+        baobab
+        cheese
+        eog
+        epiphany
+        gedit
+        gnome-calculator
+        gnome-calendar
+        gnome-characters
+        gnome-clocks
+        gnome-contacts
+        gnome-font-viewer
+        gnome-logs
+        gnome-maps
+        gnome-music
+        gnome-photos
+        gnome-screenshot
+        gnome-software
+        gnome-system-monitor
+        gnome-weather
+        nautilus
+        simple-scan
+        totem
+        yelp
+        # Unsure if sensible for NixOS
+        /* gnome-boxes */
+      ] config.environment.gnome3.excludePackages);
+
+      # Enable default programs
+      programs.evince.enable = mkDefault true;
+      programs.file-roller.enable = mkDefault true;
+      programs.geary.enable = mkDefault true;
+      programs.gnome-disks.enable = mkDefault true;
+      programs.gnome-terminal.enable = mkDefault true;
+      programs.seahorse.enable = mkDefault true;
+      services.gnome3.sushi.enable = mkDefault true;
+
+      # Let nautilus find extensions
+      # TODO: Create nautilus-with-extensions package
+      environment.sessionVariables.NAUTILUS_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-3.0";
+
+      # Override default mimeapps for nautilus
+      environment.sessionVariables.XDG_DATA_DIRS = [ "${mimeAppsList}/share" ];
+
+      environment.pathsToLink = [
+        "/share/nautilus-python/extensions"
+      ];
+    })
+
+    (mkIf serviceCfg.games.enable {
+      environment.systemPackages = (with pkgs.gnome3; removePackagesByName [
+        aisleriot atomix five-or-more four-in-a-row gnome-chess gnome-klotski
+        gnome-mahjongg gnome-mines gnome-nibbles gnome-robots gnome-sudoku
+        gnome-taquin gnome-tetravex hitori iagno lightsoff quadrapassel
+        swell-foop tali
+      ] config.environment.gnome3.excludePackages);
+    })
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix
new file mode 100644
index 000000000000..bdae9c3afdb7
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/kodi.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.desktopManager.kodi;
+in
+
+{
+  options = {
+    services.xserver.desktopManager.kodi = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable the kodi multimedia center.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.desktopManager.session = [{
+      name = "kodi";
+      start = ''
+        LIRC_SOCKET_PATH=/run/lirc/lircd ${pkgs.kodi}/bin/kodi --standalone &
+        waitPID=$!
+      '';
+    }];
+
+    environment.systemPackages = [ pkgs.kodi ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/lumina.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/lumina.nix
new file mode 100644
index 000000000000..419f5055d8be
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/lumina.nix
@@ -0,0 +1,42 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager.lumina;
+
+in
+
+{
+  options = {
+
+    services.xserver.desktopManager.lumina.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable the Lumina desktop manager";
+    };
+
+  };
+
+
+  config = mkIf cfg.enable {
+
+    services.xserver.displayManager.sessionPackages = [
+      pkgs.lumina.lumina
+    ];
+
+    environment.systemPackages =
+      pkgs.lumina.preRequisitePackages ++
+      pkgs.lumina.corePackages;
+
+    # Link some extra directories in /run/current-system/software/share
+    environment.pathsToLink = [
+      "/share/lumina"
+      # FIXME: modules should link subdirs of `/share` rather than relying on this
+      "/share"
+    ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/lxqt.nix
new file mode 100644
index 000000000000..bf53082b267d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -0,0 +1,67 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager.lxqt;
+
+in
+
+{
+  options = {
+
+    services.xserver.desktopManager.lxqt.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable the LXQt desktop manager";
+    };
+
+    environment.lxqt.excludePackages = mkOption {
+      default = [];
+      example = literalExample "[ pkgs.lxqt.qterminal ]";
+      type = types.listOf types.package;
+      description = "Which LXQt packages to exclude from the default environment";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    services.xserver.desktopManager.session = singleton {
+      name = "lxqt";
+      bgSupport = true;
+      start = ''
+        # Upstream installs default configuration files in
+        # $prefix/share/lxqt instead of $prefix/etc/xdg, (arguably)
+        # giving distributors freedom to ship custom default
+        # configuration files more easily. In order to let the session
+        # manager find them the share subdirectory is added to the
+        # XDG_CONFIG_DIRS environment variable.
+        #
+        # For an explanation see
+        # https://github.com/lxqt/lxqt/issues/1521#issuecomment-405097453
+        #
+        export XDG_CONFIG_DIRS=$XDG_CONFIG_DIRS''${XDG_CONFIG_DIRS:+:}${config.system.path}/share
+
+        exec ${pkgs.lxqt.lxqt-session}/bin/startlxqt
+      '';
+    };
+
+    environment.systemPackages =
+      pkgs.lxqt.preRequisitePackages ++
+      pkgs.lxqt.corePackages ++
+      (pkgs.gnome3.removePackagesByName
+        pkgs.lxqt.optionalPackages
+        config.environment.lxqt.excludePackages);
+
+    # Link some extra directories in /run/current-system/software/share
+    environment.pathsToLink = [ "/share" ];
+
+    services.gvfs.enable = true;
+    services.gvfs.package = pkgs.gvfs;
+
+    services.upower.enable = config.powerManagement.enable;
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix
new file mode 100644
index 000000000000..f236c14fcf3e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -0,0 +1,111 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  addToXDGDirs = p: ''
+    if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+      export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+    fi
+
+    if [ -d "${p}/lib/girepository-1.0" ]; then
+      export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+      export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+    fi
+  '';
+
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager.mate;
+
+in
+
+{
+  options = {
+
+    services.xserver.desktopManager.mate = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable the MATE desktop environment";
+      };
+
+      debug = mkEnableOption "mate-session debug messages";
+    };
+
+    environment.mate.excludePackages = mkOption {
+      default = [];
+      example = literalExample "[ pkgs.mate.mate-terminal pkgs.mate.pluma ]";
+      type = types.listOf types.package;
+      description = "Which MATE packages to exclude from the default environment";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    services.xserver.displayManager.sessionPackages = [
+      pkgs.mate.mate-session-manager
+    ];
+
+    services.xserver.displayManager.sessionCommands = ''
+      if test "$XDG_CURRENT_DESKTOP" = "MATE"; then
+          export XDG_MENU_PREFIX=mate-
+
+          # Let caja find extensions
+          export CAJA_EXTENSION_DIRS=$CAJA_EXTENSION_DIRS''${CAJA_EXTENSION_DIRS:+:}${config.system.path}/lib/caja/extensions-2.0
+
+          # Let caja extensions find gsettings schemas
+          ${concatMapStrings (p: ''
+          if [ -d "${p}/lib/caja/extensions-2.0" ]; then
+              ${addToXDGDirs p}
+          fi
+          '') config.environment.systemPackages}
+
+          # Add mate-control-center paths to some XDG variables because its schemas are needed by mate-settings-daemon, and mate-settings-daemon is a dependency for mate-control-center (that is, they are mutually recursive)
+          ${addToXDGDirs pkgs.mate.mate-control-center}
+      fi
+    '';
+
+    # Let mate-panel find applets
+    environment.sessionVariables."MATE_PANEL_APPLETS_DIR" = "${config.system.path}/share/mate-panel/applets";
+    environment.sessionVariables."MATE_PANEL_EXTRA_MODULES" = "${config.system.path}/lib/mate-panel/applets";
+
+    # Debugging
+    environment.sessionVariables.MATE_SESSION_DEBUG = mkIf cfg.debug "1";
+
+    environment.systemPackages =
+      pkgs.mate.basePackages ++
+      (pkgs.gnome3.removePackagesByName
+        pkgs.mate.extraPackages
+        config.environment.mate.excludePackages) ++
+      [
+        pkgs.desktop-file-utils
+        pkgs.glib
+        pkgs.gtk3.out
+        pkgs.shared-mime-info
+        pkgs.xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
+        pkgs.mate.mate-settings-daemon
+        pkgs.yelp # for 'Contents' in 'Help' menus
+      ];
+
+    programs.dconf.enable = true;
+    # Shell integration for VTE terminals
+    programs.bash.vteIntegration = mkDefault true;
+    programs.zsh.vteIntegration = mkDefault true;
+
+    # Mate uses this for printing
+    programs.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+
+    services.gnome3.at-spi2-core.enable = true;
+    services.gnome3.gnome-keyring.enable = true;
+    services.udev.packages = [ pkgs.mate.mate-settings-daemon ];
+    services.gvfs.enable = true;
+    services.upower.enable = config.powerManagement.enable;
+
+    security.pam.services.mate-screensaver.unixAuth = true;
+
+    environment.pathsToLink = [ "/share" ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix
new file mode 100644
index 000000000000..af7a376ae029
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/none.nix
@@ -0,0 +1,7 @@
+{
+  services.xserver.desktopManager.session =
+    [ { name = "none";
+        start = "";
+      }
+    ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
new file mode 100644
index 000000000000..6dabca6bf096
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -0,0 +1,297 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.desktopManager.pantheon;
+  serviceCfg = config.services.pantheon;
+
+  nixos-gsettings-desktop-schemas = pkgs.pantheon.elementary-gsettings-schemas.override {
+    extraGSettingsOverridePackages = cfg.extraGSettingsOverridePackages;
+    extraGSettingsOverrides = cfg.extraGSettingsOverrides;
+  };
+
+in
+
+{
+
+  meta = {
+    doc = ./pantheon.xml;
+    maintainers = pkgs.pantheon.maintainers;
+  };
+
+  options = {
+
+    services.pantheon = {
+
+      contractor = {
+         enable = mkEnableOption "contractor, a desktop-wide extension service used by Pantheon";
+      };
+
+      apps.enable = mkEnableOption "Pantheon default applications";
+
+    };
+
+    services.xserver.desktopManager.pantheon = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable the pantheon desktop manager";
+      };
+
+      sessionPath = mkOption {
+        default = [];
+        example = literalExample "[ pkgs.gnome3.gpaste ]";
+        description = ''
+          Additional list of packages to be added to the session search path.
+          Useful for GSettings-conditional autostart.
+
+          Note that this should be a last resort; patching the package is preferred (see GPaste).
+        '';
+        apply = list: list ++
+        [
+          pkgs.pantheon.pantheon-agent-geoclue2
+        ];
+      };
+
+      extraWingpanelIndicators = mkOption {
+        default = null;
+        type = with types; nullOr (listOf package);
+        description = "Indicators to add to Wingpanel.";
+      };
+
+      extraSwitchboardPlugs = mkOption {
+        default = null;
+        type = with types; nullOr (listOf package);
+        description = "Plugs to add to Switchboard.";
+      };
+
+      extraGSettingsOverrides = mkOption {
+        default = "";
+        type = types.lines;
+        description = "Additional gsettings overrides.";
+      };
+
+      extraGSettingsOverridePackages = mkOption {
+        default = [];
+        type = types.listOf types.path;
+        description = "List of packages for which gsettings are overridden.";
+      };
+
+      debug = mkEnableOption "gnome-session debug messages";
+
+    };
+
+    environment.pantheon.excludePackages = mkOption {
+      default = [];
+      example = literalExample "[ pkgs.pantheon.elementary-camera ]";
+      type = types.listOf types.package;
+      description = "Which packages pantheon should exclude from the default environment";
+    };
+
+  };
+
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+
+      services.xserver.displayManager.sessionPackages = [ pkgs.pantheon.elementary-session-settings ];
+
+      # Ensure lightdm is used when Pantheon is enabled
+      # Without it screen locking will be nonfunctional because of the use of lightlocker
+      warnings = optional (config.services.xserver.displayManager.lightdm.enable != true)
+        ''
+          Using Pantheon without LightDM as a displayManager will break screenlocking from the UI.
+        '';
+
+      services.xserver.displayManager.lightdm.greeters.pantheon.enable = mkDefault true;
+
+      # Without this, elementary LightDM greeter will pre-select non-existent `default` session
+      # https://github.com/elementary/greeter/issues/368
+      services.xserver.displayManager.defaultSession = mkDefault "pantheon";
+
+      services.xserver.displayManager.sessionCommands = ''
+        if test "$XDG_CURRENT_DESKTOP" = "Pantheon"; then
+            ${concatMapStrings (p: ''
+              if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+                export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+              fi
+
+              if [ -d "${p}/lib/girepository-1.0" ]; then
+                export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+                export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+              fi
+            '') cfg.sessionPath}
+        fi
+      '';
+
+      # Default services
+      hardware.bluetooth.enable = mkDefault true;
+      hardware.pulseaudio.enable = mkDefault true;
+      security.polkit.enable = true;
+      services.accounts-daemon.enable = true;
+      services.bamf.enable = true;
+      services.colord.enable = mkDefault true;
+      services.tumbler.enable = mkDefault true;
+      services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+      services.dbus.packages = with pkgs.pantheon; [
+        switchboard-plug-power
+        elementary-default-settings # accountsservice extensions
+      ];
+      services.pantheon.apps.enable = mkDefault true;
+      services.pantheon.contractor.enable = mkDefault true;
+      services.gnome3.at-spi2-core.enable = true;
+      services.gnome3.evolution-data-server.enable = true;
+      services.gnome3.glib-networking.enable = true;
+      services.gnome3.gnome-keyring.enable = true;
+      services.gvfs.enable = true;
+      services.gnome3.rygel.enable = mkDefault true;
+      services.gsignond.enable = mkDefault true;
+      services.gsignond.plugins = with pkgs.gsignondPlugins; [ lastfm mail oauth ];
+      services.udisks2.enable = true;
+      services.upower.enable = config.powerManagement.enable;
+      services.xserver.libinput.enable = mkDefault true;
+      services.xserver.updateDbusEnvironment = true;
+      services.zeitgeist.enable = mkDefault true;
+      services.geoclue2.enable = mkDefault true;
+      # pantheon has pantheon-agent-geoclue2
+      services.geoclue2.enableDemoAgent = false;
+      services.geoclue2.appConfig."io.elementary.desktop.agent-geoclue2" = {
+        isAllowed = true;
+        isSystem = true;
+      };
+      # Use gnome-settings-daemon fork
+      services.udev.packages = [
+        pkgs.pantheon.elementary-settings-daemon
+      ];
+      systemd.packages = [
+        pkgs.pantheon.elementary-settings-daemon
+      ];
+      programs.dconf.enable = true;
+      networking.networkmanager.enable = mkDefault true;
+
+      # Global environment
+      environment.systemPackages = with pkgs; [
+        desktop-file-utils
+        glib
+        gnome-menus
+        gnome3.adwaita-icon-theme
+        gtk3.out
+        hicolor-icon-theme
+        lightlocker
+        nixos-artwork.wallpapers.simple-dark-gray
+        onboard
+        qgnomeplatform
+        shared-mime-info
+        sound-theme-freedesktop
+        xdg-user-dirs
+      ] ++ (with pkgs.pantheon; [
+        # Artwork
+        elementary-gtk-theme
+        elementary-icon-theme
+        elementary-sound-theme
+        elementary-wallpapers
+
+        # Desktop
+        elementary-default-settings
+        elementary-dock
+        elementary-session-settings
+        elementary-shortcut-overlay
+        gala
+        (switchboard-with-plugs.override {
+          plugs = cfg.extraSwitchboardPlugs;
+        })
+        (wingpanel-with-indicators.override {
+          indicators = cfg.extraWingpanelIndicators;
+        })
+
+        # Services
+        elementary-capnet-assist
+        elementary-dpms-helper
+        elementary-notifications
+        elementary-settings-daemon
+        pantheon-agent-geoclue2
+        pantheon-agent-polkit
+      ]) ++ (gnome3.removePackagesByName [
+        gnome3.geary
+        gnome3.epiphany
+        gnome3.gnome-font-viewer
+      ] config.environment.pantheon.excludePackages);
+
+      programs.evince.enable = mkDefault true;
+      programs.file-roller.enable = mkDefault true;
+
+      # Settings from elementary-default-settings
+      environment.sessionVariables.GTK_CSD = "1";
+      environment.sessionVariables.GTK3_MODULES = [ "pantheon-filechooser-module" ];
+      environment.etc."gtk-3.0/settings.ini".source = "${pkgs.pantheon.elementary-default-settings}/etc/gtk-3.0/settings.ini";
+
+      # Override GSettings schemas
+      environment.sessionVariables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
+
+      environment.sessionVariables.GNOME_SESSION_DEBUG = mkIf cfg.debug "1";
+
+      environment.pathsToLink = [
+        # FIXME: modules should link subdirs of `/share` rather than relying on this
+        "/share"
+      ];
+
+      # Otherwise you can't store NetworkManager Secrets with
+      # "Store the password only for this user"
+      programs.nm-applet.enable = true;
+
+      # Shell integration for VTE terminals
+      programs.bash.vteIntegration = mkDefault true;
+      programs.zsh.vteIntegration = mkDefault true;
+
+      # Harmonize Qt5 applications under Pantheon
+      qt5.enable = true;
+      qt5.platformTheme = "gnome";
+      qt5.style = "adwaita";
+
+      # Default Fonts
+      fonts.fonts = with pkgs; [
+        open-sans
+        roboto-mono
+      ];
+
+      fonts.fontconfig.defaultFonts = {
+        monospace = [ "Roboto Mono" ];
+        sansSerif = [ "Open Sans" ];
+      };
+    })
+
+    (mkIf serviceCfg.apps.enable {
+      environment.systemPackages = (with pkgs.pantheon; pkgs.gnome3.removePackagesByName [
+        elementary-calculator
+        elementary-calendar
+        elementary-camera
+        elementary-code
+        elementary-files
+        elementary-music
+        elementary-photos
+        elementary-screenshot-tool
+        elementary-terminal
+        elementary-videos
+      ] config.environment.pantheon.excludePackages);
+
+      # needed by screenshot-tool
+      fonts.fonts = [
+        pkgs.pantheon.elementary-redacted-script
+      ];
+    })
+
+    (mkIf serviceCfg.contractor.enable {
+      environment.systemPackages = with  pkgs.pantheon; [
+        contractor
+        extra-elementary-contracts
+      ];
+
+      environment.pathsToLink = [
+        "/share/contractor"
+      ];
+    })
+
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.xml b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.xml
new file mode 100644
index 000000000000..7905ceebd9aa
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/pantheon.xml
@@ -0,0 +1,114 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xml:id="chap-pantheon">
+ <title>Pantheon Desktop</title>
+ <para>
+  Pantheon is the desktop environment created for the elementary OS distribution. It is written from scratch in Vala, utilizing GNOME technologies with GTK 3 and Granite.
+ </para>
+ <section xml:id="sec-pantheon-enable">
+  <title>Enabling Pantheon</title>
+
+  <para>
+   All of Pantheon is working in NixOS and the applications should be available, aside from a few <link xlink:href="https://github.com/NixOS/nixpkgs/issues/58161">exceptions</link>. To enable Pantheon, set
+<programlisting>
+<xref linkend="opt-services.xserver.desktopManager.pantheon.enable"/> = true;
+</programlisting>
+   This automatically enables LightDM and Pantheon's LightDM greeter. If you'd like to disable this, set
+<programlisting>
+<xref linkend="opt-services.xserver.displayManager.lightdm.greeters.pantheon.enable"/> = false;
+<xref linkend="opt-services.xserver.displayManager.lightdm.enable"/> = false;
+</programlisting>
+   but please be aware using Pantheon without LightDM as a display manager will break screenlocking from the UI. The NixOS module for Pantheon installs all of Pantheon's default applications. If you'd like to not install Pantheon's apps, set
+<programlisting>
+<xref linkend="opt-services.pantheon.apps.enable"/> = false;
+</programlisting>
+   You can also use <xref linkend="opt-environment.pantheon.excludePackages"/> to remove any other app (like <package>geary</package>).
+  </para>
+ </section>
+ <section xml:id="sec-pantheon-wingpanel-switchboard">
+  <title>Wingpanel and Switchboard plugins</title>
+
+  <para>
+   Wingpanel and Switchboard work differently than they do in other distributions, as far as using plugins. You cannot install a plugin globally (like with <option>environment.systemPackages</option>) to start using it. You should instead be using the following options:
+   <itemizedlist>
+    <listitem>
+     <para>
+      <xref linkend="opt-services.xserver.desktopManager.pantheon.extraWingpanelIndicators"/>
+     </para>
+    </listitem>
+    <listitem>
+     <para>
+      <xref linkend="opt-services.xserver.desktopManager.pantheon.extraSwitchboardPlugs"/>
+     </para>
+    </listitem>
+   </itemizedlist>
+   to configure the programs with plugs or indicators.
+  </para>
+
+  <para>
+   The difference in NixOS is both these programs are patched to load plugins from a directory that is the value of an environment variable. All of which is controlled in Nix. If you need to configure the particular packages manually you can override the packages like:
+<programlisting>
+wingpanel-with-indicators.override {
+  indicators = [
+    pkgs.some-special-indicator
+  ];
+};
+
+switchboard-with-plugs.override {
+  plugs = [
+    pkgs.some-special-plug
+  ];
+};
+</programlisting>
+   please note that, like how the NixOS options describe these as extra plugins, this would only add to the default plugins included with the programs. If for some reason you'd like to configure which plugins to use exactly, both packages have an argument for this:
+<programlisting>
+wingpanel-with-indicators.override {
+  useDefaultIndicators = false;
+  indicators = specialListOfIndicators;
+};
+
+switchboard-with-plugs.override {
+  useDefaultPlugs = false;
+  plugs = specialListOfPlugs;
+};
+</programlisting>
+   this could be most useful for testing a particular plug-in in isolation.
+  </para>
+ </section>
+ <section xml:id="sec-pantheon-faq">
+  <title>FAQ</title>
+
+  <variablelist>
+   <varlistentry xml:id="sec-pantheon-faq-messed-up-theme">
+    <term>
+     I have switched from a different desktop and Pantheon’s theming looks messed up.
+    </term>
+    <listitem>
+     <para>
+      Open Switchboard and go to: <guilabel>Administration</guilabel> → <guilabel>About</guilabel> → <guilabel>Restore Default Settings</guilabel> → <guibutton>Restore Settings</guibutton>. This will reset any dconf settings to their Pantheon defaults. Note this could reset certain GNOME specific preferences if that desktop was used prior.
+     </para>
+    </listitem>
+   </varlistentry>
+   <varlistentry xml:id="sec-pantheon-faq-gnome3-and-pantheon">
+    <term>
+     I cannot enable both GNOME 3 and Pantheon.
+    </term>
+    <listitem>
+     <para>
+      This is a known <link xlink:href="https://github.com/NixOS/nixpkgs/issues/64611">issue</link> and there is no known workaround.
+     </para>
+    </listitem>
+   </varlistentry>
+   <varlistentry xml:id="sec-pantheon-faq-appcenter">
+    <term>
+     Does AppCenter work, or is it available?
+    </term>
+    <listitem>
+     <para>
+      AppCenter has been available since 20.03, but it is of little use. This is because there is no functioning PackageKit backend for Nix 2.0. In the near future you will be able to install Flatpak applications from AppCenter on NixOS. See this <link xlink:href="https://github.com/NixOS/nixpkgs/issues/70214">issue</link>.
+     </para>
+    </listitem>
+   </varlistentry>
+  </variablelist>
+ </section>
+</chapter>
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
new file mode 100644
index 000000000000..6d48b899d231
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -0,0 +1,364 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  xcfg = config.services.xserver;
+  cfg = xcfg.desktopManager.plasma5;
+
+  inherit (pkgs) kdeApplications plasma5 libsForQt5 qt5;
+  inherit (pkgs) writeText;
+
+  pulseaudio = config.hardware.pulseaudio;
+  pactl = "${getBin pulseaudio.package}/bin/pactl";
+  startplasma-x11 = "${getBin plasma5.plasma-workspace}/bin/startplasma-x11";
+  sed = "${getBin pkgs.gnused}/bin/sed";
+
+  gtkrc2 = writeText "gtkrc-2.0" ''
+    # Default GTK+ 2 config for NixOS Plasma 5
+    include "/run/current-system/sw/share/themes/Breeze/gtk-2.0/gtkrc"
+    style "user-font"
+    {
+      font_name="Sans Serif Regular"
+    }
+    widget_class "*" style "user-font"
+    gtk-font-name="Sans Serif Regular 10"
+    gtk-theme-name="Breeze"
+    gtk-icon-theme-name="breeze"
+    gtk-fallback-icon-theme="hicolor"
+    gtk-cursor-theme-name="breeze_cursors"
+    gtk-toolbar-style=GTK_TOOLBAR_ICONS
+    gtk-menu-images=1
+    gtk-button-images=1
+  '';
+
+  gtk3_settings = writeText "settings.ini" ''
+    [Settings]
+    gtk-font-name=Sans Serif Regular 10
+    gtk-theme-name=Breeze
+    gtk-icon-theme-name=breeze
+    gtk-fallback-icon-theme=hicolor
+    gtk-cursor-theme-name=breeze_cursors
+    gtk-toolbar-style=GTK_TOOLBAR_ICONS
+    gtk-menu-images=1
+    gtk-button-images=1
+  '';
+
+  kcminputrc = writeText "kcminputrc" ''
+    [Mouse]
+    cursorTheme=breeze_cursors
+    cursorSize=0
+  '';
+
+  activationScript = ''
+    ${set_XDG_CONFIG_HOME}
+
+    # The KDE icon cache is supposed to update itself automatically, but it uses
+    # the timestamp on the icon theme directory as a trigger. This doesn't work
+    # on NixOS because the timestamp never changes. As a workaround, delete the
+    # icon cache at login and session activation.
+    # See also: http://lists-archives.org/kde-devel/26175-what-when-will-icon-cache-refresh.html
+    rm -fv $HOME/.cache/icon-cache.kcache
+
+    # xdg-desktop-settings generates this empty file but
+    # it makes kbuildsyscoca5 fail silently. To fix this
+    # remove that menu if it exists.
+    rm -fv ''${XDG_CONFIG_HOME}/menus/applications-merged/xdg-desktop-menu-dummy.menu
+
+    # Qt writes a weird ‘libraryPath’ line to
+    # ~/.config/Trolltech.conf that causes the KDE plugin
+    # paths of previous KDE invocations to be searched.
+    # Obviously using mismatching KDE libraries is potentially
+    # disastrous, so here we nuke references to the Nix store
+    # in Trolltech.conf.  A better solution would be to stop
+    # Qt from doing this wackiness in the first place.
+    trolltech_conf="''${XDG_CONFIG_HOME}/Trolltech.conf"
+    if [ -e "$trolltech_conf" ]; then
+        ${sed} -i "$trolltech_conf" -e '/nix\\store\|nix\/store/ d'
+    fi
+
+    # Remove the kbuildsyscoca5 cache. It will be regenerated
+    # immediately after. This is necessary for kbuildsyscoca5 to
+    # recognize that software that has been removed.
+    rm -fv $HOME/.cache/ksycoca*
+
+    ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5
+  '';
+
+  set_XDG_CONFIG_HOME = ''
+      # Set the default XDG_CONFIG_HOME if it is unset.
+      # Per the XDG Base Directory Specification:
+      # https://specifications.freedesktop.org/basedir-spec/latest
+      # 1. Never export this variable! If it is unset, then child processes are
+      # expected to set the default themselves.
+      # 2. Contaminate / if $HOME is unset; do not check if $HOME is set.
+      XDG_CONFIG_HOME=''${XDG_CONFIG_HOME:-$HOME/.config}
+  '';
+
+  startplasma =
+    ''
+      ${set_XDG_CONFIG_HOME}
+      mkdir -p "''${XDG_CONFIG_HOME}"
+
+    ''
+    + optionalString pulseaudio.enable ''
+      # Load PulseAudio module for routing support.
+      # See also: http://colin.guthr.ie/2009/10/so-how-does-the-kde-pulseaudio-support-work-anyway/
+        ${pactl} load-module module-device-manager "do_routing=1"
+
+    ''
+    + ''
+      ${activationScript}
+
+      # Create default configurations if Plasma has never been started.
+      kdeglobals="''${XDG_CONFIG_HOME}/kdeglobals"
+      if ! [ -f "$kdeglobals" ]
+      then
+          kcminputrc="''${XDG_CONFIG_HOME}/kcminputrc"
+          if ! [ -f "$kcminputrc" ]
+          then
+              cat ${kcminputrc} >"$kcminputrc"
+          fi
+
+          gtkrc2="$HOME/.gtkrc-2.0"
+          if ! [ -f "$gtkrc2" ]
+          then
+              cat ${gtkrc2} >"$gtkrc2"
+          fi
+
+          gtk3_settings="''${XDG_CONFIG_HOME}/gtk-3.0/settings.ini"
+          if ! [ -f "$gtk3_settings" ]
+          then
+              mkdir -p "$(dirname "$gtk3_settings")"
+              cat ${gtk3_settings} >"$gtk3_settings"
+          fi
+      fi
+
+    ''
+    + ''
+      exec "${startplasma-x11}"
+    '';
+
+in
+
+{
+  options = {
+
+    services.xserver.desktopManager.plasma5 = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable the Plasma 5 (KDE 5) desktop environment.";
+      };
+
+      phononBackend = mkOption {
+        type = types.enum [ "gstreamer" "vlc" ];
+        default = "gstreamer";
+        example = "vlc";
+        description = "Phonon audio backend to install.";
+      };
+
+      supportDDC = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Support setting monitor brightness via DDC.
+          </para>
+          <para>
+          This is not needed for controlling brightness of the internal monitor
+          of a laptop and as it is considered experimental by upstream, it is
+          disabled by default.
+        '';
+      };
+    };
+
+  };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "plasma5" "enableQt4Support" ] "Phonon no longer supports Qt 4.")
+    (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ])
+  ];
+
+  config = mkMerge [
+    (mkIf cfg.enable {
+      services.xserver.desktopManager.session = singleton {
+        name = "plasma5";
+        bgSupport = true;
+        start = startplasma;
+      };
+
+      security.wrappers = {
+        kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/libexec/kcheckpass";
+        start_kdeinit.source = "${lib.getBin pkgs.kinit}/libexec/kf5/start_kdeinit";
+        kwin_wayland = {
+          source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland";
+          capabilities = "cap_sys_nice+ep";
+        };
+      };
+
+      # DDC support
+      boot.kernelModules = lib.optional cfg.supportDDC "i2c_dev";
+      services.udev.extraRules = lib.optionalString cfg.supportDDC ''
+        KERNEL=="i2c-[0-9]*", TAG+="uaccess"
+      '';
+
+      environment.systemPackages = with pkgs; with qt5; with libsForQt5; with plasma5; with kdeApplications;
+        [
+          frameworkintegration
+          kactivities
+          kauth
+          kcmutils
+          kconfig
+          kconfigwidgets
+          kcoreaddons
+          kdoctools
+          kdbusaddons
+          kdeclarative
+          kded
+          kdesu
+          kdnssd
+          kemoticons
+          kfilemetadata
+          kglobalaccel
+          kguiaddons
+          kiconthemes
+          kidletime
+          kimageformats
+          kinit
+          kio
+          kjobwidgets
+          knewstuff
+          knotifications
+          knotifyconfig
+          kpackage
+          kparts
+          kpeople
+          krunner
+          kservice
+          ktextwidgets
+          kwallet
+          kwallet-pam
+          kwalletmanager
+          kwayland
+          kwidgetsaddons
+          kxmlgui
+          kxmlrpcclient
+          plasma-framework
+          solid
+          sonnet
+          threadweaver
+
+          breeze-qt5
+          kactivitymanagerd
+          kde-cli-tools
+          kdecoration
+          kdeplasma-addons
+          kgamma5
+          khotkeys
+          kinfocenter
+          kmenuedit
+          kscreen
+          kscreenlocker
+          ksysguard
+          kwayland
+          kwin
+          kwrited
+          libkscreen
+          libksysguard
+          milou
+          plasma-browser-integration
+          plasma-integration
+          polkit-kde-agent
+          systemsettings
+
+          plasma-desktop
+          plasma-workspace
+          plasma-workspace-wallpapers
+
+          dolphin
+          dolphin-plugins
+          ffmpegthumbs
+          kdegraphics-thumbnailers
+          khelpcenter
+          kio-extras
+          konsole
+          oxygen
+          print-manager
+
+          breeze-icons
+          pkgs.hicolor-icon-theme
+
+          kde-gtk-config breeze-gtk
+
+          qtvirtualkeyboard
+
+          xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
+        ]
+
+        # Phonon audio backend
+        ++ lib.optional (cfg.phononBackend == "gstreamer") libsForQt5.phonon-backend-gstreamer
+        ++ lib.optional (cfg.phononBackend == "vlc") libsForQt5.phonon-backend-vlc
+
+        # Optional hardware support features
+        ++ lib.optionals config.hardware.bluetooth.enable [ bluedevil bluez-qt openobex obexftp ]
+        ++ lib.optional config.networking.networkmanager.enable plasma-nm
+        ++ lib.optional config.hardware.pulseaudio.enable plasma-pa
+        ++ lib.optional config.powerManagement.enable powerdevil
+        ++ lib.optional config.services.colord.enable colord-kde
+        ++ lib.optionals config.services.samba.enable [ kdenetwork-filesharing pkgs.samba ]
+        ++ lib.optional config.services.xserver.wacom.enable wacomtablet;
+
+      environment.pathsToLink = [
+        # FIXME: modules should link subdirs of `/share` rather than relying on this
+        "/share"
+      ];
+
+      environment.etc."X11/xkb".source = xcfg.xkbDir;
+
+      # Enable GTK applications to load SVG icons
+      services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
+
+      fonts.fonts = with pkgs; [ noto-fonts hack-font ];
+      fonts.fontconfig.defaultFonts = {
+        monospace = [ "Hack" "Noto Mono" ];
+        sansSerif = [ "Noto Sans" ];
+        serif = [ "Noto Serif" ];
+      };
+
+      programs.ssh.askPassword = mkDefault "${plasma5.ksshaskpass.out}/bin/ksshaskpass";
+
+      # Enable helpful DBus services.
+      services.udisks2.enable = true;
+      services.upower.enable = config.powerManagement.enable;
+      services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+      services.xserver.libinput.enable = mkDefault true;
+
+      # Extra UDEV rules used by Solid
+      services.udev.packages = [
+        pkgs.libmtp
+        pkgs.media-player-info
+      ];
+
+      services.xserver.displayManager.sddm = {
+        theme = mkDefault "breeze";
+      };
+
+      security.pam.services.kde = { allowNullPassword = true; };
+
+      # Doing these one by one seems silly, but we currently lack a better
+      # construct for handling common pam configs.
+      security.pam.services.gdm.enableKwallet = true;
+      security.pam.services.kdm.enableKwallet = true;
+      security.pam.services.lightdm.enableKwallet = true;
+      security.pam.services.sddm.enableKwallet = true;
+
+      xdg.portal.enable = true;
+      xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-kde ];
+
+      # Update the start menu for each user that is currently logged in
+      system.userActivationScripts.plasmaSetup = activationScript;
+    })
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix
new file mode 100644
index 000000000000..9aeb0bbd2a88
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/surf-display.nix
@@ -0,0 +1,127 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.desktopManager.surf-display;
+
+  surfDisplayConf = ''
+    # Surf Kiosk Display: Wrap around surf browser and turn your
+    # system into a browser screen in KIOSK-mode.
+
+    # default download URI for all display screens if not configured individually
+    DEFAULT_WWW_URI="${cfg.defaultWwwUri}"
+
+    # Enforce fixed resolution for all displays (default: not set):
+    #DEFAULT_RESOLUTION="1920x1080"
+
+    # HTTP proxy URL, if needed (default: not set).
+    #HTTP_PROXY_URL="http://webcache:3128"
+
+    # Setting for internal inactivity timer to restart surf-display
+    # if the user goes inactive/idle.
+    INACTIVITY_INTERVAL="${builtins.toString cfg.inactivityInterval}"
+
+    # log to syslog instead of .xsession-errors
+    LOG_TO_SYSLOG="yes"
+
+    # Launch pulseaudio daemon if not already running.
+    WITH_PULSEAUDIO="yes"
+
+    # screensaver settings, see "man 1 xset" for possible options
+    SCREENSAVER_SETTINGS="${cfg.screensaverSettings}"
+
+    # disable right and middle pointer device click in browser sessions while keeping
+    # scrolling wheels' functionality intact... (consider "pointer" subcommand on
+    # xmodmap man page for details).
+    POINTER_BUTTON_MAP="${cfg.pointerButtonMap}"
+
+    # Hide idle mouse pointer.
+    HIDE_IDLE_POINTER="${cfg.hideIdlePointer}"
+
+    ${cfg.extraConfig}
+  '';
+
+in {
+  options = {
+    services.xserver.desktopManager.surf-display = {
+      enable = mkEnableOption "surf-display as a kiosk browser session";
+
+      defaultWwwUri = mkOption {
+        type = types.str;
+        default = "${pkgs.surf-display}/share/surf-display/empty-page.html";
+        example = "https://www.example.com/";
+        description = "Default URI to display.";
+      };
+
+      inactivityInterval = mkOption {
+        type = types.int;
+        default = 300;
+        example = "0";
+        description = ''
+          Setting for internal inactivity timer to restart surf-display if the
+          user goes inactive/idle to get a fresh session for the next user of
+          the kiosk.
+
+          If this value is set to zero, the whole feature of restarting due to
+          inactivity is disabled.
+        '';
+      };
+
+      screensaverSettings = mkOption {
+        type = types.separatedString " ";
+        default = "";
+        description = ''
+          Screensaver settings, see <literal>man 1 xset</literal> for possible options.
+        '';
+      };
+
+      pointerButtonMap = mkOption {
+        type = types.str;
+        default = "1 0 0 4 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
+        description = ''
+          Disable right and middle pointer device click in browser sessions
+          while keeping scrolling wheels' functionality intact. See pointer
+          subcommand on <literal>man xmodmap</literal> for details.
+        '';
+      };
+
+      hideIdlePointer = mkOption {
+        type = types.str;
+        default = "yes";
+        example = "no";
+        description = "Hide idle mouse pointer.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          # Enforce fixed resolution for all displays (default: not set):
+          DEFAULT_RESOLUTION="1920x1080"
+
+          # HTTP proxy URL, if needed (default: not set).
+          HTTP_PROXY_URL="http://webcache:3128"
+
+          # Configure individual display screens with host specific parameters:
+          DISPLAYS['display-host-0']="www_uri=https://www.displayserver.comany.net/display-1/index.html"
+          DISPLAYS['display-host-1']="www_uri=https://www.displayserver.comany.net/display-2/index.html"
+          DISPLAYS['display-host-2']="www_uri=https://www.displayserver.comany.net/display-3/index.html|res=1920x1280"
+          DISPLAYS['display-host-3']="www_uri=https://www.displayserver.comany.net/display-4/index.html"|res=1280x1024"
+          DISPLAYS['display-host-local-file']="www_uri=file:///usr/share/doc/surf-display/empty-page.html"
+        '';
+        description = ''
+          Extra configuration options to append to <literal>/etc/default/surf-display</literal>.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.displayManager.sessionPackages = [
+      pkgs.surf-display
+    ];
+
+    environment.etc."default/surf-display".text = surfDisplayConf;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix
new file mode 100644
index 000000000000..d39b4d64904f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -0,0 +1,172 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.desktopManager.xfce;
+in
+
+{
+
+  meta = {
+    maintainers = with maintainers; [ worldofpeace ];
+  };
+
+  imports = [
+    # added 2019-08-18
+    # needed to preserve some semblance of UI familarity
+    # with original XFCE module
+    (mkRenamedOptionModule
+      [ "services" "xserver" "desktopManager" "xfce4-14" "extraSessionCommands" ]
+      [ "services" "xserver" "displayManager" "sessionCommands" ])
+
+    # added 2019-11-04
+    # xfce4-14 module removed and promoted to xfce.
+    # Needed for configs that used xfce4-14 module to migrate to this one.
+    (mkRenamedOptionModule
+      [ "services" "xserver" "desktopManager" "xfce4-14" "enable" ]
+      [ "services" "xserver" "desktopManager" "xfce" "enable" ])
+    (mkRenamedOptionModule
+      [ "services" "xserver" "desktopManager" "xfce4-14" "noDesktop" ]
+      [ "services" "xserver" "desktopManager" "xfce" "noDesktop" ])
+    (mkRenamedOptionModule
+      [ "services" "xserver" "desktopManager" "xfce4-14" "enableXfwm" ]
+      [ "services" "xserver" "desktopManager" "xfce" "enableXfwm" ])
+    (mkRenamedOptionModule
+      [ "services" "xserver" "desktopManager" "xfce" "extraSessionCommands" ]
+      [ "services" "xserver" "displayManager" "sessionCommands" ])
+    (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "xfce" "screenLock" ] "")
+  ];
+
+  options = {
+    services.xserver.desktopManager.xfce = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable the Xfce desktop environment.";
+      };
+
+      thunarPlugins = mkOption {
+        default = [];
+        type = types.listOf types.package;
+        example = literalExample "[ pkgs.xfce.thunar-archive-plugin ]";
+        description = ''
+          A list of plugin that should be installed with Thunar.
+        '';
+      };
+
+      noDesktop = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Don't install XFCE desktop components (xfdesktop, panel and notification daemon).";
+      };
+
+      enableXfwm = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Enable the XFWM (default) window manager.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs.xfce // pkgs; [
+      glib # for gsettings
+      gtk3.out # gtk-update-icon-cache
+
+      gnome3.gnome-themes-extra
+      gnome3.adwaita-icon-theme
+      hicolor-icon-theme
+      tango-icon-theme
+      xfce4-icon-theme
+
+      desktop-file-utils
+      shared-mime-info # for update-mime-database
+
+      # For a polkit authentication agent
+      polkit_gnome
+
+      # Needed by Xfce's xinitrc script
+      xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
+
+      exo
+      garcon
+      libxfce4ui
+      xfconf
+
+      mousepad
+      parole
+      ristretto
+      xfce4-appfinder
+      xfce4-screenshooter
+      xfce4-session
+      xfce4-settings
+      xfce4-taskmanager
+      xfce4-terminal
+
+      (thunar.override { thunarPlugins = cfg.thunarPlugins; })
+    ] # TODO: NetworkManager doesn't belong here
+      ++ optional config.networking.networkmanager.enable networkmanagerapplet
+      ++ optional config.powerManagement.enable xfce4-power-manager
+      ++ optionals config.hardware.pulseaudio.enable [
+        pavucontrol
+        # volume up/down keys support:
+        # xfce4-pulseaudio-plugin includes all the functionalities of xfce4-volumed-pulse
+        # but can only be used with xfce4-panel, so for no-desktop usage we still include
+        # xfce4-volumed-pulse
+        (if cfg.noDesktop then xfce4-volumed-pulse else xfce4-pulseaudio-plugin)
+      ] ++ optionals cfg.enableXfwm [
+        xfwm4
+        xfwm4-themes
+      ] ++ optionals (!cfg.noDesktop) [
+        xfce4-notifyd
+        xfce4-panel
+        xfdesktop
+      ];
+
+    environment.pathsToLink = [
+      "/share/xfce4"
+      "/lib/xfce4"
+      "/share/gtksourceview-3.0"
+      "/share/gtksourceview-4.0"
+    ];
+
+    services.xserver.desktopManager.session = [{
+      name = "xfce";
+      desktopNames = [ "XFCE" ];
+      bgSupport = true;
+      start = ''
+        ${pkgs.runtimeShell} ${pkgs.xfce.xfce4-session.xinitrc} &
+        waitPID=$!
+      '';
+    }];
+
+    services.xserver.updateDbusEnvironment = true;
+    services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
+
+    # Enable helpful DBus services.
+    services.udisks2.enable = true;
+    security.polkit.enable = true;
+    services.accounts-daemon.enable = true;
+    services.upower.enable = config.powerManagement.enable;
+    services.gnome3.glib-networking.enable = true;
+    services.gvfs.enable = true;
+    services.gvfs.package = pkgs.xfce.gvfs;
+    services.tumbler.enable = true;
+    services.system-config-printer.enable = (mkIf config.services.printing.enable (mkDefault true));
+    services.xserver.libinput.enable = mkDefault true; # used in xfce4-settings-manager
+
+    # Enable default programs
+    programs.dconf.enable = true;
+
+    # Shell integration for VTE terminals
+    programs.bash.vteIntegration = mkDefault true;
+    programs.zsh.vteIntegration = mkDefault true;
+
+    # Systemd services
+    systemd.packages = with pkgs.xfce; [
+      (thunar.override { thunarPlugins = cfg.thunarPlugins; })
+    ] ++ optional (!cfg.noDesktop) xfce4-notifyd;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/desktop-managers/xterm.nix b/nixpkgs/nixos/modules/services/x11/desktop-managers/xterm.nix
new file mode 100644
index 000000000000..f76db278a927
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/desktop-managers/xterm.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.desktopManager.xterm;
+  xSessionEnabled = config.services.xserver.enable;
+
+in
+
+{
+  options = {
+
+    services.xserver.desktopManager.xterm.enable = mkOption {
+      type = types.bool;
+      default = (versionOlder config.system.stateVersion "19.09") && xSessionEnabled;
+      defaultText = if versionOlder config.system.stateVersion "19.09" then "config.services.xserver.enable" else "false";
+      description = "Enable a xterm terminal as a desktop manager.";
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    services.xserver.desktopManager.session = singleton
+      { name = "xterm";
+        start = ''
+          ${pkgs.xterm}/bin/xterm -ls &
+          waitPID=$!
+        '';
+      };
+
+    environment.systemPackages = [ pkgs.xterm ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/account-service-util.nix b/nixpkgs/nixos/modules/services/x11/display-managers/account-service-util.nix
new file mode 100644
index 000000000000..2b08c62d0ad1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/account-service-util.nix
@@ -0,0 +1,44 @@
+{ accountsservice
+, glib
+, gobject-introspection
+, python3
+, wrapGAppsHook
+, lib
+}:
+
+python3.pkgs.buildPythonApplication {
+  name = "set-session";
+
+  format = "other";
+
+  src = ./set-session.py;
+
+  dontUnpack = true;
+
+  strictDeps = false;
+
+  nativeBuildInputs = [
+    wrapGAppsHook
+    gobject-introspection
+  ];
+
+  buildInputs = [
+    accountsservice
+    glib
+  ];
+
+  propagatedBuildInputs = with python3.pkgs; [
+    pygobject3
+    ordered-set
+  ];
+
+  installPhase = ''
+    mkdir -p $out/bin
+    cp $src $out/bin/set-session
+    chmod +x $out/bin/set-session
+  '';
+
+  meta = with lib; {
+    maintainers = with maintainers; [ worldofpeace ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/default.nix b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
new file mode 100644
index 000000000000..aa6a5ec42be8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/default.nix
@@ -0,0 +1,444 @@
+# This module declares the options to define a *display manager*, the
+# program responsible for handling X logins (such as LightDM, GDM, or SDDM).
+# The display manager allows the user to select a *session
+# type*. When the user logs in, the display manager starts the
+# *session script* ("xsession" below) to launch the selected session
+# type. The session type defines two things: the *desktop manager*
+# (e.g., KDE, Gnome or a plain xterm), and optionally the *window
+# manager* (e.g. kwin or twm).
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver;
+  xorg = pkgs.xorg;
+
+  fontconfig = config.fonts.fontconfig;
+  xresourcesXft = pkgs.writeText "Xresources-Xft" ''
+    ${optionalString (fontconfig.dpi != 0) ''Xft.dpi: ${toString fontconfig.dpi}''}
+    Xft.antialias: ${if fontconfig.antialias then "1" else "0"}
+    Xft.rgba: ${fontconfig.subpixel.rgba}
+    Xft.lcdfilter: lcd${fontconfig.subpixel.lcdfilter}
+    Xft.hinting: ${if fontconfig.hinting.enable then "1" else "0"}
+    Xft.autohint: ${if fontconfig.hinting.autohint then "1" else "0"}
+    Xft.hintstyle: hintslight
+  '';
+
+  # file provided by services.xserver.displayManager.sessionData.wrapper
+  xsessionWrapper = pkgs.writeScript "xsession-wrapper"
+    ''
+      #! ${pkgs.bash}/bin/bash
+
+      # Shared environment setup for graphical sessions.
+
+      . /etc/profile
+      cd "$HOME"
+
+      ${optionalString cfg.startDbusSession ''
+        if test -z "$DBUS_SESSION_BUS_ADDRESS"; then
+          /run/current-system/systemd/bin/systemctl --user start dbus.socket
+          export `/run/current-system/systemd/bin/systemctl --user show-environment | grep '^DBUS_SESSION_BUS_ADDRESS'`
+        fi
+      ''}
+
+      ${optionalString cfg.displayManager.job.logToJournal ''
+        if [ -z "$_DID_SYSTEMD_CAT" ]; then
+          export _DID_SYSTEMD_CAT=1
+          exec ${config.systemd.package}/bin/systemd-cat -t xsession "$0" "$@"
+        fi
+      ''}
+
+      ${optionalString cfg.displayManager.job.logToFile ''
+        exec &> >(tee ~/.xsession-errors)
+      ''}
+
+      # Tell systemd about our $DISPLAY and $XAUTHORITY.
+      # This is needed by the ssh-agent unit.
+      #
+      # Also tell systemd about the dbus session bus address.
+      # This is required by user units using the session bus.
+      /run/current-system/systemd/bin/systemctl --user import-environment DISPLAY XAUTHORITY DBUS_SESSION_BUS_ADDRESS
+
+      # Load X defaults. This should probably be safe on wayland too.
+      ${xorg.xrdb}/bin/xrdb -merge ${xresourcesXft}
+      if test -e ~/.Xresources; then
+          ${xorg.xrdb}/bin/xrdb -merge ~/.Xresources
+      elif test -e ~/.Xdefaults; then
+          ${xorg.xrdb}/bin/xrdb -merge ~/.Xdefaults
+      fi
+
+      # Speed up application start by 50-150ms according to
+      # http://kdemonkey.blogspot.nl/2008/04/magic-trick.html
+      rm -rf "$HOME/.compose-cache"
+      mkdir "$HOME/.compose-cache"
+
+      # Work around KDE errors when a user first logs in and
+      # .local/share doesn't exist yet.
+      mkdir -p "$HOME/.local/share"
+
+      unset _DID_SYSTEMD_CAT
+
+      ${cfg.displayManager.sessionCommands}
+
+      # Allow the user to execute commands at the beginning of the X session.
+      if test -f ~/.xprofile; then
+          source ~/.xprofile
+      fi
+
+      # Start systemd user services for graphical sessions
+      /run/current-system/systemd/bin/systemctl --user start graphical-session.target
+
+      # Allow the user to setup a custom session type.
+      if test -x ~/.xsession; then
+          eval exec ~/.xsession "$@"
+      fi
+
+      if test "$1"; then
+          # Run the supplied session command. Remove any double quotes with eval.
+          eval exec "$@"
+      else
+          # TODO: Do we need this? Should not the session always exist?
+          echo "error: unknown session $1" 1>&2
+          exit 1
+      fi
+    '';
+
+  installedSessions = pkgs.runCommand "desktops"
+    { # trivial derivation
+      preferLocalBuild = true;
+      allowSubstitutes = false;
+    }
+    ''
+      mkdir -p "$out/share/"{xsessions,wayland-sessions}
+
+      ${concatMapStrings (pkg: ''
+        for n in ${concatStringsSep " " pkg.providedSessions}; do
+          if ! test -f ${pkg}/share/wayland-sessions/$n.desktop -o \
+                    -f ${pkg}/share/xsessions/$n.desktop; then
+            echo "Couldn't find provided session name, $n.desktop, in session package ${pkg.name}:"
+            echo "  ${pkg}"
+            return 1
+          fi
+        done
+
+        if test -d ${pkg}/share/xsessions; then
+          ${xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions
+        fi
+        if test -d ${pkg}/share/wayland-sessions; then
+          ${xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions
+        fi
+      '') cfg.displayManager.sessionPackages}
+    '';
+
+  dmDefault = cfg.desktopManager.default;
+  # fallback default for cases when only default wm is set
+  dmFallbackDefault = if dmDefault != null then dmDefault else "none";
+  wmDefault = cfg.windowManager.default;
+
+  defaultSessionFromLegacyOptions = dmFallbackDefault + optionalString (wmDefault != null && wmDefault != "none") "+${wmDefault}";
+
+in
+
+{
+  options = {
+
+    services.xserver.displayManager = {
+
+      xauthBin = mkOption {
+        internal = true;
+        default = "${xorg.xauth}/bin/xauth";
+        description = "Path to the <command>xauth</command> program used by display managers.";
+      };
+
+      xserverBin = mkOption {
+        type = types.path;
+        description = "Path to the X server used by display managers.";
+      };
+
+      xserverArgs = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "-ac" "-logverbose" "-verbose" "-nolisten tcp" ];
+        description = "List of arguments for the X server.";
+      };
+
+      setupCommands = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Shell commands executed just after the X server has started.
+
+          This option is only effective for display managers for which this feature
+          is supported; currently these are LightDM, GDM and SDDM.
+        '';
+      };
+
+      sessionCommands = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            xmessage "Hello World!" &
+          '';
+        description = ''
+          Shell commands executed just before the window or desktop manager is
+          started. These commands are not currently sourced for Wayland sessions.
+        '';
+      };
+
+      hiddenUsers = mkOption {
+        type = types.listOf types.str;
+        default = [ "nobody" ];
+        description = ''
+          A list of users which will not be shown in the display manager.
+        '';
+      };
+
+      sessionPackages = mkOption {
+        type = with types; listOf (package // {
+          description = "package with provided sessions";
+          check = p: assertMsg
+            (package.check p && p ? providedSessions
+            && p.providedSessions != [] && all isString p.providedSessions)
+            ''
+              Package, '${p.name}', did not specify any session names, as strings, in
+              'passthru.providedSessions'. This is required when used as a session package.
+
+              The session names can be looked up in:
+                ${p}/share/xsessions
+                ${p}/share/wayland-sessions
+           '';
+        });
+        default = [];
+        description = ''
+          A list of packages containing x11 or wayland session files to be passed to the display manager.
+        '';
+      };
+
+      session = mkOption {
+        default = [];
+        example = literalExample
+          ''
+            [ { manage = "desktop";
+                name = "xterm";
+                start = '''
+                  ''${pkgs.xterm}/bin/xterm -ls &
+                  waitPID=$!
+                ''';
+              }
+            ]
+          '';
+        description = ''
+          List of sessions supported with the command used to start each
+          session.  Each session script can set the
+          <varname>waitPID</varname> shell variable to make this script
+          wait until the end of the user session.  Each script is used
+          to define either a window manager or a desktop manager.  These
+          can be differentiated by setting the attribute
+          <varname>manage</varname> either to <literal>"window"</literal>
+          or <literal>"desktop"</literal>.
+
+          The list of desktop manager and window manager should appear
+          inside the display manager with the desktop manager name
+          followed by the window manager name.
+        '';
+      };
+
+      sessionData = mkOption {
+        description = "Data exported for display managers’ convenience";
+        internal = true;
+        default = {};
+        apply = val: {
+          wrapper = xsessionWrapper;
+          desktops = installedSessions;
+          sessionNames = concatMap (p: p.providedSessions) cfg.displayManager.sessionPackages;
+          # We do not want to force users to set defaultSession when they have only single DE.
+          autologinSession =
+            if cfg.displayManager.defaultSession != null then
+              cfg.displayManager.defaultSession
+            else if cfg.displayManager.sessionData.sessionNames != [] then
+              head cfg.displayManager.sessionData.sessionNames
+            else
+              null;
+        };
+      };
+
+      defaultSession = mkOption {
+        type = with types; nullOr str // {
+          description = "session name";
+          check = d:
+            assertMsg (d != null -> (str.check d && elem d cfg.displayManager.sessionData.sessionNames)) ''
+                Default graphical session, '${d}', not found.
+                Valid names for 'services.xserver.displayManager.defaultSession' are:
+                  ${concatStringsSep "\n  " cfg.displayManager.sessionData.sessionNames}
+              '';
+        };
+        default =
+          if dmDefault != null || wmDefault != null then
+            defaultSessionFromLegacyOptions
+          else
+            null;
+        example = "gnome";
+        description = ''
+          Graphical session to pre-select in the session chooser (only effective for GDM and LightDM).
+
+          On GDM, LightDM and SDDM, it will also be used as a session for auto-login.
+        '';
+      };
+
+      job = {
+
+        preStart = mkOption {
+          type = types.lines;
+          default = "";
+          example = "rm -f /var/log/my-display-manager.log";
+          description = "Script executed before the display manager is started.";
+        };
+
+        execCmd = mkOption {
+          type = types.str;
+          example = literalExample ''
+            "''${pkgs.lightdm}/bin/lightdm"
+          '';
+          description = "Command to start the display manager.";
+        };
+
+        environment = mkOption {
+          type = types.attrsOf types.unspecified;
+          default = {};
+          description = "Additional environment variables needed by the display manager.";
+        };
+
+        logToFile = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Whether the display manager redirects the output of the
+            session script to <filename>~/.xsession-errors</filename>.
+          '';
+        };
+
+        logToJournal = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether the display manager redirects the output of the
+            session script to the systemd journal.
+          '';
+        };
+
+      };
+
+    };
+
+  };
+
+  config = {
+    assertions = [
+      {
+        assertion = cfg.desktopManager.default != null || cfg.windowManager.default != null -> cfg.displayManager.defaultSession == defaultSessionFromLegacyOptions;
+        message = "You cannot use both services.xserver.displayManager.defaultSession option and legacy options (services.xserver.desktopManager.default and services.xserver.windowManager.default).";
+      }
+    ];
+
+    warnings =
+      mkIf (dmDefault != null || wmDefault != null) [
+        ''
+          The following options are deprecated:
+            ${concatStringsSep "\n  " (map ({c, t}: t) (filter ({c, t}: c != null) [
+            { c = dmDefault; t = "- services.xserver.desktopManager.default"; }
+            { c = wmDefault; t = "- services.xserver.windowManager.default"; }
+            ]))}
+          Please use
+            services.xserver.displayManager.defaultSession = "${defaultSessionFromLegacyOptions}";
+          instead.
+        ''
+      ];
+
+    services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X";
+
+    systemd.user.targets.graphical-session = {
+      unitConfig = {
+        RefuseManualStart = false;
+        StopWhenUnneeded = false;
+      };
+    };
+
+    # Create desktop files and scripts for starting sessions for WMs/DMs
+    # that do not have upstream session files (those defined using services.{display,desktop,window}Manager.session options).
+    services.xserver.displayManager.sessionPackages =
+      let
+        dms = filter (s: s.manage == "desktop") cfg.displayManager.session;
+        wms = filter (s: s.manage == "window") cfg.displayManager.session;
+
+        # Script responsible for starting the window manager and the desktop manager.
+        xsession = dm: wm: pkgs.writeScript "xsession" ''
+          #! ${pkgs.bash}/bin/bash
+
+          # Legacy session script used to construct .desktop files from
+          # `services.xserver.displayManager.session` entries. Called from
+          # `sessionWrapper`.
+
+          # Start the window manager.
+          ${wm.start}
+
+          # Start the desktop manager.
+          ${dm.start}
+
+          ${optionalString cfg.updateDbusEnvironment ''
+            ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all
+          ''}
+
+          test -n "$waitPID" && wait "$waitPID"
+
+          /run/current-system/systemd/bin/systemctl --user stop graphical-session.target
+
+          exit 0
+        '';
+      in
+        # We will generate every possible pair of WM and DM.
+        concatLists (
+          crossLists
+            (dm: wm: let
+              sessionName = "${dm.name}${optionalString (wm.name != "none") ("+" + wm.name)}";
+              script = xsession dm wm;
+              desktopNames = if dm ? desktopNames
+                             then concatStringsSep ";" dm.desktopNames
+                             else sessionName;
+            in
+              optional (dm.name != "none" || wm.name != "none")
+                (pkgs.writeTextFile {
+                  name = "${sessionName}-xsession";
+                  destination = "/share/xsessions/${sessionName}.desktop";
+                  # Desktop Entry Specification:
+                  # - https://standards.freedesktop.org/desktop-entry-spec/latest/
+                  # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
+                  text = ''
+                    [Desktop Entry]
+                    Version=1.0
+                    Type=XSession
+                    TryExec=${script}
+                    Exec=${script}
+                    Name=${sessionName}
+                    DesktopNames=${desktopNames}
+                  '';
+                } // {
+                  providedSessions = [ sessionName ];
+                })
+            )
+            [dms wms]
+          );
+  };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ]
+     "The option is no longer necessary because all display managers have already delegated lid management to systemd.")
+    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "xserver" "displayManager" "job" "logToFile" ])
+    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "logToJournal" ] [ "services" "xserver" "displayManager" "job" "logToJournal" ])
+    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "extraSessionFilesPackages" ] [ "services" "xserver" "displayManager" "sessionPackages" ])
+  ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
new file mode 100644
index 000000000000..622ea62f3a91
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/gdm.nix
@@ -0,0 +1,358 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.displayManager;
+  gdm = pkgs.gnome3.gdm;
+
+  xSessionWrapper = if (cfg.setupCommands == "") then null else
+    pkgs.writeScript "gdm-x-session-wrapper" ''
+      #!${pkgs.bash}/bin/bash
+      ${cfg.setupCommands}
+      exec "$@"
+    '';
+
+  # Solves problems like:
+  # https://wiki.archlinux.org/index.php/Talk:Bluetooth_headset#GDMs_pulseaudio_instance_captures_bluetooth_headset
+  # Instead of blacklisting plugins, we use Fedora's PulseAudio configuration for GDM:
+  # https://src.fedoraproject.org/rpms/gdm/blob/master/f/default.pa-for-gdm
+  pulseConfig = pkgs.writeText "default.pa" ''
+    load-module module-device-restore
+    load-module module-card-restore
+    load-module module-udev-detect
+    load-module module-native-protocol-unix
+    load-module module-default-device-restore
+    load-module module-rescue-streams
+    load-module module-always-sink
+    load-module module-intended-roles
+    load-module module-suspend-on-idle
+    load-module module-position-event-sounds
+  '';
+
+  defaultSessionName = config.services.xserver.displayManager.defaultSession;
+
+  setSessionScript = pkgs.callPackage ./account-service-util.nix { };
+in
+
+{
+
+  meta = {
+    maintainers = teams.gnome.members;
+  };
+
+  ###### interface
+
+  options = {
+
+    services.xserver.displayManager.gdm = {
+
+      enable = mkEnableOption ''
+        GDM, the GNOME Display Manager
+      '';
+
+      debug = mkEnableOption ''
+        debugging messages in GDM
+      '';
+
+      autoLogin = mkOption {
+        default = {};
+        description = ''
+          Auto login configuration attrset.
+        '';
+
+        type = types.submodule {
+          options = {
+            enable = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Automatically log in as the sepecified <option>autoLogin.user</option>.
+              '';
+            };
+
+            user = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                User to be used for the autologin.
+              '';
+            };
+
+            delay = mkOption {
+              type = types.int;
+              default = 0;
+              description = ''
+                Seconds of inactivity after which the autologin will be performed.
+              '';
+            };
+
+          };
+        };
+      };
+
+      wayland = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Allow GDM to run on Wayland instead of Xserver.
+          Note to enable Wayland with Nvidia you need to
+          enable the <option>nvidiaWayland</option>.
+        '';
+      };
+
+      nvidiaWayland = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow wayland to be used with the proprietary
+          NVidia graphics driver.
+        '';
+      };
+
+      autoSuspend = mkOption {
+        default = true;
+        description = ''
+          Suspend the machine after inactivity.
+        '';
+        type = types.bool;
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.gdm.enable {
+
+    assertions = [
+      { assertion = cfg.gdm.autoLogin.enable -> cfg.gdm.autoLogin.user != null;
+        message = "GDM auto-login requires services.xserver.displayManager.gdm.autoLogin.user to be set";
+      }
+    ];
+
+    services.xserver.displayManager.lightdm.enable = false;
+
+    users.users.gdm =
+      { name = "gdm";
+        uid = config.ids.uids.gdm;
+        group = "gdm";
+        home = "/run/gdm";
+        description = "GDM user";
+      };
+
+    users.groups.gdm.gid = config.ids.gids.gdm;
+
+    # GDM needs different xserverArgs, presumable because using wayland by default.
+    services.xserver.tty = null;
+    services.xserver.display = null;
+    services.xserver.verbose = null;
+
+    services.xserver.displayManager.job =
+      {
+        environment = {
+          GDM_X_SERVER_EXTRA_ARGS = toString
+            (filter (arg: arg != "-terminate") cfg.xserverArgs);
+          XDG_DATA_DIRS = "${cfg.sessionData.desktops}/share/";
+        } // optionalAttrs (xSessionWrapper != null) {
+          # Make GDM use this wrapper before running the session, which runs the
+          # configured setupCommands. This relies on a patched GDM which supports
+          # this environment variable.
+          GDM_X_SESSION_WRAPPER = "${xSessionWrapper}";
+        };
+        execCmd = "exec ${gdm}/bin/gdm";
+        preStart = optionalString (defaultSessionName != null) ''
+          # Set default session in session chooser to a specified values – basically ignore session history.
+          ${setSessionScript}/bin/set-session ${cfg.sessionData.autologinSession}
+        '';
+      };
+
+    systemd.tmpfiles.rules = [
+      "d /run/gdm/.config 0711 gdm gdm"
+    ] ++ optionals config.hardware.pulseaudio.enable [
+      "d /run/gdm/.config/pulse 0711 gdm gdm"
+      "L+ /run/gdm/.config/pulse/${pulseConfig.name} - - - - ${pulseConfig}"
+    ] ++ optionals config.services.gnome3.gnome-initial-setup.enable [
+      # Create stamp file for gnome-initial-setup to prevent it starting in GDM.
+      "f /run/gdm/.config/gnome-initial-setup-done 0711 gdm gdm - yes"
+    ];
+
+    # Otherwise GDM will not be able to start correctly and display Wayland sessions
+    systemd.packages = with pkgs.gnome3; [ gnome-session gnome-shell ];
+    environment.systemPackages = [ pkgs.gnome3.adwaita-icon-theme ];
+
+    systemd.services.display-manager.wants = [
+      # Because sd_login_monitor_new requires /run/systemd/machines
+      "systemd-machined.service"
+      # setSessionScript wants AccountsService
+      "accounts-daemon.service"
+      # Failed to open gpu '/dev/dri/card0': GDBus.Error:org.freedesktop.DBus.Error.AccessDenied: Operation not permitted
+      # https://github.com/NixOS/nixpkgs/pull/25311#issuecomment-609417621
+      "systemd-udev-settle.service"
+    ];
+
+    systemd.services.display-manager.after = [
+      "rc-local.service"
+      "systemd-machined.service"
+      "systemd-user-sessions.service"
+      "getty@tty${gdm.initialVT}.service"
+      "plymouth-quit.service"
+      "plymouth-start.service"
+      "systemd-udev-settle.service"
+    ];
+    systemd.services.display-manager.conflicts = [
+       "getty@tty${gdm.initialVT}.service"
+       # TODO: Add "plymouth-quit.service" so GDM can control when plymouth quits.
+       # Currently this breaks switching configurations while using plymouth.
+    ];
+    systemd.services.display-manager.onFailure = [
+      "plymouth-quit.service"
+    ];
+
+    systemd.services.display-manager.serviceConfig = {
+      # Restart = "always"; - already defined in xserver.nix
+      KillMode = "mixed";
+      IgnoreSIGPIPE = "no";
+      BusName = "org.gnome.DisplayManager";
+      StandardOutput = "syslog";
+      StandardError = "inherit";
+      ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
+      KeyringMode = "shared";
+      EnvironmentFile = "-/etc/locale.conf";
+    };
+
+    systemd.services.display-manager.path = [ pkgs.gnome3.gnome-session ];
+
+    # Allow choosing an user account
+    services.accounts-daemon.enable = true;
+
+    services.dbus.packages = [ gdm ];
+
+    # We duplicate upstream's udev rules manually to make wayland with nvidia configurable
+    services.udev.extraRules = ''
+      # disable Wayland on Cirrus chipsets
+      ATTR{vendor}=="0x1013", ATTR{device}=="0x00b8", ATTR{subsystem_vendor}=="0x1af4", ATTR{subsystem_device}=="0x1100", RUN+="${gdm}/libexec/gdm-disable-wayland"
+      # disable Wayland on Hi1710 chipsets
+      ATTR{vendor}=="0x19e5", ATTR{device}=="0x1711", RUN+="${gdm}/libexec/gdm-disable-wayland"
+      ${optionalString (!cfg.gdm.nvidiaWayland) ''
+        DRIVER=="nvidia", RUN+="${gdm}/libexec/gdm-disable-wayland"
+      ''}
+      # disable Wayland when modesetting is disabled
+      IMPORT{cmdline}="nomodeset", RUN+="${gdm}/libexec/gdm-disable-wayland"
+    '';
+
+    systemd.user.services.dbus.wantedBy = [ "default.target" ];
+
+    programs.dconf.profiles.gdm =
+    let
+      customDconf = pkgs.writeTextFile {
+        name = "gdm-dconf";
+        destination = "/dconf/gdm-custom";
+        text = ''
+          ${optionalString (!cfg.gdm.autoSuspend) ''
+            [org/gnome/settings-daemon/plugins/power]
+            sleep-inactive-ac-type='nothing'
+            sleep-inactive-battery-type='nothing'
+            sleep-inactive-ac-timeout=0
+            sleep-inactive-battery-timeout=0
+          ''}
+        '';
+      };
+
+      customDconfDb = pkgs.stdenv.mkDerivation {
+        name = "gdm-dconf-db";
+        buildCommand = ''
+          ${pkgs.dconf}/bin/dconf compile $out ${customDconf}/dconf
+        '';
+      };
+    in pkgs.stdenv.mkDerivation {
+      name = "dconf-gdm-profile";
+      buildCommand = ''
+        # Check that the GDM profile starts with what we expect.
+        if [ $(head -n 1 ${gdm}/share/dconf/profile/gdm) != "user-db:user" ]; then
+          echo "GDM dconf profile changed, please update gdm.nix"
+          exit 1
+        fi
+        # Insert our custom DB behind it.
+        sed '2ifile-db:${customDconfDb}' ${gdm}/share/dconf/profile/gdm > $out
+      '';
+    };
+
+    # Use AutomaticLogin if delay is zero, because it's immediate.
+    # Otherwise with TimedLogin with zero seconds the prompt is still
+    # presented and there's a little delay.
+    environment.etc."gdm/custom.conf".text = ''
+      [daemon]
+      WaylandEnable=${if cfg.gdm.wayland then "true" else "false"}
+      ${optionalString cfg.gdm.autoLogin.enable (
+        if cfg.gdm.autoLogin.delay > 0 then ''
+          TimedLoginEnable=true
+          TimedLogin=${cfg.gdm.autoLogin.user}
+          TimedLoginDelay=${toString cfg.gdm.autoLogin.delay}
+        '' else ''
+          AutomaticLoginEnable=true
+          AutomaticLogin=${cfg.gdm.autoLogin.user}
+        '')
+      }
+
+      [security]
+
+      [xdmcp]
+
+      [greeter]
+
+      [chooser]
+
+      [debug]
+      ${optionalString cfg.gdm.debug "Enable=true"}
+    '';
+
+    environment.etc."gdm/Xsession".source = config.services.xserver.displayManager.sessionData.wrapper;
+
+    # GDM LFS PAM modules, adapted somehow to NixOS
+    security.pam.services = {
+      gdm-launch-environment.text = ''
+        auth     required       pam_succeed_if.so audit quiet_success user = gdm
+        auth     optional       pam_permit.so
+
+        account  required       pam_succeed_if.so audit quiet_success user = gdm
+        account  sufficient     pam_unix.so
+
+        password required       pam_deny.so
+
+        session  required       pam_succeed_if.so audit quiet_success user = gdm
+        session  required       pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0
+        session  optional       ${pkgs.systemd}/lib/security/pam_systemd.so
+        session  optional       pam_keyinit.so force revoke
+        session  optional       pam_permit.so
+      '';
+
+      gdm-password.text = ''
+        auth      substack      login
+        account   include       login
+        password  substack      login
+        session   include       login
+      '';
+
+      gdm-autologin.text = ''
+        auth      requisite     pam_nologin.so
+
+        auth      required      pam_succeed_if.so uid >= 1000 quiet
+        auth      required      pam_permit.so
+
+        account   sufficient    pam_unix.so
+
+        password  requisite     pam_unix.so nullok sha512
+
+        session   optional      pam_keyinit.so revoke
+        session   include       login
+      '';
+
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
new file mode 100644
index 000000000000..129df139c61a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
@@ -0,0 +1,140 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  dmcfg = config.services.xserver.displayManager;
+  ldmcfg = dmcfg.lightdm;
+  cfg = ldmcfg.greeters.enso;
+
+  theme = cfg.theme.package;
+  icons = cfg.iconTheme.package;
+  cursors = cfg.cursorTheme.package;
+
+  ensoGreeterConf = pkgs.writeText "lightdm-enso-os-greeter.conf" ''
+    [greeter]
+    default-wallpaper=${ldmcfg.background}
+    gtk-theme=${cfg.theme.name}
+    icon-theme=${cfg.iconTheme.name}
+    cursor-theme=${cfg.cursorTheme.name}
+    blur=${toString cfg.blur}
+    brightness=${toString cfg.brightness}
+    ${cfg.extraConfig}
+  '';
+in {
+  options = {
+    services.xserver.displayManager.lightdm.greeters.enso = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable enso-os-greeter as the lightdm greeter
+        '';
+      };
+
+      theme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.gnome3.gnome-themes-extra;
+          defaultText = "pkgs.gnome3.gnome-themes-extra";
+          description = ''
+            The package path that contains the theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = ''
+            Name of the theme to use for the lightdm-enso-os-greeter
+          '';
+        };
+      };
+
+      iconTheme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.papirus-icon-theme;
+          defaultText = "pkgs.papirus-icon-theme";
+          description = ''
+            The package path that contains the icon theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "ePapirus";
+          description = ''
+            Name of the icon theme to use for the lightdm-enso-os-greeter
+          '';
+        };
+      };
+
+      cursorTheme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.capitaine-cursors;
+          defaultText = "pkgs.capitaine-cursors";
+          description = ''
+            The package path that contains the cursor theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "capitane-cursors";
+          description = ''
+            Name of the cursor theme to use for the lightdm-enso-os-greeter
+          '';
+        };
+      };
+
+      blur = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not to enable blur
+        '';
+      };
+
+      brightness = mkOption {
+        type = types.int;
+        default = 7;
+        description = ''
+          Brightness
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration that should be put in the greeter.conf
+          configuration file
+        '';
+      };
+    };
+  };
+
+  config = mkIf (ldmcfg.enable && cfg.enable) {
+    environment.etc."lightdm/greeter.conf".source = ensoGreeterConf;
+
+    environment.systemPackages = [
+      cursors
+      icons
+      theme
+    ];
+
+    services.xserver.displayManager.lightdm = {
+      greeter = mkDefault {
+        package = pkgs.lightdm-enso-os-greeter.xgreeters;
+        name = "pantheon-greeter";
+      };
+
+      greeters = {
+        gtk = {
+          enable = mkDefault false;
+        };
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
new file mode 100644
index 000000000000..de932e6e840a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
@@ -0,0 +1,173 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  dmcfg = config.services.xserver.displayManager;
+  ldmcfg = dmcfg.lightdm;
+  xcfg = config.services.xserver;
+  cfg = ldmcfg.greeters.gtk;
+
+  inherit (pkgs) writeText;
+
+  theme = cfg.theme.package;
+  icons = cfg.iconTheme.package;
+  cursors = cfg.cursorTheme.package;
+
+  gtkGreeterConf = writeText "lightdm-gtk-greeter.conf"
+    ''
+    [greeter]
+    theme-name = ${cfg.theme.name}
+    icon-theme-name = ${cfg.iconTheme.name}
+    cursor-theme-name = ${cfg.cursorTheme.name}
+    cursor-theme-size = ${toString cfg.cursorTheme.size}
+    background = ${ldmcfg.background}
+    ${optionalString (cfg.clock-format != null) "clock-format = ${cfg.clock-format}"}
+    ${optionalString (cfg.indicators != null) "indicators = ${concatStringsSep ";" cfg.indicators}"}
+    ${optionalString (xcfg.dpi != null) "xft-dpi=${toString xcfg.dpi}"}
+    ${cfg.extraConfig}
+    '';
+
+in
+{
+  options = {
+
+    services.xserver.displayManager.lightdm.greeters.gtk = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable lightdm-gtk-greeter as the lightdm greeter.
+        '';
+      };
+
+      theme = {
+
+        package = mkOption {
+          type = types.package;
+          default = pkgs.gnome3.gnome-themes-extra;
+          defaultText = "pkgs.gnome3.gnome-themes-extra";
+          description = ''
+            The package path that contains the theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = ''
+            Name of the theme to use for the lightdm-gtk-greeter.
+          '';
+        };
+
+      };
+
+      iconTheme = {
+
+        package = mkOption {
+          type = types.package;
+          default = pkgs.gnome3.adwaita-icon-theme;
+          defaultText = "pkgs.gnome3.adwaita-icon-theme";
+          description = ''
+            The package path that contains the icon theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = ''
+            Name of the icon theme to use for the lightdm-gtk-greeter.
+          '';
+        };
+
+      };
+
+      cursorTheme = {
+
+        package = mkOption {
+          default = pkgs.gnome3.adwaita-icon-theme;
+          defaultText = "pkgs.gnome3.adwaita-icon-theme";
+          description = ''
+            The package path that contains the cursor theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = ''
+            Name of the cursor theme to use for the lightdm-gtk-greeter.
+          '';
+        };
+
+        size = mkOption {
+          type = types.int;
+          default = 16;
+          description = ''
+            Size of the cursor theme to use for the lightdm-gtk-greeter.
+          '';
+        };
+      };
+
+      clock-format = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "%F";
+        description = ''
+          Clock format string (as expected by strftime, e.g. "%H:%M")
+          to use with the lightdm gtk greeter panel.
+
+          If set to null the default clock format is used.
+        '';
+      };
+
+      indicators = mkOption {
+        type = types.nullOr (types.listOf types.str);
+        default = null;
+        example = [ "~host" "~spacer" "~clock" "~spacer" "~session" "~language" "~a11y" "~power" ];
+        description = ''
+          List of allowed indicator modules to use for the lightdm gtk
+          greeter panel.
+
+          Built-in indicators include "~a11y", "~language", "~session",
+          "~power", "~clock", "~host", "~spacer". Unity indicators can be
+          represented by short name (e.g. "sound", "power"), service file name,
+          or absolute path.
+
+          If set to null the default indicators are used.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration that should be put in the lightdm-gtk-greeter.conf
+          configuration file.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf (ldmcfg.enable && cfg.enable) {
+
+    services.xserver.displayManager.lightdm.greeter = mkDefault {
+      package = pkgs.lightdm_gtk_greeter.xgreeters;
+      name = "lightdm-gtk-greeter";
+    };
+
+    environment.systemPackages = [
+      cursors
+      icons
+      theme
+    ];
+
+    environment.etc."lightdm/lightdm-gtk-greeter.conf".source = gtkGreeterConf;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
new file mode 100644
index 000000000000..16d7fdf15cf6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  dmcfg = config.services.xserver.displayManager;
+  ldmcfg = dmcfg.lightdm;
+  cfg = ldmcfg.greeters.mini;
+
+  miniGreeterConf = pkgs.writeText "lightdm-mini-greeter.conf"
+    ''
+    [greeter]
+    user = ${cfg.user}
+    show-password-label = true
+    password-label-text = Password:
+    invalid-password-text = Invalid Password
+    show-input-cursor = true
+    password-alignment = right
+
+    [greeter-hotkeys]
+    mod-key = meta
+    shutdown-key = s
+    restart-key = r
+    hibernate-key = h
+    suspend-key = u
+
+    [greeter-theme]
+    font = Sans
+    font-size = 1em
+    font-weight = bold
+    font-style = normal
+    text-color = "#080800"
+    error-color = "#F8F8F0"
+    background-image = "${ldmcfg.background}"
+    background-color = "#1B1D1E"
+    window-color = "#F92672"
+    border-color = "#080800"
+    border-width = 2px
+    layout-space = 15
+    password-color = "#F8F8F0"
+    password-background-color = "#1B1D1E"
+    password-border-color = "#080800"
+    password-border-width = 2px
+
+    ${cfg.extraConfig}
+    '';
+
+in
+{
+  options = {
+
+    services.xserver.displayManager.lightdm.greeters.mini = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable lightdm-mini-greeter as the lightdm greeter.
+
+          Note that this greeter starts only the default X session.
+          You can configure the default X session using
+          <xref linkend="opt-services.xserver.displayManager.defaultSession"/>.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "root";
+        description = ''
+          The user to login as.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration that should be put in the lightdm-mini-greeter.conf
+          configuration file.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf (ldmcfg.enable && cfg.enable) {
+
+    services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
+
+    services.xserver.displayManager.lightdm.greeter = mkDefault {
+      package = pkgs.lightdm-mini-greeter.xgreeters;
+      name = "lightdm-mini-greeter";
+    };
+
+    environment.etc."lightdm/lightdm-mini-greeter.conf".source = miniGreeterConf;
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
new file mode 100644
index 000000000000..087c6b9c38ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  dmcfg = config.services.xserver.displayManager;
+  ldmcfg = dmcfg.lightdm;
+  cfg = ldmcfg.greeters.pantheon;
+
+in
+{
+  meta = {
+    maintainers = with maintainers; [ worldofpeace ];
+  };
+
+  options = {
+
+    services.xserver.displayManager.lightdm.greeters.pantheon = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable elementary-greeter as the lightdm greeter.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf (ldmcfg.enable && cfg.enable) {
+
+    services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
+
+    services.xserver.displayManager.lightdm.greeter = mkDefault {
+      package = pkgs.pantheon.elementary-greeter.xgreeters;
+      name = "io.elementary.greeter";
+    };
+
+    # Show manual login card.
+    services.xserver.displayManager.lightdm.extraSeatDefaults = "greeter-show-manual-login=true";
+
+    environment.etc."lightdm/io.elementary.greeter.conf".source = "${pkgs.pantheon.elementary-greeter}/etc/lightdm/io.elementary.greeter.conf";
+    environment.etc."wingpanel.d/io.elementary.greeter.whitelist".source = "${pkgs.pantheon.elementary-default-settings}/etc/wingpanel.d/io.elementary.greeter.whitelist";
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
new file mode 100644
index 000000000000..a9ba8e6280d6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm-greeters/tiny.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  dmcfg = config.services.xserver.displayManager;
+  ldmcfg = dmcfg.lightdm;
+  cfg = ldmcfg.greeters.tiny;
+
+in
+{
+  options = {
+
+    services.xserver.displayManager.lightdm.greeters.tiny = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable lightdm-tiny-greeter as the lightdm greeter.
+
+          Note that this greeter starts only the default X session.
+          You can configure the default X session using
+          <xref linkend="opt-services.xserver.displayManager.defaultSession"/>.
+        '';
+      };
+
+      label = {
+        user = mkOption {
+          type = types.str;
+          default = "Username";
+          description = ''
+            The string to represent the user_text label.
+          '';
+        };
+
+        pass = mkOption {
+          type = types.str;
+          default = "Password";
+          description = ''
+            The string to represent the pass_text label.
+          '';
+        };
+      };
+
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Section to describe style and ui.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkIf (ldmcfg.enable && cfg.enable) {
+
+    services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
+
+    nixpkgs.config.lightdm-tiny-greeter.conf =
+    let
+      configHeader = ''
+        #include <gtk/gtk.h>
+        static const char *user_text = "${cfg.label.user}";
+        static const char *pass_text = "${cfg.label.pass}";
+        static const char *session = "${dmcfg.defaultSession}";
+      '';
+    in
+      optionalString (cfg.extraConfig != "")
+        (configHeader + cfg.extraConfig);
+
+    services.xserver.displayManager.lightdm.greeter =
+      mkDefault {
+        package = pkgs.lightdm-tiny-greeter.xgreeters;
+        name = "lightdm-tiny-greeter";
+      };
+
+    assertions = [
+      {
+        assertion = dmcfg.defaultSession != null;
+        message = ''
+          Please set: services.xserver.displayManager.defaultSession
+        '';
+      }
+    ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
new file mode 100644
index 000000000000..678cade44427
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -0,0 +1,343 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  xcfg = config.services.xserver;
+  dmcfg = xcfg.displayManager;
+  xEnv = config.systemd.services.display-manager.environment;
+  cfg = dmcfg.lightdm;
+  sessionData = dmcfg.sessionData;
+
+  setSessionScript = pkgs.callPackage ./account-service-util.nix { };
+
+  inherit (pkgs) lightdm writeScript writeText;
+
+  # lightdm runs with clearenv(), but we need a few things in the environment for X to startup
+  xserverWrapper = writeScript "xserver-wrapper"
+    ''
+      #! ${pkgs.bash}/bin/bash
+      ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}
+
+      display=$(echo "$@" | xargs -n 1 | grep -P ^:\\d\$ | head -n 1 | sed s/^://)
+      if [ -z "$display" ]
+      then additionalArgs=":0 -logfile /var/log/X.0.log"
+      else additionalArgs="-logfile /var/log/X.$display.log"
+      fi
+
+      exec ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} $additionalArgs "$@"
+    '';
+
+  usersConf = writeText "users.conf"
+    ''
+      [UserList]
+      minimum-uid=500
+      hidden-users=${concatStringsSep " " dmcfg.hiddenUsers}
+      hidden-shells=/run/current-system/sw/bin/nologin
+    '';
+
+  lightdmConf = writeText "lightdm.conf"
+    ''
+      [LightDM]
+      ${optionalString cfg.greeter.enable ''
+        greeter-user = ${config.users.users.lightdm.name}
+        greeters-directory = ${cfg.greeter.package}
+      ''}
+      sessions-directory = ${dmcfg.sessionData.desktops}/share/xsessions:${dmcfg.sessionData.desktops}/share/wayland-sessions
+      ${cfg.extraConfig}
+
+      [Seat:*]
+      xserver-command = ${xserverWrapper}
+      session-wrapper = ${dmcfg.sessionData.wrapper}
+      ${optionalString cfg.greeter.enable ''
+        greeter-session = ${cfg.greeter.name}
+      ''}
+      ${optionalString cfg.autoLogin.enable ''
+        autologin-user = ${cfg.autoLogin.user}
+        autologin-user-timeout = ${toString cfg.autoLogin.timeout}
+        autologin-session = ${sessionData.autologinSession}
+      ''}
+      ${optionalString (dmcfg.setupCommands != "") ''
+        display-setup-script=${pkgs.writeScript "lightdm-display-setup" ''
+          #!${pkgs.bash}/bin/bash
+          ${dmcfg.setupCommands}
+        ''}
+      ''}
+      ${cfg.extraSeatDefaults}
+    '';
+
+in
+{
+  meta = {
+    maintainers = with maintainers; [ worldofpeace ];
+  };
+
+  # Note: the order in which lightdm greeter modules are imported
+  # here determines the default: later modules (if enable) are
+  # preferred.
+  imports = [
+    ./lightdm-greeters/gtk.nix
+    ./lightdm-greeters/mini.nix
+    ./lightdm-greeters/enso-os.nix
+    ./lightdm-greeters/pantheon.nix
+    ./lightdm-greeters/tiny.nix
+  ];
+
+  options = {
+
+    services.xserver.displayManager.lightdm = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable lightdm as the display manager.
+        '';
+      };
+
+      greeter =  {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            If set to false, run lightdm in greeterless mode. This only works if autologin
+            is enabled and autoLogin.timeout is zero.
+          '';
+        };
+        package = mkOption {
+          type = types.package;
+          description = ''
+            The LightDM greeter to login via. The package should be a directory
+            containing a .desktop file matching the name in the 'name' option.
+          '';
+
+        };
+        name = mkOption {
+          type = types.str;
+          description = ''
+            The name of a .desktop file in the directory specified
+            in the 'package' option.
+          '';
+        };
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          user-authority-in-system-dir = true
+        '';
+        description = "Extra lines to append to LightDM section.";
+      };
+
+      background = mkOption {
+        type = types.path;
+        # Manual cannot depend on packages, we are actually setting the default in config below.
+        defaultText = "pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath";
+        description = ''
+          The background image or color to use.
+        '';
+      };
+
+      extraSeatDefaults = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          greeter-show-manual-login=true
+        '';
+        description = "Extra lines to append to SeatDefaults section.";
+      };
+
+      autoLogin = mkOption {
+        default = {};
+        description = ''
+          Configuration for automatic login.
+        '';
+
+        type = types.submodule {
+          options = {
+            enable = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Automatically log in as the specified <option>autoLogin.user</option>.
+              '';
+            };
+
+            user = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                User to be used for the automatic login.
+              '';
+            };
+
+            timeout = mkOption {
+              type = types.int;
+              default = 0;
+              description = ''
+                Show the greeter for this many seconds before automatic login occurs.
+              '';
+            };
+          };
+        };
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = xcfg.enable;
+        message = ''
+          LightDM requires services.xserver.enable to be true
+        '';
+      }
+      { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null;
+        message = ''
+          LightDM auto-login requires services.xserver.displayManager.lightdm.autoLogin.user to be set
+        '';
+      }
+      { assertion = cfg.autoLogin.enable -> sessionData.autologinSession != null;
+        message = ''
+          LightDM auto-login requires that services.xserver.displayManager.defaultSession is set.
+        '';
+      }
+      { assertion = !cfg.greeter.enable -> (cfg.autoLogin.enable && cfg.autoLogin.timeout == 0);
+        message = ''
+          LightDM can only run without greeter if automatic login is enabled and the timeout for it
+          is set to zero.
+        '';
+      }
+    ];
+
+    # Keep in sync with the defaultText value from the option definition.
+    services.xserver.displayManager.lightdm.background = mkDefault pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom.gnomeFilePath;
+
+    # Set default session in session chooser to a specified values – basically ignore session history.
+    # Auto-login is already covered by a config value.
+    services.xserver.displayManager.job.preStart = optionalString (!cfg.autoLogin.enable && dmcfg.defaultSession != null) ''
+      ${setSessionScript}/bin/set-session ${dmcfg.defaultSession}
+    '';
+
+    # setSessionScript needs session-files in XDG_DATA_DIRS
+    services.xserver.displayManager.job.environment.XDG_DATA_DIRS = "${dmcfg.sessionData.desktops}/share/";
+
+    # setSessionScript wants AccountsService
+    systemd.services.display-manager.wants = [
+      "accounts-daemon.service"
+    ];
+
+    # lightdm relaunches itself via just `lightdm`, so needs to be on the PATH
+    services.xserver.displayManager.job.execCmd = ''
+      export PATH=${lightdm}/sbin:$PATH
+      exec ${lightdm}/sbin/lightdm
+    '';
+
+    # Replaces getty
+    systemd.services.display-manager.conflicts = [
+      "getty@tty7.service"
+      # TODO: Add "plymouth-quit.service" so LightDM can control when plymouth
+      # quits. Currently this breaks switching to configurations with plymouth.
+     ];
+
+    # Pull in dependencies of services we replace.
+    systemd.services.display-manager.after = [
+      "rc-local.service"
+      "systemd-machined.service"
+      "systemd-user-sessions.service"
+      "getty@tty7.service"
+      "user.slice"
+    ];
+
+    # user.slice needs to be present
+    systemd.services.display-manager.requires = [
+      "user.slice"
+    ];
+
+    # lightdm stops plymouth so when it fails make sure plymouth stops.
+    systemd.services.display-manager.onFailure = [
+      "plymouth-quit.service"
+    ];
+
+    systemd.services.display-manager.serviceConfig = {
+      BusName = "org.freedesktop.DisplayManager";
+      IgnoreSIGPIPE = "no";
+      # This allows lightdm to pass the LUKS password through to PAM.
+      # login keyring is unlocked automatic when autologin is used.
+      KeyringMode = "shared";
+      KillMode = "mixed";
+      StandardError = "inherit";
+      StandardOutput = "syslog";
+    };
+
+    environment.etc."lightdm/lightdm.conf".source = lightdmConf;
+    environment.etc."lightdm/users.conf".source = usersConf;
+
+    services.dbus.enable = true;
+    services.dbus.packages = [ lightdm ];
+
+    # lightdm uses the accounts daemon to remember language/window-manager per user
+    services.accounts-daemon.enable = true;
+
+    # Enable the accounts daemon to find lightdm's dbus interface
+    environment.systemPackages = [ lightdm ];
+
+    security.pam.services.lightdm.text = ''
+        auth      substack      login
+        account   include       login
+        password  substack      login
+        session   include       login
+    '';
+
+    security.pam.services.lightdm-greeter.text = ''
+        auth     required       pam_succeed_if.so audit quiet_success user = lightdm
+        auth     optional       pam_permit.so
+
+        account  required       pam_succeed_if.so audit quiet_success user = lightdm
+        account  sufficient     pam_unix.so
+
+        password required       pam_deny.so
+
+        session  required       pam_succeed_if.so audit quiet_success user = lightdm
+        session  required       pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0
+        session  optional       ${pkgs.systemd}/lib/security/pam_systemd.so
+        session  optional       pam_keyinit.so force revoke
+        session  optional       pam_permit.so
+    '';
+
+    security.pam.services.lightdm-autologin.text = ''
+        auth      requisite     pam_nologin.so
+
+        auth      required      pam_succeed_if.so uid >= 1000 quiet
+        auth      required      pam_permit.so
+
+        account   sufficient    pam_unix.so
+
+        password  requisite     pam_unix.so nullok sha512
+
+        session   optional      pam_keyinit.so revoke
+        session   include       login
+    '';
+
+    users.users.lightdm = {
+      home = "/var/lib/lightdm";
+      group = "lightdm";
+      uid = config.ids.uids.lightdm;
+    };
+
+    systemd.tmpfiles.rules = [
+      "d /run/lightdm 0711 lightdm lightdm 0"
+      "d /var/cache/lightdm 0711 root lightdm -"
+      "d /var/lib/lightdm 1770 lightdm lightdm -"
+      "d /var/lib/lightdm-data 1775 lightdm lightdm -"
+      "d /var/log/lightdm 0711 root lightdm -"
+    ];
+
+    users.groups.lightdm.gid = config.ids.gids.lightdm;
+    services.xserver.tty     = null; # We might start multiple X servers so let the tty increment themselves..
+    services.xserver.display = null; # We specify our own display (and logfile) in xserver-wrapper up there
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix b/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix
new file mode 100644
index 000000000000..2f42271da872
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/sddm.nix
@@ -0,0 +1,297 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  xcfg = config.services.xserver;
+  dmcfg = xcfg.displayManager;
+  cfg = dmcfg.sddm;
+  xEnv = config.systemd.services.display-manager.environment;
+
+  inherit (pkgs) sddm;
+
+  xserverWrapper = pkgs.writeScript "xserver-wrapper" ''
+    #!/bin/sh
+    ${concatMapStrings (n: "export ${n}=\"${getAttr n xEnv}\"\n") (attrNames xEnv)}
+    exec systemd-cat -t xserver-wrapper ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} "$@"
+  '';
+
+  Xsetup = pkgs.writeScript "Xsetup" ''
+    #!/bin/sh
+    ${cfg.setupScript}
+    ${dmcfg.setupCommands}
+  '';
+
+  Xstop = pkgs.writeScript "Xstop" ''
+    #!/bin/sh
+    ${cfg.stopScript}
+  '';
+
+  cfgFile = pkgs.writeText "sddm.conf" ''
+    [General]
+    HaltCommand=/run/current-system/systemd/bin/systemctl poweroff
+    RebootCommand=/run/current-system/systemd/bin/systemctl reboot
+    ${optionalString cfg.autoNumlock ''
+    Numlock=on
+    ''}
+
+    [Theme]
+    Current=${cfg.theme}
+    ThemeDir=/run/current-system/sw/share/sddm/themes
+    FacesDir=/run/current-system/sw/share/sddm/faces
+
+    [Users]
+    MaximumUid=${toString config.ids.uids.nixbld}
+    HideUsers=${concatStringsSep "," dmcfg.hiddenUsers}
+    HideShells=/run/current-system/sw/bin/nologin
+
+    [X11]
+    MinimumVT=${toString (if xcfg.tty != null then xcfg.tty else 7)}
+    ServerPath=${xserverWrapper}
+    XephyrPath=${pkgs.xorg.xorgserver.out}/bin/Xephyr
+    SessionCommand=${dmcfg.sessionData.wrapper}
+    SessionDir=${dmcfg.sessionData.desktops}/share/xsessions
+    XauthPath=${pkgs.xorg.xauth}/bin/xauth
+    DisplayCommand=${Xsetup}
+    DisplayStopCommand=${Xstop}
+    EnableHidpi=${if cfg.enableHidpi then "true" else "false"}
+
+    [Wayland]
+    EnableHidpi=${if cfg.enableHidpi then "true" else "false"}
+    SessionDir=${dmcfg.sessionData.desktops}/share/wayland-sessions
+
+    ${optionalString cfg.autoLogin.enable ''
+    [Autologin]
+    User=${cfg.autoLogin.user}
+    Session=${autoLoginSessionName}.desktop
+    Relogin=${boolToString cfg.autoLogin.relogin}
+    ''}
+
+    ${cfg.extraConfig}
+  '';
+
+  autoLoginSessionName = dmcfg.sessionData.autologinSession;
+
+in
+{
+  imports = [
+    (mkRemovedOptionModule [ "services" "xserver" "displayManager" "sddm" "themes" ]
+      "Set the option `services.xserver.displayManager.sddm.package' instead.")
+  ];
+
+  options = {
+
+    services.xserver.displayManager.sddm = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable sddm as the display manager.
+        '';
+      };
+
+      enableHidpi = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable automatic HiDPI mode.
+          </para>
+          <para>
+          Versions up to 0.17 are broken so this only works from 0.18 onwards.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          [Autologin]
+          User=john
+          Session=plasma.desktop
+        '';
+        description = ''
+          Extra lines appended to the configuration of SDDM.
+        '';
+      };
+
+      theme = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Greeter theme to use.
+        '';
+      };
+
+      autoNumlock = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable numlock at login.
+        '';
+      };
+
+      setupScript = mkOption {
+        type = types.str;
+        default = "";
+        example = ''
+          # workaround for using NVIDIA Optimus without Bumblebee
+          xrandr --setprovideroutputsource modesetting NVIDIA-0
+          xrandr --auto
+        '';
+        description = ''
+          A script to execute when starting the display server. DEPRECATED, please
+          use <option>services.xserver.displayManager.setupCommands</option>.
+        '';
+      };
+
+      stopScript = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          A script to execute when stopping the display server.
+        '';
+      };
+
+      autoLogin = mkOption {
+        default = {};
+        description = ''
+          Configuration for automatic login.
+        '';
+
+        type = types.submodule {
+          options = {
+            enable = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Automatically log in as <option>autoLogin.user</option>.
+              '';
+            };
+
+            user = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              description = ''
+                User to be used for the automatic login.
+              '';
+            };
+
+            relogin = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                If true automatic login will kick in again on session exit (logout), otherwise it
+                will only log in automatically when the display-manager is started.
+              '';
+            };
+          };
+        };
+      };
+
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = xcfg.enable;
+        message = ''
+          SDDM requires services.xserver.enable to be true
+        '';
+      }
+      { assertion = cfg.autoLogin.enable -> cfg.autoLogin.user != null;
+        message = ''
+          SDDM auto-login requires services.xserver.displayManager.sddm.autoLogin.user to be set
+        '';
+      }
+      { assertion = cfg.autoLogin.enable -> autoLoginSessionName != null;
+        message = ''
+          SDDM auto-login requires that services.xserver.displayManager.defaultSession is set.
+        '';
+      }
+    ];
+
+    services.xserver.displayManager.job = {
+      environment = {
+        # Load themes from system environment
+        QT_PLUGIN_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtPluginPrefix;
+        QML2_IMPORT_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtQmlPrefix;
+      };
+
+      execCmd = "exec /run/current-system/sw/bin/sddm";
+    };
+
+    security.pam.services = {
+      sddm = {
+        allowNullPassword = true;
+        startSession = true;
+      };
+
+      sddm-greeter.text = ''
+        auth     required       pam_succeed_if.so audit quiet_success user = sddm
+        auth     optional       pam_permit.so
+
+        account  required       pam_succeed_if.so audit quiet_success user = sddm
+        account  sufficient     pam_unix.so
+
+        password required       pam_deny.so
+
+        session  required       pam_succeed_if.so audit quiet_success user = sddm
+        session  required       pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0
+        session  optional       ${pkgs.systemd}/lib/security/pam_systemd.so
+        session  optional       pam_keyinit.so force revoke
+        session  optional       pam_permit.so
+      '';
+
+      sddm-autologin.text = ''
+        auth     requisite pam_nologin.so
+        auth     required  pam_succeed_if.so uid >= 1000 quiet
+        auth     required  pam_permit.so
+
+        account  include   sddm
+
+        password include   sddm
+
+        session  include   sddm
+      '';
+    };
+
+    users.users.sddm = {
+      createHome = true;
+      home = "/var/lib/sddm";
+      group = "sddm";
+      uid = config.ids.uids.sddm;
+    };
+
+    environment.etc."sddm.conf".source = cfgFile;
+    environment.pathsToLink = [
+      "/share/sddm"
+    ];
+
+    users.groups.sddm.gid = config.ids.gids.sddm;
+
+    environment.systemPackages = [ sddm ];
+    services.dbus.packages = [ sddm ];
+
+    # To enable user switching, allow sddm to allocate TTYs/displays dynamically.
+    services.xserver.tty = null;
+    services.xserver.display = null;
+
+    systemd.tmpfiles.rules = [
+      # Prior to Qt 5.9.2, there is a QML cache invalidation bug which sometimes
+      # strikes new Plasma 5 releases. If the QML cache is not invalidated, SDDM
+      # will segfault without explanation. We really tore our hair out for awhile
+      # before finding the bug:
+      # https://bugreports.qt.io/browse/QTBUG-62302
+      # We work around the problem by deleting the QML cache before startup.
+      # This was supposedly fixed in Qt 5.9.2 however it has been reported with
+      # 5.10 and 5.11 as well. The initial workaround was to delete the directory
+      # in the Xsetup script but that doesn't do anything.
+      # Instead we use tmpfiles.d to ensure it gets wiped.
+      # This causes a small but perceptible delay when SDDM starts.
+      "e ${config.users.users.sddm.home}/.cache - - - 0"
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/set-session.py b/nixpkgs/nixos/modules/services/x11/display-managers/set-session.py
new file mode 100755
index 000000000000..0cca80af44e8
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/set-session.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+
+import gi, argparse, os, logging, sys
+
+gi.require_version("AccountsService", "1.0")
+from gi.repository import AccountsService, GLib
+from ordered_set import OrderedSet
+
+
+def get_session_file(session):
+    system_data_dirs = GLib.get_system_data_dirs()
+
+    session_dirs = OrderedSet(
+        os.path.join(data_dir, session)
+        for data_dir in system_data_dirs
+        for session in {"wayland-sessions", "xsessions"}
+    )
+
+    session_files = OrderedSet(
+        os.path.join(dir, session + ".desktop")
+        for dir in session_dirs
+        if os.path.exists(os.path.join(dir, session + ".desktop"))
+    )
+
+    # Deal with duplicate wayland-sessions and xsessions.
+    # Needed for the situation in gnome-session, where there's
+    # a xsession named the same as a wayland session.
+    if any(map(is_session_wayland, session_files)):
+        session_files = OrderedSet(
+            session for session in session_files if is_session_wayland(session)
+        )
+    else:
+        session_files = OrderedSet(
+            session for session in session_files if is_session_xsession(session)
+        )
+
+    if len(session_files) == 0:
+        logging.warning("No session files are found.")
+        sys.exit(0)
+    else:
+        return session_files[0]
+
+
+def is_session_xsession(session_file):
+    return "/xsessions/" in session_file
+
+
+def is_session_wayland(session_file):
+    return "/wayland-sessions/" in session_file
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Set session type for all normal users."
+    )
+    parser.add_argument("session", help="Name of session to set.")
+
+    args = parser.parse_args()
+
+    session = getattr(args, "session")
+    session_file = get_session_file(session)
+
+    user_manager = AccountsService.UserManager.get_default()
+    users = user_manager.list_users()
+
+    for user in users:
+        if user.is_system_account():
+            continue
+        else:
+            if is_session_wayland(session_file):
+                logging.debug(
+                    f"Setting session name: {session}, as we found the existing wayland-session: {session_file}"
+                )
+                user.set_session(session)
+            elif is_session_xsession(session_file):
+                logging.debug(
+                    f"Setting session name: {session}, as we found the existing xsession: {session_file}"
+                )
+                user.set_x_session(session)
+            else:
+                logging.error(f"Couldn't figure out session type for {session_file}")
+                sys.exit(1)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/slim.nix b/nixpkgs/nixos/modules/services/x11/display-managers/slim.nix
new file mode 100644
index 000000000000..4b0948a5b7a5
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/slim.nix
@@ -0,0 +1,16 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  # added 2019-11-11
+  imports = [
+    (mkRemovedOptionModule [ "services" "xserver" "displayManager" "slim" ] ''
+      The SLIM project is abandoned and their last release was in 2013.
+      Because of this it poses a security risk to your system.
+      Other issues include it not fully supporting systemd and logind sessions.
+      Please use a different display manager such as LightDM, SDDM, or GDM.
+      You can also use the startx module which uses Xinitrc.
+    '')
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/startx.nix b/nixpkgs/nixos/modules/services/x11/display-managers/startx.nix
new file mode 100644
index 000000000000..3980203b9457
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/startx.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.displayManager.startx;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.xserver.displayManager.startx = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the dummy "startx" pseudo-display manager,
+          which allows users to start X manually via the "startx" command
+          from a vt shell. The X server runs under the user's id, not as root.
+          The user must provide a ~/.xinitrc file containing session startup
+          commands, see startx(1). This is not automatically generated
+          from the desktopManager and windowManager settings.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.xserver = {
+      exportConfiguration = true;
+      displayManager.job.execCmd = "";
+      displayManager.lightdm.enable = lib.mkForce false;
+    };
+    systemd.services.display-manager.enable = false;
+    environment.systemPackages =  with pkgs; [ xorg.xinit ];
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix b/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix
new file mode 100644
index 000000000000..c23e479140f0
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/display-managers/xpra.nix
@@ -0,0 +1,252 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.displayManager.xpra;
+  dmcfg = config.services.xserver.displayManager;
+
+in
+
+{
+  ###### interface
+
+  options = {
+    services.xserver.displayManager.xpra = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable xpra as display manager.";
+      };
+
+      bindTcp = mkOption {
+        default = "127.0.0.1:10000";
+        example = "0.0.0.0:10000";
+        type = types.nullOr types.str;
+        description = "Bind xpra to TCP";
+      };
+
+      auth = mkOption {
+        type = types.str;
+        default = "pam";
+        example = "password:value=mysecret";
+        description = "Authentication to use when connecting to xpra";
+      };
+
+      pulseaudio = mkEnableOption "pulseaudio audio streaming";
+
+      extraOptions = mkOption {
+        description = "Extra xpra options";
+        default = [];
+        type = types.listOf types.str;
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.xserver.videoDrivers = ["dummy"];
+
+    services.xserver.monitorSection = ''
+      HorizSync   1.0 - 2000.0
+      VertRefresh 1.0 - 200.0
+      #To add your own modes here, use a modeline calculator, like:
+      # cvt:
+      # http://www.x.org/archive/X11R7.5/doc/man/man1/cvt.1.html
+      # xtiming:
+      # http://xtiming.sourceforge.net/cgi-bin/xtiming.pl
+      # gtf:
+      # http://gtf.sourceforge.net/
+      #This can be used to get a specific DPI, but only for the default resolution:
+      #DisplaySize 508 317
+      #NOTE: the highest modes will not work without increasing the VideoRam
+      # for the dummy video card.
+      #Modeline "16000x15000" 300.00  16000 16408 18000 20000  15000 15003 15013 15016
+      #Modeline "15000x15000" 281.25  15000 15376 16872 18744  15000 15003 15013 15016
+      #Modeline "16384x8192" 167.75  16384 16800 18432 20480  8192 8195 8205 8208
+      #Modeline "15360x8640" 249.00 15360 15752 17280 19200 8640 8643 8648 8651
+      Modeline "8192x4096" 193.35 8192 8224 8952 8984 4096 4196 4200 4301
+      Modeline "7680x4320" 208.00 7680 7880 8640 9600 4320 4323 4328 4335
+      Modeline "6400x4096" 151.38 6400 6432 7000 7032 4096 4196 4200 4301
+      Modeline "6400x2560" 91.59 6400 6432 6776 6808 2560 2623 2626 2689
+      Modeline "6400x2160" 160.51 6400 6432 7040 7072 2160 2212 2216 2269
+      Modeline "5760x2160" 149.50 5760 5768 6320 6880 2160 2161 2164 2173
+      Modeline "5680x1440" 142.66 5680 5712 6248 6280 1440 1474 1478 1513
+      Modeline "5496x1200" 199.13 5496 5528 6280 6312 1200 1228 1233 1261
+      Modeline "5280x2560" 75.72 5280 5312 5592 5624 2560 2623 2626 2689
+      Modeline "5280x1920" 56.04 5280 5312 5520 5552 1920 1967 1969 2017
+      Modeline "5280x1200" 191.40 5280 5312 6032 6064 1200 1228 1233 1261
+      Modeline "5280x1080" 169.96 5280 5312 5952 5984 1080 1105 1110 1135
+      Modeline "5120x3200" 199.75 5120 5152 5904 5936 3200 3277 3283 3361
+      Modeline "5120x2560" 73.45 5120 5152 5424 5456 2560 2623 2626 2689
+      Modeline "5120x2880" 185.50 5120 5256 5760 6400 2880 2883 2888 2899
+      Modeline "4800x1200" 64.42 4800 4832 5072 5104 1200 1229 1231 1261
+      Modeline "4720x3840" 227.86 4720 4752 5616 5648 3840 3933 3940 4033
+      Modeline "4400x2560" 133.70 4400 4432 4936 4968 2560 2622 2627 2689
+      Modeline "4480x1440" 72.94 4480 4512 4784 4816 1440 1475 1478 1513
+      Modeline "4240x1440" 69.09 4240 4272 4528 4560 1440 1475 1478 1513
+      Modeline "4160x1440" 67.81 4160 4192 4448 4480 1440 1475 1478 1513
+      Modeline "4096x2304" 249.25 4096 4296 4720 5344 2304 2307 2312 2333
+      Modeline "4096x2160" 111.25 4096 4200 4608 5120 2160 2163 2173 2176
+      Modeline "4000x1660" 170.32 4000 4128 4536 5072 1660 1661 1664 1679
+      Modeline "4000x1440" 145.00 4000 4088 4488 4976 1440 1441 1444 1457
+      Modeline "3904x1440" 63.70 3904 3936 4176 4208 1440 1475 1478 1513
+      Modeline "3840x2880" 133.43 3840 3872 4376 4408 2880 2950 2955 3025
+      Modeline "3840x2560" 116.93 3840 3872 4312 4344 2560 2622 2627 2689
+      Modeline "3840x2160" 104.25 3840 3944 4320 4800 2160 2163 2168 2175
+      Modeline "3840x2048" 91.45 3840 3872 4216 4248 2048 2097 2101 2151
+      Modeline "3840x1200" 108.89 3840 3872 4280 4312 1200 1228 1232 1261
+      Modeline "3840x1080" 100.38 3840 3848 4216 4592 1080 1081 1084 1093
+      Modeline "3864x1050" 94.58 3864 3896 4248 4280 1050 1074 1078 1103
+      Modeline "3600x1200" 106.06 3600 3632 3984 4368 1200 1201 1204 1214
+      Modeline "3600x1080" 91.02 3600 3632 3976 4008 1080 1105 1109 1135
+      Modeline "3520x1196" 99.53 3520 3552 3928 3960 1196 1224 1228 1256
+      Modeline "3360x2560" 102.55 3360 3392 3776 3808 2560 2622 2627 2689
+      Modeline "3360x1050" 293.75 3360 3576 3928 4496 1050 1053 1063 1089
+      Modeline "3288x1080" 39.76 3288 3320 3464 3496 1080 1106 1108 1135
+      Modeline "3200x1800" 233.00 3200 3384 3720 4240  1800 1803 1808 1834
+      Modeline "3200x1080" 236.16 3200 3232 4128 4160 1080 1103 1112 1135
+      Modeline "3120x2560" 95.36 3120 3152 3512 3544 2560 2622 2627 2689
+      Modeline "3120x1050" 272.75 3120 3320 3648 4176 1050 1053 1063 1089
+      Modeline "3072x2560" 93.92 3072 3104 3456 3488 2560 2622 2627 2689
+      Modeline "3008x1692" 130.93 3008 3112 3416 3824 1692 1693 1696 1712
+      Modeline "3000x2560" 91.77 3000 3032 3376 3408 2560 2622 2627 2689
+      Modeline "2880x1620" 396.25 2880 3096 3408 3936 1620 1623 1628 1679
+      Modeline "2728x1680" 148.02 2728 2760 3320 3352 1680 1719 1726 1765
+      Modeline "2560x2240" 151.55 2560 2688 2952 3344 2240 2241 2244 2266
+      Modeline "2560x1600" 47.12 2560 2592 2768 2800 1600 1639 1642 1681
+      Modeline "2560x1440" 42.12 2560 2592 2752 2784 1440 1475 1478 1513
+      Modeline "2560x1400" 267.86 2560 2592 3608 3640 1400 1429 1441 1471
+      Modeline "2048x2048" 49.47 2048 2080 2264 2296 2048 2097 2101 2151
+      Modeline "2048x1536" 80.06 2048 2104 2312 2576 1536 1537 1540 1554
+      Modeline "2048x1152" 197.97 2048 2184 2408 2768 1152 1153 1156 1192
+      Modeline "2048x1152" 165.92 2048 2080 2704 2736 1152 1176 1186 1210
+      Modeline "1920x1440" 69.47 1920 1960 2152 2384 1440 1441 1444 1457
+      Modeline "1920x1200" 26.28 1920 1952 2048 2080 1200 1229 1231 1261
+      Modeline "1920x1080" 23.53 1920 1952 2040 2072 1080 1106 1108 1135
+      Modeline "1728x1520" 205.42 1728 1760 2536 2568 1520 1552 1564 1597
+      Modeline "1680x1050" 20.08 1680 1712 1784 1816 1050 1075 1077 1103
+      Modeline "1600x1200" 22.04 1600 1632 1712 1744 1200 1229 1231 1261
+      Modeline "1600x900" 33.92 1600 1632 1760 1792 900 921 924 946
+      Modeline "1440x900" 30.66 1440 1472 1584 1616 900 921 924 946
+      Modeline "1400x900" 103.50 1400 1480 1624 1848 900 903 913 934
+      ModeLine "1366x768" 72.00 1366 1414 1446 1494  768 771 777 803
+      Modeline "1360x768" 24.49 1360 1392 1480 1512 768 786 789 807
+      Modeline "1280x1024" 31.50 1280 1312 1424 1456 1024 1048 1052 1076
+      Modeline "1280x800" 24.15 1280 1312 1400 1432 800 819 822 841
+      Modeline "1280x768" 23.11 1280 1312 1392 1424 768 786 789 807
+      Modeline "1280x720" 59.42 1280 1312 1536 1568 720 735 741 757
+      Modeline "1024x768" 18.71 1024 1056 1120 1152 768 786 789 807
+      Modeline "1024x640" 41.98 1024 1056 1208 1240 640 653 659 673
+      Modeline "1024x576" 46.50 1024 1064 1160 1296  576 579 584 599
+      Modeline "768x1024" 19.50 768 800 872 904 1024 1048 1052 1076
+      Modeline "960x540" 40.75 960 992 1088 1216 540 543 548 562
+      Modeline "864x486"  32.50 864 888 968 1072 486 489 494 506
+      Modeline "720x405" 22.50 720 744 808 896  405 408 413 422
+      Modeline "640x360" 14.75 640 664 720 800 360 363 368 374
+      #common resolutions for android devices (both orientations):
+      Modeline "800x1280" 25.89 800 832 928 960 1280 1310 1315 1345
+      Modeline "1280x800" 24.15 1280 1312 1400 1432 800 819 822 841
+      Modeline "720x1280" 30.22 720 752 864 896 1280 1309 1315 1345
+      Modeline "1280x720" 27.41 1280 1312 1416 1448 720 737 740 757
+      Modeline "768x1024" 24.93 768 800 888 920 1024 1047 1052 1076
+      Modeline "1024x768" 23.77 1024 1056 1144 1176 768 785 789 807
+      Modeline "600x1024" 19.90 600 632 704 736 1024 1047 1052 1076
+      Modeline "1024x600" 18.26 1024 1056 1120 1152 600 614 617 631
+      Modeline "536x960" 16.74 536 568 624 656 960 982 986 1009
+      Modeline "960x536" 15.23 960 992 1048 1080 536 548 551 563
+      Modeline "600x800" 15.17 600 632 688 720 800 818 822 841
+      Modeline "800x600" 14.50 800 832 880 912 600 614 617 631
+      Modeline "480x854" 13.34 480 512 560 592 854 873 877 897
+      Modeline "848x480" 12.09 848 880 920 952 480 491 493 505
+      Modeline "480x800" 12.43 480 512 552 584 800 818 822 841
+      Modeline "800x480" 11.46 800 832 872 904 480 491 493 505
+      #resolutions for android devices (both orientations)
+      #minus the status bar
+      #38px status bar (and width rounded up)
+      Modeline "800x1242" 25.03 800 832 920 952 1242 1271 1275 1305
+      Modeline "1280x762" 22.93 1280 1312 1392 1424 762 780 783 801
+      Modeline "720x1242" 29.20 720 752 856 888 1242 1271 1276 1305
+      Modeline "1280x682" 25.85 1280 1312 1408 1440 682 698 701 717
+      Modeline "768x986" 23.90 768 800 888 920 986 1009 1013 1036
+      Modeline "1024x730" 22.50 1024 1056 1136 1168 730 747 750 767
+      Modeline "600x986" 19.07 600 632 704 736 986 1009 1013 1036
+      Modeline "1024x562" 17.03 1024 1056 1120 1152 562 575 578 591
+      Modeline "536x922" 16.01 536 568 624 656 922 943 947 969
+      Modeline "960x498" 14.09 960 992 1040 1072 498 509 511 523
+      Modeline "600x762" 14.39 600 632 680 712 762 779 783 801
+      Modeline "800x562" 13.52 800 832 880 912 562 575 578 591
+      Modeline "480x810" 12.59 480 512 552 584 810 828 832 851
+      Modeline "848x442" 11.09 848 880 920 952 442 452 454 465
+      Modeline "480x762" 11.79 480 512 552 584 762 779 783 801
+    '';
+
+    services.xserver.resolutions = [
+      {x="8192"; y="4096";}
+      {x="5120"; y="3200";}
+      {x="3840"; y="2880";}
+      {x="3840"; y="2560";}
+      {x="3840"; y="2048";}
+      {x="3840"; y="2160";}
+      {x="2048"; y="2048";}
+      {x="2560"; y="1600";}
+      {x="1920"; y="1440";}
+      {x="1920"; y="1200";}
+      {x="1920"; y="1080";}
+      {x="1600"; y="1200";}
+      {x="1680"; y="1050";}
+      {x="1600"; y="900";}
+      {x="1400"; y="1050";}
+      {x="1440"; y="900";}
+      {x="1280"; y="1024";}
+      {x="1366"; y="768";}
+      {x="1280"; y="800";}
+      {x="1024"; y="768";}
+      {x="1024"; y="600";}
+      {x="800"; y="600";}
+      {x="320"; y="200";}
+    ];
+
+    services.xserver.serverFlagsSection = ''
+      Option "DontVTSwitch" "true"
+      Option "PciForceNone" "true"
+      Option "AutoEnableDevices" "false"
+      Option "AutoAddDevices" "false"
+    '';
+
+    services.xserver.deviceSection = ''
+      VideoRam 192000
+    '';
+
+    services.xserver.displayManager.job.execCmd = ''
+      ${optionalString (cfg.pulseaudio)
+        "export PULSE_COOKIE=/run/pulse/.config/pulse/cookie"}
+      exec ${pkgs.xpra}/bin/xpra start \
+        --daemon=off \
+        --log-dir=/var/log \
+        --log-file=xpra.log \
+        --opengl=on \
+        --clipboard=on \
+        --notifications=on \
+        --speaker=yes \
+        --mdns=no \
+        --pulseaudio=no \
+        ${optionalString (cfg.pulseaudio) "--sound-source=pulse"} \
+        --socket-dirs=/run/xpra \
+        --xvfb="xpra_Xdummy ${concatStringsSep " " dmcfg.xserverArgs}" \
+        ${optionalString (cfg.bindTcp != null) "--bind-tcp=${cfg.bindTcp}"} \
+        --auth=${cfg.auth} \
+        ${concatStringsSep " " cfg.extraOptions}
+    '';
+
+    services.xserver.terminateOnReset = false;
+
+    environment.systemPackages = [pkgs.xpra];
+
+    virtualisation.virtualbox.guest.x11 = false;
+    hardware.pulseaudio.enable = mkDefault cfg.pulseaudio;
+    hardware.pulseaudio.systemWide = mkDefault cfg.pulseaudio;
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/extra-layouts.nix b/nixpkgs/nixos/modules/services/x11/extra-layouts.nix
new file mode 100644
index 000000000000..f48216ff446f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/extra-layouts.nix
@@ -0,0 +1,174 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  layouts = config.services.xserver.extraLayouts;
+
+  layoutOpts = {
+    options = {
+      description = mkOption {
+        type = types.str;
+        description = "A short description of the layout.";
+      };
+
+      languages = mkOption {
+        type = types.listOf types.str;
+        description =
+        ''
+          A list of languages provided by the layout.
+          (Use ISO 639-2 codes, for example: "eng" for english)
+        '';
+      };
+
+      compatFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb compat file.
+          This file sets the compatibility state, used to preserve
+          compatibility with xkb-unaware programs.
+          It must contain a <literal>xkb_compat "name" { ... }</literal> block.
+        '';
+      };
+
+      geometryFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb geometry file.
+          This (completely optional) file describes the physical layout of
+          keyboard, which maybe be used by programs to depict it.
+          It must contain a <literal>xkb_geometry "name" { ... }</literal> block.
+        '';
+      };
+
+      keycodesFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb keycodes file.
+          This file specifies the range and the interpretation of the raw
+          keycodes sent by the keyboard.
+          It must contain a <literal>xkb_keycodes "name" { ... }</literal> block.
+        '';
+      };
+
+      symbolsFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb symbols file.
+          This is the most important file: it defines which symbol or action
+          maps to each key and must contain a
+          <literal>xkb_symbols "name" { ... }</literal> block.
+        '';
+      };
+
+      typesFile = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = ''
+          The path to the xkb types file.
+          This file specifies the key types that can be associated with
+          the various keyboard keys.
+          It must contain a <literal>xkb_types "name" { ... }</literal> block.
+        '';
+      };
+
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options.services.xserver = {
+    extraLayouts = mkOption {
+      type = types.attrsOf (types.submodule layoutOpts);
+      default = {};
+      example = literalExample
+      ''
+        {
+          mine = {
+            description = "My custom xkb layout.";
+            languages = [ "eng" ];
+            symbolsFile = /path/to/my/layout;
+          };
+        }
+      '';
+      description = ''
+        Extra custom layouts that will be included in the xkb configuration.
+        Information on how to create a new layout can be found here:
+        <link xlink:href="https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts"></link>.
+        For more examples see
+        <link xlink:href="https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples"></link>
+      '';
+    };
+
+  };
+
+  ###### implementation
+
+  config = mkIf (layouts != { }) {
+
+    # We don't override xkeyboard_config directly to
+    # reduce the amount of packages to be recompiled.
+    # Only the following packages are necessary to set
+    # a custom layout anyway:
+    nixpkgs.overlays = lib.singleton (self: super: {
+
+      xkb_patched = self.xorg.xkeyboardconfig_custom {
+        layouts = config.services.xserver.extraLayouts;
+      };
+
+      xorg = super.xorg // {
+        xorgserver = super.xorg.xorgserver.overrideAttrs (old: {
+          configureFlags = old.configureFlags ++ [
+            "--with-xkb-bin-directory=${self.xorg.xkbcomp}/bin"
+            "--with-xkb-path=${self.xkb_patched}/share/X11/xkb"
+          ];
+        });
+
+        setxkbmap = super.xorg.setxkbmap.overrideAttrs (old: {
+          postInstall =
+            ''
+              mkdir -p $out/share
+              ln -sfn ${self.xkb_patched}/etc/X11 $out/share/X11
+            '';
+        });
+
+        xkbcomp = super.xorg.xkbcomp.overrideAttrs (old: {
+          configureFlags = [ "--with-xkb-config-root=${self.xkb_patched}/share/X11/xkb" ];
+        });
+
+      };
+
+      ckbcomp = super.ckbcomp.override {
+        xkeyboard_config = self.xkb_patched;
+      };
+
+      xkbvalidate = super.xkbvalidate.override {
+        libxkbcommon = self.libxkbcommon.override {
+          xkeyboard_config = self.xkb_patched;
+        };
+      };
+
+    });
+
+    environment.sessionVariables = {
+      # runtime override supported by multiple libraries e. g. libxkbcommon
+      # https://xkbcommon.org/doc/current/group__include-path.html
+      XKB_CONFIG_ROOT = "${pkgs.xkb_patched}/etc/X11/xkb";
+    };
+
+    services.xserver = {
+      xkbDir = "${pkgs.xkb_patched}/etc/X11/xkb";
+      exportConfiguration = config.services.xserver.displayManager.startx.enable;
+    };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/fractalart.nix b/nixpkgs/nixos/modules/services/x11/fractalart.nix
new file mode 100644
index 000000000000..448248a58794
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/fractalart.nix
@@ -0,0 +1,36 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.fractalart;
+in {
+  options.services.fractalart = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      example = true;
+      description = "Enable FractalArt for generating colorful wallpapers on login";
+    };
+
+    width = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      example = 1920;
+      description = "Screen width";
+    };
+
+    height = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      example = 1080;
+      description = "Screen height";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.haskellPackages.FractalArt ];
+    services.xserver.displayManager.sessionCommands =
+      "${pkgs.haskellPackages.FractalArt}/bin/FractalArt --no-bg -f .background-image"
+        + optionalString (cfg.width  != null) " -w ${toString cfg.width}"
+        + optionalString (cfg.height != null) " -h ${toString cfg.height}";
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix b/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix
new file mode 100644
index 000000000000..3fd6fed91e13
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/gdk-pixbuf.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.gdk-pixbuf;
+
+  # Get packages to generate the cache for. We always include gdk-pixbuf.
+  effectivePackages = unique ([pkgs.gdk-pixbuf] ++ cfg.modulePackages);
+
+  # Generate the cache file by running gdk-pixbuf-query-loaders for each
+  # package and concatenating the results.
+  loadersCache = pkgs.runCommand "gdk-pixbuf-loaders.cache" { preferLocalBuild = true; } ''
+    (
+      for package in ${concatStringsSep " " effectivePackages}; do
+        module_dir="$package/${pkgs.gdk-pixbuf.moduleDir}"
+        if [[ ! -d $module_dir ]]; then
+          echo "Warning (services.xserver.gdk-pixbuf): missing module directory $module_dir" 1>&2
+          continue
+        fi
+        GDK_PIXBUF_MODULEDIR="$module_dir" \
+          ${pkgs.stdenv.hostPlatform.emulator pkgs.buildPackages} ${pkgs.gdk-pixbuf.dev}/bin/gdk-pixbuf-query-loaders
+      done
+    ) > "$out"
+  '';
+in
+
+{
+  options = {
+    services.xserver.gdk-pixbuf.modulePackages = mkOption {
+      type = types.listOf types.package;
+      default = [ ];
+      description = "Packages providing GDK-Pixbuf modules, for cache generation.";
+    };
+  };
+
+  # If there is any package configured in modulePackages, we generate the
+  # loaders.cache based on that and set the environment variable
+  # GDK_PIXBUF_MODULE_FILE to point to it.
+  config = mkIf (cfg.modulePackages != []) {
+    environment.variables = {
+      GDK_PIXBUF_MODULE_FILE = "${loadersCache}";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/cmt.nix b/nixpkgs/nixos/modules/services/x11/hardware/cmt.nix
new file mode 100644
index 000000000000..5ac824c5e419
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/cmt.nix
@@ -0,0 +1,59 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+cfg = config.services.xserver.cmt;
+etcPath = "X11/xorg.conf.d";
+
+in {
+
+  options = {
+
+    services.xserver.cmt = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable chrome multitouch input (cmt). Touchpad drivers that are configured for chromebooks.";
+      };
+      models = mkOption {
+        type = types.enum [ "atlas" "banjo" "candy" "caroline" "cave" "celes" "clapper" "cyan" "daisy" "elan" "elm" "enguarde" "eve" "expresso" "falco" "gandof" "glimmer" "gnawty" "heli" "kevin" "kip" "leon" "lulu" "orco" "pbody" "peppy" "pi" "pit" "puppy" "quawks" "rambi" "samus" "snappy" "spring" "squawks" "swanky" "winky" "wolf" "auron_paine" "auron_yuna" "daisy_skate" "nyan_big" "nyan_blaze" "veyron_jaq" "veyron_jerry" "veyron_mighty" "veyron_minnie" "veyron_speedy" ];
+        example = "banjo";
+        description = ''
+          Which models to enable cmt for. Enter the Code Name for your Chromebook.
+          Code Name can be found at <link xlink:href="https://www.chromium.org/chromium-os/developer-information-for-chrome-os-devices" />.
+        '';
+      };
+    }; #closes services
+  }; #closes options
+
+  config = mkIf cfg.enable {
+
+    services.xserver.modules = [ pkgs.xf86_input_cmt ];
+
+    environment.etc = {
+      "${etcPath}/40-touchpad-cmt.conf" = {
+        source = "${pkgs.chromium-xorg-conf}/40-touchpad-cmt.conf";
+      };
+      "${etcPath}/50-touchpad-cmt-${cfg.models}.conf" = {
+        source = "${pkgs.chromium-xorg-conf}/50-touchpad-cmt-${cfg.models}.conf";
+      };
+      "${etcPath}/60-touchpad-cmt-${cfg.models}.conf" = {
+        source = "${pkgs.chromium-xorg-conf}/60-touchpad-cmt-${cfg.models}.conf";
+      };
+    };
+
+    assertions = [
+      {
+        assertion = !config.services.xserver.libinput.enable;
+        message = ''
+          cmt and libinput are incompatible, meaning you cannot enable them both.
+          To use cmt you need to disable libinput with `services.xserver.libinput.enable = false`
+          If you haven't enabled it in configuration.nix, it's enabled by default on a
+          different xserver module.
+        '';
+      }
+    ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/digimend.nix b/nixpkgs/nixos/modules/services/x11/hardware/digimend.nix
new file mode 100644
index 000000000000..b1b1682f00b2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/digimend.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.digimend;
+
+  pkg = config.boot.kernelPackages.digimend;
+
+in
+
+{
+
+  options = {
+
+    services.xserver.digimend = {
+
+      enable = mkEnableOption "the digimend drivers for Huion/XP-Pen/etc. tablets";
+
+    };
+
+  };
+
+
+  config = mkIf cfg.enable {
+
+    # digimend drivers use xsetwacom and wacom X11 drivers
+    services.xserver.wacom.enable = true;
+
+    boot.extraModulePackages = [ pkg ];
+
+    environment.etc."X11/xorg.conf.d/50-digimend.conf".source =
+      "${pkg}/usr/share/X11/xorg.conf.d/50-digimend.conf";
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix b/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix
new file mode 100644
index 000000000000..9548ecb8ef6d
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/libinput.nix
@@ -0,0 +1,252 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.xserver.libinput;
+    xorgBool = v: if v then "on" else "off";
+in {
+
+  options = {
+
+    services.xserver.libinput = {
+
+      enable = mkEnableOption "libinput";
+
+      dev = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/dev/input/event0";
+        description =
+          ''
+            Path for touchpad device.  Set to null to apply to any
+            auto-detected touchpad.
+          '';
+      };
+
+      accelProfile = mkOption {
+        type = types.enum [ "flat" "adaptive" ];
+        default = "adaptive";
+        example = "flat";
+        description =
+          ''
+            Sets  the pointer acceleration profile to the given profile.
+            Permitted values are adaptive, flat.
+            Not all devices support this option or all profiles.
+            If a profile is unsupported, the default profile for this is used.
+            <literal>flat</literal>: Pointer motion is accelerated by a constant
+            (device-specific) factor, depending on the current speed.
+            <literal>adaptive</literal>: Pointer acceleration depends on the input speed.
+            This is the default profile for most devices.
+          '';
+      };
+
+      accelSpeed = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
+      };
+
+      buttonMapping = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description =
+          ''
+            Sets the logical button mapping for this device, see XSetPointerMapping(3). The string  must
+            be  a  space-separated  list  of  button mappings in the order of the logical buttons on the
+            device, starting with button 1.  The default mapping is "1 2 3 ... 32". A mapping of 0 deac‐
+            tivates the button. Multiple buttons can have the same mapping.  Invalid mapping strings are
+            discarded and the default mapping is used for all buttons.  Buttons  not  specified  in  the
+            user's mapping use the default mapping. See section BUTTON MAPPING for more details.
+          '';
+      };
+
+      calibrationMatrix = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description =
+          ''
+            A  string  of  9 space-separated floating point numbers.  Sets the calibration matrix to the
+            3x3 matrix where the first row is (abc), the second row is (def) and the third row is (ghi).
+          '';
+      };
+
+      clickMethod = mkOption {
+        type = types.nullOr (types.enum [ "none" "buttonareas" "clickfinger" ]);
+        default = null;
+        description =
+          ''
+            Enables a click method. Permitted values are <literal>none</literal>,
+            <literal>buttonareas</literal>, <literal>clickfinger</literal>.
+            Not all devices support all methods, if an option is unsupported,
+            the default click method for this device is used.
+          '';
+      };
+
+      leftHanded = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enables left-handed button orientation, i.e. swapping left and right buttons.";
+      };
+
+      middleEmulation = mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          ''
+            Enables middle button emulation. When enabled, pressing the left and right buttons
+            simultaneously produces a middle mouse button click.
+          '';
+      };
+
+      naturalScrolling = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enables or disables natural scrolling behavior.";
+      };
+
+      scrollButton = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 1;
+        description =
+          ''
+            Designates a button as scroll button. If the ScrollMethod is button and the button is logically
+            held down, x/y axis movement is converted into scroll events.
+          '';
+      };
+
+      scrollMethod = mkOption {
+        type = types.enum [ "twofinger" "edge" "button" "none" ];
+        default = "twofinger";
+        example = "edge";
+        description =
+          ''
+            Specify the scrolling method: <literal>twofinger</literal>, <literal>edge</literal>,
+            <literal>button</literal>, or <literal>none</literal>
+          '';
+      };
+
+      horizontalScrolling = mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          ''
+            Disables horizontal scrolling. When disabled, this driver will discard any horizontal scroll
+            events from libinput. Note that this does not disable horizontal scrolling, it merely
+            discards the horizontal axis from any scroll events.
+          '';
+      };
+
+      sendEventsMode = mkOption {
+        type = types.enum [ "disabled" "enabled" "disabled-on-external-mouse" ];
+        default = "enabled";
+        example = "disabled";
+        description =
+          ''
+            Sets the send events mode to <literal>disabled</literal>, <literal>enabled</literal>,
+            or <literal>disabled-on-external-mouse</literal>
+          '';
+      };
+
+      tapping = mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          ''
+            Enables or disables tap-to-click behavior.
+          '';
+      };
+
+      tappingDragLock = mkOption {
+        type = types.bool;
+        default = true;
+        description =
+          ''
+            Enables or disables drag lock during tapping behavior. When enabled, a finger up during tap-
+            and-drag will not immediately release the button. If the finger is set down again within the
+            timeout, the draging process continues.
+          '';
+      };
+
+      disableWhileTyping = mkOption {
+        type = types.bool;
+        default = false;
+        description =
+          ''
+            Disable input method while typing.
+          '';
+      };
+
+      additionalOptions = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+        ''
+          Option "DragLockButtons" "L1 B1 L2 B2"
+        '';
+        description = ''
+          Additional options for libinput touchpad driver. See
+          <citerefentry><refentrytitle>libinput</refentrytitle><manvolnum>4</manvolnum></citerefentry>
+          for available options.";
+        '';
+      };
+
+    };
+
+  };
+
+
+  config = mkIf cfg.enable {
+
+    services.xserver.modules = [ pkgs.xorg.xf86inputlibinput ];
+
+    environment.systemPackages = [ pkgs.xorg.xf86inputlibinput ];
+
+    environment.etc =
+      let cfgPath = "X11/xorg.conf.d/40-libinput.conf";
+      in {
+        ${cfgPath} = {
+          source = pkgs.xorg.xf86inputlibinput.out + "/share/" + cfgPath;
+        };
+      };
+
+    services.udev.packages = [ pkgs.libinput.out ];
+
+    services.xserver.config =
+      ''
+        # General libinput configuration.
+        # See CONFIGURATION DETAILS section of man:libinput(4).
+        Section "InputClass"
+          Identifier "libinputConfiguration"
+          MatchDriver "libinput"
+          ${optionalString (cfg.dev != null) ''MatchDevicePath "${cfg.dev}"''}
+          Option "AccelProfile" "${cfg.accelProfile}"
+          ${optionalString (cfg.accelSpeed != null) ''Option "AccelSpeed" "${cfg.accelSpeed}"''}
+          ${optionalString (cfg.buttonMapping != null) ''Option "ButtonMapping" "${cfg.buttonMapping}"''}
+          ${optionalString (cfg.calibrationMatrix != null) ''Option "CalibrationMatrix" "${cfg.calibrationMatrix}"''}
+          ${optionalString (cfg.clickMethod != null) ''Option "ClickMethod" "${cfg.clickMethod}"''}
+          Option "LeftHanded" "${xorgBool cfg.leftHanded}"
+          Option "MiddleEmulation" "${xorgBool cfg.middleEmulation}"
+          Option "NaturalScrolling" "${xorgBool cfg.naturalScrolling}"
+          ${optionalString (cfg.scrollButton != null) ''Option "ScrollButton" "${toString cfg.scrollButton}"''}
+          Option "ScrollMethod" "${cfg.scrollMethod}"
+          Option "HorizontalScrolling" "${xorgBool cfg.horizontalScrolling}"
+          Option "SendEventsMode" "${cfg.sendEventsMode}"
+          Option "Tapping" "${xorgBool cfg.tapping}"
+          Option "TappingDragLock" "${xorgBool cfg.tappingDragLock}"
+          Option "DisableWhileTyping" "${xorgBool cfg.disableWhileTyping}"
+          ${cfg.additionalOptions}
+        EndSection
+      '';
+
+    assertions = [
+      # already present in synaptics.nix
+      /* {
+        assertion = !config.services.xserver.synaptics.enable;
+        message = "Synaptics and libinput are incompatible, you cannot enable both (in services.xserver).";
+      } */
+    ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix b/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix
new file mode 100644
index 000000000000..22af869f1f8a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/synaptics.nix
@@ -0,0 +1,213 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.xserver.synaptics;
+    tapConfig = if cfg.tapButtons then enabledTapConfig else disabledTapConfig;
+    enabledTapConfig = ''
+      Option "MaxTapTime" "180"
+      Option "MaxTapMove" "220"
+      Option "TapButton1" "${builtins.elemAt cfg.fingersMap 0}"
+      Option "TapButton2" "${builtins.elemAt cfg.fingersMap 1}"
+      Option "TapButton3" "${builtins.elemAt cfg.fingersMap 2}"
+    '';
+    disabledTapConfig = ''
+      Option "MaxTapTime" "0"
+      Option "MaxTapMove" "0"
+      Option "TapButton1" "0"
+      Option "TapButton2" "0"
+      Option "TapButton3" "0"
+    '';
+  pkg = pkgs.xorg.xf86inputsynaptics;
+  etcFile = "X11/xorg.conf.d/70-synaptics.conf";
+in {
+
+  options = {
+
+    services.xserver.synaptics = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable touchpad support. Deprecated: Consider services.xserver.libinput.enable.";
+      };
+
+      dev = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/dev/input/event0";
+        description =
+          ''
+            Path for touchpad device.  Set to null to apply to any
+            auto-detected touchpad.
+          '';
+      };
+
+      accelFactor = mkOption {
+        type = types.nullOr types.str;
+        default = "0.001";
+        description = "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
+      };
+
+      minSpeed = mkOption {
+        type = types.nullOr types.str;
+        default = "0.6";
+        description = "Cursor speed factor for precision finger motion.";
+      };
+
+      maxSpeed = mkOption {
+        type = types.nullOr types.str;
+        default = "1.0";
+        description = "Cursor speed factor for highest-speed finger motion.";
+      };
+
+      scrollDelta = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 75;
+        description = "Move distance of the finger for a scroll event.";
+      };
+
+      twoFingerScroll = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable two-finger drag-scrolling. Overridden by horizTwoFingerScroll and vertTwoFingerScroll.";
+      };
+
+      horizTwoFingerScroll = mkOption {
+        type = types.bool;
+        default = cfg.twoFingerScroll;
+        description = "Whether to enable horizontal two-finger drag-scrolling.";
+      };
+
+      vertTwoFingerScroll = mkOption {
+        type = types.bool;
+        default = cfg.twoFingerScroll;
+        description = "Whether to enable vertical two-finger drag-scrolling.";
+      };
+
+      horizEdgeScroll = mkOption {
+        type = types.bool;
+        default = ! cfg.horizTwoFingerScroll;
+        description = "Whether to enable horizontal edge drag-scrolling.";
+      };
+
+      vertEdgeScroll = mkOption {
+        type = types.bool;
+        default = ! cfg.vertTwoFingerScroll;
+        description = "Whether to enable vertical edge drag-scrolling.";
+      };
+
+      tapButtons = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether to enable tap buttons.";
+      };
+
+      buttonsMap = mkOption {
+        type = types.listOf types.int;
+        default = [1 2 3];
+        example = [1 3 2];
+        description = "Remap touchpad buttons.";
+        apply = map toString;
+      };
+
+      fingersMap = mkOption {
+        type = types.listOf types.int;
+        default = [1 2 3];
+        example = [1 3 2];
+        description = "Remap several-fingers taps.";
+        apply = map toString;
+      };
+
+      palmDetect = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable palm detection (hardware support required)";
+      };
+
+      palmMinWidth = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 5;
+        description = "Minimum finger width at which touch is considered a palm";
+      };
+
+      palmMinZ = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 20;
+        description = "Minimum finger pressure at which touch is considered a palm";
+      };
+
+      horizontalScroll = mkOption {
+        type = types.bool;
+        default = true;
+        description = "Whether to enable horizontal scrolling (on touchpad)";
+      };
+
+      additionalOptions = mkOption {
+        type = types.str;
+        default = "";
+        example = ''
+          Option "RTCornerButton" "2"
+          Option "RBCornerButton" "3"
+        '';
+        description = ''
+          Additional options for synaptics touchpad driver.
+        '';
+      };
+
+    };
+
+  };
+
+
+  config = mkIf cfg.enable {
+
+    services.xserver.modules = [ pkg.out ];
+
+    environment.etc.${etcFile}.source =
+      "${pkg.out}/share/X11/xorg.conf.d/70-synaptics.conf";
+
+    environment.systemPackages = [ pkg ];
+
+    services.xserver.config =
+      ''
+        # Automatically enable the synaptics driver for all touchpads.
+        Section "InputClass"
+          Identifier "synaptics touchpad catchall"
+          MatchIsTouchpad "on"
+          ${optionalString (cfg.dev != null) ''MatchDevicePath "${cfg.dev}"''}
+          Driver "synaptics"
+          ${optionalString (cfg.minSpeed != null) ''Option "MinSpeed" "${cfg.minSpeed}"''}
+          ${optionalString (cfg.maxSpeed != null) ''Option "MaxSpeed" "${cfg.maxSpeed}"''}
+          ${optionalString (cfg.accelFactor != null) ''Option "AccelFactor" "${cfg.accelFactor}"''}
+          ${optionalString cfg.tapButtons tapConfig}
+          Option "ClickFinger1" "${builtins.elemAt cfg.buttonsMap 0}"
+          Option "ClickFinger2" "${builtins.elemAt cfg.buttonsMap 1}"
+          Option "ClickFinger3" "${builtins.elemAt cfg.buttonsMap 2}"
+          Option "VertTwoFingerScroll" "${if cfg.vertTwoFingerScroll then "1" else "0"}"
+          Option "HorizTwoFingerScroll" "${if cfg.horizTwoFingerScroll then "1" else "0"}"
+          Option "VertEdgeScroll" "${if cfg.vertEdgeScroll then "1" else "0"}"
+          Option "HorizEdgeScroll" "${if cfg.horizEdgeScroll then "1" else "0"}"
+          ${optionalString cfg.palmDetect ''Option "PalmDetect" "1"''}
+          ${optionalString (cfg.palmMinWidth != null) ''Option "PalmMinWidth" "${toString cfg.palmMinWidth}"''}
+          ${optionalString (cfg.palmMinZ != null) ''Option "PalmMinZ" "${toString cfg.palmMinZ}"''}
+          ${optionalString (cfg.scrollDelta != null) ''Option "VertScrollDelta" "${toString cfg.scrollDelta}"''}
+          ${if !cfg.horizontalScroll then ''Option "HorizScrollDelta" "0"''
+            else (optionalString (cfg.scrollDelta != null) ''Option "HorizScrollDelta" "${toString cfg.scrollDelta}"'')}
+          ${cfg.additionalOptions}
+        EndSection
+      '';
+
+    assertions = [
+      {
+        assertion = !config.services.xserver.libinput.enable;
+        message = "Synaptics and libinput are incompatible, you cannot enable both (in services.xserver).";
+      }
+    ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/hardware/wacom.nix b/nixpkgs/nixos/modules/services/x11/hardware/wacom.nix
new file mode 100644
index 000000000000..dad2b308d1b4
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/hardware/wacom.nix
@@ -0,0 +1,48 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.wacom;
+
+in
+
+{
+
+  options = {
+
+    services.xserver.wacom = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the Wacom touchscreen/digitizer/tablet.
+          If you ever have any issues such as, try switching to terminal (ctrl-alt-F1) and back
+          which will make Xorg reconfigure the device ?
+
+          If you're not satisfied by the default behaviour you can override
+          <option>environment.etc."X11/xorg.conf.d/70-wacom.conf"</option> in
+          configuration.nix easily.
+        '';
+      };
+
+    };
+
+  };
+
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.xf86_input_wacom ]; # provides xsetwacom
+
+    services.xserver.modules = [ pkgs.xf86_input_wacom ];
+
+    services.udev.packages = [ pkgs.xf86_input_wacom ];
+
+    environment.etc."X11/xorg.conf.d/70-wacom.conf".source = "${pkgs.xf86_input_wacom}/share/X11/xorg.conf.d/70-wacom.conf";
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/imwheel.nix b/nixpkgs/nixos/modules/services/x11/imwheel.nix
new file mode 100644
index 000000000000..3923df498e79
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/imwheel.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.xserver.imwheel;
+in
+  {
+    options = {
+      services.xserver.imwheel = {
+        enable = mkEnableOption "IMWheel service";
+
+        extraOptions = mkOption {
+          type = types.listOf types.str;
+          default = [ "--buttons=45" ];
+          example = [ "--debug" ];
+          description = ''
+            Additional command-line arguments to pass to
+            <command>imwheel</command>.
+          '';
+        };
+
+        rules = mkOption {
+          type = types.attrsOf types.str;
+          default = {};
+          example = literalExample ''
+            ".*" = '''
+              None,      Up,   Button4, 8
+              None,      Down, Button5, 8
+              Shift_L,   Up,   Shift_L|Button4, 4
+              Shift_L,   Down, Shift_L|Button5, 4
+              Control_L, Up,   Control_L|Button4
+              Control_L, Down, Control_L|Button5
+            ''';
+          '';
+          description = ''
+            Window class translation rules.
+            /etc/X11/imwheelrc is generated based on this config
+            which means this config is global for all users.
+            See <link xlink:href="http://imwheel.sourceforge.net/imwheel.1.html">offical man pages</link>
+            for more informations.
+          '';
+        };
+      };
+    };
+
+    config = mkIf cfg.enable {
+      environment.systemPackages = [ pkgs.imwheel ];
+
+      environment.etc."X11/imwheel/imwheelrc".source =
+        pkgs.writeText "imwheelrc" (concatStringsSep "\n\n"
+          (mapAttrsToList
+            (rule: conf: "\"${rule}\"\n${conf}") cfg.rules
+          ));
+
+      systemd.user.services.imwheel = {
+        description = "imwheel service";
+        wantedBy = [ "graphical-session.target" ];
+        partOf = [ "graphical-session.target" ];
+        serviceConfig = {
+          ExecStart = "${pkgs.imwheel}/bin/imwheel " + escapeShellArgs ([
+            "--detach"
+            "--kill"
+          ] ++ cfg.extraOptions);
+          ExecStop = "${pkgs.procps}/bin/pkill imwheel";
+          Restart = "on-failure";
+        };
+      };
+    };
+  }
diff --git a/nixpkgs/nixos/modules/services/x11/picom.nix b/nixpkgs/nixos/modules/services/x11/picom.nix
new file mode 100644
index 000000000000..1289edd2904a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/picom.nix
@@ -0,0 +1,316 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.picom;
+
+  pairOf = x: with types;
+    addCheck (listOf x) (y: length y == 2)
+    // { description = "pair of ${x.description}"; };
+
+  floatBetween = a: b: with types;
+    let
+      # toString prints floats with hardcoded high precision
+      floatToString = f: builtins.toJSON f;
+    in
+      addCheck float (x: x <= b && x >= a)
+      // { description = "a floating point number in " +
+                         "range [${floatToString a}, ${floatToString b}]"; };
+
+  mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
+
+  # Basically a tinkered lib.generators.mkKeyValueDefault
+  # It either serializes a top-level definition "key: { values };"
+  # or an expression "key = { values };"
+  mkAttrsString = top:
+    mapAttrsToList (k: v:
+      let sep = if (top && isAttrs v) then ":" else "=";
+      in "${escape [ sep ] k}${sep}${mkValueString v};");
+
+  # This serializes a Nix expression to the libconfig format.
+  mkValueString = v:
+         if types.bool.check  v then boolToString v
+    else if types.int.check   v then toString v
+    else if types.float.check v then toString v
+    else if types.str.check   v then "\"${escape [ "\"" ] v}\""
+    else if builtins.isList   v then "[ ${concatMapStringsSep " , " mkValueString v} ]"
+    else if types.attrs.check v then "{ ${concatStringsSep " " (mkAttrsString false v) } }"
+    else throw ''
+                 invalid expression used in option services.picom.settings:
+                 ${v}
+               '';
+
+  toConf = attrs: concatStringsSep "\n" (mkAttrsString true cfg.settings);
+
+  configFile = pkgs.writeText "picom.conf" (toConf cfg.settings);
+
+in {
+
+  imports = [
+    (mkAliasOptionModule [ "services" "compton" ] [ "services" "picom" ])
+  ];
+
+  options.services.picom = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether of not to enable Picom as the X.org composite manager.
+      '';
+    };
+
+    fade = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Fade windows in and out.
+      '';
+    };
+
+    fadeDelta = mkOption {
+      type = types.ints.positive;
+      default = 10;
+      example = 5;
+      description = ''
+        Time between fade animation step (in ms).
+      '';
+    };
+
+    fadeSteps = mkOption {
+      type = pairOf (floatBetween 0.01 1);
+      default = [ 0.028 0.03 ];
+      example = [ 0.04 0.04 ];
+      description = ''
+        Opacity change between fade steps (in and out).
+      '';
+    };
+
+    fadeExclude = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [
+        "window_type *= 'menu'"
+        "name ~= 'Firefox$'"
+        "focused = 1"
+      ];
+      description = ''
+        List of conditions of windows that should not be faded.
+        See <literal>picom(1)</literal> man page for more examples.
+      '';
+    };
+
+    shadow = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Draw window shadows.
+      '';
+    };
+
+    shadowOffsets = mkOption {
+      type = pairOf types.int;
+      default = [ (-15) (-15) ];
+      example = [ (-10) (-15) ];
+      description = ''
+        Left and right offset for shadows (in pixels).
+      '';
+    };
+
+    shadowOpacity = mkOption {
+      type = floatBetween 0 1;
+      default = 0.75;
+      example = 0.8;
+      description = ''
+        Window shadows opacity.
+      '';
+    };
+
+    shadowExclude = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [
+        "window_type *= 'menu'"
+        "name ~= 'Firefox$'"
+        "focused = 1"
+      ];
+      description = ''
+        List of conditions of windows that should have no shadow.
+        See <literal>picom(1)</literal> man page for more examples.
+      '';
+    };
+
+    activeOpacity = mkOption {
+      type = floatBetween 0 1;
+      default = 1.0;
+      example = 0.8;
+      description = ''
+        Opacity of active windows.
+      '';
+    };
+
+    inactiveOpacity = mkOption {
+      type = floatBetween 0.1 1;
+      default = 1.0;
+      example = 0.8;
+      description = ''
+        Opacity of inactive windows.
+      '';
+    };
+
+    menuOpacity = mkOption {
+      type = floatBetween 0 1;
+      default = 1.0;
+      example = 0.8;
+      description = ''
+        Opacity of dropdown and popup menu.
+      '';
+    };
+
+    wintypes = mkOption {
+      type = types.attrs;
+      default = { popup_menu = { opacity = cfg.menuOpacity; }; dropdown_menu = { opacity = cfg.menuOpacity; }; };
+      example = {};
+      description = ''
+        Rules for specific window types.
+      '';
+    };
+
+    opacityRules = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [
+        "95:class_g = 'URxvt' && !_NET_WM_STATE@:32a"
+        "0:_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'"
+      ];
+      description = ''
+        Rules that control the opacity of windows, in format PERCENT:PATTERN.
+      '';
+    };
+
+    backend = mkOption {
+      type = types.enum [ "glx" "xrender" "xr_glx_hybrid" ];
+      default = "xrender";
+      description = ''
+        Backend to use: <literal>glx</literal>, <literal>xrender</literal> or <literal>xr_glx_hybrid</literal>.
+      '';
+    };
+
+    vSync = mkOption {
+      type = with types; either bool
+        (enum [ "none" "drm" "opengl" "opengl-oml" "opengl-swc" "opengl-mswc" ]);
+      default = false;
+      apply = x:
+        let
+          res = x != "none";
+          msg = "The type of services.picom.vSync has changed to bool:"
+                + " interpreting ${x} as ${boolToString res}";
+        in
+          if isBool x then x
+          else warn msg res;
+
+      description = ''
+        Enable vertical synchronization. Chooses the best method
+        (drm, opengl, opengl-oml, opengl-swc, opengl-mswc) automatically.
+        The bool value should be used, the others are just for backwards compatibility.
+      '';
+    };
+
+    refreshRate = mkOption {
+      type = types.ints.unsigned;
+      default = 0;
+      example = 60;
+      description = ''
+       Screen refresh rate (0 = automatically detect).
+      '';
+    };
+
+    settings = with types;
+    let
+      scalar = oneOf [ bool int float str ]
+        // { description = "scalar types"; };
+
+      libConfig = oneOf [ scalar (listOf libConfig) (attrsOf libConfig) ]
+        // { description = "libconfig type"; };
+
+      topLevel = attrsOf libConfig
+        // { description = ''
+               libconfig configuration. The format consists of an attributes
+               set (called a group) of settings. Each setting can be a scalar type
+               (boolean, integer, floating point number or string), a list of
+               scalars or a group itself
+             '';
+           };
+
+    in mkOption {
+      type = topLevel;
+      default = { };
+      example = literalExample ''
+        blur =
+          { method = "gaussian";
+            size = 10;
+            deviation = 5.0;
+          };
+      '';
+      description = ''
+        Picom settings. Use this option to configure Picom settings not exposed
+        in a NixOS option or to bypass one.  For the available options see the
+        CONFIGURATION FILES section at <literal>picom(1)</literal>.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.picom.settings = mkDefaultAttrs {
+      # fading
+      fading           = cfg.fade;
+      fade-delta       = cfg.fadeDelta;
+      fade-in-step     = elemAt cfg.fadeSteps 0;
+      fade-out-step    = elemAt cfg.fadeSteps 1;
+      fade-exclude     = cfg.fadeExclude;
+
+      # shadows
+      shadow           = cfg.shadow;
+      shadow-offset-x  = elemAt cfg.shadowOffsets 0;
+      shadow-offset-y  = elemAt cfg.shadowOffsets 1;
+      shadow-opacity   = cfg.shadowOpacity;
+      shadow-exclude   = cfg.shadowExclude;
+
+      # opacity
+      active-opacity   = cfg.activeOpacity;
+      inactive-opacity = cfg.inactiveOpacity;
+
+      wintypes         = cfg.wintypes;
+
+      opacity-rule     = cfg.opacityRules;
+
+      # other options
+      backend          = cfg.backend;
+      vsync            = cfg.vSync;
+      refresh-rate     = cfg.refreshRate;
+    };
+
+    systemd.user.services.picom = {
+      description = "Picom composite manager";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+
+      # Temporarily fixes corrupt colours with Mesa 18
+      environment = mkIf (cfg.backend == "glx") {
+        allow_rgb10_configs = "false";
+      };
+
+      serviceConfig = {
+        ExecStart = "${pkgs.picom}/bin/picom --config ${configFile}";
+        RestartSec = 3;
+        Restart = "always";
+      };
+    };
+
+    environment.systemPackages = [ pkgs.picom ];
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/redshift.nix b/nixpkgs/nixos/modules/services/x11/redshift.nix
new file mode 100644
index 000000000000..21b0b33553ac
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/redshift.nix
@@ -0,0 +1,129 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.redshift;
+  lcfg = config.location;
+
+in {
+
+  imports = [
+    (mkChangedOptionModule [ "services" "redshift" "latitude" ] [ "location" "latitude" ]
+      (config:
+        let value = getAttrFromPath [ "services" "redshift" "latitude" ] config;
+        in if value == null then
+          throw "services.redshift.latitude is set to null, you can remove this"
+          else builtins.fromJSON value))
+    (mkChangedOptionModule [ "services" "redshift" "longitude" ] [ "location" "longitude" ]
+      (config:
+        let value = getAttrFromPath [ "services" "redshift" "longitude" ] config;
+        in if value == null then
+          throw "services.redshift.longitude is set to null, you can remove this"
+          else builtins.fromJSON value))
+    (mkRenamedOptionModule [ "services" "redshift" "provider" ] [ "location" "provider" ])
+  ];
+
+  options.services.redshift = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable Redshift to change your screen's colour temperature depending on
+        the time of day.
+      '';
+    };
+
+    temperature = {
+      day = mkOption {
+        type = types.int;
+        default = 5500;
+        description = ''
+          Colour temperature to use during the day, between
+          <literal>1000</literal> and <literal>25000</literal> K.
+        '';
+      };
+      night = mkOption {
+        type = types.int;
+        default = 3700;
+        description = ''
+          Colour temperature to use at night, between
+          <literal>1000</literal> and <literal>25000</literal> K.
+        '';
+      };
+    };
+
+    brightness = {
+      day = mkOption {
+        type = types.str;
+        default = "1";
+        description = ''
+          Screen brightness to apply during the day,
+          between <literal>0.1</literal> and <literal>1.0</literal>.
+        '';
+      };
+      night = mkOption {
+        type = types.str;
+        default = "1";
+        description = ''
+          Screen brightness to apply during the night,
+          between <literal>0.1</literal> and <literal>1.0</literal>.
+        '';
+      };
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.redshift;
+      defaultText = "pkgs.redshift";
+      description = ''
+        redshift derivation to use.
+      '';
+    };
+
+    extraOptions = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [ "-v" "-m randr" ];
+      description = ''
+        Additional command-line arguments to pass to
+        <command>redshift</command>.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    # needed so that .desktop files are installed, which geoclue cares about
+    environment.systemPackages = [ cfg.package ];
+
+    services.geoclue2.appConfig.redshift = {
+      isAllowed = true;
+      isSystem = true;
+    };
+
+    systemd.user.services.redshift =
+    let
+      providerString = if lcfg.provider == "manual"
+        then "${toString lcfg.latitude}:${toString lcfg.longitude}"
+        else lcfg.provider;
+    in
+    {
+      description = "Redshift colour temperature adjuster";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/redshift \
+            -l ${providerString} \
+            -t ${toString cfg.temperature.day}:${toString cfg.temperature.night} \
+            -b ${toString cfg.brightness.day}:${toString cfg.brightness.night} \
+            ${lib.strings.concatStringsSep " " cfg.extraOptions}
+        '';
+        RestartSec = 3;
+        Restart = "always";
+      };
+    };
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/terminal-server.nix b/nixpkgs/nixos/modules/services/x11/terminal-server.nix
new file mode 100644
index 000000000000..503c14c9b624
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/terminal-server.nix
@@ -0,0 +1,56 @@
+# This module implements a terminal service based on ‘x11vnc’.  It
+# listens on port 5900 for VNC connections.  It then presents a login
+# screen to the user.  If the user successfully authenticates, x11vnc
+# checks to see if a X server is already running for that user.  If
+# not, a X server (Xvfb) is started for that user.  The Xvfb instances
+# persist across VNC sessions.
+
+{ lib, pkgs, ... }:
+
+with lib;
+
+{
+
+  config = {
+
+    services.xserver.enable = true;
+    services.xserver.videoDrivers = [];
+
+    # Enable GDM.  Any display manager will do as long as it supports XDMCP.
+    services.xserver.displayManager.gdm.enable = true;
+
+    systemd.sockets.terminal-server =
+      { description = "Terminal Server Socket";
+        wantedBy = [ "sockets.target" ];
+        before = [ "multi-user.target" ];
+        socketConfig.Accept = true;
+        socketConfig.ListenStream = 5900;
+      };
+
+    systemd.services."terminal-server@" =
+      { description = "Terminal Server";
+
+        path =
+          [ pkgs.xorg.xorgserver.out pkgs.gawk pkgs.which pkgs.openssl pkgs.xorg.xauth
+            pkgs.nettools pkgs.shadow pkgs.procps pkgs.utillinux pkgs.bash
+          ];
+
+        environment.FD_GEOM = "1024x786x24";
+        environment.FD_XDMCP_IF = "127.0.0.1";
+        #environment.FIND_DISPLAY_OUTPUT = "/tmp/foo"; # to debug the "find display" script
+
+        serviceConfig =
+          { StandardInput = "socket";
+            StandardOutput = "socket";
+            StandardError = "journal";
+            ExecStart = "@${pkgs.x11vnc}/bin/x11vnc x11vnc -inetd -display WAIT:1024x786:cmd=FINDCREATEDISPLAY-Xvfb.xdmcp -unixpw -ssl SAVE";
+            # Don't kill the X server when the user quits the VNC
+            # connection.  FIXME: the X server should run in a
+            # separate systemd session.
+            KillMode = "process";
+          };
+      };
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/unclutter-xfixes.nix b/nixpkgs/nixos/modules/services/x11/unclutter-xfixes.nix
new file mode 100644
index 000000000000..71262431b685
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/unclutter-xfixes.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.unclutter-xfixes;
+
+in {
+  options.services.unclutter-xfixes = {
+
+    enable = mkOption {
+      description = "Enable unclutter-xfixes to hide your mouse cursor when inactive.";
+      type = types.bool;
+      default = false;
+    };
+
+    package = mkOption {
+      description = "unclutter-xfixes derivation to use.";
+      type = types.package;
+      default = pkgs.unclutter-xfixes;
+      defaultText = "pkgs.unclutter-xfixes";
+    };
+
+    timeout = mkOption {
+      description = "Number of seconds before the cursor is marked inactive.";
+      type = types.int;
+      default = 1;
+    };
+
+    threshold = mkOption {
+      description = "Minimum number of pixels considered cursor movement.";
+      type = types.int;
+      default = 1;
+    };
+
+    extraOptions = mkOption {
+      description = "More arguments to pass to the unclutter-xfixes command.";
+      type = types.listOf types.str;
+      default = [];
+      example = [ "exclude-root" "ignore-scrolling" "fork" ];
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.unclutter-xfixes = {
+      description = "unclutter-xfixes";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+      serviceConfig.ExecStart = ''
+        ${cfg.package}/bin/unclutter \
+          --timeout ${toString cfg.timeout} \
+          --jitter ${toString (cfg.threshold - 1)} \
+          ${concatMapStrings (x: " --"+x) cfg.extraOptions} \
+      '';
+      serviceConfig.RestartSec = 3;
+      serviceConfig.Restart = "always";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/unclutter.nix b/nixpkgs/nixos/modules/services/x11/unclutter.nix
new file mode 100644
index 000000000000..56e30c79d1f1
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/unclutter.nix
@@ -0,0 +1,82 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.unclutter;
+
+in {
+  options.services.unclutter = {
+
+    enable = mkOption {
+      description = "Enable unclutter to hide your mouse cursor when inactive";
+      type = types.bool;
+      default = false;
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.unclutter;
+      defaultText = "pkgs.unclutter";
+      description = "unclutter derivation to use.";
+    };
+
+    keystroke = mkOption {
+      description = "Wait for a keystroke before hiding the cursor";
+      type = types.bool;
+      default = false;
+    };
+
+    timeout = mkOption {
+      description = "Number of seconds before the cursor is marked inactive";
+      type = types.int;
+      default = 1;
+    };
+
+    threshold = mkOption {
+      description = "Minimum number of pixels considered cursor movement";
+      type = types.int;
+      default = 1;
+    };
+
+    excluded = mkOption {
+      description = "Names of windows where unclutter should not apply";
+      type = types.listOf types.str;
+      default = [];
+      example = [ "" ];
+    };
+
+    extraOptions = mkOption {
+      description = "More arguments to pass to the unclutter command";
+      type = types.listOf types.str;
+      default = [];
+      example = [ "noevent" "grab" ];
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.unclutter = {
+      description = "unclutter";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+      serviceConfig.ExecStart = ''
+        ${cfg.package}/bin/unclutter \
+          -idle ${toString cfg.timeout} \
+          -jitter ${toString (cfg.threshold - 1)} \
+          ${optionalString cfg.keystroke "-keystroke"} \
+          ${concatMapStrings (x: " -"+x) cfg.extraOptions} \
+          -not ${concatStringsSep " " cfg.excluded} \
+      '';
+      serviceConfig.PassEnvironment = "DISPLAY";
+      serviceConfig.RestartSec = 3;
+      serviceConfig.Restart = "always";
+    };
+  };
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "unclutter" "threeshold" ]
+                           [ "services"  "unclutter" "threshold" ])
+  ];
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/urxvtd.nix b/nixpkgs/nixos/modules/services/x11/urxvtd.nix
new file mode 100644
index 000000000000..867ac38a944f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/urxvtd.nix
@@ -0,0 +1,50 @@
+{ config, lib, pkgs, ... }:
+
+# maintainer: siddharthist
+
+with lib;
+
+let
+  cfg = config.services.urxvtd;
+in {
+  options.services.urxvtd = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable urxvtd, the urxvt terminal daemon. To use urxvtd, run
+        "urxvtc".
+      '';
+    };
+
+    package = mkOption {
+      default = pkgs.rxvt-unicode;
+      defaultText = "pkgs.rxvt-unicode";
+      description = ''
+        Package to install. Usually pkgs.rxvt-unicode.
+      '';
+      type = types.package;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.urxvtd = {
+      description = "urxvt terminal daemon";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+      path = [ pkgs.xsel ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/urxvtd -o";
+        Environment = "RXVT_SOCKET=%t/urxvtd-socket";
+        Restart = "on-failure";
+        RestartSec = "5s";
+      };
+    };
+
+    environment.systemPackages = [ cfg.package ];
+    environment.variables.RXVT_SOCKET = "/run/user/$(id -u)/urxvtd-socket";
+  };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix
new file mode 100644
index 000000000000..fdbdf35b0f5a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/2bwm.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.windowManager."2bwm";
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.xserver.windowManager."2bwm".enable = mkEnableOption "2bwm";
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.xserver.windowManager.session = singleton
+      { name = "2bwm";
+        start =
+          ''
+            ${pkgs._2bwm}/bin/2bwm &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ pkgs._2bwm ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix b/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix
new file mode 100644
index 000000000000..ba88a64c702a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/afterstep.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.afterstep;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.afterstep.enable = mkEnableOption "afterstep";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "afterstep";
+      start = ''
+        ${pkgs.afterstep}/bin/afterstep &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.afterstep ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix b/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix
new file mode 100644
index 000000000000..089e9f769f0a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/awesome.nix
@@ -0,0 +1,66 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.windowManager.awesome;
+  awesome = cfg.package;
+  getLuaPath = lib : dir : "${lib}/${dir}/lua/${pkgs.luaPackages.lua.luaversion}";
+  makeSearchPath = lib.concatMapStrings (path:
+    " --search " + (getLuaPath path "share") +
+    " --search " + (getLuaPath path "lib")
+  );
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.xserver.windowManager.awesome = {
+
+      enable = mkEnableOption "Awesome window manager";
+
+      luaModules = mkOption {
+        default = [];
+        type = types.listOf types.package;
+        description = "List of lua packages available for being used in the Awesome configuration.";
+        example = literalExample "[ luaPackages.oocairo ]";
+      };
+
+      package = mkOption {
+        default = null;
+        type = types.nullOr types.package;
+        description = "Package to use for running the Awesome WM.";
+        apply = pkg: if pkg == null then pkgs.awesome else pkg;
+      };
+
+      noArgb = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Disable client transparency support, which can be greatly detrimental to performance in some setups";
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.xserver.windowManager.session = singleton
+      { name = "awesome";
+        start =
+          ''
+            ${awesome}/bin/awesome ${lib.optionalString cfg.noArgb "--no-argb"} ${makeSearchPath cfg.luaModules} &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ awesome ];
+
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/berry.nix b/nixpkgs/nixos/modules/services/x11/window-managers/berry.nix
new file mode 100644
index 000000000000..0d2285e7a60e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/berry.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.berry;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.berry.enable = mkEnableOption "berry";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "berry";
+      start = ''
+        ${pkgs.berry}/bin/berry &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.berry ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix
new file mode 100644
index 000000000000..23cd4f6529a6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/bspwm.nix
@@ -0,0 +1,77 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.bspwm;
+in
+
+{
+  options = {
+    services.xserver.windowManager.bspwm = {
+      enable = mkEnableOption "bspwm";
+
+      package = mkOption {
+        type        = types.package;
+        default     = pkgs.bspwm;
+        defaultText = "pkgs.bspwm";
+        example     = "pkgs.bspwm-unstable";
+        description = ''
+          bspwm package to use.
+        '';
+      };
+      configFile = mkOption {
+        type        = with types; nullOr path;
+        example     = "${pkgs.bspwm}/share/doc/bspwm/examples/bspwmrc";
+        default     = null;
+        description = ''
+          Path to the bspwm configuration file.
+          If null, $HOME/.config/bspwm/bspwmrc will be used.
+        '';
+      };
+
+      sxhkd = {
+        package = mkOption {
+          type        = types.package;
+          default     = pkgs.sxhkd;
+          defaultText = "pkgs.sxhkd";
+          example     = "pkgs.sxhkd-unstable";
+          description = ''
+            sxhkd package to use.
+          '';
+        };
+        configFile = mkOption {
+          type        = with types; nullOr path;
+          example     = "${pkgs.bspwm}/share/doc/bspwm/examples/sxhkdrc";
+          default     = null;
+          description = ''
+            Path to the sxhkd configuration file.
+            If null, $HOME/.config/sxhkd/sxhkdrc will be used.
+          '';
+        };
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name  = "bspwm";
+      start = ''
+        export _JAVA_AWT_WM_NONREPARENTING=1
+        SXHKD_SHELL=/bin/sh ${cfg.sxhkd.package}/bin/sxhkd ${optionalString (cfg.sxhkd.configFile != null) "-c \"${cfg.sxhkd.configFile}\""} &
+        ${cfg.package}/bin/bspwm ${optionalString (cfg.configFile != null) "-c \"${cfg.configFile}\""} &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ cfg.package ];
+  };
+
+  imports = [
+   (mkRemovedOptionModule [ "services" "xserver" "windowManager" "bspwm-unstable" "enable" ]
+     "Use services.xserver.windowManager.bspwm.enable and set services.xserver.windowManager.bspwm.package to pkgs.bspwm-unstable to use the unstable version of bspwm.")
+   (mkRemovedOptionModule [ "services" "xserver" "windowManager" "bspwm" "startThroughSession" ]
+     "bspwm package does not provide bspwm-session anymore.")
+   (mkRemovedOptionModule [ "services" "xserver" "windowManager" "bspwm" "sessionScript" ]
+     "bspwm package does not provide bspwm-session anymore.")
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix
new file mode 100644
index 000000000000..176c1f461271
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/clfswm.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.clfswm;
+in
+
+{
+  options = {
+    services.xserver.windowManager.clfswm.enable = mkEnableOption "clfswm";
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "clfswm";
+      start = ''
+        ${pkgs.clfswm}/bin/clfswm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.clfswm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/cwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/cwm.nix
new file mode 100644
index 000000000000..03375a226bb6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/cwm.nix
@@ -0,0 +1,23 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.cwm;
+in
+{
+  options = {
+    services.xserver.windowManager.cwm.enable = mkEnableOption "cwm";
+  };
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton
+      { name = "cwm";
+        start =
+          ''
+            cwm &
+            waitPID=$!
+          '';
+      };
+    environment.systemPackages = [ pkgs.cwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/default.nix b/nixpkgs/nixos/modules/services/x11/window-managers/default.nix
new file mode 100644
index 000000000000..87702c58727a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/default.nix
@@ -0,0 +1,84 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager;
+in
+
+{
+  imports = [
+    ./2bwm.nix
+    ./afterstep.nix
+    ./berry.nix
+    ./bspwm.nix
+    ./cwm.nix
+    ./dwm.nix
+    ./evilwm.nix
+    ./exwm.nix
+    ./fluxbox.nix
+    ./fvwm.nix
+    ./herbstluftwm.nix
+    ./i3.nix
+    ./jwm.nix
+    ./leftwm.nix
+    ./lwm.nix
+    ./metacity.nix
+    ./mwm.nix
+    ./openbox.nix
+    ./pekwm.nix
+    ./notion.nix
+    ./ratpoison.nix
+    ./sawfish.nix
+    ./smallwm.nix
+    ./stumpwm.nix
+    ./spectrwm.nix
+    ./tinywm.nix
+    ./twm.nix
+    ./windowmaker.nix
+    ./wmii.nix
+    ./xmonad.nix
+    ./yeahwm.nix
+    ./qtile.nix
+    ./none.nix ];
+
+  options = {
+
+    services.xserver.windowManager = {
+
+      session = mkOption {
+        internal = true;
+        default = [];
+        example = [{
+          name = "wmii";
+          start = "...";
+        }];
+        description = ''
+          Internal option used to add some common line to window manager
+          scripts before forwarding the value to the
+          <varname>displayManager</varname>.
+        '';
+        apply = map (d: d // {
+          manage = "window";
+        });
+      };
+
+      default = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "wmii";
+        description = ''
+          <emphasis role="strong">Deprecated</emphasis>, please use <xref linkend="opt-services.xserver.displayManager.defaultSession"/> instead.
+
+          Default window manager loaded if none have been chosen.
+        '';
+      };
+
+    };
+
+  };
+
+  config = {
+    services.xserver.displayManager.session = cfg.session;
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix
new file mode 100644
index 000000000000..7777913ce1e6
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/dwm.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.windowManager.dwm;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.xserver.windowManager.dwm.enable = mkEnableOption "dwm";
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.xserver.windowManager.session = singleton
+      { name = "dwm";
+        start =
+          ''
+            dwm &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ pkgs.dwm ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix
new file mode 100644
index 000000000000..6e19e3572c79
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/evilwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.evilwm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.evilwm.enable = mkEnableOption "evilwm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "evilwm";
+      start = ''
+	${pkgs.evilwm}/bin/evilwm &
+	waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.evilwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix
new file mode 100644
index 000000000000..dc1d957c1709
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/exwm.nix
@@ -0,0 +1,54 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.exwm;
+  loadScript = pkgs.writeText "emacs-exwm-load" ''
+    (require 'exwm)
+    ${optionalString cfg.enableDefaultConfig ''
+      (require 'exwm-config)
+      (exwm-config-default)
+    ''}
+  '';
+  packages = epkgs: cfg.extraPackages epkgs ++ [ epkgs.exwm ];
+  exwm-emacs = pkgs.emacsWithPackages packages;
+in
+
+{
+  options = {
+    services.xserver.windowManager.exwm = {
+      enable = mkEnableOption "exwm";
+      enableDefaultConfig = mkOption {
+        default = true;
+        type = lib.types.bool;
+        description = "Enable an uncustomised exwm configuration.";
+      };
+      extraPackages = mkOption {
+        default = self: [];
+        example = literalExample ''
+          epkgs: [
+            epkgs.emms
+            epkgs.magit
+            epkgs.proofgeneral
+          ]
+        '';
+        description = ''
+          Extra packages available to Emacs. The value must be a
+          function which receives the attrset defined in
+          <varname>emacsPackages</varname> as the sole argument.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "exwm";
+      start = ''
+        ${exwm-emacs}/bin/emacs -l ${loadScript}
+      '';
+    };
+    environment.systemPackages = [ exwm-emacs ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix b/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix
new file mode 100644
index 000000000000..b409335702af
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/fluxbox.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.fluxbox;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.fluxbox.enable = mkEnableOption "fluxbox";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "fluxbox";
+      start = ''
+        ${pkgs.fluxbox}/bin/startfluxbox &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.fluxbox ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/fvwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/fvwm.nix
new file mode 100644
index 000000000000..9a51b9cd6602
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/fvwm.nix
@@ -0,0 +1,41 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.fvwm;
+  fvwm = pkgs.fvwm.override { gestures = cfg.gestures; };
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.xserver.windowManager.fvwm = {
+      enable = mkEnableOption "Fvwm window manager";
+
+      gestures = mkOption {
+        default = false;
+        type = types.bool;
+        description = "Whether or not to enable libstroke for gesture support";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton
+      { name = "fvwm";
+        start =
+          ''
+            ${fvwm}/bin/fvwm &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ fvwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix
new file mode 100644
index 000000000000..e3ea61cb9a6b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/herbstluftwm.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.herbstluftwm;
+in
+
+{
+  options = {
+    services.xserver.windowManager.herbstluftwm = {
+      enable = mkEnableOption "herbstluftwm";
+
+      configFile = mkOption {
+        default     = null;
+        type        = with types; nullOr path;
+        description = ''
+          Path to the herbstluftwm configuration file.  If left at the
+          default value, $XDG_CONFIG_HOME/herbstluftwm/autostart will
+          be used.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "herbstluftwm";
+      start =
+        let configFileClause = optionalString
+            (cfg.configFile != null)
+            ''-c "${cfg.configFile}"''
+            ;
+        in "${pkgs.herbstluftwm}/bin/herbstluftwm ${configFileClause}";
+    };
+    environment.systemPackages = [ pkgs.herbstluftwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix b/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix
new file mode 100644
index 000000000000..0ef55d5f2c03
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/i3.nix
@@ -0,0 +1,78 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.i3;
+in
+
+{
+  options.services.xserver.windowManager.i3 = {
+    enable = mkEnableOption "i3 window manager";
+
+    configFile = mkOption {
+      default     = null;
+      type        = with types; nullOr path;
+      description = ''
+        Path to the i3 configuration file.
+        If left at the default value, $HOME/.i3/config will be used.
+      '';
+    };
+
+    extraSessionCommands = mkOption {
+      default     = "";
+      type        = types.lines;
+      description = ''
+        Shell commands executed just before i3 is started.
+      '';
+    };
+
+    package = mkOption {
+      type        = types.package;
+      default     = pkgs.i3;
+      defaultText = "pkgs.i3";
+      example     = "pkgs.i3-gaps";
+      description = ''
+        i3 package to use.
+      '';
+    };
+
+    extraPackages = mkOption {
+      type = with types; listOf package;
+      default = with pkgs; [ dmenu i3status i3lock ];
+      example = literalExample ''
+        with pkgs; [
+          dmenu
+          i3status
+          i3lock
+        ]
+      '';
+      description = ''
+        Extra packages to be installed system wide.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = [{
+      name  = "i3";
+      start = ''
+        ${cfg.extraSessionCommands}
+
+        ${cfg.package}/bin/i3 ${optionalString (cfg.configFile != null)
+          "-c /etc/i3/config"
+        } &
+        waitPID=$!
+      '';
+    }];
+    environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
+    environment.etc."i3/config" = mkIf (cfg.configFile != null) {
+      source = cfg.configFile;
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule [ "services" "xserver" "windowManager" "i3-gaps" "enable" ]
+      "Use services.xserver.windowManager.i3.enable and set services.xserver.windowManager.i3.package to pkgs.i3-gaps to use i3-gaps.")
+  ];
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix
new file mode 100644
index 000000000000..f4ae9222df67
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/icewm.nix
@@ -0,0 +1,27 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.icewm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.icewm.enable = mkEnableOption "icewm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton
+      { name = "icewm";
+        start =
+          ''
+            ${pkgs.icewm}/bin/icewm &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ pkgs.icewm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix
new file mode 100644
index 000000000000..0e8dab2e9224
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/jwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.jwm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.jwm.enable = mkEnableOption "jwm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "jwm";
+      start = ''
+        ${pkgs.jwm}/bin/jwm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.jwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix
new file mode 100644
index 000000000000..3ef40df95df2
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/leftwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.leftwm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.leftwm.enable = mkEnableOption "leftwm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "leftwm";
+      start = ''
+        ${pkgs.leftwm}/bin/leftwm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.leftwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/lwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/lwm.nix
new file mode 100644
index 000000000000..e2aa062fd13b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/lwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.lwm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.lwm.enable = mkEnableOption "lwm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "lwm";
+      start = ''
+        ${pkgs.lwm}/bin/lwm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.lwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix b/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix
new file mode 100644
index 000000000000..5175fd7f3b1f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/metacity.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.windowManager.metacity;
+  inherit (pkgs) gnome3;
+in
+
+{
+  options = {
+    services.xserver.windowManager.metacity.enable = mkEnableOption "metacity";
+  };
+
+  config = mkIf cfg.enable {
+
+    services.xserver.windowManager.session = singleton
+      { name = "metacity";
+        start = ''
+          ${gnome3.metacity}/bin/metacity &
+          waitPID=$!
+        '';
+      };
+
+    environment.systemPackages = [ gnome3.metacity ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix
new file mode 100644
index 000000000000..31f7b725f747
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/mwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.mwm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.mwm.enable = mkEnableOption "mwm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "mwm";
+      start = ''
+        ${pkgs.motif}/bin/mwm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.motif ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/none.nix b/nixpkgs/nixos/modules/services/x11/window-managers/none.nix
new file mode 100644
index 000000000000..84cf1d770776
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/none.nix
@@ -0,0 +1,12 @@
+{
+  services = {
+    xserver = {
+      windowManager = {
+        session = [{
+          name = "none";
+          start = "";
+        }];
+      };
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix b/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix
new file mode 100644
index 000000000000..4ece0d241c90
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/notion.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.notion;
+in
+
+{
+  options = {
+    services.xserver.windowManager.notion.enable = mkEnableOption "notion";
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager = {
+      session = [{
+        name = "notion";
+        start = ''
+          ${pkgs.notion}/bin/notion &
+          waitPID=$!
+        '';
+      }];
+    };
+    environment.systemPackages = [ pkgs.notion ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix b/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix
new file mode 100644
index 000000000000..165772d1aa09
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/openbox.nix
@@ -0,0 +1,24 @@
+{lib, pkgs, config, ...}:
+
+with lib;
+let
+  cfg = config.services.xserver.windowManager.openbox;
+in
+
+{
+  options = {
+    services.xserver.windowManager.openbox.enable = mkEnableOption "openbox";
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager = {
+      session = [{
+        name = "openbox";
+        start = "
+          ${pkgs.openbox}/bin/openbox-session
+        ";
+      }];
+    };
+    environment.systemPackages = [ pkgs.openbox ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix b/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix
new file mode 100644
index 000000000000..bd7e3396864b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/oroborus.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.oroborus;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.oroborus.enable = mkEnableOption "oroborus";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "oroborus";
+      start = ''
+        ${pkgs.oroborus}/bin/oroborus &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.oroborus ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix
new file mode 100644
index 000000000000..850335ce7ddf
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/pekwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.pekwm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.pekwm.enable = mkEnableOption "pekwm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "pekwm";
+      start = ''
+        ${pkgs.pekwm}/bin/pekwm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.pekwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix b/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix
new file mode 100644
index 000000000000..ad3b65150b01
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/qtile.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.qtile;
+in
+
+{
+  options = {
+    services.xserver.windowManager.qtile.enable = mkEnableOption "qtile";
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = [{
+      name = "qtile";
+      start = ''
+        ${pkgs.qtile}/bin/qtile &
+        waitPID=$!
+      '';
+    }];
+    
+    environment.systemPackages = [ pkgs.qtile ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix b/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix
new file mode 100644
index 000000000000..0d58481d4579
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/ratpoison.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.ratpoison;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.ratpoison.enable = mkEnableOption "ratpoison";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "ratpoison";
+      start = ''
+        ${pkgs.ratpoison}/bin/ratpoison &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.ratpoison ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix b/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix
new file mode 100644
index 000000000000..b988b5e1829e
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/sawfish.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.sawfish;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.sawfish.enable = mkEnableOption "sawfish";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "sawfish";
+      start = ''
+        ${pkgs.sawfish}/bin/sawfish &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.sawfish ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/smallwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/smallwm.nix
new file mode 100644
index 000000000000..091ba4f92b94
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/smallwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.smallwm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.smallwm.enable = mkEnableOption "smallwm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "smallwm";
+      start = ''
+        ${pkgs.smallwm}/bin/smallwm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.smallwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix
new file mode 100644
index 000000000000..a1dc298d2426
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/spectrwm.nix
@@ -0,0 +1,27 @@
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.spectrwm;
+in
+
+{
+  options = {
+    services.xserver.windowManager.spectrwm.enable = mkEnableOption "spectrwm";
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager = {
+      session = [{
+        name = "spectrwm";
+        start = ''
+          ${pkgs.spectrwm}/bin/spectrwm &
+          waitPID=$!
+        '';
+      }];
+    };
+    environment.systemPackages = [ pkgs.spectrwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix
new file mode 100644
index 000000000000..27a17178476a
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/stumpwm.nix
@@ -0,0 +1,24 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.stumpwm;
+in
+
+{
+  options = {
+    services.xserver.windowManager.stumpwm.enable = mkEnableOption "stumpwm";
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "stumpwm";
+      start = ''
+        ${pkgs.lispPackages.stumpwm}/bin/stumpwm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.lispPackages.stumpwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/tinywm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/tinywm.nix
new file mode 100644
index 000000000000..8e5d9b9170ca
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/tinywm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.tinywm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.tinywm.enable = mkEnableOption "tinywm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "tinywm";
+      start = ''
+        ${pkgs.tinywm}/bin/tinywm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.tinywm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix
new file mode 100644
index 000000000000..fc09901aae3b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/twm.nix
@@ -0,0 +1,37 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.windowManager.twm;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.xserver.windowManager.twm.enable = mkEnableOption "twm";
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.xserver.windowManager.session = singleton
+      { name = "twm";
+        start =
+          ''
+            ${pkgs.xorg.twm}/bin/twm &
+            waitPID=$!
+          '';
+      };
+
+    environment.systemPackages = [ pkgs.xorg.twm ];
+
+  };
+
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix b/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix
new file mode 100644
index 000000000000..fb891a39fa41
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/windowlab.nix
@@ -0,0 +1,22 @@
+{lib, pkgs, config, ...}:
+
+let
+  cfg = config.services.xserver.windowManager.windowlab;
+in
+
+{
+  options = {
+    services.xserver.windowManager.windowlab.enable =
+      lib.mkEnableOption "windowlab";
+  };
+
+  config = lib.mkIf cfg.enable {
+    services.xserver.windowManager = {
+      session =
+        [{ name  = "windowlab";
+           start = "${pkgs.windowlab}/bin/windowlab";
+        }];
+    };
+    environment.systemPackages = [ pkgs.windowlab ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix b/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix
new file mode 100644
index 000000000000..b62723758056
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/windowmaker.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.windowmaker;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.windowmaker.enable = mkEnableOption "windowmaker";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "windowmaker";
+      start = ''
+        ${pkgs.windowmaker}/bin/wmaker &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.windowmaker ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix b/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix
new file mode 100644
index 000000000000..9b50a99bf23f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/wmii.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.services.xserver.windowManager.wmii;
+  wmii = pkgs.wmii_hg;
+in
+{
+  options = {
+    services.xserver.windowManager.wmii.enable = mkEnableOption "wmii";
+  };
+
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton
+      # stop wmii by
+      #   $wmiir xwrite /ctl quit
+      # this will cause wmii exiting with exit code 0
+      # (or "mod+a quit", which is bound to do the same thing in wmiirc
+      # by default)
+      #
+      # why this loop?
+      # wmii crashes once a month here. That doesn't matter that much
+      # wmii can recover very well. However without loop the X session
+      # terminates and then your workspace setup is lost and all
+      # applications running on X will terminate.
+      # Another use case is kill -9 wmii; after rotating screen.
+      # Note: we don't like kill for that purpose. But it works (->
+      # subject "wmii and xrandr" on mailinglist)
+      { name = "wmii";
+        start = ''
+          while :; do
+            ${wmii}/bin/wmii && break
+          done
+        '';
+      };
+
+    environment.systemPackages = [ wmii ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix b/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
new file mode 100644
index 000000000000..30c59b88f82f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -0,0 +1,97 @@
+{pkgs, lib, config, ...}:
+
+with lib;
+let
+  inherit (lib) mkOption mkIf optionals literalExample;
+  cfg = config.services.xserver.windowManager.xmonad;
+  xmonad = pkgs.xmonad-with-packages.override {
+    ghcWithPackages = cfg.haskellPackages.ghcWithPackages;
+    packages = self: cfg.extraPackages self ++
+                     optionals cfg.enableContribAndExtras
+                     [ self.xmonad-contrib self.xmonad-extras ];
+  };
+  xmonadBin = pkgs.writers.writeHaskell "xmonad" {
+    ghc = cfg.haskellPackages.ghc;
+    libraries = [ cfg.haskellPackages.xmonad ] ++
+                cfg.extraPackages cfg.haskellPackages ++
+                optionals cfg.enableContribAndExtras
+                (with cfg.haskellPackages; [ xmonad-contrib xmonad-extras ]);
+  } cfg.config;
+
+in
+{
+  options = {
+    services.xserver.windowManager.xmonad = {
+      enable = mkEnableOption "xmonad";
+      haskellPackages = mkOption {
+        default = pkgs.haskellPackages;
+        defaultText = "pkgs.haskellPackages";
+        example = literalExample "pkgs.haskell.packages.ghc784";
+        description = ''
+          haskellPackages used to build Xmonad and other packages.
+          This can be used to change the GHC version used to build
+          Xmonad and the packages listed in
+          <varname>extraPackages</varname>.
+        '';
+      };
+
+      extraPackages = mkOption {
+        default = self: [];
+        defaultText = "self: []";
+        example = literalExample ''
+          haskellPackages: [
+            haskellPackages.xmonad-contrib
+            haskellPackages.monad-logger
+          ]
+        '';
+        description = ''
+          Extra packages available to ghc when rebuilding Xmonad. The
+          value must be a function which receives the attrset defined
+          in <varname>haskellPackages</varname> as the sole argument.
+        '';
+      };
+
+      enableContribAndExtras = mkOption {
+        default = false;
+        type = lib.types.bool;
+        description = "Enable xmonad-{contrib,extras} in Xmonad.";
+      };
+
+      config = mkOption {
+        default = null;
+        type = with lib.types; nullOr (either path str);
+        description = ''
+          Configuration from which XMonad gets compiled. If no value
+          is specified, the xmonad config from $HOME/.xmonad is taken.
+          If you use xmonad --recompile, $HOME/.xmonad will be taken as
+          the configuration, but on the next restart of display-manager
+          this config will be reapplied.
+        '';
+        example = ''
+          import XMonad
+
+          main = launch defaultConfig
+                 { modMask = mod4Mask -- Use Super instead of Alt
+                 , terminal = "urxvt"
+                 }
+        '';
+      };
+    };
+  };
+  config = mkIf cfg.enable {
+    services.xserver.windowManager = {
+      session = [{
+        name = "xmonad";
+        start = if (cfg.config != null) then ''
+          ${xmonadBin}
+          waitPID=$!
+        '' else ''
+          systemd-cat -t xmonad ${xmonad}/bin/xmonad &
+          waitPID=$!
+        '';
+      }];
+    };
+
+    environment.systemPackages = [ xmonad ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/window-managers/yeahwm.nix b/nixpkgs/nixos/modules/services/x11/window-managers/yeahwm.nix
new file mode 100644
index 000000000000..351bd7dfe48b
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/window-managers/yeahwm.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.windowManager.yeahwm;
+in
+{
+  ###### interface
+  options = {
+    services.xserver.windowManager.yeahwm.enable = mkEnableOption "yeahwm";
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    services.xserver.windowManager.session = singleton {
+      name = "yeahwm";
+      start = ''
+        ${pkgs.yeahwm}/bin/yeahwm &
+        waitPID=$!
+      '';
+    };
+    environment.systemPackages = [ pkgs.yeahwm ];
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/xautolock.nix b/nixpkgs/nixos/modules/services/x11/xautolock.nix
new file mode 100644
index 000000000000..5ce08fce7c43
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xautolock.nix
@@ -0,0 +1,140 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.xautolock;
+in
+  {
+    options = {
+      services.xserver.xautolock = {
+        enable = mkEnableOption "xautolock";
+        enableNotifier = mkEnableOption "xautolock.notify" // {
+          description = ''
+            Whether to enable the notifier feature of xautolock.
+            This publishes a notification before the autolock.
+          '';
+        };
+
+        time = mkOption {
+          default = 15;
+          type = types.int;
+
+          description = ''
+            Idle time (in minutes) to wait until xautolock locks the computer.
+          '';
+        };
+
+        locker = mkOption {
+          default = "${pkgs.xlockmore}/bin/xlock"; # default according to `man xautolock`
+          example = "${pkgs.i3lock}/bin/i3lock -i /path/to/img";
+          type = types.str;
+
+          description = ''
+            The script to use when automatically locking the computer.
+          '';
+        };
+
+        nowlocker = mkOption {
+          default = null;
+          example = "${pkgs.i3lock}/bin/i3lock -i /path/to/img";
+          type = types.nullOr types.str;
+
+          description = ''
+            The script to use when manually locking the computer with <command>xautolock -locknow</command>.
+          '';
+        };
+
+        notify = mkOption {
+          default = 10;
+          type = types.int;
+
+          description = ''
+            Time (in seconds) before the actual lock when the notification about the pending lock should be published.
+          '';
+        };
+
+        notifier = mkOption {
+          default = null;
+          example = "${pkgs.libnotify}/bin/notify-send \"Locking in 10 seconds\"";
+          type = types.nullOr types.str;
+
+          description = ''
+            Notification script to be used to warn about the pending autolock.
+          '';
+        };
+
+        killer = mkOption {
+          default = null; # default according to `man xautolock` is none
+          example = "/run/current-system/systemd/bin/systemctl suspend";
+          type = types.nullOr types.str;
+
+          description = ''
+            The script to use when nothing has happend for as long as <option>killtime</option>
+          '';
+        };
+
+        killtime = mkOption {
+          default = 20; # default according to `man xautolock`
+          type = types.int;
+
+          description = ''
+            Minutes xautolock waits until it executes the script specified in <option>killer</option>
+            (Has to be at least 10 minutes)
+          '';
+        };
+
+        extraOptions = mkOption {
+          type = types.listOf types.str;
+          default = [ ];
+          example = [ "-detectsleep" ];
+          description = ''
+            Additional command-line arguments to pass to
+            <command>xautolock</command>.
+          '';
+        };
+      };
+    };
+
+    config = mkIf cfg.enable {
+      environment.systemPackages = with pkgs; [ xautolock ];
+      systemd.user.services.xautolock = {
+        description = "xautolock service";
+        wantedBy = [ "graphical-session.target" ];
+        partOf = [ "graphical-session.target" ];
+        serviceConfig = with lib; {
+          ExecStart = strings.concatStringsSep " " ([
+            "${pkgs.xautolock}/bin/xautolock"
+            "-noclose"
+            "-time ${toString cfg.time}"
+            "-locker '${cfg.locker}'"
+          ] ++ optionals cfg.enableNotifier [
+            "-notify ${toString cfg.notify}"
+            "-notifier '${cfg.notifier}'"
+          ] ++ optionals (cfg.nowlocker != null) [
+            "-nowlocker '${cfg.nowlocker}'"
+          ] ++ optionals (cfg.killer != null) [
+            "-killer '${cfg.killer}'"
+            "-killtime ${toString cfg.killtime}"
+          ] ++ cfg.extraOptions);
+          Restart = "always";
+        };
+      };
+      assertions = [
+        {
+          assertion = cfg.enableNotifier -> cfg.notifier != null;
+          message = "When enabling the notifier for xautolock, you also need to specify the notify script";
+        }
+        {
+          assertion = cfg.killer != null -> cfg.killtime >= 10;
+          message = "killtime has to be at least 10 minutes according to `man xautolock`";
+        }
+      ] ++ (lib.forEach [ "locker" "notifier" "nowlocker" "killer" ]
+        (option:
+        {
+          assertion = cfg.${option} != null -> builtins.substring 0 1 cfg.${option} == "/";
+          message = "Please specify a canonical path for `services.xserver.xautolock.${option}`";
+        })
+      );
+    };
+  }
diff --git a/nixpkgs/nixos/modules/services/x11/xbanish.nix b/nixpkgs/nixos/modules/services/x11/xbanish.nix
new file mode 100644
index 000000000000..b95fac68f165
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xbanish.nix
@@ -0,0 +1,31 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.services.xbanish;
+
+in {
+  options.services.xbanish = {
+
+    enable = mkEnableOption "xbanish";
+
+    arguments = mkOption {
+      description = "Arguments to pass to xbanish command";
+      default = "";
+      example = "-d -i shift";
+      type = types.str;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.user.services.xbanish = {
+      description = "xbanish hides the mouse pointer";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+      serviceConfig.ExecStart = ''
+        ${pkgs.xbanish}/bin/xbanish ${cfg.arguments}
+      '';
+      serviceConfig.Restart = "always";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/xfs.conf b/nixpkgs/nixos/modules/services/x11/xfs.conf
new file mode 100644
index 000000000000..13dcf803db29
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xfs.conf
@@ -0,0 +1,15 @@
+# font server configuration file
+# $Xorg: config.cpp,v 1.3 2000/08/17 19:54:19 cpqbld Exp $
+
+clone-self = on
+use-syslog = off
+error-file = /var/log/xfs.log
+# in decipoints
+default-point-size = 120
+default-resolutions = 75,75,100,100
+
+# font cache control, specified in KB
+cache-hi-mark = 2048
+cache-low-mark = 1433
+cache-balance = 70
+catalogue = /run/current-system/sw/share/X11-fonts/
diff --git a/nixpkgs/nixos/modules/services/x11/xfs.nix b/nixpkgs/nixos/modules/services/x11/xfs.nix
new file mode 100644
index 000000000000..ea7cfa1aa43c
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xfs.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  configFile = ./xfs.conf;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.xfs = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the X Font Server.";
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.xfs.enable {
+    assertions = singleton
+      { assertion = config.fonts.enableFontDir;
+        message = "Please enable fonts.enableFontDir to use the X Font Server.";
+      };
+
+    systemd.services.xfs = {
+      description = "X Font Server";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ pkgs.xorg.xfs ];
+      script = "xfs -config ${configFile}";
+    };
+  };
+}
diff --git a/nixpkgs/nixos/modules/services/x11/xserver.nix b/nixpkgs/nixos/modules/services/x11/xserver.nix
new file mode 100644
index 000000000000..400173745d3f
--- /dev/null
+++ b/nixpkgs/nixos/modules/services/x11/xserver.nix
@@ -0,0 +1,815 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  # Abbreviations.
+  cfg = config.services.xserver;
+  xorg = pkgs.xorg;
+
+
+  # Map video driver names to driver packages. FIXME: move into card-specific modules.
+  knownVideoDrivers = {
+    # Alias so people can keep using "virtualbox" instead of "vboxvideo".
+    virtualbox = { modules = [ xorg.xf86videovboxvideo ]; driverName = "vboxvideo"; };
+
+    # Alias so that "radeon" uses the xf86-video-ati driver.
+    radeon = { modules = [ xorg.xf86videoati ]; driverName = "ati"; };
+
+    # modesetting does not have a xf86videomodesetting package as it is included in xorgserver
+    modesetting = {};
+  };
+
+  fontsForXServer =
+    config.fonts.fonts ++
+    # We don't want these fonts in fonts.conf, because then modern,
+    # fontconfig-based applications will get horrible bitmapped
+    # Helvetica fonts.  It's better to get a substitution (like Nimbus
+    # Sans) than that horror.  But we do need the Adobe fonts for some
+    # old non-fontconfig applications.  (Possibly this could be done
+    # better using a fontconfig rule.)
+    [ pkgs.xorg.fontadobe100dpi
+      pkgs.xorg.fontadobe75dpi
+    ];
+
+  xrandrOptions = {
+    output = mkOption {
+      type = types.str;
+      example = "DVI-0";
+      description = ''
+        The output name of the monitor, as shown by <citerefentry>
+          <refentrytitle>xrandr</refentrytitle>
+          <manvolnum>1</manvolnum>
+        </citerefentry> invoked without arguments.
+      '';
+    };
+
+    primary = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether this head is treated as the primary monitor,
+      '';
+    };
+
+    monitorConfig = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        DisplaySize 408 306
+        Option "DPMS" "false"
+      '';
+      description = ''
+        Extra lines to append to the <literal>Monitor</literal> section
+        verbatim. Available options are documented in the MONITOR section in
+        <citerefentry><refentrytitle>xorg.conf</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry>.
+      '';
+    };
+  };
+
+  # Just enumerate all heads without discarding XRandR output information.
+  xrandrHeads = let
+    mkHead = num: config: {
+      name = "multihead${toString num}";
+      inherit config;
+    };
+  in imap1 mkHead cfg.xrandrHeads;
+
+  xrandrDeviceSection = let
+    monitors = forEach xrandrHeads (h: ''
+      Option "monitor-${h.config.output}" "${h.name}"
+    '');
+    # First option is indented through the space in the config but any
+    # subsequent options aren't so we need to apply indentation to
+    # them here
+    monitorsIndented = if length monitors > 1
+      then singleton (head monitors) ++ map (m: "  " + m) (tail monitors)
+      else monitors;
+  in concatStrings monitorsIndented;
+
+  # Here we chain every monitor from the left to right, so we have:
+  # m4 right of m3 right of m2 right of m1   .----.----.----.----.
+  # Which will end up in reverse ----------> | m1 | m2 | m3 | m4 |
+  #                                          `----^----^----^----'
+  xrandrMonitorSections = let
+    mkMonitor = previous: current: singleton {
+      inherit (current) name;
+      value = ''
+        Section "Monitor"
+          Identifier "${current.name}"
+          ${optionalString (current.config.primary) ''
+          Option "Primary" "true"
+          ''}
+          ${optionalString (previous != []) ''
+          Option "RightOf" "${(head previous).name}"
+          ''}
+          ${current.config.monitorConfig}
+        EndSection
+      '';
+    } ++ previous;
+    monitors = reverseList (foldl mkMonitor [] xrandrHeads);
+  in concatMapStrings (getAttr "value") monitors;
+
+  configFile = pkgs.runCommand "xserver.conf"
+    { xfs = optionalString (cfg.useXFS != false)
+        ''FontPath "${toString cfg.useXFS}"'';
+      inherit (cfg) config;
+      preferLocalBuild = true;
+    }
+      ''
+        echo 'Section "Files"' >> $out
+        echo $xfs >> $out
+
+        for i in ${toString fontsForXServer}; do
+          if test "''${i:0:''${#NIX_STORE}}" == "$NIX_STORE"; then
+            for j in $(find $i -name fonts.dir); do
+              echo "  FontPath \"$(dirname $j)\"" >> $out
+            done
+          fi
+        done
+
+        for i in $(find ${toString cfg.modules} -type d); do
+          if test $(echo $i/*.so* | wc -w) -ne 0; then
+            echo "  ModulePath \"$i\"" >> $out
+          fi
+        done
+
+        echo 'EndSection' >> $out
+
+        echo "$config" >> $out
+      ''; # */
+
+in
+
+{
+
+  imports =
+    [ ./display-managers/default.nix
+      ./window-managers/default.nix
+      ./desktop-managers/default.nix
+      (mkRemovedOptionModule [ "services" "xserver" "startGnuPGAgent" ]
+        "See the 16.09 release notes for more information.")
+    ];
+
+
+  ###### interface
+
+  options = {
+
+    services.xserver = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the X server.
+        '';
+      };
+
+      autorun = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to start the X server automatically.
+        '';
+      };
+
+      exportConfiguration = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to symlink the X server configuration under
+          <filename>/etc/X11/xorg.conf</filename>.
+        '';
+      };
+
+      enableTCP = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow the X server to accept TCP connections.
+        '';
+      };
+
+      autoRepeatDelay = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Sets the autorepeat delay (length of time in milliseconds that a key must be depressed before autorepeat starts).
+        '';
+      };
+
+      autoRepeatInterval = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = ''
+          Sets the autorepeat interval (length of time in milliseconds that should elapse between autorepeat-generated keystrokes).
+        '';
+      };
+
+      inputClassSections = mkOption {
+        type = types.listOf types.lines;
+        default = [];
+        example = literalExample ''
+          [ '''
+              Identifier      "Trackpoint Wheel Emulation"
+              MatchProduct    "ThinkPad USB Keyboard with TrackPoint"
+              Option          "EmulateWheel"          "true"
+              Option          "EmulateWheelButton"    "2"
+              Option          "Emulate3Buttons"       "false"
+            '''
+          ]
+        '';
+        description = "Content of additional InputClass sections of the X server configuration file.";
+      };
+
+      modules = mkOption {
+        type = types.listOf types.path;
+        default = [];
+        example = literalExample "[ pkgs.xf86_input_wacom ]";
+        description = "Packages to be added to the module search path of the X server.";
+      };
+
+      resolutions = mkOption {
+        type = types.listOf types.attrs;
+        default = [];
+        example = [ { x = 1600; y = 1200; } { x = 1024; y = 786; } ];
+        description = ''
+          The screen resolutions for the X server.  The first element
+          is the default resolution.  If this list is empty, the X
+          server will automatically configure the resolution.
+        '';
+      };
+
+      videoDrivers = mkOption {
+        type = types.listOf types.str;
+        # !!! We'd like "nv" here, but it segfaults the X server.
+        default = [ "radeon" "cirrus" "vesa" "modesetting" ];
+        example = [
+          "ati_unfree" "amdgpu" "amdgpu-pro"
+          "nv" "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304"
+        ];
+        # TODO(@oxij): think how to easily add the rest, like those nvidia things
+        relatedPackages = concatLists
+          (mapAttrsToList (n: v:
+            optional (hasPrefix "xf86video" n) {
+              path  = [ "xorg" n ];
+              title = removePrefix "xf86video" n;
+            }) pkgs.xorg);
+        description = ''
+          The names of the video drivers the configuration
+          supports. They will be tried in order until one that
+          supports your card is found.
+          Don't combine those with "incompatible" OpenGL implementations,
+          e.g. free ones (mesa-based) with proprietary ones.
+
+          For unfree "nvidia*", the supported GPU lists are on
+          https://www.nvidia.com/object/unix.html
+        '';
+      };
+
+      videoDriver = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "i810";
+        description = ''
+          The name of the video driver for your graphics card.  This
+          option is obsolete; please set the
+          <option>services.xserver.videoDrivers</option> instead.
+        '';
+      };
+
+      drivers = mkOption {
+        type = types.listOf types.attrs;
+        internal = true;
+        description = ''
+          A list of attribute sets specifying drivers to be loaded by
+          the X11 server.
+        '';
+      };
+
+      dpi = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        description = "DPI resolution to use for X server.";
+      };
+
+      startDbusSession = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to start a new DBus session when you log in with dbus-launch.
+        '';
+      };
+
+      updateDbusEnvironment = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to update the DBus activation environment after launching the
+          desktop manager.
+        '';
+      };
+
+      layout = mkOption {
+        type = types.str;
+        default = "us";
+        description = ''
+          Keyboard layout, or multiple keyboard layouts separated by commas.
+        '';
+      };
+
+      xkbModel = mkOption {
+        type = types.str;
+        default = "pc104";
+        example = "presario";
+        description = ''
+          Keyboard model.
+        '';
+      };
+
+      xkbOptions = mkOption {
+        type = types.commas;
+        default = "terminate:ctrl_alt_bksp";
+        example = "grp:caps_toggle,grp_led:scroll";
+        description = ''
+          X keyboard options; layout switching goes here.
+        '';
+      };
+
+      xkbVariant = mkOption {
+        type = types.str;
+        default = "";
+        example = "colemak";
+        description = ''
+          X keyboard variant.
+        '';
+      };
+
+      xkbDir = mkOption {
+        type = types.path;
+        default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
+        description = ''
+          Path used for -xkbdir xserver parameter.
+        '';
+      };
+
+      config = mkOption {
+        type = types.lines;
+        description = ''
+          The contents of the configuration file of the X server
+          (<filename>xorg.conf</filename>).
+        '';
+      };
+
+      deviceSection = mkOption {
+        type = types.lines;
+        default = "";
+        example = "VideoRAM 131072";
+        description = "Contents of the first Device section of the X server configuration file.";
+      };
+
+      screenSection = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          Option "RandRRotation" "on"
+        '';
+        description = "Contents of the first Screen section of the X server configuration file.";
+      };
+
+      monitorSection = mkOption {
+        type = types.lines;
+        default = "";
+        example = "HorizSync 28-49";
+        description = "Contents of the first Monitor section of the X server configuration file.";
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional contents (sections) included in the X server configuration file";
+      };
+
+      xrandrHeads = mkOption {
+        default = [];
+        example = [
+          "HDMI-0"
+          { output = "DVI-0"; primary = true; }
+          { output = "DVI-1"; monitorConfig = "Option \"Rotate\" \"left\""; }
+        ];
+        type = with types; listOf (coercedTo str (output: {
+          inherit output;
+        }) (submodule { options = xrandrOptions; }));
+        # Set primary to true for the first head if no other has been set
+        # primary already.
+        apply = heads: let
+          hasPrimary = any (x: x.primary) heads;
+          firstPrimary = head heads // { primary = true; };
+          newHeads = singleton firstPrimary ++ tail heads;
+        in if heads != [] && !hasPrimary then newHeads else heads;
+        description = ''
+          Multiple monitor configuration, just specify a list of XRandR
+          outputs. The individual elements should be either simple strings or
+          an attribute set of output options.
+
+          If the element is a string, it is denoting the physical output for a
+          monitor, if it's an attribute set, you must at least provide the
+          <option>output</option> option.
+
+          The monitors will be mapped from left to right in the order of the
+          list.
+
+          By default, the first monitor will be set as the primary monitor if
+          none of the elements contain an option that has set
+          <option>primary</option> to <literal>true</literal>.
+
+          <note><para>Only one monitor is allowed to be primary.</para></note>
+
+          Be careful using this option with multiple graphic adapters or with
+          drivers that have poor support for XRandR, unexpected things might
+          happen with those.
+        '';
+      };
+
+      serverFlagsSection = mkOption {
+        default = "";
+        example =
+          ''
+          Option "BlankTime" "0"
+          Option "StandbyTime" "0"
+          Option "SuspendTime" "0"
+          Option "OffTime" "0"
+          '';
+        description = "Contents of the ServerFlags section of the X server configuration file.";
+      };
+
+      moduleSection = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            SubSection "extmod"
+            EndSubsection
+          '';
+        description = "Contents of the Module section of the X server configuration file.";
+      };
+
+      serverLayoutSection = mkOption {
+        type = types.lines;
+        default = "";
+        example =
+          ''
+            Option "AIGLX" "true"
+          '';
+        description = "Contents of the ServerLayout section of the X server configuration file.";
+      };
+
+      extraDisplaySettings = mkOption {
+        type = types.lines;
+        default = "";
+        example = "Virtual 2048 2048";
+        description = "Lines to be added to every Display subsection of the Screen section.";
+      };
+
+      defaultDepth = mkOption {
+        type = types.int;
+        default = 0;
+        example = 8;
+        description = "Default colour depth.";
+      };
+
+      useXFS = mkOption {
+        # FIXME: what's the type of this option?
+        default = false;
+        example = "unix/:7100";
+        description = "Determines how to connect to the X Font Server.";
+      };
+
+      tty = mkOption {
+        type = types.nullOr types.int;
+        default = 7;
+        description = "Virtual console for the X server.";
+      };
+
+      display = mkOption {
+        type = types.nullOr types.int;
+        default = 0;
+        description = "Display number for the X server.";
+      };
+
+      virtualScreen = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        example = { x = 2048; y = 2048; };
+        description = ''
+          Virtual screen size for Xrandr.
+        '';
+      };
+
+      verbose = mkOption {
+        type = types.nullOr types.int;
+        default = 3;
+        example = 7;
+        description = ''
+          Controls verbosity of X logging.
+        '';
+      };
+
+      useGlamor = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use the Glamor module for 2D acceleration,
+          if possible.
+        '';
+      };
+
+      enableCtrlAltBackspace = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the DontZap option, which binds Ctrl+Alt+Backspace
+          to forcefully kill X. This can lead to data loss and is disabled
+          by default.
+        '';
+      };
+
+      terminateOnReset = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to terminate X upon server reset.
+        '';
+      };
+    };
+
+  };
+
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    services.xserver.displayManager.lightdm.enable =
+      let dmconf = cfg.displayManager;
+          default = !(dmconf.gdm.enable
+                    || dmconf.sddm.enable
+                    || dmconf.xpra.enable );
+      in mkIf (default) true;
+
+    hardware.opengl.enable = mkDefault true;
+
+    services.xserver.videoDrivers = mkIf (cfg.videoDriver != null) [ cfg.videoDriver ];
+
+    # FIXME: somehow check for unknown driver names.
+    services.xserver.drivers = flip concatMap cfg.videoDrivers (name:
+      let driver =
+        attrByPath [name]
+          (if xorg ? ${"xf86video" + name}
+           then { modules = [xorg.${"xf86video" + name}]; }
+           else null)
+          knownVideoDrivers;
+      in optional (driver != null) ({ inherit name; modules = []; driverName = name; display = true; } // driver));
+
+    assertions = [
+      { assertion = config.security.polkit.enable;
+        message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
+      }
+      (let primaryHeads = filter (x: x.primary) cfg.xrandrHeads; in {
+        assertion = length primaryHeads < 2;
+        message = "Only one head is allowed to be primary in "
+                + "‘services.xserver.xrandrHeads’, but there are "
+                + "${toString (length primaryHeads)} heads set to primary: "
+                + concatMapStringsSep ", " (x: x.output) primaryHeads;
+      })
+    ];
+
+    environment.etc =
+      (optionalAttrs cfg.exportConfiguration
+        {
+          "X11/xorg.conf".source = "${configFile}";
+          # -xkbdir command line option does not seems to be passed to xkbcomp.
+          "X11/xkb".source = "${cfg.xkbDir}";
+        })
+      # localectl looks into 00-keyboard.conf
+      //{
+          "X11/xorg.conf.d/00-keyboard.conf".text = ''
+            Section "InputClass"
+              Identifier "Keyboard catchall"
+              MatchIsKeyboard "on"
+              Option "XkbModel" "${cfg.xkbModel}"
+              Option "XkbLayout" "${cfg.layout}"
+              Option "XkbOptions" "${cfg.xkbOptions}"
+              Option "XkbVariant" "${cfg.xkbVariant}"
+            EndSection
+          '';
+        }
+      # Needed since 1.18; see https://bugs.freedesktop.org/show_bug.cgi?id=89023#c5
+      // (let cfgPath = "/X11/xorg.conf.d/10-evdev.conf"; in
+        {
+          ${cfgPath}.source = xorg.xf86inputevdev.out + "/share" + cfgPath;
+        });
+
+    environment.systemPackages =
+      [ xorg.xorgserver.out
+        xorg.xrandr
+        xorg.xrdb
+        xorg.setxkbmap
+        xorg.iceauth # required for KDE applications (it's called by dcopserver)
+        xorg.xlsclients
+        xorg.xset
+        xorg.xsetroot
+        xorg.xinput
+        xorg.xprop
+        xorg.xauth
+        pkgs.xterm
+        pkgs.xdg_utils
+        xorg.xf86inputevdev.out # get evdev.4 man page
+      ]
+      ++ optional (elem "virtualbox" cfg.videoDrivers) xorg.xrefresh;
+
+    environment.pathsToLink = [ "/share/X11" ];
+
+    xdg = {
+      autostart.enable = true;
+      menus.enable = true;
+      mime.enable = true;
+      icons.enable = true;
+    };
+
+    # The default max inotify watches is 8192.
+    # Nowadays most apps require a good number of inotify watches,
+    # the value below is used by default on several other distros.
+    boot.kernel.sysctl."fs.inotify.max_user_watches" = mkDefault 524288;
+
+    systemd.defaultUnit = mkIf cfg.autorun "graphical.target";
+
+    systemd.services.display-manager =
+      { description = "X11 Server";
+
+        after = [ "acpid.service" "systemd-logind.service" ];
+
+        restartIfChanged = false;
+
+        environment =
+          optionalAttrs config.hardware.opengl.setLdLibraryPath
+            { LD_LIBRARY_PATH = pkgs.addOpenGLRunpath.driverLink; }
+          // cfg.displayManager.job.environment;
+
+        preStart =
+          ''
+            ${cfg.displayManager.job.preStart}
+
+            rm -f /tmp/.X0-lock
+          '';
+
+        script = "${cfg.displayManager.job.execCmd}";
+
+        serviceConfig = {
+          Restart = "always";
+          RestartSec = "200ms";
+          SyslogIdentifier = "display-manager";
+          # Stop restarting if the display manager stops (crashes) 2 times
+          # in one minute. Starting X typically takes 3-4s.
+          StartLimitInterval = "30s";
+          StartLimitBurst = "3";
+        };
+      };
+
+    services.xserver.displayManager.xserverArgs =
+      [ "-config ${configFile}"
+        "-xkbdir" "${cfg.xkbDir}"
+        # Log at the default verbosity level to stderr rather than /var/log/X.*.log.
+         "-logfile" "/dev/null"
+      ] ++ optional (cfg.display != null) ":${toString cfg.display}"
+        ++ optional (cfg.tty     != null) "vt${toString cfg.tty}"
+        ++ optional (cfg.dpi     != null) "-dpi ${toString cfg.dpi}"
+        ++ optional (cfg.verbose != null) "-verbose ${toString cfg.verbose}"
+        ++ optional (!cfg.enableTCP) "-nolisten tcp"
+        ++ optional (cfg.autoRepeatDelay != null) "-ardelay ${toString cfg.autoRepeatDelay}"
+        ++ optional (cfg.autoRepeatInterval != null) "-arinterval ${toString cfg.autoRepeatInterval}"
+        ++ optional cfg.terminateOnReset "-terminate";
+
+    services.xserver.modules =
+      concatLists (catAttrs "modules" cfg.drivers) ++
+      [ xorg.xorgserver.out
+        xorg.xf86inputevdev.out
+      ];
+
+    system.extraDependencies = singleton (pkgs.runCommand "xkb-validated" {
+      inherit (cfg) xkbModel layout xkbVariant xkbOptions;
+      nativeBuildInputs = [ pkgs.xkbvalidate ];
+      preferLocalBuild = true;
+    } ''
+      xkbvalidate "$xkbModel" "$layout" "$xkbVariant" "$xkbOptions"
+      touch "$out"
+    '');
+
+    services.xserver.config =
+      ''
+        Section "ServerFlags"
+          Option "AllowMouseOpenFail" "on"
+          Option "DontZap" "${if cfg.enableCtrlAltBackspace then "off" else "on"}"
+          ${cfg.serverFlagsSection}
+        EndSection
+
+        Section "Module"
+          ${cfg.moduleSection}
+        EndSection
+
+        Section "Monitor"
+          Identifier "Monitor[0]"
+          ${cfg.monitorSection}
+        EndSection
+
+        # Additional "InputClass" sections
+        ${flip concatMapStrings cfg.inputClassSections (inputClassSection: ''
+        Section "InputClass"
+          ${inputClassSection}
+        EndSection
+        '')}
+
+
+        Section "ServerLayout"
+          Identifier "Layout[all]"
+          ${cfg.serverLayoutSection}
+          # Reference the Screen sections for each driver.  This will
+          # cause the X server to try each in turn.
+          ${flip concatMapStrings (filter (d: d.display) cfg.drivers) (d: ''
+            Screen "Screen-${d.name}[0]"
+          '')}
+        EndSection
+
+        ${if cfg.useGlamor then ''
+          Section "Module"
+            Load "dri2"
+            Load "glamoregl"
+          EndSection
+        '' else ""}
+
+        # For each supported driver, add a "Device" and "Screen"
+        # section.
+        ${flip concatMapStrings cfg.drivers (driver: ''
+
+          Section "Device"
+            Identifier "Device-${driver.name}[0]"
+            Driver "${driver.driverName or driver.name}"
+            ${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""}
+            ${cfg.deviceSection}
+            ${driver.deviceSection or ""}
+            ${xrandrDeviceSection}
+          EndSection
+          ${optionalString driver.display ''
+
+            Section "Screen"
+              Identifier "Screen-${driver.name}[0]"
+              Device "Device-${driver.name}[0]"
+              ${optionalString (cfg.monitorSection != "") ''
+                Monitor "Monitor[0]"
+              ''}
+
+              ${cfg.screenSection}
+              ${driver.screenSection or ""}
+
+              ${optionalString (cfg.defaultDepth != 0) ''
+                DefaultDepth ${toString cfg.defaultDepth}
+              ''}
+
+              ${optionalString
+                  (driver.name != "virtualbox" &&
+                  (cfg.resolutions != [] ||
+                    cfg.extraDisplaySettings != "" ||
+                    cfg.virtualScreen != null))
+                (let
+                  f = depth:
+                    ''
+                      SubSection "Display"
+                        Depth ${toString depth}
+                        ${optionalString (cfg.resolutions != [])
+                          "Modes ${concatMapStrings (res: ''"${toString res.x}x${toString res.y}"'') cfg.resolutions}"}
+                        ${cfg.extraDisplaySettings}
+                        ${optionalString (cfg.virtualScreen != null)
+                          "Virtual ${toString cfg.virtualScreen.x} ${toString cfg.virtualScreen.y}"}
+                      EndSubSection
+                    '';
+                in concatMapStrings f [8 16 24]
+              )}
+
+            EndSection
+          ''}
+        '')}
+
+        ${xrandrMonitorSections}
+
+        ${cfg.extraConfig}
+      '';
+
+    fonts.enableDefaultFonts = mkDefault true;
+
+  };
+
+}