about summary refs log tree commit diff
path: root/nixos/modules
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules')
-rw-r--r--nixos/modules/config/console.nix203
-rw-r--r--nixos/modules/config/fonts/corefonts.nix36
-rw-r--r--nixos/modules/config/fonts/fontconfig-penultimate.nix176
-rw-r--r--nixos/modules/config/fonts/fontconfig-ultimate.nix86
-rw-r--r--nixos/modules/config/fonts/fontconfig.nix487
-rw-r--r--nixos/modules/config/fonts/fonts.nix4
-rw-r--r--nixos/modules/config/gtk/gtk-icon-cache.nix2
-rw-r--r--nixos/modules/config/i18n.nix80
-rw-r--r--nixos/modules/config/krb5/default.nix4
-rw-r--r--nixos/modules/config/ldap.nix4
-rw-r--r--nixos/modules/config/locale.nix (renamed from nixos/modules/config/timezone.nix)35
-rw-r--r--nixos/modules/config/malloc.nix91
-rw-r--r--nixos/modules/config/networking.nix133
-rw-r--r--nixos/modules/config/no-x-libs.nix1
-rw-r--r--nixos/modules/config/power-management.nix4
-rw-r--r--nixos/modules/config/pulseaudio.nix38
-rw-r--r--nixos/modules/config/qt5.nix102
-rw-r--r--nixos/modules/config/resolvconf.nix155
-rw-r--r--nixos/modules/config/shells-environment.nix20
-rw-r--r--nixos/modules/config/swap.nix2
-rw-r--r--nixos/modules/config/sysctl.nix14
-rw-r--r--nixos/modules/config/system-environment.nix83
-rw-r--r--nixos/modules/config/system-path.nix3
-rw-r--r--nixos/modules/config/terminfo.nix4
-rw-r--r--nixos/modules/config/unix-odbc-drivers.nix2
-rw-r--r--nixos/modules/config/update-users-groups.pl5
-rw-r--r--nixos/modules/config/users-groups.nix27
-rw-r--r--nixos/modules/config/vpnc.nix41
-rw-r--r--nixos/modules/config/vte.nix52
-rw-r--r--nixos/modules/config/xdg/icons.nix27
-rw-r--r--nixos/modules/config/xdg/mime.nix2
-rw-r--r--nixos/modules/config/xdg/portal.nix62
-rw-r--r--nixos/modules/config/xdg/sounds.nix6
-rw-r--r--nixos/modules/hardware/all-firmware.nix11
-rw-r--r--nixos/modules/hardware/brightnessctl.nix1
-rw-r--r--nixos/modules/hardware/brillo.nix22
-rw-r--r--nixos/modules/hardware/ckb-next.nix5
-rw-r--r--nixos/modules/hardware/device-tree.nix56
-rw-r--r--nixos/modules/hardware/ksm.nix26
-rw-r--r--nixos/modules/hardware/nitrokey.nix2
-rw-r--r--nixos/modules/hardware/opengl.nix35
-rw-r--r--nixos/modules/hardware/openrazer.nix133
-rw-r--r--nixos/modules/hardware/printers.nix135
-rw-r--r--nixos/modules/hardware/raid/hpsa.nix4
-rw-r--r--nixos/modules/hardware/steam-hardware.nix7
-rw-r--r--nixos/modules/hardware/tuxedo-keyboard.nix35
-rw-r--r--nixos/modules/hardware/usb-wwan.nix13
-rw-r--r--nixos/modules/hardware/video/amdgpu-pro.nix3
-rw-r--r--nixos/modules/hardware/video/ati.nix5
-rw-r--r--nixos/modules/hardware/video/displaylink.nix22
-rw-r--r--nixos/modules/hardware/video/nvidia.nix32
-rw-r--r--nixos/modules/i18n/input-method/ibus.nix16
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix32
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix4
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix44
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5-new-kernel.nix (renamed from nixos/modules/installer/cd-dvd/installation-cd-graphical-kde-new-kernel.nix)2
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix48
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix36
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-aarch64.nix24
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix19
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix21
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix31
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image.nix138
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-pc.nix7
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix5
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball.nix2
-rw-r--r--nixos/modules/installer/netboot/netboot.nix7
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix8
-rw-r--r--nixos/modules/installer/tools/nixos-enter.sh21
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl156
-rw-r--r--nixos/modules/installer/tools/nixos-install.sh15
-rw-r--r--nixos/modules/installer/tools/nixos-option.sh327
-rw-r--r--nixos/modules/installer/tools/nixos-option/CMakeLists.txt8
-rw-r--r--nixos/modules/installer/tools/nixos-option/default.nix11
-rw-r--r--nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc83
-rw-r--r--nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh9
-rw-r--r--nixos/modules/installer/tools/nixos-option/nixos-option.cc618
-rw-r--r--nixos/modules/installer/tools/nixos-rebuild.sh17
-rw-r--r--nixos/modules/installer/tools/tools.nix119
-rw-r--r--nixos/modules/misc/crashdump.nix1
-rw-r--r--nixos/modules/misc/documentation.nix20
-rw-r--r--nixos/modules/misc/ids.nix43
-rw-r--r--nixos/modules/misc/locate.nix10
-rw-r--r--nixos/modules/misc/nixops-autoluks.nix43
-rw-r--r--nixos/modules/misc/nixpkgs.nix10
-rw-r--r--nixos/modules/misc/version.nix29
-rw-r--r--nixos/modules/module-list.nix149
-rw-r--r--nixos/modules/profiles/graphical.nix2
-rw-r--r--nixos/modules/profiles/hardened.nix59
-rw-r--r--nixos/modules/profiles/installation-device.nix39
-rw-r--r--nixos/modules/profiles/qemu-guest.nix4
-rw-r--r--nixos/modules/programs/adb.nix3
-rw-r--r--nixos/modules/programs/atop.nix2
-rw-r--r--nixos/modules/programs/bandwhich.nix29
-rw-r--r--nixos/modules/programs/bash/bash.nix14
-rw-r--r--nixos/modules/programs/blcr.nix27
-rw-r--r--nixos/modules/programs/browserpass.nix10
-rw-r--r--nixos/modules/programs/captive-browser.nix122
-rw-r--r--nixos/modules/programs/clickshare.nix21
-rw-r--r--nixos/modules/programs/dconf.nix21
-rw-r--r--nixos/modules/programs/environment.nix9
-rw-r--r--nixos/modules/programs/evince.nix (renamed from nixos/modules/services/desktops/gnome3/evince.nix)13
-rw-r--r--nixos/modules/programs/file-roller.nix (renamed from nixos/modules/services/desktops/gnome3/file-roller.nix)11
-rw-r--r--nixos/modules/programs/firejail.nix2
-rw-r--r--nixos/modules/programs/fish.nix2
-rw-r--r--nixos/modules/programs/fuse.nix37
-rw-r--r--nixos/modules/programs/geary.nix20
-rw-r--r--nixos/modules/programs/gnome-disks.nix (renamed from nixos/modules/services/desktops/gnome3/gnome-disks.nix)15
-rw-r--r--nixos/modules/programs/gnome-documents.nix (renamed from nixos/modules/services/desktops/gnome3/gnome-documents.nix)15
-rw-r--r--nixos/modules/programs/gnome-terminal.nix36
-rw-r--r--nixos/modules/programs/gnupg.nix40
-rw-r--r--nixos/modules/programs/gpaste.nix (renamed from nixos/modules/services/desktops/gnome3/gpaste.nix)15
-rw-r--r--nixos/modules/programs/less.nix12
-rw-r--r--nixos/modules/programs/liboping.nix22
-rw-r--r--nixos/modules/programs/mtr.nix14
-rw-r--r--nixos/modules/programs/nano.nix2
-rw-r--r--nixos/modules/programs/nm-applet.nix2
-rw-r--r--nixos/modules/programs/npm.nix2
-rw-r--r--nixos/modules/programs/nylas-mail.nix36
-rw-r--r--nixos/modules/programs/oblogout.nix171
-rw-r--r--nixos/modules/programs/plotinus.nix2
-rw-r--r--nixos/modules/programs/plotinus.xml4
-rw-r--r--nixos/modules/programs/screen.nix3
-rw-r--r--nixos/modules/programs/seahorse.nix46
-rw-r--r--nixos/modules/programs/shadow.nix42
-rw-r--r--nixos/modules/programs/shell.nix54
-rw-r--r--nixos/modules/programs/ssh.nix22
-rw-r--r--nixos/modules/programs/ssmtp.nix21
-rw-r--r--nixos/modules/programs/sway.nix89
-rw-r--r--nixos/modules/programs/system-config-printer.nix32
-rw-r--r--nixos/modules/programs/thefuck.nix2
-rw-r--r--nixos/modules/programs/tmux.nix2
-rw-r--r--nixos/modules/programs/traceroute.nix26
-rw-r--r--nixos/modules/programs/tsm-client.nix287
-rw-r--r--nixos/modules/programs/usbtop.nix21
-rw-r--r--nixos/modules/programs/way-cooler.nix78
-rw-r--r--nixos/modules/programs/x2goserver.nix149
-rw-r--r--nixos/modules/programs/xfs_quota.nix2
-rw-r--r--nixos/modules/programs/xonsh.nix7
-rw-r--r--nixos/modules/programs/xss-lock.nix21
-rw-r--r--nixos/modules/programs/yabar.nix8
-rw-r--r--nixos/modules/programs/zmap.nix18
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.nix7
-rw-r--r--nixos/modules/programs/zsh/zsh-autosuggestions.nix4
-rw-r--r--nixos/modules/programs/zsh/zsh-syntax-highlighting.nix15
-rw-r--r--nixos/modules/programs/zsh/zsh.nix26
-rw-r--r--nixos/modules/rename.nix253
-rw-r--r--nixos/modules/security/acme.nix219
-rw-r--r--nixos/modules/security/acme.xml4
-rw-r--r--nixos/modules/security/apparmor-suid.nix3
-rw-r--r--nixos/modules/security/apparmor.nix12
-rw-r--r--nixos/modules/security/auditd.nix4
-rw-r--r--nixos/modules/security/chromium-suid-sandbox.nix6
-rw-r--r--nixos/modules/security/duosec.nix28
-rw-r--r--nixos/modules/security/misc.nix42
-rw-r--r--nixos/modules/security/pam.nix46
-rw-r--r--nixos/modules/security/pam_mount.nix11
-rw-r--r--nixos/modules/security/polkit.nix7
-rw-r--r--nixos/modules/security/prey.nix2
-rw-r--r--nixos/modules/security/rngd.nix38
-rw-r--r--nixos/modules/security/rtkit.nix5
-rw-r--r--nixos/modules/security/sudo.nix15
-rw-r--r--nixos/modules/security/wrappers/default.nix14
-rw-r--r--nixos/modules/services/admin/oxidized.nix6
-rw-r--r--nixos/modules/services/amqp/activemq/default.nix6
-rw-r--r--nixos/modules/services/amqp/rabbitmq.nix15
-rw-r--r--nixos/modules/services/audio/alsa.nix7
-rw-r--r--nixos/modules/services/audio/jack.nix291
-rw-r--r--nixos/modules/services/audio/mopidy.nix10
-rw-r--r--nixos/modules/services/audio/mpd.nix28
-rw-r--r--nixos/modules/services/audio/roon-server.nix74
-rw-r--r--nixos/modules/services/audio/slimserver.nix6
-rw-r--r--nixos/modules/services/audio/snapserver.nix1
-rw-r--r--nixos/modules/services/audio/spotifyd.nix42
-rw-r--r--nixos/modules/services/audio/ympd.nix2
-rw-r--r--nixos/modules/services/backup/automysqlbackup.nix7
-rw-r--r--nixos/modules/services/backup/bacula.nix166
-rw-r--r--nixos/modules/services/backup/borgbackup.nix52
-rw-r--r--nixos/modules/services/backup/crashplan-small-business.nix73
-rw-r--r--nixos/modules/services/backup/crashplan.nix67
-rw-r--r--nixos/modules/services/backup/duplicati.nix16
-rw-r--r--nixos/modules/services/backup/mysql-backup.nix11
-rw-r--r--nixos/modules/services/backup/postgresql-backup.nix11
-rw-r--r--nixos/modules/services/backup/postgresql-wal-receiver.nix204
-rw-r--r--nixos/modules/services/backup/rsnapshot.nix4
-rw-r--r--nixos/modules/services/backup/tarsnap.nix4
-rw-r--r--nixos/modules/services/backup/tsm.nix106
-rw-r--r--nixos/modules/services/backup/zfs-replication.nix90
-rw-r--r--nixos/modules/services/backup/znapzend.nix67
-rw-r--r--nixos/modules/services/cluster/hadoop/hdfs.nix4
-rw-r--r--nixos/modules/services/cluster/hadoop/yarn.nix4
-rw-r--r--nixos/modules/services/cluster/kubernetes/addon-manager.nix85
-rw-r--r--nixos/modules/services/cluster/kubernetes/addons/dashboard.nix42
-rw-r--r--nixos/modules/services/cluster/kubernetes/addons/dns.nix26
-rw-r--r--nixos/modules/services/cluster/kubernetes/apiserver.nix68
-rw-r--r--nixos/modules/services/cluster/kubernetes/controller-manager.nix44
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix40
-rw-r--r--nixos/modules/services/cluster/kubernetes/flannel.nix70
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix116
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix173
-rw-r--r--nixos/modules/services/cluster/kubernetes/proxy.nix47
-rw-r--r--nixos/modules/services/cluster/kubernetes/scheduler.nix34
-rw-r--r--nixos/modules/services/computing/boinc/client.nix11
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix51
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix23
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/worker.nix23
-rw-r--r--nixos/modules/services/continuous-integration/buildkite-agent.nix99
-rw-r--r--nixos/modules/services/continuous-integration/gitlab-runner.nix5
-rw-r--r--nixos/modules/services/continuous-integration/gocd-agent/default.nix24
-rw-r--r--nixos/modules/services/continuous-integration/gocd-server/default.nix24
-rw-r--r--nixos/modules/services/continuous-integration/hydra/default.nix30
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/default.nix28
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/slave.nix22
-rw-r--r--nixos/modules/services/databases/4store-endpoint.nix74
-rw-r--r--nixos/modules/services/databases/4store.nix72
-rw-r--r--nixos/modules/services/databases/cassandra.nix230
-rw-r--r--nixos/modules/services/databases/cockroachdb.nix18
-rw-r--r--nixos/modules/services/databases/couchdb.nix16
-rw-r--r--nixos/modules/services/databases/firebird.nix14
-rw-r--r--nixos/modules/services/databases/foundationdb.nix65
-rw-r--r--nixos/modules/services/databases/foundationdb.xml38
-rw-r--r--nixos/modules/services/databases/hbase.nix21
-rw-r--r--nixos/modules/services/databases/influxdb.nix18
-rw-r--r--nixos/modules/services/databases/memcached.nix23
-rw-r--r--nixos/modules/services/databases/mongodb.nix67
-rw-r--r--nixos/modules/services/databases/mysql.nix257
-rw-r--r--nixos/modules/services/databases/neo4j.nix15
-rw-r--r--nixos/modules/services/databases/openldap.nix43
-rw-r--r--nixos/modules/services/databases/opentsdb.nix4
-rw-r--r--nixos/modules/services/databases/pgmanage.nix12
-rw-r--r--nixos/modules/services/databases/postgresql.nix141
-rw-r--r--nixos/modules/services/databases/postgresql.xml76
-rw-r--r--nixos/modules/services/databases/redis.nix89
-rw-r--r--nixos/modules/services/databases/rethinkdb.nix1
-rw-r--r--nixos/modules/services/databases/riak.nix4
-rw-r--r--nixos/modules/services/databases/virtuoso.nix5
-rw-r--r--nixos/modules/services/desktops/blueman.nix25
-rw-r--r--nixos/modules/services/desktops/deepin/dde-daemon.nix41
-rw-r--r--nixos/modules/services/desktops/deepin/deepin-menu.nix29
-rw-r--r--nixos/modules/services/desktops/deepin/deepin.nix125
-rw-r--r--nixos/modules/services/desktops/flatpak.nix35
-rw-r--r--nixos/modules/services/desktops/flatpak.xml24
-rw-r--r--nixos/modules/services/desktops/geoclue2.nix212
-rw-r--r--nixos/modules/services/desktops/gnome3/at-spi2-core.nix3
-rw-r--r--nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix2
-rw-r--r--nixos/modules/services/desktops/gnome3/glib-networking.nix6
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix86
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-keyring.nix5
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix4
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix49
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-terminal-server.nix41
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-user-share.nix20
-rw-r--r--nixos/modules/services/desktops/gnome3/gvfs.nix43
-rw-r--r--nixos/modules/services/desktops/gnome3/seahorse.nix38
-rw-r--r--nixos/modules/services/desktops/gnome3/tracker-miners.nix6
-rw-r--r--nixos/modules/services/desktops/gnome3/tracker.nix6
-rw-r--r--nixos/modules/services/desktops/gvfs.nix59
-rw-r--r--nixos/modules/services/desktops/neard.nix23
-rw-r--r--nixos/modules/services/desktops/pantheon/contractor.nix25
-rw-r--r--nixos/modules/services/desktops/pantheon/files.nix31
-rw-r--r--nixos/modules/services/desktops/profile-sync-daemon.nix2
-rw-r--r--nixos/modules/services/desktops/system-config-printer.nix41
-rw-r--r--nixos/modules/services/desktops/tumbler.nix18
-rw-r--r--nixos/modules/services/development/bloop.nix23
-rw-r--r--nixos/modules/services/development/lorri.nix47
-rw-r--r--nixos/modules/services/editors/emacs.nix8
-rw-r--r--nixos/modules/services/editors/emacs.xml41
-rw-r--r--nixos/modules/services/editors/infinoted.nix14
-rw-r--r--nixos/modules/services/games/factorio.nix58
-rw-r--r--nixos/modules/services/games/minecraft-server.nix6
-rw-r--r--nixos/modules/services/games/openarena.nix56
-rw-r--r--nixos/modules/services/games/terraria.nix2
-rw-r--r--nixos/modules/services/hardware/80-net-setup-link.rules13
-rw-r--r--nixos/modules/services/hardware/actkbd.nix2
-rw-r--r--nixos/modules/services/hardware/bluetooth.nix34
-rw-r--r--nixos/modules/services/hardware/fancontrol.nix45
-rw-r--r--nixos/modules/services/hardware/freefall.nix2
-rw-r--r--nixos/modules/services/hardware/fwupd.nix32
-rw-r--r--nixos/modules/services/hardware/sane.nix6
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/brscan4.nix11
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix6
-rw-r--r--nixos/modules/services/hardware/tcsd.nix24
-rw-r--r--nixos/modules/services/hardware/throttled.nix30
-rw-r--r--nixos/modules/services/hardware/tlp.nix21
-rw-r--r--nixos/modules/services/hardware/trezord.nix40
-rw-r--r--nixos/modules/services/hardware/trezord.xml26
-rw-r--r--nixos/modules/services/hardware/triggerhappy.nix3
-rw-r--r--nixos/modules/services/hardware/udev.nix23
-rw-r--r--nixos/modules/services/hardware/udisks2.nix5
-rw-r--r--nixos/modules/services/hardware/upower.nix234
-rw-r--r--nixos/modules/services/hardware/usbmuxd.nix14
-rw-r--r--nixos/modules/services/hardware/vdr.nix1
-rw-r--r--nixos/modules/services/logging/SystemdJournal2Gelf.nix6
-rw-r--r--nixos/modules/services/logging/awstats.nix304
-rw-r--r--nixos/modules/services/logging/graylog.nix17
-rw-r--r--nixos/modules/services/logging/heartbeat.nix6
-rw-r--r--nixos/modules/services/logging/journalbeat.nix43
-rw-r--r--nixos/modules/services/logging/journalwatch.nix42
-rw-r--r--nixos/modules/services/logging/logcheck.nix17
-rw-r--r--nixos/modules/services/logging/logstash.nix7
-rw-r--r--nixos/modules/services/logging/rsyslogd.nix4
-rw-r--r--nixos/modules/services/logging/syslog-ng.nix4
-rw-r--r--nixos/modules/services/mail/davmail.nix2
-rw-r--r--nixos/modules/services/mail/dovecot.nix74
-rw-r--r--nixos/modules/services/mail/dspam.nix27
-rw-r--r--nixos/modules/services/mail/exim.nix14
-rw-r--r--nixos/modules/services/mail/mailcatcher.nix12
-rw-r--r--nixos/modules/services/mail/mailhog.nix1
-rw-r--r--nixos/modules/services/mail/mailman.nix297
-rw-r--r--nixos/modules/services/mail/mlmmj.nix10
-rw-r--r--nixos/modules/services/mail/nullmailer.nix11
-rw-r--r--nixos/modules/services/mail/opendkim.nix24
-rw-r--r--nixos/modules/services/mail/opensmtpd.nix18
-rw-r--r--nixos/modules/services/mail/pfix-srsd.nix2
-rw-r--r--nixos/modules/services/mail/postfix.nix67
-rw-r--r--nixos/modules/services/mail/postgrey.nix27
-rw-r--r--nixos/modules/services/mail/postsrsd.nix14
-rw-r--r--nixos/modules/services/mail/rmilter.nix252
-rw-r--r--nixos/modules/services/mail/roundcube.nix119
-rw-r--r--nixos/modules/services/mail/rspamd.nix21
-rw-r--r--nixos/modules/services/mail/rss2email.nix7
-rw-r--r--nixos/modules/services/mail/spamassassin.nix12
-rw-r--r--nixos/modules/services/misc/airsonic.nix9
-rw-r--r--nixos/modules/services/misc/apache-kafka.nix24
-rw-r--r--nixos/modules/services/misc/beanstalkd.nix3
-rw-r--r--nixos/modules/services/misc/bepasty.nix20
-rw-r--r--nixos/modules/services/misc/cgminer.nix7
-rw-r--r--nixos/modules/services/misc/couchpotato.nix18
-rw-r--r--nixos/modules/services/misc/cpuminer-cryptonight.nix8
-rw-r--r--nixos/modules/services/misc/dictd.nix10
-rw-r--r--nixos/modules/services/misc/docker-registry.nix6
-rw-r--r--nixos/modules/services/misc/dwm-status.nix73
-rw-r--r--nixos/modules/services/misc/dysnomia.nix11
-rw-r--r--nixos/modules/services/misc/emby.nix76
-rw-r--r--nixos/modules/services/misc/errbot.nix7
-rw-r--r--nixos/modules/services/misc/etcd.nix3
-rw-r--r--nixos/modules/services/misc/ethminer.nix117
-rw-r--r--nixos/modules/services/misc/exhibitor.nix7
-rw-r--r--nixos/modules/services/misc/felix.nix10
-rw-r--r--nixos/modules/services/misc/folding-at-home.nix5
-rw-r--r--nixos/modules/services/misc/fstrim.nix2
-rw-r--r--nixos/modules/services/misc/gitea.nix163
-rw-r--r--nixos/modules/services/misc/gitlab.nix486
-rw-r--r--nixos/modules/services/misc/gitlab.xml68
-rw-r--r--nixos/modules/services/misc/gitolite.nix33
-rw-r--r--nixos/modules/services/misc/gollum.nix16
-rw-r--r--nixos/modules/services/misc/gpsd.nix10
-rw-r--r--nixos/modules/services/misc/greenclip.nix31
-rw-r--r--nixos/modules/services/misc/headphones.nix24
-rw-r--r--nixos/modules/services/misc/home-assistant.nix46
-rw-r--r--nixos/modules/services/misc/jackett.nix9
-rw-r--r--nixos/modules/services/misc/jellyfin.nix57
-rw-r--r--nixos/modules/services/misc/lidarr.nix70
-rw-r--r--nixos/modules/services/misc/logkeys.nix2
-rw-r--r--nixos/modules/services/misc/mame.nix67
-rw-r--r--nixos/modules/services/misc/mantisbt.nix68
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix90
-rw-r--r--nixos/modules/services/misc/mediatomb.nix28
-rw-r--r--nixos/modules/services/misc/metabase.nix103
-rw-r--r--nixos/modules/services/misc/mwlib.nix5
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix141
-rw-r--r--nixos/modules/services/misc/nix-optimise.nix4
-rw-r--r--nixos/modules/services/misc/nixos-manual.nix4
-rw-r--r--nixos/modules/services/misc/nzbget.nix102
-rw-r--r--nixos/modules/services/misc/octoprint.nix23
-rw-r--r--nixos/modules/services/misc/osrm.nix1
-rw-r--r--nixos/modules/services/misc/packagekit.nix51
-rw-r--r--nixos/modules/services/misc/paperless.nix183
-rw-r--r--nixos/modules/services/misc/parsoid.nix4
-rw-r--r--nixos/modules/services/misc/phd.nix52
-rw-r--r--nixos/modules/services/misc/plex.nix104
-rw-r--r--nixos/modules/services/misc/pykms.nix45
-rw-r--r--nixos/modules/services/misc/redmine.nix104
-rw-r--r--nixos/modules/services/misc/ripple-data-api.nix5
-rw-r--r--nixos/modules/services/misc/rippled.nix5
-rw-r--r--nixos/modules/services/misc/rogue.nix2
-rw-r--r--nixos/modules/services/misc/serviio.nix25
-rw-r--r--nixos/modules/services/misc/sickbeard.nix24
-rw-r--r--nixos/modules/services/misc/siproxd.nix3
-rw-r--r--nixos/modules/services/misc/snapper.nix2
-rw-r--r--nixos/modules/services/misc/subsonic.nix4
-rw-r--r--nixos/modules/services/misc/synergy.nix4
-rw-r--r--nixos/modules/services/misc/taskserver/default.nix22
-rw-r--r--nixos/modules/services/misc/taskserver/doc.xml2
-rw-r--r--nixos/modules/services/misc/tautulli.nix4
-rw-r--r--nixos/modules/services/misc/tiddlywiki.nix52
-rw-r--r--nixos/modules/services/misc/uhub.nix34
-rw-r--r--nixos/modules/services/misc/zoneminder.nix84
-rw-r--r--nixos/modules/services/misc/zookeeper.nix4
-rw-r--r--nixos/modules/services/monitoring/alerta.nix11
-rw-r--r--nixos/modules/services/monitoring/apcupsd.nix4
-rw-r--r--nixos/modules/services/monitoring/bosun.nix10
-rw-r--r--nixos/modules/services/monitoring/cadvisor.nix11
-rw-r--r--nixos/modules/services/monitoring/collectd.nix45
-rw-r--r--nixos/modules/services/monitoring/datadog-agent.nix31
-rw-r--r--nixos/modules/services/monitoring/dd-agent/dd-agent.nix77
-rw-r--r--nixos/modules/services/monitoring/do-agent.nix34
-rw-r--r--nixos/modules/services/monitoring/fusion-inventory.nix6
-rw-r--r--nixos/modules/services/monitoring/grafana-reporter.nix4
-rw-r--r--nixos/modules/services/monitoring/grafana.nix10
-rw-r--r--nixos/modules/services/monitoring/graphite.nix25
-rw-r--r--nixos/modules/services/monitoring/heapster.nix9
-rw-r--r--nixos/modules/services/monitoring/kapacitor.nix19
-rw-r--r--nixos/modules/services/monitoring/loki.nix112
-rw-r--r--nixos/modules/services/monitoring/monit.nix4
-rw-r--r--nixos/modules/services/monitoring/munin.nix12
-rw-r--r--nixos/modules/services/monitoring/nagios.nix174
-rw-r--r--nixos/modules/services/monitoring/netdata.nix41
-rw-r--r--nixos/modules/services/monitoring/osquery.nix91
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix30
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix537
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix110
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.xml152
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bind.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix51
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/collectd.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix7
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix5
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/json.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/mail.nix157
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/minio.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix58
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/nginx.nix27
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/node.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/postfix.nix5
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/postgres.nix47
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix92
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/tor.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/unifi.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/varnish.nix4
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix66
-rw-r--r--nixos/modules/services/monitoring/riemann-dash.nix8
-rw-r--r--nixos/modules/services/monitoring/riemann-tools.nix13
-rw-r--r--nixos/modules/services/monitoring/scollector.nix7
-rw-r--r--nixos/modules/services/monitoring/statsd.nix3
-rw-r--r--nixos/modules/services/monitoring/sysstat.nix12
-rw-r--r--nixos/modules/services/monitoring/systemhealth.nix133
-rw-r--r--nixos/modules/services/monitoring/telegraf.nix5
-rw-r--r--nixos/modules/services/monitoring/thanos.nix835
-rw-r--r--nixos/modules/services/monitoring/ups.nix51
-rw-r--r--nixos/modules/services/monitoring/uptime.nix2
-rw-r--r--nixos/modules/services/monitoring/vnstat.nix19
-rw-r--r--nixos/modules/services/monitoring/zabbix-agent.nix164
-rw-r--r--nixos/modules/services/monitoring/zabbix-proxy.nix299
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix347
-rw-r--r--nixos/modules/services/network-filesystems/beegfs.nix357
-rw-r--r--nixos/modules/services/network-filesystems/ceph.nix163
-rw-r--r--nixos/modules/services/network-filesystems/davfs2.nix27
-rw-r--r--nixos/modules/services/network-filesystems/drbd.nix8
-rw-r--r--nixos/modules/services/network-filesystems/glusterfs.nix2
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix13
-rw-r--r--nixos/modules/services/network-filesystems/kbfs.nix89
-rw-r--r--nixos/modules/services/network-filesystems/nfsd.nix4
-rw-r--r--nixos/modules/services/network-filesystems/openafs/lib.nix2
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/client.nix97
-rw-r--r--nixos/modules/services/network-filesystems/orangefs/server.nix225
-rw-r--r--nixos/modules/services/network-filesystems/rsyncd.nix2
-rw-r--r--nixos/modules/services/network-filesystems/samba.nix40
-rw-r--r--nixos/modules/services/network-filesystems/tahoe.nix13
-rw-r--r--nixos/modules/services/network-filesystems/u9fs.nix1
-rw-r--r--nixos/modules/services/network-filesystems/yandex-disk.nix6
-rw-r--r--nixos/modules/services/networking/3proxy.nix424
-rw-r--r--nixos/modules/services/networking/aria2.nix24
-rw-r--r--nixos/modules/services/networking/autossh.nix7
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix344
-rw-r--r--nixos/modules/services/networking/babeld.nix6
-rw-r--r--nixos/modules/services/networking/bind.nix21
-rw-r--r--nixos/modules/services/networking/bird.nix21
-rw-r--r--nixos/modules/services/networking/bitcoind.nix193
-rw-r--r--nixos/modules/services/networking/bitlbee.nix6
-rw-r--r--nixos/modules/services/networking/btsync.nix324
-rw-r--r--nixos/modules/services/networking/charybdis.nix25
-rw-r--r--nixos/modules/services/networking/cjdns.nix21
-rw-r--r--nixos/modules/services/networking/connman.nix54
-rw-r--r--nixos/modules/services/networking/consul.nix4
-rw-r--r--nixos/modules/services/networking/corerad.nix46
-rw-r--r--nixos/modules/services/networking/coturn.nix14
-rw-r--r--nixos/modules/services/networking/ddclient.nix10
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix8
-rw-r--r--nixos/modules/services/networking/dhcpd.nix4
-rw-r--r--nixos/modules/services/networking/dnscache.nix2
-rw-r--r--nixos/modules/services/networking/dnschain.nix19
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy.xml8
-rw-r--r--nixos/modules/services/networking/dnscrypt-wrapper.nix4
-rw-r--r--nixos/modules/services/networking/dnsdist.nix5
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix14
-rw-r--r--nixos/modules/services/networking/ejabberd.nix42
-rw-r--r--nixos/modules/services/networking/eternal-terminal.nix6
-rw-r--r--nixos/modules/services/networking/fakeroute.nix2
-rw-r--r--nixos/modules/services/networking/firewall.nix32
-rw-r--r--nixos/modules/services/networking/flannel.nix2
-rw-r--r--nixos/modules/services/networking/gale.nix5
-rw-r--r--nixos/modules/services/networking/git-daemon.nix14
-rw-r--r--nixos/modules/services/networking/gnunet.nix8
-rw-r--r--nixos/modules/services/networking/go-shadowsocks2.nix30
-rw-r--r--nixos/modules/services/networking/gogoclient.nix2
-rw-r--r--nixos/modules/services/networking/hans.nix4
-rw-r--r--nixos/modules/services/networking/haproxy.nix36
-rw-r--r--nixos/modules/services/networking/helpers.nix11
-rw-r--r--nixos/modules/services/networking/hostapd.nix30
-rw-r--r--nixos/modules/services/networking/hylafax/systemd.nix18
-rw-r--r--nixos/modules/services/networking/i2pd.nix30
-rw-r--r--nixos/modules/services/networking/iodine.nix10
-rw-r--r--nixos/modules/services/networking/iperf3.nix10
-rw-r--r--nixos/modules/services/networking/ircd-hybrid/default.nix7
-rw-r--r--nixos/modules/services/networking/iwd.nix1
-rw-r--r--nixos/modules/services/networking/keybase.nix12
-rw-r--r--nixos/modules/services/networking/kippo.nix19
-rw-r--r--nixos/modules/services/networking/knot.nix2
-rw-r--r--nixos/modules/services/networking/kresd.nix76
-rw-r--r--nixos/modules/services/networking/logmein-hamachi.nix2
-rw-r--r--nixos/modules/services/networking/matterbridge.nix14
-rw-r--r--nixos/modules/services/networking/minidlna.nix77
-rw-r--r--nixos/modules/services/networking/mjpg-streamer.nix9
-rw-r--r--nixos/modules/services/networking/monero.nix20
-rw-r--r--nixos/modules/services/networking/morty.nix5
-rw-r--r--nixos/modules/services/networking/mosquitto.nix6
-rw-r--r--nixos/modules/services/networking/mtprotoproxy.nix110
-rw-r--r--nixos/modules/services/networking/murmur.nix35
-rw-r--r--nixos/modules/services/networking/mxisd.nix53
-rw-r--r--nixos/modules/services/networking/namecoind.nix10
-rw-r--r--nixos/modules/services/networking/nat.nix34
-rw-r--r--nixos/modules/services/networking/ndppd.nix24
-rw-r--r--nixos/modules/services/networking/networkmanager.nix438
-rw-r--r--nixos/modules/services/networking/nghttpx/default.nix1
-rw-r--r--nixos/modules/services/networking/nix-serve.nix4
-rw-r--r--nixos/modules/services/networking/nntp-proxy.nix5
-rw-r--r--nixos/modules/services/networking/nsd.nix15
-rw-r--r--nixos/modules/services/networking/ntp/chrony.nix (renamed from nixos/modules/services/networking/chrony.nix)21
-rw-r--r--nixos/modules/services/networking/ntp/ntpd.nix (renamed from nixos/modules/services/networking/ntpd.nix)6
-rw-r--r--nixos/modules/services/networking/ntp/openntpd.nix (renamed from nixos/modules/services/networking/openntpd.nix)6
-rw-r--r--nixos/modules/services/networking/nylon.nix8
-rw-r--r--nixos/modules/services/networking/ofono.nix44
-rw-r--r--nixos/modules/services/networking/openvpn.nix9
-rw-r--r--nixos/modules/services/networking/ostinato.nix4
-rw-r--r--nixos/modules/services/networking/owamp.nix8
-rw-r--r--nixos/modules/services/networking/pdns-recursor.nix98
-rw-r--r--nixos/modules/services/networking/pdnsd.nix6
-rw-r--r--nixos/modules/services/networking/polipo.nix20
-rw-r--r--nixos/modules/services/networking/pppd.nix136
-rw-r--r--nixos/modules/services/networking/pptpd.nix4
-rw-r--r--nixos/modules/services/networking/prayer.nix11
-rw-r--r--nixos/modules/services/networking/privoxy.nix2
-rw-r--r--nixos/modules/services/networking/prosody.nix12
-rw-r--r--nixos/modules/services/networking/quassel.nix26
-rw-r--r--nixos/modules/services/networking/quicktun.nix24
-rw-r--r--nixos/modules/services/networking/radicale.nix15
-rw-r--r--nixos/modules/services/networking/rdnssd.nix5
-rw-r--r--nixos/modules/services/networking/resilio.nix2
-rw-r--r--nixos/modules/services/networking/searx.nix2
-rw-r--r--nixos/modules/services/networking/shadowsocks.nix6
-rw-r--r--nixos/modules/services/networking/shairport-sync.nix5
-rw-r--r--nixos/modules/services/networking/shorewall.nix75
-rw-r--r--nixos/modules/services/networking/shorewall6.nix75
-rw-r--r--nixos/modules/services/networking/shout.nix5
-rw-r--r--nixos/modules/services/networking/smokeping.nix68
-rw-r--r--nixos/modules/services/networking/softether.nix4
-rw-r--r--nixos/modules/services/networking/spacecookie.nix83
-rw-r--r--nixos/modules/services/networking/squid.nix3
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix25
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/module.nix5
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix4
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/param-lib.nix12
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix50
-rw-r--r--nixos/modules/services/networking/strongswan.nix3
-rw-r--r--nixos/modules/services/networking/stubby.nix2
-rw-r--r--nixos/modules/services/networking/stunnel.nix25
-rw-r--r--nixos/modules/services/networking/supybot.nix4
-rw-r--r--nixos/modules/services/networking/syncplay.nix80
-rw-r--r--nixos/modules/services/networking/syncthing.nix366
-rw-r--r--nixos/modules/services/networking/tcpcrypt.nix3
-rw-r--r--nixos/modules/services/networking/teamspeak3.nix10
-rw-r--r--nixos/modules/services/networking/tedicross.nix100
-rw-r--r--nixos/modules/services/networking/thelounge.nix76
-rw-r--r--nixos/modules/services/networking/tinc.nix1
-rw-r--r--nixos/modules/services/networking/tinydns.nix3
-rw-r--r--nixos/modules/services/networking/tox-bootstrapd.nix5
-rw-r--r--nixos/modules/services/networking/toxvpn.nix6
-rw-r--r--nixos/modules/services/networking/trickster.nix112
-rw-r--r--nixos/modules/services/networking/unbound.nix15
-rw-r--r--nixos/modules/services/networking/unifi.nix12
-rw-r--r--nixos/modules/services/networking/v2ray.nix81
-rw-r--r--nixos/modules/services/networking/vsftpd.nix146
-rw-r--r--nixos/modules/services/networking/websockify.nix4
-rw-r--r--nixos/modules/services/networking/wg-quick.nix312
-rw-r--r--nixos/modules/services/networking/wireguard.nix233
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix29
-rw-r--r--nixos/modules/services/networking/xandikos.nix148
-rw-r--r--nixos/modules/services/networking/xinetd.nix16
-rw-r--r--nixos/modules/services/networking/xl2tpd.nix4
-rw-r--r--nixos/modules/services/networking/yggdrasil.nix187
-rw-r--r--nixos/modules/services/networking/zerobin.nix4
-rw-r--r--nixos/modules/services/networking/zeronet.nix102
-rw-r--r--nixos/modules/services/networking/zerotierone.nix10
-rw-r--r--nixos/modules/services/networking/znc/default.nix36
-rw-r--r--nixos/modules/services/printing/cupsd.nix52
-rw-r--r--nixos/modules/services/scheduling/atd.nix10
-rw-r--r--nixos/modules/services/scheduling/fcron.nix6
-rw-r--r--nixos/modules/services/scheduling/marathon.nix2
-rw-r--r--nixos/modules/services/search/elasticsearch.nix1
-rw-r--r--nixos/modules/services/search/hound.nix22
-rw-r--r--nixos/modules/services/search/kibana.nix9
-rw-r--r--nixos/modules/services/search/solr.nix26
-rw-r--r--nixos/modules/services/security/bitwarden_rs/backup.sh17
-rw-r--r--nixos/modules/services/security/bitwarden_rs/default.nix129
-rw-r--r--nixos/modules/services/security/certmgr.nix4
-rw-r--r--nixos/modules/services/security/clamav.nix13
-rw-r--r--nixos/modules/services/security/fprintd.nix30
-rw-r--r--nixos/modules/services/security/fprot.nix16
-rw-r--r--nixos/modules/services/security/haka.nix4
-rw-r--r--nixos/modules/services/security/munge.nix2
-rw-r--r--nixos/modules/services/security/oauth2_proxy.nix13
-rw-r--r--nixos/modules/services/security/oauth2_proxy_nginx.nix4
-rw-r--r--nixos/modules/services/security/physlock.nix2
-rw-r--r--nixos/modules/services/security/sks.nix26
-rw-r--r--nixos/modules/services/security/sshguard.nix27
-rw-r--r--nixos/modules/services/security/tor.nix18
-rw-r--r--nixos/modules/services/security/torify.nix3
-rw-r--r--nixos/modules/services/security/torsocks.nix9
-rw-r--r--nixos/modules/services/security/usbguard.nix16
-rw-r--r--nixos/modules/services/security/vault.nix8
-rw-r--r--nixos/modules/services/system/cgmanager.nix1
-rw-r--r--nixos/modules/services/system/cloud-init.nix6
-rw-r--r--nixos/modules/services/system/dbus.nix7
-rw-r--r--nixos/modules/services/system/earlyoom.nix15
-rw-r--r--nixos/modules/services/system/kerberos/default.nix2
-rw-r--r--nixos/modules/services/system/kerberos/heimdal.nix2
-rw-r--r--nixos/modules/services/system/kerberos/mit.nix2
-rw-r--r--nixos/modules/services/system/localtime.nix39
-rw-r--r--nixos/modules/services/system/nscd.conf18
-rw-r--r--nixos/modules/services/system/nscd.nix31
-rw-r--r--nixos/modules/services/torrent/deluge.nix222
-rw-r--r--nixos/modules/services/torrent/flexget.nix4
-rw-r--r--nixos/modules/services/torrent/magnetico.nix218
-rw-r--r--nixos/modules/services/torrent/transmission.nix63
-rw-r--r--nixos/modules/services/ttys/agetty.nix5
-rw-r--r--nixos/modules/services/web-apps/atlassian/confluence.nix22
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix21
-rw-r--r--nixos/modules/services/web-apps/atlassian/jira.nix22
-rw-r--r--nixos/modules/services/web-apps/codimd.nix1
-rw-r--r--nixos/modules/services/web-apps/cryptpad.nix54
-rw-r--r--nixos/modules/services/web-apps/documize.nix182
-rw-r--r--nixos/modules/services/web-apps/dokuwiki.nix272
-rw-r--r--nixos/modules/services/web-apps/frab.nix23
-rw-r--r--nixos/modules/services/web-apps/gotify-server.nix49
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix49
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix2
-rw-r--r--nixos/modules/services/web-apps/ihatemoney/default.nix141
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix280
-rw-r--r--nixos/modules/services/web-apps/matomo-doc.xml30
-rw-r--r--nixos/modules/services/web-apps/matomo.nix82
-rw-r--r--nixos/modules/services/web-apps/mattermost.nix22
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix462
-rw-r--r--nixos/modules/services/web-apps/miniflux.nix2
-rw-r--r--nixos/modules/services/web-apps/moinmoin.nix303
-rw-r--r--nixos/modules/services/web-apps/moodle.nix311
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix166
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml58
-rw-r--r--nixos/modules/services/web-apps/nexus.nix5
-rw-r--r--nixos/modules/services/web-apps/pgpkeyserver-lite.nix2
-rw-r--r--nixos/modules/services/web-apps/restya-board.nix60
-rw-r--r--nixos/modules/services/web-apps/selfoss.nix33
-rw-r--r--nixos/modules/services/web-apps/shiori.nix50
-rw-r--r--nixos/modules/services/web-apps/trac.nix79
-rw-r--r--nixos/modules/services/web-apps/trilium.nix137
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix98
-rw-r--r--nixos/modules/services/web-apps/virtlyst.nix1
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix366
-rw-r--r--nixos/modules/services/web-apps/youtrack.nix18
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix217
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix689
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/foswiki.nix78
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/limesurvey.nix196
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/mediawiki.nix349
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/mercurial.nix75
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/per-server-options.nix415
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/phabricator.nix50
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix103
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/trac.nix121
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/wordpress.nix285
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/zabbix.nix84
-rw-r--r--nixos/modules/services/web-servers/caddy.nix6
-rw-r--r--nixos/modules/services/web-servers/darkhttpd.nix77
-rw-r--r--nixos/modules/services/web-servers/hitch/default.nix5
-rw-r--r--nixos/modules/services/web-servers/hydron.nix46
-rw-r--r--nixos/modules/services/web-servers/lighttpd/cgit.nix2
-rw-r--r--nixos/modules/services/web-servers/lighttpd/collectd.nix2
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix161
-rw-r--r--nixos/modules/services/web-servers/nginx/gitweb.nix53
-rw-r--r--nixos/modules/services/web-servers/nginx/location-options.nix5
-rw-r--r--nixos/modules/services/web-servers/nginx/vhost-options.nix3
-rw-r--r--nixos/modules/services/web-servers/phpfpm/default.nix258
-rw-r--r--nixos/modules/services/web-servers/phpfpm/pool-options.nix57
-rw-r--r--nixos/modules/services/web-servers/tomcat.nix10
-rw-r--r--nixos/modules/services/web-servers/traefik.nix3
-rw-r--r--nixos/modules/services/web-servers/ttyd.nix196
-rw-r--r--nixos/modules/services/web-servers/unit/default.nix45
-rw-r--r--nixos/modules/services/web-servers/uwsgi.nix35
-rw-r--r--nixos/modules/services/web-servers/varnish/default.nix6
-rw-r--r--nixos/modules/services/web-servers/winstone.nix129
-rw-r--r--nixos/modules/services/web-servers/zope2.nix10
-rw-r--r--nixos/modules/services/x11/clight.nix115
-rw-r--r--nixos/modules/services/x11/colord.nix1
-rw-r--r--nixos/modules/services/x11/desktop-managers/cde.nix55
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix51
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix27
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix373
-rw-r--r--nixos/modules/services/x11/desktop-managers/lumina.nix15
-rw-r--r--nixos/modules/services/x11/desktop-managers/lxqt.nix5
-rw-r--r--nixos/modules/services/x11/desktop-managers/mate.nix25
-rw-r--r--nixos/modules/services/x11/desktop-managers/maxx.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix316
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix48
-rw-r--r--nixos/modules/services/x11/desktop-managers/surf-display.nix127
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix134
-rw-r--r--nixos/modules/services/x11/desktop-managers/xterm.nix6
-rw-r--r--nixos/modules/services/x11/display-managers/account-service-util.nix39
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix270
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix144
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix36
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix36
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix12
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix17
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix132
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix42
-rwxr-xr-xnixos/modules/services/x11/display-managers/set-session.py86
-rw-r--r--nixos/modules/services/x11/display-managers/slim.nix160
-rw-r--r--nixos/modules/services/x11/extra-layouts.nix174
-rw-r--r--nixos/modules/services/x11/gdk-pixbuf.nix8
-rw-r--r--nixos/modules/services/x11/hardware/cmt.nix59
-rw-r--r--nixos/modules/services/x11/hardware/digimend.nix43
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix29
-rw-r--r--nixos/modules/services/x11/hardware/multitouch.nix94
-rw-r--r--nixos/modules/services/x11/hardware/synaptics.nix8
-rw-r--r--nixos/modules/services/x11/imwheel.nix68
-rw-r--r--nixos/modules/services/x11/picom.nix (renamed from nixos/modules/services/x11/compton.nix)186
-rw-r--r--nixos/modules/services/x11/redshift.nix70
-rw-r--r--nixos/modules/services/x11/unclutter.nix10
-rw-r--r--nixos/modules/services/x11/urxvtd.nix2
-rw-r--r--nixos/modules/services/x11/window-managers/cwm.nix23
-rw-r--r--nixos/modules/services/x11/window-managers/default.nix17
-rw-r--r--nixos/modules/services/x11/window-managers/i3.nix5
-rw-r--r--nixos/modules/services/x11/window-managers/leftwm.nix25
-rw-r--r--nixos/modules/services/x11/window-managers/xmonad.nix35
-rw-r--r--nixos/modules/services/x11/xautolock.nix4
-rw-r--r--nixos/modules/services/x11/xserver.nix60
-rw-r--r--nixos/modules/system/activation/activation-script.nix35
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl37
-rw-r--r--nixos/modules/system/activation/top-level.nix4
-rw-r--r--nixos/modules/system/boot/binfmt.nix327
-rw-r--r--nixos/modules/system/boot/coredump.nix66
-rw-r--r--nixos/modules/system/boot/grow-partition.nix3
-rw-r--r--nixos/modules/system/boot/kernel.nix12
-rw-r--r--nixos/modules/system/boot/kernel_config.nix1
-rw-r--r--nixos/modules/system/boot/kexec.nix14
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir.nix4
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix2
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix4
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh5
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix21
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl23
-rw-r--r--nixos/modules/system/boot/loader/init-script/init-script.nix4
-rw-r--r--nixos/modules/system/boot/loader/loader.nix7
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh10
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix10
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix13
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py33
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix35
-rw-r--r--nixos/modules/system/boot/luksroot.nix259
-rw-r--r--nixos/modules/system/boot/networkd.nix154
-rw-r--r--nixos/modules/system/boot/plymouth.nix33
-rw-r--r--nixos/modules/system/boot/resolved.nix50
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh31
-rw-r--r--nixos/modules/system/boot/stage-1.nix8
-rw-r--r--nixos/modules/system/boot/stage-2-init.sh2
-rw-r--r--nixos/modules/system/boot/stage-2.nix9
-rw-r--r--nixos/modules/system/boot/systemd-lib.nix8
-rw-r--r--nixos/modules/system/boot/systemd-nspawn.nix24
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix6
-rw-r--r--nixos/modules/system/boot/systemd.nix110
-rw-r--r--nixos/modules/system/boot/timesyncd.nix27
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix32
-rw-r--r--nixos/modules/tasks/filesystems.nix4
-rw-r--r--nixos/modules/tasks/filesystems/nfs.nix7
-rw-r--r--nixos/modules/tasks/filesystems/xfs.nix1
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix134
-rw-r--r--nixos/modules/tasks/kbd.nix127
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix6
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix44
-rw-r--r--nixos/modules/tasks/network-interfaces.nix66
-rw-r--r--nixos/modules/tasks/powertop.nix1
-rw-r--r--nixos/modules/tasks/swraid.nix43
-rw-r--r--nixos/modules/testing/test-instrumentation.nix5
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix24
-rw-r--r--nixos/modules/virtualisation/amazon-init.nix11
-rw-r--r--nixos/modules/virtualisation/amazon-options.nix9
-rw-r--r--nixos/modules/virtualisation/anbox.nix9
-rw-r--r--nixos/modules/virtualisation/azure-agent.nix1
-rw-r--r--nixos/modules/virtualisation/azure-image.nix1
-rw-r--r--nixos/modules/virtualisation/brightbox-image.nix2
-rw-r--r--nixos/modules/virtualisation/cloudstack-config.nix2
-rw-r--r--nixos/modules/virtualisation/container-config.nix2
-rw-r--r--nixos/modules/virtualisation/containers.nix142
-rw-r--r--nixos/modules/virtualisation/cri-o.nix106
-rw-r--r--nixos/modules/virtualisation/digital-ocean-config.nix197
-rw-r--r--nixos/modules/virtualisation/digital-ocean-image.nix69
-rw-r--r--nixos/modules/virtualisation/digital-ocean-init.nix95
-rw-r--r--nixos/modules/virtualisation/docker-containers.nix76
-rw-r--r--nixos/modules/virtualisation/docker.nix4
-rw-r--r--nixos/modules/virtualisation/ec2-amis.nix18
-rw-r--r--nixos/modules/virtualisation/ec2-data.nix6
-rw-r--r--nixos/modules/virtualisation/google-compute-config.nix167
-rw-r--r--nixos/modules/virtualisation/google-compute-image.nix2
-rw-r--r--nixos/modules/virtualisation/kvmgt.nix36
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix123
-rw-r--r--nixos/modules/virtualisation/lxc.nix2
-rw-r--r--nixos/modules/virtualisation/lxd.nix28
-rw-r--r--nixos/modules/virtualisation/openvswitch.nix15
-rw-r--r--nixos/modules/virtualisation/parallels-guest.nix3
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix137
-rw-r--r--nixos/modules/virtualisation/railcar.nix125
-rw-r--r--nixos/modules/virtualisation/virtualbox-host.nix13
-rw-r--r--nixos/modules/virtualisation/vmware-guest.nix6
-rw-r--r--nixos/modules/virtualisation/xen-dom0.nix38
827 files changed, 28856 insertions, 14041 deletions
diff --git a/nixos/modules/config/console.nix b/nixos/modules/config/console.nix
new file mode 100644
index 000000000000..f662ed62d31d
--- /dev/null
+++ b/nixos/modules/config/console.nix
@@ -0,0 +1,203 @@
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.console;
+
+  makeColor = i: concatMapStringsSep "," (x: "0x" + substring (2*i) 2 x);
+
+  isUnicode = hasSuffix "UTF-8" (toUpper config.i18n.defaultLocale);
+
+  optimizedKeymap = pkgs.runCommand "keymap" {
+    nativeBuildInputs = [ pkgs.buildPackages.kbd ];
+    LOADKEYS_KEYMAP_PATH = "${consoleEnv}/share/keymaps/**";
+    preferLocalBuild = true;
+  } ''
+    loadkeys -b ${optionalString isUnicode "-u"} "${cfg.keyMap}" > $out
+  '';
+
+  # Sadly, systemd-vconsole-setup doesn't support binary keymaps.
+  vconsoleConf = pkgs.writeText "vconsole.conf" ''
+    KEYMAP=${cfg.keyMap}
+    FONT=${cfg.font}
+  '';
+
+  consoleEnv = pkgs.buildEnv {
+    name = "console-env";
+    paths = [ pkgs.kbd ] ++ cfg.packages;
+    pathsToLink = [
+      "/share/consolefonts"
+      "/share/consoletrans"
+      "/share/keymaps"
+      "/share/unimaps"
+    ];
+  };
+
+  setVconsole = !config.boot.isContainer;
+in
+
+{
+  ###### interface
+
+  options.console  = {
+    font = mkOption {
+      type = types.str;
+      default = "Lat2-Terminus16";
+      example = "LatArCyrHeb-16";
+      description = ''
+        The font used for the virtual consoles.  Leave empty to use
+        whatever the <command>setfont</command> program considers the
+        default font.
+      '';
+    };
+
+    keyMap = mkOption {
+      type = with types; either str path;
+      default = "us";
+      example = "fr";
+      description = ''
+        The keyboard mapping table for the virtual consoles.
+      '';
+    };
+
+    colors = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [
+        "002b36" "dc322f" "859900" "b58900"
+        "268bd2" "d33682" "2aa198" "eee8d5"
+        "002b36" "cb4b16" "586e75" "657b83"
+        "839496" "6c71c4" "93a1a1" "fdf6e3"
+      ];
+      description = ''
+        The 16 colors palette used by the virtual consoles.
+        Leave empty to use the default colors.
+        Colors must be in hexadecimal format and listed in
+        order from color 0 to color 15.
+      '';
+
+    };
+
+    packages = mkOption {
+      type = types.listOf types.package;
+      default = with pkgs.kbdKeymaps; [ dvp neo ];
+      defaultText = ''with pkgs.kbdKeymaps; [ dvp neo ]'';
+      description = ''
+        List of additional packages that provide console fonts, keymaps and
+        other resources for virtual consoles use.
+      '';
+    };
+
+    extraTTYs = mkOption {
+      default = [];
+      type = types.listOf types.str;
+      example = ["tty8" "tty9"];
+      description = ''
+        TTY (virtual console) devices, in addition to the consoles on
+        which mingetty and syslogd run, that must be initialised.
+        Only useful if you have some program that you want to run on
+        some fixed console.  For example, the NixOS installation CD
+        opens the manual in a web browser on console 7, so it sets
+        <option>console.extraTTYs</option> to <literal>["tty7"]</literal>.
+      '';
+    };
+
+    useXkbConfig = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        If set, configure the virtual console keymap from the xserver
+        keyboard settings.
+      '';
+    };
+
+    earlySetup = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enable setting virtual console options as early as possible (in initrd).
+      '';
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkMerge [
+    { console.keyMap = with config.services.xserver;
+        mkIf cfg.useXkbConfig
+          (pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } ''
+            '${pkgs.ckbcomp}/bin/ckbcomp' -model '${xkbModel}' -layout '${layout}' \
+              -option '${xkbOptions}' -variant '${xkbVariant}' > "$out"
+          '');
+    }
+
+    (mkIf (!setVconsole) {
+      systemd.services.systemd-vconsole-setup.enable = false;
+    })
+
+    (mkIf setVconsole (mkMerge [
+      { environment.systemPackages = [ pkgs.kbd ];
+
+        # Let systemd-vconsole-setup.service do the work of setting up the
+        # virtual consoles.
+        environment.etc."vconsole.conf".source = vconsoleConf;
+        # Provide kbd with additional packages.
+        environment.etc.kbd.source = "${consoleEnv}/share";
+
+        boot.initrd.preLVMCommands = mkBefore ''
+          kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console
+          printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console
+          loadkmap < ${optimizedKeymap}
+
+          ${optionalString cfg.earlySetup ''
+            setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
+          ''}
+        '';
+
+        systemd.services.systemd-vconsole-setup =
+          { before = [ "display-manager.service" ];
+            after = [ "systemd-udev-settle.service" ];
+            restartTriggers = [ vconsoleConf consoleEnv ];
+          };
+      }
+
+      (mkIf (cfg.colors != []) {
+        boot.kernelParams = [
+          "vt.default_red=${makeColor 0 cfg.colors}"
+          "vt.default_grn=${makeColor 1 cfg.colors}"
+          "vt.default_blu=${makeColor 2 cfg.colors}"
+        ];
+      })
+
+      (mkIf cfg.earlySetup {
+        boot.initrd.extraUtilsCommands = ''
+          mkdir -p $out/share/consolefonts
+          ${if substring 0 1 cfg.font == "/" then ''
+            font="${cfg.font}"
+          '' else ''
+            font="$(echo ${consoleEnv}/share/consolefonts/${cfg.font}.*)"
+          ''}
+          if [[ $font == *.gz ]]; then
+            gzip -cd $font > $out/share/consolefonts/font.psf
+          else
+            cp -L $font $out/share/consolefonts/font.psf
+          fi
+        '';
+      })
+    ]))
+  ];
+
+  imports = [
+    (mkRenamedOptionModule [ "i18n" "consoleFont" ] [ "console" "font" ])
+    (mkRenamedOptionModule [ "i18n" "consoleKeyMap" ] [ "console" "keyMap" ])
+    (mkRenamedOptionModule [ "i18n" "consoleColors" ] [ "console" "colors" ])
+    (mkRenamedOptionModule [ "i18n" "consolePackages" ] [ "console" "packages" ])
+    (mkRenamedOptionModule [ "i18n" "consoleUseXkbConfig" ] [ "console" "useXkbConfig" ])
+    (mkRenamedOptionModule [ "boot" "earlyVconsoleSetup" ] [ "console" "earlySetup" ])
+    (mkRenamedOptionModule [ "boot" "extraTTYs" ] [ "console" "extraTTYs" ])
+  ];
+}
diff --git a/nixos/modules/config/fonts/corefonts.nix b/nixos/modules/config/fonts/corefonts.nix
deleted file mode 100644
index b9f69879a103..000000000000
--- a/nixos/modules/config/fonts/corefonts.nix
+++ /dev/null
@@ -1,36 +0,0 @@
-# This module is deprecated, since you can just say ‘fonts.fonts = [
-# pkgs.corefonts ];’ instead.
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-{
-
-  options = {
-
-    fonts = {
-
-      enableCoreFonts = mkOption {
-        visible = false;
-        default = false;
-        description = ''
-          Whether to include Microsoft's proprietary Core Fonts.  These fonts
-          are redistributable, but only verbatim, among other restrictions.
-          See <link xlink:href="http://corefonts.sourceforge.net/eula.htm"/>
-          for details.
-       '';
-      };
-
-    };
-
-  };
-
-
-  config = mkIf config.fonts.enableCoreFonts {
-
-    fonts.fonts = [ pkgs.corefonts ];
-
-  };
-
-}
diff --git a/nixos/modules/config/fonts/fontconfig-penultimate.nix b/nixos/modules/config/fonts/fontconfig-penultimate.nix
index 2c18244621af..7e311a21acf6 100644
--- a/nixos/modules/config/fonts/fontconfig-penultimate.nix
+++ b/nixos/modules/config/fonts/fontconfig-penultimate.nix
@@ -31,12 +31,12 @@ let
   # use latest when no version is passed
   makeCacheConf = { version ? null }:
     let
-      fcPackage = if builtins.isNull version
+      fcPackage = if version == null
                   then "fontconfig"
                   else "fontconfig_${version}";
       makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
-      cache     = makeCache pkgs."${fcPackage}";
-      cache32   = makeCache pkgs.pkgsi686Linux."${fcPackage}";
+      cache     = makeCache pkgs.${fcPackage};
+      cache32   = makeCache pkgs.pkgsi686Linux.${fcPackage};
     in
     pkgs.writeText "fc-00-nixos-cache.conf" ''
       <?xml version='1.0'?>
@@ -52,85 +52,11 @@ let
       </fontconfig>
     '';
 
+  # local configuration file
   localConf = pkgs.writeText "fc-local.conf" cfg.localConf;
 
-  # The configuration to be included in /etc/font/
-  penultimateConf = pkgs.runCommand "font-penultimate-conf" {
-    preferLocalBuild = true;
-    } ''
-    support_folder=$out/etc/fonts/conf.d
-    latest_folder=$out/etc/fonts/${latestVersion}/conf.d
-
-    mkdir -p $support_folder
-    mkdir -p $latest_folder
-
-    ln -s ${supportFontsConf} $support_folder/../fonts.conf
-    ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
-          $latest_folder/../fonts.conf
-
-    # fontconfig-penultimate various configuration files
-    ln -s ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/*.conf \
-          $support_folder
-    ln -s ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/*.conf \
-          $latest_folder
-
-    ln -s ${cacheConfSupport} $support_folder/00-nixos-cache.conf
-    ln -s ${cacheConfLatest}  $latest_folder/00-nixos-cache.conf
-
-    rm $support_folder/10-antialias.conf $latest_folder/10-antialias.conf
-    ln -s ${antialiasConf} $support_folder/10-antialias.conf
-    ln -s ${antialiasConf} $latest_folder/10-antialias.conf
-
-    rm $support_folder/10-hinting.conf $latest_folder/10-hinting.conf
-    ln -s ${hintingConf} $support_folder/10-hinting.conf
-    ln -s ${hintingConf} $latest_folder/10-hinting.conf
-
-    ${optionalString cfg.useEmbeddedBitmaps ''
-    rm $support_folder/10-no-embedded-bitmaps.conf
-    rm $latest_folder/10-no-embedded-bitmaps.conf
-    ''}
-
-    rm $support_folder/10-subpixel.conf $latest_folder/10-subpixel.conf
-    ln -s ${subpixelConf} $support_folder/10-subpixel.conf
-    ln -s ${subpixelConf} $latest_folder/10-subpixel.conf
-
-    ${optionalString (cfg.dpi != 0) ''
-    ln -s ${dpiConf} $support_folder/11-dpi.conf
-    ln -s ${dpiConf} $latest_folder/11-dpi.conf
-    ''}
-
-    ${optionalString (!cfg.includeUserConf) ''
-    rm $support_folder/50-user.conf
-    rm $latest_folder/50-user.conf
-    ''}
-
-    # 51-local.conf
-    rm $latest_folder/51-local.conf
-    substitute \
-      ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/51-local.conf \
-      $latest_folder/51-local.conf \
-      --replace local.conf /etc/fonts/${latestVersion}/local.conf
-
-    # local.conf (indirect priority 51)
-    ${optionalString (cfg.localConf != "") ''
-    ln -s ${localConf}        $out/etc/fonts/local.conf
-    ln -s ${localConf}        $out/etc/fonts/${latestVersion}/local.conf
-    ''}
-
-    ln -s ${defaultFontsConf} $support_folder/52-default-fonts.conf
-    ln -s ${defaultFontsConf} $latest_folder/52-default-fonts.conf
-
-    ${optionalString cfg.allowBitmaps ''
-    rm $support_folder/53-no-bitmaps.conf
-    rm $latest_folder/53-no-bitmaps.conf
-    ''}
-
-    ${optionalString (!cfg.allowType1) ''
-    ln -s ${rejectType1} $support_folder/53-no-type1.conf
-    ln -s ${rejectType1} $latest_folder/53-no-type1.conf
-    ''}
-  '';
-
+  # rendering settings configuration files
+  # priority 10
   hintingConf = pkgs.writeText "fc-10-hinting.conf" ''
     <?xml version='1.0'?>
     <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
@@ -199,6 +125,8 @@ let
     </fontconfig>
   '';
 
+  # default fonts configuration file
+  # priority 52
   defaultFontsConf =
     let genDefault = fonts: name:
       optionalString (fonts != []) ''
@@ -228,7 +156,9 @@ let
     </fontconfig>
   '';
 
-  rejectType1 = pkgs.writeText "fc-53-no-type1.conf" ''
+  # reject Type 1 fonts
+  # priority 53
+  rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
     <?xml version="1.0"?>
     <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
     <fontconfig>
@@ -245,6 +175,88 @@ let
     </fontconfig>
   '';
 
+  # The configuration to be included in /etc/font/
+  penultimateConf = pkgs.runCommand "fontconfig-penultimate-conf" {
+    preferLocalBuild = true;
+  } ''
+    support_folder=$out/etc/fonts/conf.d
+    latest_folder=$out/etc/fonts/${latestVersion}/conf.d
+
+    mkdir -p $support_folder
+    mkdir -p $latest_folder
+
+    # fonts.conf
+    ln -s ${supportFontsConf} $support_folder/../fonts.conf
+    ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
+          $latest_folder/../fonts.conf
+
+    # fontconfig-penultimate various configuration files
+    ln -s ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/*.conf \
+          $support_folder
+    ln -s ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/*.conf \
+          $latest_folder
+
+    ln -s ${cacheConfSupport} $support_folder/00-nixos-cache.conf
+    ln -s ${cacheConfLatest}  $latest_folder/00-nixos-cache.conf
+
+    rm $support_folder/10-antialias.conf $latest_folder/10-antialias.conf
+    ln -s ${antialiasConf} $support_folder/10-antialias.conf
+    ln -s ${antialiasConf} $latest_folder/10-antialias.conf
+
+    rm $support_folder/10-hinting.conf $latest_folder/10-hinting.conf
+    ln -s ${hintingConf} $support_folder/10-hinting.conf
+    ln -s ${hintingConf} $latest_folder/10-hinting.conf
+
+    ${optionalString cfg.useEmbeddedBitmaps ''
+    rm $support_folder/10-no-embedded-bitmaps.conf
+    rm $latest_folder/10-no-embedded-bitmaps.conf
+    ''}
+
+    rm $support_folder/10-subpixel.conf $latest_folder/10-subpixel.conf
+    ln -s ${subpixelConf} $support_folder/10-subpixel.conf
+    ln -s ${subpixelConf} $latest_folder/10-subpixel.conf
+
+    ${optionalString (cfg.dpi != 0) ''
+    ln -s ${dpiConf} $support_folder/11-dpi.conf
+    ln -s ${dpiConf} $latest_folder/11-dpi.conf
+    ''}
+
+    # 50-user.conf
+    ${optionalString (!cfg.includeUserConf) ''
+    rm $support_folder/50-user.conf
+    rm $latest_folder/50-user.conf
+    ''}
+
+    # 51-local.conf
+    rm $latest_folder/51-local.conf
+    substitute \
+      ${pkgs.fontconfig-penultimate}/etc/fonts/conf.d/51-local.conf \
+      $latest_folder/51-local.conf \
+      --replace local.conf /etc/fonts/${latestVersion}/local.conf
+
+    # local.conf (indirect priority 51)
+    ${optionalString (cfg.localConf != "") ''
+    ln -s ${localConf}        $support_folder/../local.conf
+    ln -s ${localConf}        $latest_folder/../local.conf
+    ''}
+
+    # 52-nixos-default-fonts.conf
+    ln -s ${defaultFontsConf} $support_folder/52-nixos-default-fonts.conf
+    ln -s ${defaultFontsConf} $latest_folder/52-nixos-default-fonts.conf
+
+    # 53-no-bitmaps.conf
+    ${optionalString cfg.allowBitmaps ''
+    rm $support_folder/53-no-bitmaps.conf
+    rm $latest_folder/53-no-bitmaps.conf
+    ''}
+
+    ${optionalString (!cfg.allowType1) ''
+    # 53-nixos-reject-type1.conf
+    ln -s ${rejectType1} $support_folder/53-nixos-reject-type1.conf
+    ln -s ${rejectType1} $latest_folder/53-nixos-reject-type1.conf
+    ''}
+  '';
+
 in
 {
 
@@ -257,7 +269,7 @@ in
         penultimate = {
           enable = mkOption {
             type = types.bool;
-            default = true;
+            default = false;
             description = ''
               Enable fontconfig-penultimate settings to supplement the
               NixOS defaults by providing per-font rendering defaults and
diff --git a/nixos/modules/config/fonts/fontconfig-ultimate.nix b/nixos/modules/config/fonts/fontconfig-ultimate.nix
deleted file mode 100644
index 45328f3eaf13..000000000000
--- a/nixos/modules/config/fonts/fontconfig-ultimate.nix
+++ /dev/null
@@ -1,86 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let cfg = config.fonts.fontconfig.ultimate;
-
-    latestVersion  = pkgs.fontconfig.configVersion;
-
-    # The configuration to be included in /etc/font/
-    confPkg = pkgs.runCommand "font-ultimate-conf" { preferLocalBuild = true; } ''
-      support_folder=$out/etc/fonts/conf.d
-      latest_folder=$out/etc/fonts/${latestVersion}/conf.d
-
-      mkdir -p $support_folder
-      mkdir -p $latest_folder
-
-      # fontconfig ultimate substitutions
-      ${optionalString (cfg.substitutions != "none") ''
-      ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/presets/${cfg.substitutions}/*.conf \
-            $support_folder
-      ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/presets/${cfg.substitutions}/*.conf \
-            $latest_folder
-      ''}
-
-      # fontconfig ultimate various configuration files
-      ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/conf.d/*.conf \
-            $support_folder
-      ln -s ${pkgs.fontconfig-ultimate}/etc/fonts/conf.d/*.conf \
-            $latest_folder
-    '';
-
-in
-{
-
-  options = {
-
-    fonts = {
-
-      fontconfig = {
-
-        ultimate = {
-          enable = mkOption {
-            type = types.bool;
-            default = false;
-            description = ''
-              Enable fontconfig-ultimate settings (formerly known as
-              Infinality). Besides the customizable settings in this NixOS
-              module, fontconfig-ultimate also provides many font-specific
-              rendering tweaks.
-            '';
-          };
-
-          substitutions = mkOption {
-            type = types.enum ["free" "combi" "ms" "none"];
-            default = "free";
-            description = ''
-              Font substitutions to replace common Type 1 fonts with nicer
-              TrueType fonts. <literal>free</literal> uses free fonts,
-              <literal>ms</literal> uses Microsoft fonts,
-              <literal>combi</literal> uses a combination, and
-              <literal>none</literal> disables the substitutions.
-            '';
-          };
-
-          preset = mkOption {
-            type = types.enum ["ultimate1" "ultimate2" "ultimate3" "ultimate4" "ultimate5" "osx" "windowsxp"];
-            default = "ultimate3";
-            description = ''
-              FreeType rendering settings preset. Any of the presets may be
-              customized by setting environment variables.
-            '';
-          };
-        };
-      };
-    };
-
-  };
-
-  config = mkIf (config.fonts.fontconfig.enable && cfg.enable) {
-
-    fonts.fontconfig.confPackages = [ confPkg ];
-    environment.variables."INFINALITY_FT" = cfg.preset;
-
-  };
-
-}
diff --git a/nixos/modules/config/fonts/fontconfig.nix b/nixos/modules/config/fonts/fontconfig.nix
index d79c43c0b5b9..3bfa1893a8ba 100644
--- a/nixos/modules/config/fonts/fontconfig.nix
+++ b/nixos/modules/config/fonts/fontconfig.nix
@@ -14,252 +14,266 @@ Low number means high priority.
 
 */
 
-{ config, lib, pkgs, ... }:
+{ config, pkgs, lib, ... }:
 
 with lib;
 
-let cfg = config.fonts.fontconfig;
-
-    fcBool = x: "<bool>" + (boolToString x) + "</bool>";
-
-    # back-supported fontconfig version and package
-    # version is used for font cache generation
-    supportVersion = "210";
-    supportPkg     = pkgs."fontconfig_${supportVersion}";
-
-    # latest fontconfig version and package
-    # version is used for configuration folder name, /etc/fonts/VERSION/
-    # note: format differs from supportVersion and can not be used with makeCacheConf
-    latestVersion  = pkgs.fontconfig.configVersion;
-    latestPkg      = pkgs.fontconfig;
-
-    # supported version fonts.conf
-    supportFontsConf = pkgs.makeFontsConf { fontconfig = supportPkg; fontDirectories = config.fonts.fonts; };
-
-    # configuration file to read fontconfig cache
-    # version dependent
-    # priority 0
-    cacheConfSupport = makeCacheConf { version = supportVersion; };
-    cacheConfLatest  = makeCacheConf {};
-
-    # generate the font cache setting file for a fontconfig version
-    # use latest when no version is passed
-    makeCacheConf = { version ? null }:
-      let
-        fcPackage = if builtins.isNull version
-                    then "fontconfig"
-                    else "fontconfig_${version}";
-        makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
-        cache     = makeCache pkgs."${fcPackage}";
-        cache32   = makeCache pkgs.pkgsi686Linux."${fcPackage}";
-      in
-      pkgs.writeText "fc-00-nixos-cache.conf" ''
-        <?xml version='1.0'?>
-        <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
-        <fontconfig>
-          <!-- Font directories -->
-          ${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.fonts)}
-          <!-- Pre-generated font caches -->
-          <cachedir>${cache}</cachedir>
-          ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) ''
-            <cachedir>${cache32}</cachedir>
-          ''}
-        </fontconfig>
-      '';
-
-    # rendering settings configuration file
-    # priority 10
-    renderConf = pkgs.writeText "fc-10-nixos-rendering.conf" ''
+let
+  cfg = config.fonts.fontconfig;
+
+  fcBool = x: "<bool>" + (boolToString x) + "</bool>";
+
+  # back-supported fontconfig version and package
+  # version is used for font cache generation
+  supportVersion = "210";
+  supportPkg     = pkgs."fontconfig_${supportVersion}";
+
+  # latest fontconfig version and package
+  # version is used for configuration folder name, /etc/fonts/VERSION/
+  # note: format differs from supportVersion and can not be used with makeCacheConf
+  latestVersion  = pkgs.fontconfig.configVersion;
+  latestPkg      = pkgs.fontconfig;
+
+  # supported version fonts.conf
+  supportFontsConf = pkgs.makeFontsConf { fontconfig = supportPkg; fontDirectories = config.fonts.fonts; };
+
+  # configuration file to read fontconfig cache
+  # version dependent
+  # priority 0
+  cacheConfSupport = makeCacheConf { version = supportVersion; };
+  cacheConfLatest  = makeCacheConf {};
+
+  # generate the font cache setting file for a fontconfig version
+  # use latest when no version is passed
+  makeCacheConf = { version ? null }:
+    let
+      fcPackage = if version == null
+                  then "fontconfig"
+                  else "fontconfig_${version}";
+      makeCache = fontconfig: pkgs.makeFontsCache { inherit fontconfig; fontDirectories = config.fonts.fonts; };
+      cache     = makeCache pkgs.${fcPackage};
+      cache32   = makeCache pkgs.pkgsi686Linux.${fcPackage};
+    in
+    pkgs.writeText "fc-00-nixos-cache.conf" ''
       <?xml version='1.0'?>
       <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
       <fontconfig>
-
-        <!-- Default rendering settings -->
-        <match target="pattern">
-          <edit mode="append" name="hinting">
-            ${fcBool cfg.hinting.enable}
-          </edit>
-          <edit mode="append" name="autohint">
-            ${fcBool cfg.hinting.autohint}
-          </edit>
-          <edit mode="append" name="hintstyle">
-            <const>hintslight</const>
-          </edit>
-          <edit mode="append" name="antialias">
-            ${fcBool cfg.antialias}
-          </edit>
-          <edit mode="append" name="rgba">
-            <const>${cfg.subpixel.rgba}</const>
-          </edit>
-          <edit mode="append" name="lcdfilter">
-            <const>lcd${cfg.subpixel.lcdfilter}</const>
-          </edit>
-        </match>
-
-        ${optionalString (cfg.dpi != 0) ''
-        <match target="pattern">
-          <edit name="dpi" mode="assign">
-            <double>${toString cfg.dpi}</double>
-          </edit>
-        </match>
+        <!-- Font directories -->
+        ${concatStringsSep "\n" (map (font: "<dir>${font}</dir>") config.fonts.fonts)}
+        <!-- Pre-generated font caches -->
+        <cachedir>${cache}</cachedir>
+        ${optionalString (pkgs.stdenv.isx86_64 && cfg.cache32Bit) ''
+          <cachedir>${cache32}</cachedir>
         ''}
-
       </fontconfig>
     '';
 
-    # local configuration file
-    # priority 51
-    localConf = pkgs.writeText "fc-local.conf" cfg.localConf;
-
-    # default fonts configuration file
-    # priority 52
-    defaultFontsConf =
-      let genDefault = fonts: name:
-        optionalString (fonts != []) ''
-          <alias>
-            <family>${name}</family>
-            <prefer>
-            ${concatStringsSep ""
-            (map (font: ''
-              <family>${font}</family>
-            '') fonts)}
-            </prefer>
-          </alias>
-        '';
-      in
-      pkgs.writeText "fc-52-nixos-default-fonts.conf" ''
-      <?xml version='1.0'?>
-      <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
-      <fontconfig>
-
-        <!-- Default fonts -->
-        ${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
-
-        ${genDefault cfg.defaultFonts.serif     "serif"}
-
-        ${genDefault cfg.defaultFonts.monospace "monospace"}
-
-      </fontconfig>
-    '';
-
-    # bitmap font options
-    # priority 53
-    rejectBitmaps = pkgs.writeText "fc-53-nixos-bitmaps.conf" ''
-      <?xml version="1.0"?>
-      <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
-      <fontconfig>
-
-      ${optionalString (!cfg.allowBitmaps) ''
-      <!-- Reject bitmap fonts -->
-      <selectfont>
-        <rejectfont>
-          <pattern>
-            <patelt name="scalable"><bool>false</bool></patelt>
-          </pattern>
-        </rejectfont>
-      </selectfont>
-      ''}
-
-      <!-- Use embedded bitmaps in fonts like Calibri? -->
-      <match target="font">
-        <edit name="embeddedbitmap" mode="assign">
-          ${fcBool cfg.useEmbeddedBitmaps}
+  # rendering settings configuration file
+  # priority 10
+  renderConf = pkgs.writeText "fc-10-nixos-rendering.conf" ''
+    <?xml version='1.0'?>
+    <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+    <fontconfig>
+
+      <!-- Default rendering settings -->
+      <match target="pattern">
+        <edit mode="append" name="hinting">
+          ${fcBool cfg.hinting.enable}
+        </edit>
+        <edit mode="append" name="autohint">
+          ${fcBool cfg.hinting.autohint}
+        </edit>
+        <edit mode="append" name="hintstyle">
+          <const>hintslight</const>
+        </edit>
+        <edit mode="append" name="antialias">
+          ${fcBool cfg.antialias}
+        </edit>
+        <edit mode="append" name="rgba">
+          <const>${cfg.subpixel.rgba}</const>
+        </edit>
+        <edit mode="append" name="lcdfilter">
+          <const>lcd${cfg.subpixel.lcdfilter}</const>
         </edit>
       </match>
 
-      </fontconfig>
-    '';
-
-    # reject Type 1 fonts
-    # priority 53
-    rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
-      <?xml version="1.0"?>
-      <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
-      <fontconfig>
-
-      <!-- Reject Type 1 fonts -->
-      <selectfont>
-        <rejectfont>
-          <pattern>
-            <patelt name="fontformat"><string>Type 1</string></patelt>
-          </pattern>
-        </rejectfont>
-      </selectfont>
-
-      </fontconfig>
-    '';
-
-    # fontconfig configuration package
-    confPkg = pkgs.runCommand "fontconfig-conf" { preferLocalBuild = true; } ''
-      support_folder=$out/etc/fonts
-      latest_folder=$out/etc/fonts/${latestVersion}
-
-      mkdir -p $support_folder/conf.d
-      mkdir -p $latest_folder/conf.d
-
-      # fonts.conf
-      ln -s ${supportFontsConf} $support_folder/fonts.conf
-      ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
-            $latest_folder/fonts.conf
-
-      # fontconfig default config files
-      ln -s ${supportPkg.out}/etc/fonts/conf.d/*.conf \
-            $support_folder/conf.d/
-      ln -s ${latestPkg.out}/etc/fonts/conf.d/*.conf \
-            $latest_folder/conf.d/
-
-      # update latest 51-local.conf path to look at the latest local.conf
-      rm    $latest_folder/conf.d/51-local.conf
-
-      substitute ${latestPkg.out}/etc/fonts/conf.d/51-local.conf \
-                 $latest_folder/conf.d/51-local.conf \
-                 --replace local.conf /etc/fonts/${latestVersion}/local.conf
-
-      # 00-nixos-cache.conf
-      ln -s ${cacheConfSupport} \
-            $support_folder/conf.d/00-nixos-cache.conf
-      ln -s ${cacheConfLatest}  $latest_folder/conf.d/00-nixos-cache.conf
-
-      # 10-nixos-rendering.conf
-      ln -s ${renderConf}       $support_folder/conf.d/10-nixos-rendering.conf
-      ln -s ${renderConf}       $latest_folder/conf.d/10-nixos-rendering.conf
-
-      # 50-user.conf
-      ${optionalString (! cfg.includeUserConf) ''
-      rm    $support_folder/conf.d/50-user.conf
-      rm    $latest_folder/conf.d/50-user.conf
-      ''}
-
-      # local.conf (indirect priority 51)
-      ${optionalString (cfg.localConf != "") ''
-      ln -s ${localConf}        $support_folder/local.conf
-      ln -s ${localConf}        $latest_folder/local.conf
-      ''}
-
-      # 52-nixos-default-fonts.conf
-      ln -s ${defaultFontsConf} $support_folder/conf.d/52-nixos-default-fonts.conf
-      ln -s ${defaultFontsConf} $latest_folder/conf.d/52-nixos-default-fonts.conf
-
-      # 53-nixos-bitmaps.conf
-      ln -s ${rejectBitmaps} $support_folder/conf.d/53-nixos-bitmaps.conf
-      ln -s ${rejectBitmaps} $latest_folder/conf.d/53-nixos-bitmaps.conf
-
-      ${optionalString (! cfg.allowType1) ''
-      # 53-nixos-reject-type1.conf
-      ln -s ${rejectType1} $support_folder/conf.d/53-nixos-reject-type1.conf
-      ln -s ${rejectType1} $latest_folder/conf.d/53-nixos-reject-type1.conf
+      ${optionalString (cfg.dpi != 0) ''
+      <match target="pattern">
+        <edit name="dpi" mode="assign">
+          <double>${toString cfg.dpi}</double>
+        </edit>
+      </match>
       ''}
-    '';
 
-    # Package with configuration files
-    # this merge all the packages in the fonts.fontconfig.confPackages list
-    fontconfigEtc = pkgs.buildEnv {
-      name  = "fontconfig-etc";
-      paths = cfg.confPackages;
-      ignoreCollisions = true;
-    };
+    </fontconfig>
+  '';
+
+  # local configuration file
+  localConf = pkgs.writeText "fc-local.conf" cfg.localConf;
+
+  # default fonts configuration file
+  # priority 52
+  defaultFontsConf =
+    let genDefault = fonts: name:
+      optionalString (fonts != []) ''
+        <alias binding="same">
+          <family>${name}</family>
+          <prefer>
+          ${concatStringsSep ""
+          (map (font: ''
+            <family>${font}</family>
+          '') fonts)}
+          </prefer>
+        </alias>
+      '';
+    in
+    pkgs.writeText "fc-52-nixos-default-fonts.conf" ''
+    <?xml version='1.0'?>
+    <!DOCTYPE fontconfig SYSTEM 'fonts.dtd'>
+    <fontconfig>
+
+      <!-- Default fonts -->
+      ${genDefault cfg.defaultFonts.sansSerif "sans-serif"}
+
+      ${genDefault cfg.defaultFonts.serif     "serif"}
+
+      ${genDefault cfg.defaultFonts.monospace "monospace"}
+
+      ${genDefault cfg.defaultFonts.emoji "emoji"}
+
+    </fontconfig>
+  '';
+
+  # bitmap font options
+  # priority 53
+  rejectBitmaps = pkgs.writeText "fc-53-no-bitmaps.conf" ''
+    <?xml version="1.0"?>
+    <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+    <fontconfig>
+
+    ${optionalString (!cfg.allowBitmaps) ''
+    <!-- Reject bitmap fonts -->
+    <selectfont>
+      <rejectfont>
+        <pattern>
+          <patelt name="scalable"><bool>false</bool></patelt>
+        </pattern>
+      </rejectfont>
+    </selectfont>
+    ''}
+
+    <!-- Use embedded bitmaps in fonts like Calibri? -->
+    <match target="font">
+      <edit name="embeddedbitmap" mode="assign">
+        ${fcBool cfg.useEmbeddedBitmaps}
+      </edit>
+    </match>
+
+    </fontconfig>
+  '';
+
+  # reject Type 1 fonts
+  # priority 53
+  rejectType1 = pkgs.writeText "fc-53-nixos-reject-type1.conf" ''
+    <?xml version="1.0"?>
+    <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+    <fontconfig>
+
+    <!-- Reject Type 1 fonts -->
+    <selectfont>
+      <rejectfont>
+        <pattern>
+          <patelt name="fontformat"><string>Type 1</string></patelt>
+        </pattern>
+      </rejectfont>
+    </selectfont>
+
+    </fontconfig>
+  '';
+
+  # fontconfig configuration package
+  confPkg = pkgs.runCommand "fontconfig-conf" {
+    preferLocalBuild = true;
+  } ''
+    support_folder=$out/etc/fonts/conf.d
+    latest_folder=$out/etc/fonts/${latestVersion}/conf.d
+
+    mkdir -p $support_folder
+    mkdir -p $latest_folder
+
+    # fonts.conf
+    ln -s ${supportFontsConf} $support_folder/../fonts.conf
+    ln -s ${latestPkg.out}/etc/fonts/fonts.conf \
+          $latest_folder/../fonts.conf
+
+    # fontconfig default config files
+    ln -s ${supportPkg.out}/etc/fonts/conf.d/*.conf \
+          $support_folder/
+    ln -s ${latestPkg.out}/etc/fonts/conf.d/*.conf \
+          $latest_folder/
+
+    # update latest 51-local.conf path to look at the latest local.conf
+    rm    $latest_folder/51-local.conf
+
+    substitute ${latestPkg.out}/etc/fonts/conf.d/51-local.conf \
+               $latest_folder/51-local.conf \
+               --replace local.conf /etc/fonts/${latestVersion}/local.conf
+
+    # 00-nixos-cache.conf
+    ln -s ${cacheConfSupport} \
+          $support_folder/00-nixos-cache.conf
+    ln -s ${cacheConfLatest}  $latest_folder/00-nixos-cache.conf
+
+    # 10-nixos-rendering.conf
+    ln -s ${renderConf}       $support_folder/10-nixos-rendering.conf
+    ln -s ${renderConf}       $latest_folder/10-nixos-rendering.conf
+
+    # 50-user.conf
+    ${optionalString (!cfg.includeUserConf) ''
+    rm $support_folder/50-user.conf
+    rm $latest_folder/50-user.conf
+    ''}
+
+    # local.conf (indirect priority 51)
+    ${optionalString (cfg.localConf != "") ''
+    ln -s ${localConf}        $support_folder/../local.conf
+    ln -s ${localConf}        $latest_folder/../local.conf
+    ''}
+
+    # 52-nixos-default-fonts.conf
+    ln -s ${defaultFontsConf} $support_folder/52-nixos-default-fonts.conf
+    ln -s ${defaultFontsConf} $latest_folder/52-nixos-default-fonts.conf
+
+    # 53-no-bitmaps.conf
+    ln -s ${rejectBitmaps} $support_folder/53-no-bitmaps.conf
+    ln -s ${rejectBitmaps} $latest_folder/53-no-bitmaps.conf
+
+    ${optionalString (!cfg.allowType1) ''
+    # 53-nixos-reject-type1.conf
+    ln -s ${rejectType1} $support_folder/53-nixos-reject-type1.conf
+    ln -s ${rejectType1} $latest_folder/53-nixos-reject-type1.conf
+    ''}
+  '';
+
+  # Package with configuration files
+  # this merge all the packages in the fonts.fontconfig.confPackages list
+  fontconfigEtc = pkgs.buildEnv {
+    name  = "fontconfig-etc";
+    paths = cfg.confPackages;
+    ignoreCollisions = true;
+  };
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowBitmaps" ] [ "fonts" "fontconfig" "allowBitmaps" ])
+    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowType1" ] [ "fonts" "fontconfig" "allowType1" ])
+    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "useEmbeddedBitmaps" ] [ "fonts" "fontconfig" "useEmbeddedBitmaps" ])
+    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "forceAutohint" ] [ "fonts" "fontconfig" "forceAutohint" ])
+    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "renderMonoTTFAsBitmap" ] [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ])
+    (mkRemovedOptionModule [ "fonts" "fontconfig" "hinting" "style" ] "")
+    (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "")
+    (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "")
+  ];
 
   options = {
 
@@ -342,6 +356,21 @@ in
               in case multiple languages must be supported.
             '';
           };
+
+          emoji = mkOption {
+            type = types.listOf types.str;
+            default = ["Noto Color Emoji"];
+            description = ''
+              System-wide default emoji font(s). Multiple fonts may be listed
+              in case a font does not support all emoji.
+
+              Note that fontconfig matches color emoji fonts preferentially,
+              so if you want to use a black and white font while having
+              a color font installed (eg. Noto Color Emoji installed alongside
+              Noto Emoji), fontconfig will still choose the color font even
+              when it is later in the list.
+            '';
+          };
         };
 
         hinting = {
diff --git a/nixos/modules/config/fonts/fonts.nix b/nixos/modules/config/fonts/fonts.nix
index 0dd01df9da74..b9bae44b2f9c 100644
--- a/nixos/modules/config/fonts/fonts.nix
+++ b/nixos/modules/config/fonts/fonts.nix
@@ -3,6 +3,9 @@
 with lib;
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "fonts" "enableCoreFonts" ] "Use fonts.fonts = [ pkgs.corefonts ]; instead.")
+  ];
 
   options = {
 
@@ -43,6 +46,7 @@ with lib;
         pkgs.xorg.fontmiscmisc
         pkgs.xorg.fontcursormisc
         pkgs.unifont
+        pkgs.noto-fonts-emoji
       ];
 
   };
diff --git a/nixos/modules/config/gtk/gtk-icon-cache.nix b/nixos/modules/config/gtk/gtk-icon-cache.nix
index 9c5d993b9c59..86a6bfb5af41 100644
--- a/nixos/modules/config/gtk/gtk-icon-cache.nix
+++ b/nixos/modules/config/gtk/gtk-icon-cache.nix
@@ -7,7 +7,7 @@ with lib;
       type = types.bool;
       default = config.services.xserver.enable;
       description = ''
-        Whether to build icon theme caches for GTK+ applications.
+        Whether to build icon theme caches for GTK applications.
       '';
     };
   };
diff --git a/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix
index dc7305b1ba24..cc2ddda9d32f 100644
--- a/nixos/modules/config/i18n.nix
+++ b/nixos/modules/config/i18n.nix
@@ -58,66 +58,6 @@ with lib;
         '';
       };
 
-      consolePackages = mkOption {
-        type = types.listOf types.package;
-        default = with pkgs.kbdKeymaps; [ dvp neo ];
-        defaultText = ''with pkgs.kbdKeymaps; [ dvp neo ]'';
-        description = ''
-          List of additional packages that provide console fonts, keymaps and
-          other resources.
-        '';
-      };
-
-      consoleFont = mkOption {
-        type = types.str;
-        default = "Lat2-Terminus16";
-        example = "LatArCyrHeb-16";
-        description = ''
-          The font used for the virtual consoles.  Leave empty to use
-          whatever the <command>setfont</command> program considers the
-          default font.
-        '';
-      };
-
-      consoleUseXkbConfig = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          If set, configure the console keymap from the xserver keyboard
-          settings.
-        '';
-      };
-
-      consoleKeyMap = mkOption {
-        type = mkOptionType {
-          name = "string or path";
-          check = t: (isString t || types.path.check t);
-        };
-
-        default = "us";
-        example = "fr";
-        description = ''
-          The keyboard mapping table for the virtual consoles.
-        '';
-      };
-
-      consoleColors = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [
-          "002b36" "dc322f" "859900" "b58900"
-          "268bd2" "d33682" "2aa198" "eee8d5"
-          "002b36" "cb4b16" "586e75" "657b83"
-          "839496" "6c71c4" "93a1a1" "fdf6e3"
-        ];
-        description = ''
-          The 16 colors palette used by the virtual consoles.
-          Leave empty to use the default colors.
-          Colors must be in hexadecimal format and listed in
-          order from color 0 to color 15.
-        '';
-      };
-
     };
 
   };
@@ -127,13 +67,6 @@ with lib;
 
   config = {
 
-    i18n.consoleKeyMap = with config.services.xserver;
-      mkIf config.i18n.consoleUseXkbConfig
-        (pkgs.runCommand "xkb-console-keymap" { preferLocalBuild = true; } ''
-          '${pkgs.ckbcomp}/bin/ckbcomp' -model '${xkbModel}' -layout '${layout}' \
-            -option '${xkbOptions}' -variant '${xkbVariant}' > "$out"
-        '');
-
     environment.systemPackages =
       optional (config.i18n.supportedLocales != []) config.i18n.glibcLocales;
 
@@ -147,14 +80,11 @@ with lib;
     };
 
     # ‘/etc/locale.conf’ is used by systemd.
-    environment.etc = singleton
-      { target = "locale.conf";
-        source = pkgs.writeText "locale.conf"
-          ''
-            LANG=${config.i18n.defaultLocale}
-            ${concatStringsSep "\n" (mapAttrsToList (n: v: ''${n}=${v}'') config.i18n.extraLocaleSettings)}
-          '';
-      };
+    environment.etc."locale.conf".source = pkgs.writeText "locale.conf"
+      ''
+        LANG=${config.i18n.defaultLocale}
+        ${concatStringsSep "\n" (mapAttrsToList (n: v: ''${n}=${v}'') config.i18n.extraLocaleSettings)}
+      '';
 
   };
 }
diff --git a/nixos/modules/config/krb5/default.nix b/nixos/modules/config/krb5/default.nix
index 87021a27d34f..ff16ffcf9c65 100644
--- a/nixos/modules/config/krb5/default.nix
+++ b/nixos/modules/config/krb5/default.nix
@@ -15,7 +15,7 @@ let
     realms = optionalAttrs (lib.all (value: value != null) [
       cfg.defaultRealm cfg.kdc cfg.kerberosAdminServer
     ]) {
-      "${cfg.defaultRealm}" = {
+      ${cfg.defaultRealm} = {
         kdc = cfg.kdc;
         admin_server = cfg.kerberosAdminServer;
       };
@@ -25,7 +25,7 @@ let
       cfg.domainRealm cfg.defaultRealm
     ]) {
       ".${cfg.domainRealm}" = cfg.defaultRealm;
-      "${cfg.domainRealm}" = cfg.defaultRealm;
+      ${cfg.domainRealm} = cfg.defaultRealm;
     };
   };
 
diff --git a/nixos/modules/config/ldap.nix b/nixos/modules/config/ldap.nix
index e008497a2a6e..9c8e9d149371 100644
--- a/nixos/modules/config/ldap.nix
+++ b/nixos/modules/config/ldap.nix
@@ -224,7 +224,9 @@ in
 
   config = mkIf cfg.enable {
 
-    environment.etc = optional (!cfg.daemon.enable) ldapConfig;
+    environment.etc = optionalAttrs (!cfg.daemon.enable) {
+      "ldap.conf" = ldapConfig;
+    };
 
     system.activationScripts = mkIf (!cfg.daemon.enable) {
       ldap = stringAfter [ "etc" "groups" "users" ] ''
diff --git a/nixos/modules/config/timezone.nix b/nixos/modules/config/locale.nix
index b15948f6e2e5..6f0565881877 100644
--- a/nixos/modules/config/timezone.nix
+++ b/nixos/modules/config/locale.nix
@@ -9,6 +9,8 @@ let
   timezone = types.nullOr (types.addCheck types.str nospace)
     // { description = "null or string without spaces"; };
 
+  lcfg = config.location;
+
 in
 
 {
@@ -37,12 +39,45 @@ in
       };
 
     };
+
+    location = {
+
+      latitude = mkOption {
+        type = types.float;
+        description = ''
+          Your current latitude, between
+          <literal>-90.0</literal> and <literal>90.0</literal>. Must be provided
+          along with longitude.
+        '';
+      };
+
+      longitude = mkOption {
+        type = types.float;
+        description = ''
+          Your current longitude, between
+          between <literal>-180.0</literal> and <literal>180.0</literal>. Must be
+          provided along with latitude.
+        '';
+      };
+
+      provider = mkOption {
+        type = types.enum [ "manual" "geoclue2" ];
+        default = "manual";
+        description = ''
+          The location provider to use for determining your location. If set to
+          <literal>manual</literal> you must also provide latitude/longitude.
+        '';
+      };
+
+    };
   };
 
   config = {
 
     environment.sessionVariables.TZDIR = "/etc/zoneinfo";
 
+    services.geoclue2.enable = mkIf (lcfg.provider == "geoclue2") true;
+
     # This way services are restarted when tzdata changes.
     systemd.globalEnvironment.TZDIR = tzdir;
 
diff --git a/nixos/modules/config/malloc.nix b/nixos/modules/config/malloc.nix
new file mode 100644
index 000000000000..31a659ee83fe
--- /dev/null
+++ b/nixos/modules/config/malloc.nix
@@ -0,0 +1,91 @@
+{ config, lib, pkgs, ... }:
+with lib;
+
+let
+  cfg = config.environment.memoryAllocator;
+
+  # The set of alternative malloc(3) providers.
+  providers = {
+    graphene-hardened = {
+      libPath = "${pkgs.graphene-hardened-malloc}/lib/libhardened_malloc.so";
+      description = ''
+        An allocator designed to mitigate memory corruption attacks, such as
+        those caused by use-after-free bugs.
+      '';
+    };
+
+    jemalloc = {
+      libPath = "${pkgs.jemalloc}/lib/libjemalloc.so";
+      description = ''
+        A general purpose allocator that emphasizes fragmentation avoidance
+        and scalable concurrency support.
+      '';
+    };
+
+    scudo = {
+      libPath = "${pkgs.llvmPackages.compiler-rt}/lib/linux/libclang_rt.scudo-x86_64.so";
+      description = ''
+        A user-mode allocator based on LLVM Sanitizer’s CombinedAllocator,
+        which aims at providing additional mitigations against heap based
+        vulnerabilities, while maintaining good performance.
+      '';
+    };
+  };
+
+  providerConf = providers.${cfg.provider};
+
+  # An output that contains only the shared library, to avoid
+  # needlessly bloating the system closure
+  mallocLib = pkgs.runCommand "malloc-provider-${cfg.provider}"
+    rec {
+      preferLocalBuild = true;
+      allowSubstitutes = false;
+      origLibPath = providerConf.libPath;
+      libName = baseNameOf origLibPath;
+    }
+    ''
+      mkdir -p $out/lib
+      cp -L $origLibPath $out/lib/$libName
+    '';
+
+  # The full path to the selected provider shlib.
+  providerLibPath = "${mallocLib}/lib/${mallocLib.libName}";
+in
+
+{
+  meta = {
+    maintainers = [ maintainers.joachifm ];
+  };
+
+  options = {
+    environment.memoryAllocator.provider = mkOption {
+      type = types.enum ([ "libc" ] ++ attrNames providers);
+      default = "libc";
+      description = ''
+        The system-wide memory allocator.
+
+        Briefly, the system-wide memory allocator providers are:
+        <itemizedlist>
+        <listitem><para><literal>libc</literal>: the standard allocator provided by libc</para></listitem>
+        ${toString (mapAttrsToList
+            (name: value: "<listitem><para><literal>${name}</literal>: ${value.description}</para></listitem>")
+            providers)}
+        </itemizedlist>
+
+        <warning>
+        <para>
+        Selecting an alternative allocator (i.e., anything other than
+        <literal>libc</literal>) may result in instability, data loss,
+        and/or service failure.
+        </para>
+        </warning>
+      '';
+    };
+  };
+
+  config = mkIf (cfg.provider != "libc") {
+    environment.etc."ld-nix.so.preload".text = ''
+      ${providerLibPath}
+    '';
+  };
+}
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 25253cf02e1d..81427bb8ee64 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -7,16 +7,6 @@ with lib;
 let
 
   cfg = config.networking;
-  dnsmasqResolve = config.services.dnsmasq.enable &&
-                   config.services.dnsmasq.resolveLocalQueries;
-  hasLocalResolver = config.services.bind.enable ||
-                     config.services.unbound.enable ||
-                     dnsmasqResolve;
-
-  resolvconfOptions = cfg.resolvconfOptions
-    ++ optional cfg.dnsSingleRequest "single-request"
-    ++ optional cfg.dnsExtensionMechanism "edns0";
-
 
   localhostMapped4 = cfg.hosts ? "127.0.0.1" && elem "localhost" cfg.hosts."127.0.0.1";
   localhostMapped6 = cfg.hosts ? "::1"       && elem "localhost" cfg.hosts."::1";
@@ -26,6 +16,9 @@ let
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "networking" "hostConf" ] "Use environment.etc.\"host.conf\" instead.")
+  ];
 
   options = {
 
@@ -51,61 +44,6 @@ in
       '';
     };
 
-    networking.hostConf = lib.mkOption {
-      type = types.lines;
-      default = "multi on";
-      example = ''
-        multi on
-        reorder on
-        trim lan
-      '';
-      description = ''
-        The contents of <filename>/etc/host.conf</filename>. See also <citerefentry><refentrytitle>host.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
-      '';
-    };
-
-    networking.dnsSingleRequest = lib.mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Recent versions of glibc will issue both ipv4 (A) and ipv6 (AAAA)
-        address queries at the same time, from the same port. Sometimes upstream
-        routers will systemically drop the ipv4 queries. The symptom of this problem is
-        that 'getent hosts example.com' only returns ipv6 (or perhaps only ipv4) addresses. The
-        workaround for this is to specify the option 'single-request' in
-        /etc/resolv.conf. This option enables that.
-      '';
-    };
-
-    networking.dnsExtensionMechanism = lib.mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Enable the <code>edns0</code> option in <filename>resolv.conf</filename>. With
-        that option set, <code>glibc</code> supports use of the extension mechanisms for
-        DNS (EDNS) specified in RFC 2671. The most popular user of that feature is DNSSEC,
-        which does not work without it.
-      '';
-    };
-
-    networking.extraResolvconfConf = lib.mkOption {
-      type = types.lines;
-      default = "";
-      example = "libc=NO";
-      description = ''
-        Extra configuration to append to <filename>resolvconf.conf</filename>.
-      '';
-    };
-
-    networking.resolvconfOptions = lib.mkOption {
-      type = types.listOf types.str;
-      default = [];
-      example = [ "ndots:1" "rotate" ];
-      description = ''
-        Set the options in <filename>/etc/resolv.conf</filename>.
-      '';
-    };
-
     networking.timeServers = mkOption {
       default = [
         "0.nixos.pool.ntp.org"
@@ -223,55 +161,28 @@ in
 
     environment.etc =
       { # /etc/services: TCP/UDP port assignments.
-        "services".source = pkgs.iana-etc + "/etc/services";
+        services.source = pkgs.iana-etc + "/etc/services";
 
         # /etc/protocols: IP protocol numbers.
-        "protocols".source  = pkgs.iana-etc + "/etc/protocols";
+        protocols.source  = pkgs.iana-etc + "/etc/protocols";
 
         # /etc/hosts: Hostname-to-IP mappings.
-        "hosts".text = let
+        hosts.text = let
           oneToString = set: ip: ip + " " + concatStringsSep " " set.${ip};
           allToString = set: concatMapStringsSep "\n" (oneToString set) (attrNames set);
         in ''
-          ${allToString cfg.hosts}
+          ${allToString (filterAttrs (_: v: v != []) cfg.hosts)}
           ${cfg.extraHosts}
         '';
 
         # /etc/host.conf: resolver configuration file
-        "host.conf".text = cfg.hostConf;
-
-        # /etc/resolvconf.conf: Configuration for openresolv.
-        "resolvconf.conf".text =
-            ''
-              # This is the default, but we must set it here to prevent
-              # a collision with an apparently unrelated environment
-              # variable with the same name exported by dhcpcd.
-              interface_order='lo lo[0-9]*'
-            '' + optionalString config.services.nscd.enable ''
-              # Invalidate the nscd cache whenever resolv.conf is
-              # regenerated.
-              libc_restart='${pkgs.systemd}/bin/systemctl try-restart --no-block nscd.service 2> /dev/null'
-            '' + optionalString (length resolvconfOptions > 0) ''
-              # Options as described in resolv.conf(5)
-              resolv_conf_options='${concatStringsSep " " resolvconfOptions}'
-            '' + optionalString hasLocalResolver ''
-              # This hosts runs a full-blown DNS resolver.
-              name_servers='127.0.0.1'
-            '' + optionalString dnsmasqResolve ''
-              dnsmasq_conf=/etc/dnsmasq-conf.conf
-              dnsmasq_resolv=/etc/dnsmasq-resolv.conf
-            '' + cfg.extraResolvconfConf + ''
-            '';
-
-      } // optionalAttrs config.services.resolved.enable {
-        # symlink the dynamic stub resolver of resolv.conf as recommended by upstream:
-        # https://www.freedesktop.org/software/systemd/man/systemd-resolved.html#/etc/resolv.conf
-        "resolv.conf".source = "/run/systemd/resolve/stub-resolv.conf";
-      } // optionalAttrs (config.services.resolved.enable && dnsmasqResolve) {
-        "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+        "host.conf".text = ''
+          multi on
+        '';
+
       } // optionalAttrs (pkgs.stdenv.hostPlatform.libc == "glibc") {
         # /etc/rpc: RPC program numbers.
-        "rpc".source = pkgs.glibc.out + "/etc/rpc";
+        rpc.source = pkgs.glibc.out + "/etc/rpc";
       };
 
       networking.proxy.envVars =
@@ -295,26 +206,6 @@ in
     # Install the proxy environment variables
     environment.sessionVariables = cfg.proxy.envVars;
 
-    # This is needed when /etc/resolv.conf is being overriden by networkd
-    # and other configurations. If the file is destroyed by an environment
-    # activation then it must be rebuilt so that applications which interface
-    # with /etc/resolv.conf directly don't break.
-    system.activationScripts.resolvconf = stringAfter [ "etc" "specialfs" "var" ]
-      ''
-        # Systemd resolved controls its own resolv.conf
-        rm -f /run/resolvconf/interfaces/systemd
-        ${optionalString config.services.resolved.enable ''
-          rm -rf /run/resolvconf/interfaces
-          mkdir -p /run/resolvconf/interfaces
-          ln -s /run/systemd/resolve/resolv.conf /run/resolvconf/interfaces/systemd
-        ''}
-
-        # Make sure resolv.conf is up to date if not managed manually or by systemd
-        ${optionalString (!config.environment.etc?"resolv.conf") ''
-          ${pkgs.openresolv}/bin/resolvconf -u
-        ''}
-      '';
-
   };
 
 }
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index aad02a9ca4e3..873b8073fed9 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -34,7 +34,6 @@ with lib;
       networkmanager-openvpn = super.networkmanager-openvpn.override { withGnome = false; };
       networkmanager-vpnc = super.networkmanager-vpnc.override { withGnome = false; };
       networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
-      pinentry = super.pinentry.override { gtk2 = null; gcr = null; qt = null; };
       gobject-introspection = super.gobject-introspection.override { x11Support = false; };
     }));
   };
diff --git a/nixos/modules/config/power-management.nix b/nixos/modules/config/power-management.nix
index 0277f1ad11e9..64cdf50f1413 100644
--- a/nixos/modules/config/power-management.nix
+++ b/nixos/modules/config/power-management.nix
@@ -78,7 +78,7 @@ in
     };
 
     # Service executed before suspending/hibernating.
-    systemd.services."pre-sleep" =
+    systemd.services.pre-sleep =
       { description = "Pre-Sleep Actions";
         wantedBy = [ "sleep.target" ];
         before = [ "sleep.target" ];
@@ -89,7 +89,7 @@ in
         serviceConfig.Type = "oneshot";
       };
 
-    systemd.services."post-resume" =
+    systemd.services.post-resume =
       { description = "Post-Resume Actions";
         after = [ "suspend.target" "hibernate.target" "hybrid-sleep.target" ];
         script =
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index e61a3a731201..048bbb30c73d 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -51,8 +51,7 @@ let
   # that we can disable the autospawn feature in programs that
   # are built with PulseAudio support (like KDE).
   clientConf = writeText "client.conf" ''
-    autospawn=${if nonSystemWide then "yes" else "no"}
-    ${optionalString nonSystemWide "daemon-binary=${binary}"}
+    autospawn=no
     ${cfg.extraClientConf}
   '';
 
@@ -99,11 +98,12 @@ in {
         description = ''
           If false, a PulseAudio server is launched automatically for
           each user that tries to use the sound system. The server runs
-          with user privileges. This is the recommended and most secure
-          way to use PulseAudio. If true, one system-wide PulseAudio
+          with user privileges. If true, one system-wide PulseAudio
           server is launched on boot, running as the user "pulse", and
           only users in the "audio" group will have access to the server.
           Please read the PulseAudio documentation for more details.
+
+          Don't enable this option unless you know what you are doing.
         '';
       };
 
@@ -215,9 +215,8 @@ in {
 
   config = mkMerge [
     {
-      environment.etc = singleton {
-        target = "pulse/client.conf";
-        source = clientConf;
+      environment.etc = {
+        "pulse/client.conf".source = clientConf;
       };
 
       hardware.pulseaudio.configFile = mkDefault "${getBin overriddenPackage}/etc/pulse/default.pa";
@@ -228,23 +227,23 @@ in {
 
       sound.enable = true;
 
-      environment.etc = [
-        { target = "asound.conf";
-          source = alsaConf; }
+      environment.etc = {
+        "asound.conf".source = alsaConf;
 
-        { target = "pulse/daemon.conf";
-          source = writeText "daemon.conf" (lib.generators.toKeyValue {} cfg.daemon.config); }
+        "pulse/daemon.conf".source = writeText "daemon.conf"
+          (lib.generators.toKeyValue {} cfg.daemon.config);
 
-        { target = "openal/alsoft.conf";
-          source = writeText "alsoft.conf" "drivers=pulse"; }
+        "openal/alsoft.conf".source = writeText "alsoft.conf" "drivers=pulse";
 
-        { target = "libao.conf";
-          source = writeText "libao.conf" "default_driver=pulse"; }
-      ];
+        "libao.conf".source = writeText "libao.conf" "default_driver=pulse";
+      };
 
       # Disable flat volumes to enable relative ones
       hardware.pulseaudio.daemon.config.flat-volumes = mkDefault "no";
 
+      # Upstream defaults to speex-float-1 which results in audible artifacts
+      hardware.pulseaudio.daemon.config.resample-method = mkDefault "speex-float-5";
+
       # Allow PulseAudio to get realtime priority using rtkit.
       security.rtkit.enable = true;
 
@@ -272,9 +271,8 @@ in {
     })
 
     (mkIf nonSystemWide {
-      environment.etc = singleton {
-        target = "pulse/default.pa";
-        source = myConfigFile;
+      environment.etc = {
+        "pulse/default.pa".source = myConfigFile;
       };
       systemd.user = {
         services.pulseaudio = {
diff --git a/nixos/modules/config/qt5.nix b/nixos/modules/config/qt5.nix
new file mode 100644
index 000000000000..d9dec74f1552
--- /dev/null
+++ b/nixos/modules/config/qt5.nix
@@ -0,0 +1,102 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.qt5;
+
+  isQGnome = cfg.platformTheme == "gnome" && cfg.style == "adwaita";
+  isQtStyle = cfg.platformTheme == "gtk2" && cfg.style != "adwaita";
+
+  packages = if isQGnome then [ pkgs.qgnomeplatform pkgs.adwaita-qt ]
+    else if isQtStyle then [ pkgs.libsForQt5.qtstyleplugins ]
+    else throw "`qt5.platformTheme` ${cfg.platformTheme} and `qt5.style` ${cfg.style} are not compatible.";
+
+in
+
+{
+
+  options = {
+    qt5 = {
+
+      enable = mkEnableOption "Qt5 theming configuration";
+
+      platformTheme = mkOption {
+        type = types.enum [
+          "gtk2"
+          "gnome"
+        ];
+        example = "gnome";
+        relatedPackages = [
+          "qgnomeplatform"
+          ["libsForQt5" "qtstyleplugins"]
+        ];
+        description = ''
+          Selects the platform theme to use for Qt5 applications.</para>
+          <para>The options are
+          <variablelist>
+            <varlistentry>
+              <term><literal>gtk</literal></term>
+              <listitem><para>Use GTK theme with
+                <link xlink:href="https://github.com/qt/qtstyleplugins">qtstyleplugins</link>
+              </para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><literal>gnome</literal></term>
+              <listitem><para>Use GNOME theme with
+                <link xlink:href="https://github.com/FedoraQt/QGnomePlatform">qgnomeplatform</link>
+              </para></listitem>
+            </varlistentry>
+          </variablelist>
+        '';
+      };
+
+      style = mkOption {
+        type = types.enum [
+          "adwaita"
+          "cleanlooks"
+          "gtk2"
+          "motif"
+          "plastique"
+        ];
+        example = "adwaita";
+        relatedPackages = [
+          "adwaita-qt"
+          ["libsForQt5" "qtstyleplugins"]
+        ];
+        description = ''
+          Selects the style to use for Qt5 applications.</para>
+          <para>The options are
+          <variablelist>
+            <varlistentry>
+              <term><literal>adwaita</literal></term>
+              <listitem><para>Use Adwaita Qt style with
+                <link xlink:href="https://github.com/FedoraQt/adwaita-qt">adwaita</link>
+              </para></listitem>
+            </varlistentry>
+            <varlistentry>
+              <term><literal>cleanlooks</literal></term>
+              <term><literal>gtk2</literal></term>
+              <term><literal>motif</literal></term>
+              <term><literal>plastique</literal></term>
+              <listitem><para>Use styles from
+                <link xlink:href="https://github.com/qt/qtstyleplugins">qtstyleplugins</link>
+              </para></listitem>
+            </varlistentry>
+          </variablelist>
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.variables.QT_QPA_PLATFORMTHEME = cfg.platformTheme;
+
+    environment.variables.QT_STYLE_OVERRIDE = cfg.style;
+
+    environment.systemPackages = packages;
+
+  };
+}
diff --git a/nixos/modules/config/resolvconf.nix b/nixos/modules/config/resolvconf.nix
new file mode 100644
index 000000000000..7d2f252a8886
--- /dev/null
+++ b/nixos/modules/config/resolvconf.nix
@@ -0,0 +1,155 @@
+# /etc files related to networking, such as /etc/services.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.networking.resolvconf;
+
+  resolvconfOptions = cfg.extraOptions
+    ++ optional cfg.dnsSingleRequest "single-request"
+    ++ optional cfg.dnsExtensionMechanism "edns0";
+
+  configText =
+    ''
+      # This is the default, but we must set it here to prevent
+      # a collision with an apparently unrelated environment
+      # variable with the same name exported by dhcpcd.
+      interface_order='lo lo[0-9]*'
+    '' + optionalString config.services.nscd.enable ''
+      # Invalidate the nscd cache whenever resolv.conf is
+      # regenerated.
+      libc_restart='${pkgs.systemd}/bin/systemctl try-restart --no-block nscd.service 2> /dev/null'
+    '' + optionalString (length resolvconfOptions > 0) ''
+      # Options as described in resolv.conf(5)
+      resolv_conf_options='${concatStringsSep " " resolvconfOptions}'
+    '' + optionalString cfg.useLocalResolver ''
+      # This hosts runs a full-blown DNS resolver.
+      name_servers='127.0.0.1'
+    '' + cfg.extraConfig;
+
+in
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "networking" "dnsSingleRequest" ] [ "networking" "resolvconf" "dnsSingleRequest" ])
+    (mkRenamedOptionModule [ "networking" "dnsExtensionMechanism" ] [ "networking" "resolvconf" "dnsExtensionMechanism" ])
+    (mkRenamedOptionModule [ "networking" "extraResolvconfConf" ] [ "networking" "resolvconf" "extraConfig" ])
+    (mkRenamedOptionModule [ "networking" "resolvconfOptions" ] [ "networking" "resolvconf" "extraOptions" ])
+  ];
+
+  options = {
+
+    networking.resolvconf = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        internal = true;
+        description = ''
+          DNS configuration is managed by resolvconf.
+        '';
+      };
+
+      useHostResolvConf = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          In containers, whether to use the
+          <filename>resolv.conf</filename> supplied by the host.
+        '';
+      };
+
+      dnsSingleRequest = lib.mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Recent versions of glibc will issue both ipv4 (A) and ipv6 (AAAA)
+          address queries at the same time, from the same port. Sometimes upstream
+          routers will systemically drop the ipv4 queries. The symptom of this problem is
+          that 'getent hosts example.com' only returns ipv6 (or perhaps only ipv4) addresses. The
+          workaround for this is to specify the option 'single-request' in
+          /etc/resolv.conf. This option enables that.
+        '';
+      };
+
+      dnsExtensionMechanism = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enable the <code>edns0</code> option in <filename>resolv.conf</filename>. With
+          that option set, <code>glibc</code> supports use of the extension mechanisms for
+          DNS (EDNS) specified in RFC 2671. The most popular user of that feature is DNSSEC,
+          which does not work without it.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = "libc=NO";
+        description = ''
+          Extra configuration to append to <filename>resolvconf.conf</filename>.
+        '';
+      };
+
+      extraOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "ndots:1" "rotate" ];
+        description = ''
+          Set the options in <filename>/etc/resolv.conf</filename>.
+        '';
+      };
+
+      useLocalResolver = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Use local DNS server for resolving.
+        '';
+      };
+
+    };
+
+  };
+
+  config = mkMerge [
+    {
+      networking.resolvconf.enable = !(config.environment.etc ? "resolv.conf");
+
+      environment.etc."resolvconf.conf".text =
+        if !cfg.enable then
+          # Force-stop any attempts to use resolvconf
+          ''
+            echo "resolvconf is disabled on this system but was used anyway:" >&2
+            echo "$0 $*" >&2
+            exit 1
+          ''
+        else configText;
+    }
+
+    (mkIf cfg.enable {
+      environment.systemPackages = [ pkgs.openresolv ];
+
+      systemd.services.resolvconf = {
+        description = "resolvconf update";
+
+        before = [ "network-pre.target" ];
+        wants = [ "network-pre.target" ];
+        wantedBy = [ "multi-user.target" ];
+        restartTriggers = [ config.environment.etc."resolvconf.conf".source ];
+
+        serviceConfig = {
+          Type = "oneshot";
+          ExecStart = "${pkgs.openresolv}/bin/resolvconf -u";
+          RemainAfterExit = true;
+        };
+      };
+
+    })
+  ];
+
+}
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index 6379b52870ea..a0a20228a742 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -118,6 +118,14 @@ in
       type = with types; attrsOf (nullOr (either str path));
     };
 
+    environment.homeBinInPath = mkOption {
+      description = ''
+        Include ~/bin/ in $PATH.
+      '';
+      default = false;
+      type = types.bool;
+    };
+
     environment.binsh = mkOption {
       default = "${config.system.build.binsh}/bin/sh";
       defaultText = "\${config.system.build.binsh}/bin/sh";
@@ -157,13 +165,15 @@ in
     # terminal instead of logging out of X11).
     environment.variables = config.environment.sessionVariables;
 
+    environment.profileRelativeEnvVars = config.environment.profileRelativeSessionVariables;
+
     environment.shellAliases = mapAttrs (name: mkDefault) {
       ls = "ls --color=tty";
       ll = "ls -l";
       l  = "ls -alh";
     };
 
-    environment.etc."shells".text =
+    environment.etc.shells.text =
       ''
         ${concatStringsSep "\n" (map utils.toShellPath cfg.shells)}
         /bin/sh
@@ -171,7 +181,7 @@ in
 
     # For resetting environment with `. /etc/set-environment` when needed
     # and discoverability (see motivation of #30418).
-    environment.etc."set-environment".source = config.system.build.setEnvironment;
+    environment.etc.set-environment.source = config.system.build.setEnvironment;
 
     system.build.setEnvironment = pkgs.writeText "set-environment"
       ''
@@ -184,8 +194,10 @@ in
 
         ${cfg.extraInit}
 
-        # ~/bin if it exists overrides other bin directories.
-        export PATH="$HOME/bin:$PATH"
+        ${optionalString cfg.homeBinInPath ''
+          # ~/bin if it exists overrides other bin directories.
+          export PATH="$HOME/bin:$PATH"
+        ''}
       '';
 
     system.activationScripts.binsh = stringAfter [ "stdio" ]
diff --git a/nixos/modules/config/swap.nix b/nixos/modules/config/swap.nix
index fed3fa3bc7c8..d0fc0d4a3ea4 100644
--- a/nixos/modules/config/swap.nix
+++ b/nixos/modules/config/swap.nix
@@ -58,7 +58,7 @@ let
       device = mkOption {
         example = "/dev/sda3";
         type = types.str;
-        description = "Path of the device.";
+        description = "Path of the device or swap file.";
       };
 
       label = mkOption {
diff --git a/nixos/modules/config/sysctl.nix b/nixos/modules/config/sysctl.nix
index 74bff602a477..e59c7a32c287 100644
--- a/nixos/modules/config/sysctl.nix
+++ b/nixos/modules/config/sysctl.nix
@@ -8,7 +8,7 @@ let
     name = "sysctl option value";
     check = val:
       let
-        checkType = x: isBool x || isString x || isInt x || isNull x;
+        checkType = x: isBool x || isString x || isInt x || x == null;
       in
         checkType val || (val._type or "" == "override" && checkType val.content);
     merge = loc: defs: mergeOneOption loc (filterOverrides defs);
@@ -42,25 +42,19 @@ in
 
   config = {
 
-    environment.etc."sysctl.d/nixos.conf".text =
+    environment.etc."sysctl.d/60-nixos.conf".text =
       concatStrings (mapAttrsToList (n: v:
         optionalString (v != null) "${n}=${if v == false then "0" else toString v}\n"
       ) config.boot.kernel.sysctl);
 
     systemd.services.systemd-sysctl =
       { wantedBy = [ "multi-user.target" ];
-        restartTriggers = [ config.environment.etc."sysctl.d/nixos.conf".source ];
+        restartTriggers = [ config.environment.etc."sysctl.d/60-nixos.conf".source ];
       };
 
-    # Enable hardlink and symlink restrictions.  See
-    # https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=800179c9b8a1e796e441674776d11cd4c05d61d7
-    # for details.
-    boot.kernel.sysctl."fs.protected_hardlinks" = true;
-    boot.kernel.sysctl."fs.protected_symlinks" = true;
-
     # Hide kernel pointers (e.g. in /proc/modules) for unprivileged
     # users as these make it easier to exploit kernel vulnerabilities.
-    boot.kernel.sysctl."kernel.kptr_restrict" = 1;
+    boot.kernel.sysctl."kernel.kptr_restrict" = mkDefault 1;
 
     # Disable YAMA by default to allow easy debugging.
     boot.kernel.sysctl."kernel.yama.ptrace_scope" = mkDefault 0;
diff --git a/nixos/modules/config/system-environment.nix b/nixos/modules/config/system-environment.nix
index 6011e354ece4..4888740ba3d5 100644
--- a/nixos/modules/config/system-environment.nix
+++ b/nixos/modules/config/system-environment.nix
@@ -18,25 +18,88 @@ in
       default = {};
       description = ''
         A set of environment variables used in the global environment.
-        These variables will be set by PAM.
-        The value of each variable can be either a string or a list of
-        strings.  The latter is concatenated, interspersed with colon
-        characters.
+        These variables will be set by PAM early in the login process.
+
+        The value of each session variable can be either a string or a
+        list of strings. The latter is concatenated, interspersed with
+        colon characters.
+
+        Note, due to limitations in the PAM format values may not
+        contain the <literal>"</literal> character.
+
+        Also, these variables are merged into
+        <xref linkend="opt-environment.variables"/> and it is
+        therefore not possible to use PAM style variables such as
+        <code>@{HOME}</code>.
       '';
       type = with types; attrsOf (either str (listOf str));
       apply = mapAttrs (n: v: if isList v then concatStringsSep ":" v else v);
     };
 
+    environment.profileRelativeSessionVariables = mkOption {
+      type = types.attrsOf (types.listOf types.str);
+      example = { PATH = [ "/bin" ]; MANPATH = [ "/man" "/share/man" ]; };
+      description = ''
+        Attribute set of environment variable used in the global
+        environment. These variables will be set by PAM early in the
+        login process.
+
+        Variable substitution is available as described in
+        <citerefentry>
+          <refentrytitle>pam_env.conf</refentrytitle>
+          <manvolnum>5</manvolnum>
+        </citerefentry>.
+
+        Each attribute maps to a list of relative paths. Each relative
+        path is appended to the each profile of
+        <option>environment.profiles</option> to form the content of
+        the corresponding environment variable.
+
+        Also, these variables are merged into
+        <xref linkend="opt-environment.profileRelativeEnvVars"/> and it is
+        therefore not possible to use PAM style variables such as
+        <code>@{HOME}</code>.
+      '';
+    };
+
   };
 
   config = {
 
-    system.build.pamEnvironment = pkgs.writeText "pam-environment"
-       ''
-         ${concatStringsSep "\n" (
-           (mapAttrsToList (n: v: ''${n}="${concatStringsSep ":" v}"'')
-             (zipAttrsWith (const concatLists) ([ (mapAttrs (n: v: [ v ]) cfg.sessionVariables) ]))))}
-       '';
+    system.build.pamEnvironment =
+      let
+        suffixedVariables =
+          flip mapAttrs cfg.profileRelativeSessionVariables (envVar: suffixes:
+            flip concatMap cfg.profiles (profile:
+              map (suffix: "${profile}${suffix}") suffixes
+            )
+          );
+
+        # We're trying to use the same syntax for PAM variables and env variables.
+        # That means we need to map the env variables that people might use to their
+        # equivalent PAM variable.
+        replaceEnvVars = replaceStrings ["$HOME" "$USER"] ["@{HOME}" "@{PAM_USER}"];
+
+        pamVariable = n: v:
+          ''${n}   DEFAULT="${concatStringsSep ":" (map replaceEnvVars (toList v))}"'';
+
+        pamVariables =
+          concatStringsSep "\n"
+          (mapAttrsToList pamVariable
+          (zipAttrsWith (n: concatLists)
+            [
+              # Make sure security wrappers are prioritized without polluting
+              # shell environments with an extra entry. Sessions which depend on
+              # pam for its environment will otherwise have eg. broken sudo. In
+              # particular Gnome Shell sometimes fails to source a proper
+              # environment from a shell.
+              { PATH = [ config.security.wrapperDir ]; }
+
+              (mapAttrs (n: toList) cfg.sessionVariables)
+              suffixedVariables
+            ]));
+      in
+        pkgs.writeText "pam-environment" "${pamVariables}\n";
 
   };
 
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index fae2fc740082..aba9bc0945b1 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -135,6 +135,9 @@ in
       # outputs TODO: note that the tools will often not be linked by default
       postBuild =
         ''
+          # Remove wrapped binaries, they shouldn't be accessible via PATH.
+          find $out/bin -maxdepth 1 -name ".*-wrapped" -type l -delete
+
           if [ -x $out/bin/glib-compile-schemas -a -w $out/share/glib-2.0/schemas ]; then
               $out/bin/glib-compile-schemas $out/share/glib-2.0/schemas
           fi
diff --git a/nixos/modules/config/terminfo.nix b/nixos/modules/config/terminfo.nix
index 4fd6ba5ea605..1396640af672 100644
--- a/nixos/modules/config/terminfo.nix
+++ b/nixos/modules/config/terminfo.nix
@@ -8,11 +8,11 @@
       "/share/terminfo"
     ];
 
-    environment.etc."terminfo" = {
+    environment.etc.terminfo = {
       source = "${config.system.path}/share/terminfo";
     };
 
-    environment.profileRelativeEnvVars = {
+    environment.profileRelativeSessionVariables = {
       TERMINFO_DIRS = [ "/share/terminfo" ];
     };
 
diff --git a/nixos/modules/config/unix-odbc-drivers.nix b/nixos/modules/config/unix-odbc-drivers.nix
index 8dd811727389..abc12a627d6f 100644
--- a/nixos/modules/config/unix-odbc-drivers.nix
+++ b/nixos/modules/config/unix-odbc-drivers.nix
@@ -24,7 +24,7 @@ in {
         Specifies Unix ODBC drivers to be registered in
         <filename>/etc/odbcinst.ini</filename>.  You may also want to
         add <literal>pkgs.unixODBC</literal> to the system path to get
-        a command line client to connnect to ODBC databases.
+        a command line client to connect to ODBC databases.
       '';
     };
   };
diff --git a/nixos/modules/config/update-users-groups.pl b/nixos/modules/config/update-users-groups.pl
index ef5e6346f02e..15e448b787aa 100644
--- a/nixos/modules/config/update-users-groups.pl
+++ b/nixos/modules/config/update-users-groups.pl
@@ -56,12 +56,12 @@ sub allocGid {
         $gidsUsed{$prevGid} = 1;
         return $prevGid;
     }
-    return allocId(\%gidsUsed, \%gidsPrevUsed, 400, 499, 0, sub { my ($gid) = @_; getgrgid($gid) });
+    return allocId(\%gidsUsed, \%gidsPrevUsed, 400, 999, 0, sub { my ($gid) = @_; getgrgid($gid) });
 }
 
 sub allocUid {
     my ($name, $isSystemUser) = @_;
-    my ($min, $max, $up) = $isSystemUser ? (400, 499, 0) : (1000, 29999, 1);
+    my ($min, $max, $up) = $isSystemUser ? (400, 999, 0) : (1000, 29999, 1);
     my $prevUid = $uidMap->{$name};
     if (defined $prevUid && $prevUid >= $min && $prevUid <= $max && !defined $uidsUsed{$prevUid}) {
         print STDERR "reviving user '$name' with UID $prevUid\n";
@@ -267,6 +267,7 @@ foreach my $line (-f "/etc/shadow" ? read_file("/etc/shadow") : ()) {
     next if !defined $u;
     $hashedPassword = "!" if !$spec->{mutableUsers};
     $hashedPassword = $u->{hashedPassword} if defined $u->{hashedPassword} && !$spec->{mutableUsers}; # FIXME
+    chomp $hashedPassword;
     push @shadowNew, join(":", $name, $hashedPassword, @rest) . "\n";
     $shadowSeen{$name} = 1;
 }
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index c3f228c9bcc4..141e43fec39b 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -181,7 +181,7 @@ let
       };
 
       hashedPassword = mkOption {
-        type = with types; uniq (nullOr str);
+        type = with types; nullOr str;
         default = null;
         description = ''
           Specifies the hashed password for the user.
@@ -191,7 +191,7 @@ let
       };
 
       password = mkOption {
-        type = with types; uniq (nullOr str);
+        type = with types; nullOr str;
         default = null;
         description = ''
           Specifies the (clear text) password for the user.
@@ -203,7 +203,7 @@ let
       };
 
       passwordFile = mkOption {
-        type = with types; uniq (nullOr string);
+        type = with types; nullOr str;
         default = null;
         description = ''
           The full path to a file that contains the user's password. The password
@@ -215,7 +215,7 @@ let
       };
 
       initialHashedPassword = mkOption {
-        type = with types; uniq (nullOr str);
+        type = with types; nullOr str;
         default = null;
         description = ''
           Specifies the initial hashed password for the user, i.e. the
@@ -230,7 +230,7 @@ let
       };
 
       initialPassword = mkOption {
-        type = with types; uniq (nullOr str);
+        type = with types; nullOr str;
         default = null;
         description = ''
           Specifies the initial password for the user, i.e. the
@@ -251,7 +251,7 @@ let
         default = [];
         example = literalExample "[ pkgs.firefox pkgs.thunderbird ]";
         description = ''
-          The set of packages that should be made availabe to the user.
+          The set of packages that should be made available to the user.
           This is in contrast to <option>environment.systemPackages</option>,
           which adds packages to all users.
         '';
@@ -304,7 +304,7 @@ let
       };
 
       members = mkOption {
-        type = with types; listOf string;
+        type = with types; listOf str;
         default = [];
         description = ''
           The user names of the group members, added to the
@@ -403,6 +403,10 @@ let
       filter types.shellPackage.check shells;
 
 in {
+  imports = [
+    (mkAliasOptionModule [ "users" "extraUsers" ] [ "users" "users" ])
+    (mkAliasOptionModule [ "users" "extraGroups" ] [ "users" "groups" ])
+  ];
 
   ###### interface
 
@@ -546,11 +550,11 @@ in {
     environment.systemPackages = systemShells;
 
     environment.etc = {
-      "subuid" = {
+      subuid = {
         text = subuidFile;
         mode = "0644";
       };
-      "subgid" = {
+      subgid = {
         text = subgidFile;
         mode = "0644";
       };
@@ -564,7 +568,10 @@ in {
       };
     }) (filterAttrs (_: u: u.packages != []) cfg.users));
 
-    environment.profiles = [ "/etc/profiles/per-user/$USER" ];
+    environment.profiles = [
+      "$HOME/.nix-profile"
+      "/etc/profiles/per-user/$USER"
+    ];
 
     assertions = [
       { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique);
diff --git a/nixos/modules/config/vpnc.nix b/nixos/modules/config/vpnc.nix
deleted file mode 100644
index 356e007c0a3e..000000000000
--- a/nixos/modules/config/vpnc.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-{ config, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.networking.vpnc;
-  mkServiceDef = name: value:
-    {
-      name = "vpnc/${name}.conf";
-      value = { text = value; };
-    };
-
-in
-{
-  options = {
-    networking.vpnc = {
-      services = mkOption {
-       type = types.attrsOf types.str;
-       default = {};
-       example = literalExample ''
-         { test = '''
-             IPSec gateway 192.168.1.1
-             IPSec ID someID
-             IPSec secret secretKey
-             Xauth username name
-             Xauth password pass
-           ''';
-         }
-       '';
-       description = 
-         ''
-           The names of cisco VPNs and their associated definitions
-         '';
-      };
-    };
-  };
-
-  config.environment.etc = mapAttrs' mkServiceDef cfg.services;
-}
-
-
diff --git a/nixos/modules/config/vte.nix b/nixos/modules/config/vte.nix
new file mode 100644
index 000000000000..d4a8c926fef2
--- /dev/null
+++ b/nixos/modules/config/vte.nix
@@ -0,0 +1,52 @@
+# VTE
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  vteInitSnippet = ''
+    # Show current working directory in VTE terminals window title.
+    # Supports both bash and zsh, requires interactive shell.
+    . ${pkgs.vte}/etc/profile.d/vte.sh
+  '';
+
+in
+
+{
+
+  options = {
+
+    programs.bash.vteIntegration = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable Bash integration for VTE terminals.
+        This allows it to preserve the current directory of the shell
+        across terminals.
+      '';
+    };
+
+    programs.zsh.vteIntegration = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to enable Zsh integration for VTE terminals.
+        This allows it to preserve the current directory of the shell
+        across terminals.
+      '';
+    };
+
+  };
+
+  config = mkMerge [
+    (mkIf config.programs.bash.vteIntegration {
+      programs.bash.interactiveShellInit = mkBefore vteInitSnippet;
+    })
+
+    (mkIf config.programs.zsh.vteIntegration {
+      programs.zsh.interactiveShellInit = vteInitSnippet;
+    })
+  ];
+}
diff --git a/nixos/modules/config/xdg/icons.nix b/nixos/modules/config/xdg/icons.nix
index 8268a3771a0e..4677ce090b0b 100644
--- a/nixos/modules/config/xdg/icons.nix
+++ b/nixos/modules/config/xdg/icons.nix
@@ -7,21 +7,32 @@ with lib;
       type = types.bool;
       default = true;
       description = ''
-        Whether to install files to support the 
+        Whether to install files to support the
         <link xlink:href="https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html">XDG Icon Theme specification</link>.
       '';
     };
   };
 
   config = mkIf config.xdg.icons.enable {
-    environment.pathsToLink = [ 
-      "/share/icons" 
-      "/share/pixmaps" 
+    environment.pathsToLink = [
+      "/share/icons"
+      "/share/pixmaps"
+    ];
+
+    # libXcursor looks for cursors in XCURSOR_PATH
+    # it mostly follows the spec for icons
+    # See: https://www.x.org/releases/current/doc/man/man3/Xcursor.3.xhtml Themes
+
+    # These are preferred so they come first in the list
+    environment.sessionVariables.XCURSOR_PATH = [
+      "$HOME/.icons"
+      "$HOME/.local/share/icons"
+    ];
+
+    environment.profileRelativeSessionVariables.XCURSOR_PATH = [
+      "/share/icons"
+      "/share/pixmaps"
     ];
-    
-    environment.profileRelativeEnvVars = {
-      XCURSOR_PATH = [ "/share/icons" ];
-    };
   };
 
 }
diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix
index cd1064630fbc..a5374c2b468d 100644
--- a/nixos/modules/config/xdg/mime.nix
+++ b/nixos/modules/config/xdg/mime.nix
@@ -24,7 +24,7 @@ with lib;
 
     environment.extraSetup = ''
       if [ -w $out/share/mime ] && [ -d $out/share/mime/packages ]; then
-          XDG_DATA_DIRS=$out/share ${pkgs.buildPackages.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
+          XDG_DATA_DIRS=$out/share PKGSYSTEM_ENABLE_FSYNC=0 ${pkgs.buildPackages.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
       fi
 
       if [ -w $out/share/applications ]; then
diff --git a/nixos/modules/config/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix
new file mode 100644
index 000000000000..95fa8e05fa3f
--- /dev/null
+++ b/nixos/modules/config/xdg/portal.nix
@@ -0,0 +1,62 @@
+{ config, pkgs ,lib ,... }:
+
+with lib;
+
+{
+  imports = [
+    (mkRenamedOptionModule [ "services" "flatpak" "extraPortals" ] [ "xdg" "portal" "extraPortals" ])
+  ];
+
+  options.xdg.portal = {
+    enable =
+      mkEnableOption "<link xlink:href='https://github.com/flatpak/xdg-desktop-portal'>xdg desktop integration</link>"//{
+        default = false;
+      };
+
+    extraPortals = mkOption {
+      type = types.listOf types.package;
+      default = [];
+      description = ''
+        List of additional portals to add to path. Portals allow interaction
+        with system, like choosing files or taking screenshots. At minimum,
+        a desktop portal implementation should be listed. GNOME and KDE already
+        adds <package>xdg-desktop-portal-gtk</package>; and
+        <package>xdg-desktop-portal-kde</package> respectively. On other desktop
+        environments you probably want to add them yourself.
+      '';
+    };
+
+    gtkUsePortal = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Sets environment variable <literal>GTK_USE_PORTAL</literal> to <literal>1</literal>.
+        This is needed for packages ran outside Flatpak to respect and use XDG Desktop Portals.
+        For example, you'd need to set this for non-flatpak Firefox to use native filechoosers.
+        Defaults to <literal>false</literal> to respect its opt-in nature.
+      '';
+    };
+  };
+
+  config =
+    let
+      cfg = config.xdg.portal;
+      packages = [ pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
+
+    in mkIf cfg.enable {
+
+      assertions = [
+        { assertion = (cfg.gtkUsePortal -> cfg.extraPortals != []);
+          message = "Setting xdg.portal.gtkUsePortal to true requires a portal implementation in xdg.portal.extraPortals such as xdg-desktop-portal-gtk or xdg-desktop-portal-kde.";
+        }
+      ];
+
+      services.dbus.packages  = packages;
+      systemd.packages = packages;
+
+      environment.variables = {
+        GTK_USE_PORTAL = mkIf cfg.gtkUsePortal "1";
+        XDG_DESKTOP_PORTAL_PATH = map (p: "${p}/share/xdg-desktop-portal/portals") cfg.extraPortals;
+      };
+    };
+}
diff --git a/nixos/modules/config/xdg/sounds.nix b/nixos/modules/config/xdg/sounds.nix
index 148240d631cf..14d6340fc33b 100644
--- a/nixos/modules/config/xdg/sounds.nix
+++ b/nixos/modules/config/xdg/sounds.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 {
@@ -14,6 +14,10 @@ with lib;
   };
 
   config = mkIf config.xdg.sounds.enable {
+    environment.systemPackages = [
+      pkgs.sound-theme-freedesktop
+    ];
+
     environment.pathsToLink = [
       "/share/sounds"
     ];
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index c79be810eefd..16be8bcfdd7d 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -6,6 +6,14 @@ let
   cfg = config.hardware;
 in {
 
+  imports = [
+    (mkRenamedOptionModule [ "networking" "enableRT73Firmware" ] [ "hardware" "enableRedistributableFirmware" ])
+    (mkRenamedOptionModule [ "networking" "enableIntel3945ABGFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
+    (mkRenamedOptionModule [ "networking" "enableIntel2100BGFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
+    (mkRenamedOptionModule [ "networking" "enableRalinkFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
+    (mkRenamedOptionModule [ "networking" "enableRTL8192cFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
+  ];
+
   ###### interface
 
   options = {
@@ -63,8 +71,7 @@ in {
         b43Firmware_5_1_138
         b43Firmware_6_30_163_46
         b43FirmwareCutter
-        facetimehd-firmware
-      ];
+      ] ++ optional (pkgs.stdenv.hostPlatform.isi686 || pkgs.stdenv.hostPlatform.isx86_64) facetimehd-firmware;
     })
   ];
 }
diff --git a/nixos/modules/hardware/brightnessctl.nix b/nixos/modules/hardware/brightnessctl.nix
index 341e4b791c23..2d54398d10df 100644
--- a/nixos/modules/hardware/brightnessctl.nix
+++ b/nixos/modules/hardware/brightnessctl.nix
@@ -25,6 +25,7 @@ in
 
   config = mkIf cfg.enable {
     services.udev.packages = with pkgs; [ brightnessctl ];
+    environment.systemPackages = with pkgs; [ brightnessctl ];
   };
 
 }
diff --git a/nixos/modules/hardware/brillo.nix b/nixos/modules/hardware/brillo.nix
new file mode 100644
index 000000000000..e970c9480998
--- /dev/null
+++ b/nixos/modules/hardware/brillo.nix
@@ -0,0 +1,22 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.hardware.brillo;
+in
+{
+  options = {
+    hardware.brillo = {
+      enable = mkEnableOption ''
+        Enable brillo in userspace.
+        This will allow brightness control from users in the video group.
+      '';
+    };
+  };
+
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ pkgs.brillo ];
+    environment.systemPackages = [ pkgs.brillo ];
+  };
+}
diff --git a/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix
index 20b2756d8b26..fe0ca9f26d54 100644
--- a/nixos/modules/hardware/ckb-next.nix
+++ b/nixos/modules/hardware/ckb-next.nix
@@ -7,6 +7,11 @@ let
 
 in
   {
+    imports = [
+      (mkRenamedOptionModule [ "hardware" "ckb" "enable" ] [ "hardware" "ckb-next" "enable" ])
+      (mkRenamedOptionModule [ "hardware" "ckb" "package" ] [ "hardware" "ckb-next" "package" ])
+    ];
+
     options.hardware.ckb-next = {
       enable = mkEnableOption "the Corsair keyboard/mouse driver";
 
diff --git a/nixos/modules/hardware/device-tree.nix b/nixos/modules/hardware/device-tree.nix
new file mode 100644
index 000000000000..f57502d4c83e
--- /dev/null
+++ b/nixos/modules/hardware/device-tree.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.deviceTree;
+in {
+  options = {
+      hardware.deviceTree = {
+        enable = mkOption {
+          default = pkgs.stdenv.hostPlatform.platform.kernelDTB or false;
+          type = types.bool;
+          description = ''
+            Build device tree files. These are used to describe the
+            non-discoverable hardware of a system.
+          '';
+        };
+
+        base = mkOption {
+          default = "${config.boot.kernelPackages.kernel}/dtbs";
+          defaultText = "\${config.boot.kernelPackages.kernel}/dtbs";
+          example = literalExample "pkgs.deviceTree_rpi";
+          type = types.path;
+          description = ''
+            The package containing the base device-tree (.dtb) to boot. Contains
+            device trees bundled with the Linux kernel by default.
+          '';
+        };
+
+        overlays = mkOption {
+          default = [];
+          example = literalExample
+            "[\"\${pkgs.deviceTree_rpi.overlays}/w1-gpio.dtbo\"]";
+          type = types.listOf types.path;
+          description = ''
+            A path containing device tree overlays (.dtbo) to be applied to all
+            base device-trees.
+          '';
+        };
+
+        package = mkOption {
+          default = null;
+          type = types.nullOr types.path;
+          internal = true;
+          description = ''
+            A path containing the result of applying `overlays` to `base`.
+          '';
+        };
+      };
+  };
+
+  config = mkIf (cfg.enable) {
+    hardware.deviceTree.package = if (cfg.overlays != [])
+      then pkgs.deviceTree.applyOverlays cfg.base cfg.overlays else cfg.base;
+  };
+}
diff --git a/nixos/modules/hardware/ksm.nix b/nixos/modules/hardware/ksm.nix
index d6ac69b5d65e..0938dbdc1101 100644
--- a/nixos/modules/hardware/ksm.nix
+++ b/nixos/modules/hardware/ksm.nix
@@ -1,9 +1,28 @@
 { config, lib, ... }:
 
-{
-  options.hardware.enableKSM = lib.mkEnableOption "Kernel Same-Page Merging";
+with lib;
 
-  config = lib.mkIf config.hardware.enableKSM {
+let
+  cfg = config.hardware.ksm;
+
+in {
+  imports = [
+    (mkRenamedOptionModule [ "hardware" "enableKSM" ] [ "hardware" "ksm" "enable" ])
+  ];
+
+  options.hardware.ksm = {
+    enable = mkEnableOption "Kernel Same-Page Merging";
+    sleep = mkOption {
+      type = types.nullOr types.int;
+      default = null;
+      description = ''
+        How many milliseconds ksmd should sleep between scans.
+        Setting it to <literal>null</literal> uses the kernel's default time.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
     systemd.services.enable-ksm = {
       description = "Enable Kernel Same-Page Merging";
       wantedBy = [ "multi-user.target" ];
@@ -11,6 +30,7 @@
       script = ''
         if [ -e /sys/kernel/mm/ksm ]; then
           echo 1 > /sys/kernel/mm/ksm/run
+          ${optionalString (cfg.sleep != null) ''echo ${toString cfg.sleep} > /sys/kernel/mm/ksm/sleep_millisecs''}
         fi
       '';
     };
diff --git a/nixos/modules/hardware/nitrokey.nix b/nixos/modules/hardware/nitrokey.nix
index 60fc95a75828..02e4c3f46f8d 100644
--- a/nixos/modules/hardware/nitrokey.nix
+++ b/nixos/modules/hardware/nitrokey.nix
@@ -36,6 +36,6 @@ in
         { inherit (cfg) group; }
       ))
     ];
-    users.groups."${cfg.group}" = {};
+    users.groups.${cfg.group} = {};
   };
 }
diff --git a/nixos/modules/hardware/opengl.nix b/nixos/modules/hardware/opengl.nix
index 6b7b8069fd44..28cddea8b79c 100644
--- a/nixos/modules/hardware/opengl.nix
+++ b/nixos/modules/hardware/opengl.nix
@@ -11,9 +11,9 @@ let
   videoDrivers = config.services.xserver.videoDrivers;
 
   makePackage = p: pkgs.buildEnv {
-    name = "mesa-drivers+txc-${p.mesa_drivers.version}";
+    name = "mesa-drivers+txc-${p.mesa.version}";
     paths =
-      [ p.mesa_drivers
+      [ p.mesa.drivers
         (if cfg.s3tcSupport then p.libtxc_dxtn else p.libtxc_dxtn_s2tc)
       ];
   };
@@ -31,6 +31,11 @@ let
 in
 
 {
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "hardware" "opengl" "extraPackages" ])
+  ];
+
   options = {
 
     hardware.opengl = {
@@ -38,11 +43,11 @@ in
         description = ''
           Whether to enable OpenGL drivers. This is needed to enable
           OpenGL support in X11 systems, as well as for Wayland compositors
-          like sway, way-cooler and Weston. It is enabled by default
+          like sway and Weston. It is enabled by default
           by the corresponding modules, so you do not usually have to
           set it yourself, only if there is no module for your wayland
-          compositor of choice. See services.xserver.enable,
-          programs.sway.enable, and programs.way-cooler.enable.
+          compositor of choice. See services.xserver.enable and
+          programs.sway.enable.
         '';
         type = types.bool;
         default = false;
@@ -118,6 +123,19 @@ in
           set. This can be used to add OpenCL drivers, VA-API/VDPAU drivers etc.
         '';
       };
+
+      setLdLibraryPath = mkOption {
+        type = types.bool;
+        internal = true;
+        default = false;
+        description = ''
+          Whether the <literal>LD_LIBRARY_PATH</literal> environment variable
+          should be set to the locations of driver libraries. Drivers which
+          rely on overriding libraries should set this to true. Drivers which
+          support <literal>libglvnd</literal> and other dispatch libraries
+          instead of overriding libraries should not set this.
+        '';
+      };
     };
 
   };
@@ -145,11 +163,8 @@ in
       )
     ];
 
-    environment.sessionVariables.LD_LIBRARY_PATH =
-      [ "/run/opengl-driver/lib" ] ++ optional cfg.driSupport32Bit "/run/opengl-driver-32/lib";
-
-    environment.variables.XDG_DATA_DIRS =
-      [ "/run/opengl-driver/share" ] ++ optional cfg.driSupport32Bit "/run/opengl-driver-32/share";
+    environment.sessionVariables.LD_LIBRARY_PATH = mkIf cfg.setLdLibraryPath
+      ([ "/run/opengl-driver/lib" ] ++ optional cfg.driSupport32Bit "/run/opengl-driver-32/lib");
 
     hardware.opengl.package = mkDefault (makePackage pkgs);
     hardware.opengl.package32 = mkDefault (makePackage pkgs.pkgsi686Linux);
diff --git a/nixos/modules/hardware/openrazer.nix b/nixos/modules/hardware/openrazer.nix
new file mode 100644
index 000000000000..883db7f2f4f1
--- /dev/null
+++ b/nixos/modules/hardware/openrazer.nix
@@ -0,0 +1,133 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.openrazer;
+  kernelPackages = config.boot.kernelPackages;
+
+  toPyBoolStr = b: if b then "True" else "False";
+
+  daemonExe = "${pkgs.openrazer-daemon}/bin/openrazer-daemon --config ${daemonConfFile}";
+
+  daemonConfFile = pkgs.writeTextFile {
+    name = "razer.conf";
+    text = ''
+      [General]
+      verbose_logging = ${toPyBoolStr cfg.verboseLogging}
+
+      [Startup]
+      sync_effects_enabled = ${toPyBoolStr cfg.syncEffectsEnabled}
+      devices_off_on_screensaver = ${toPyBoolStr cfg.devicesOffOnScreensaver}
+      mouse_battery_notifier = ${toPyBoolStr cfg.mouseBatteryNotifier}
+
+      [Statistics]
+      key_statistics = ${toPyBoolStr cfg.keyStatistics}
+    '';
+  };
+
+  dbusServiceFile = pkgs.writeTextFile rec {
+    name = "org.razer.service";
+    destination = "/share/dbus-1/services/${name}";
+    text = ''
+      [D-BUS Service]
+      Name=org.razer
+      Exec=${daemonExe}
+      SystemdService=openrazer-daemon.service
+    '';
+  };
+
+  drivers = [
+    "razerkbd"
+    "razermouse"
+    "razerfirefly"
+    "razerkraken"
+    "razermug"
+    "razercore"
+  ];
+in
+{
+  options = {
+    hardware.openrazer = {
+      enable = mkEnableOption "OpenRazer drivers and userspace daemon.";
+
+      verboseLogging = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable verbose logging. Logs debug messages.
+        '';
+      };
+
+      syncEffectsEnabled = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Set the sync effects flag to true so any assignment of
+          effects will work across devices.
+        '';
+      };
+
+      devicesOffOnScreensaver = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Turn off the devices when the systems screensaver kicks in.
+        '';
+      };
+
+      mouseBatteryNotifier = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Mouse battery notifier.
+        '';
+      };
+
+      keyStatistics = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Collects number of keypresses per hour per key used to
+          generate a heatmap.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    boot.extraModulePackages = [ kernelPackages.openrazer ];
+    boot.kernelModules = drivers;
+
+    # Makes the man pages available so you can succesfully run
+    # > systemctl --user help openrazer-daemon
+    environment.systemPackages = [ pkgs.python3Packages.openrazer-daemon.man ];
+
+    services.udev.packages = [ kernelPackages.openrazer ];
+    services.dbus.packages = [ dbusServiceFile ];
+
+    # A user must be a member of the plugdev group in order to start
+    # the openrazer-daemon. Therefore we make sure that the plugdev
+    # group exists.
+    users.groups.plugdev = {};
+
+    systemd.user.services.openrazer-daemon = {
+      description = "Daemon to manage razer devices in userspace";
+      unitConfig.Documentation = "man:openrazer-daemon(8)";
+        # Requires a graphical session so the daemon knows when the screensaver
+        # starts. See the 'devicesOffOnScreensaver' option.
+        wantedBy = [ "graphical-session.target" ];
+        partOf = [ "graphical-session.target" ];
+        serviceConfig = {
+          Type = "dbus";
+          BusName = "org.razer";
+          ExecStart = "${daemonExe} --foreground";
+          Restart = "always";
+      };
+    };
+  };
+
+  meta = {
+    maintainers = with lib.maintainers; [ roelvandijk ];
+  };
+}
diff --git a/nixos/modules/hardware/printers.nix b/nixos/modules/hardware/printers.nix
new file mode 100644
index 000000000000..56b91933477d
--- /dev/null
+++ b/nixos/modules/hardware/printers.nix
@@ -0,0 +1,135 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.hardware.printers;
+  ppdOptionsString = options: optionalString (options != {})
+    (concatStringsSep " "
+      (mapAttrsToList (name: value: "-o '${name}'='${value}'") options)
+    );
+  ensurePrinter = p: ''
+    ${pkgs.cups}/bin/lpadmin -p '${p.name}' -E \
+      ${optionalString (p.location != null) "-L '${p.location}'"} \
+      ${optionalString (p.description != null) "-D '${p.description}'"} \
+      -v '${p.deviceUri}' \
+      -m '${p.model}' \
+      ${ppdOptionsString p.ppdOptions}
+  '';
+  ensureDefaultPrinter = name: ''
+    ${pkgs.cups}/bin/lpoptions -d '${name}'
+  '';
+
+  # "graph but not # or /" can't be implemented as regex alone due to missing lookahead support
+  noInvalidChars = str: all (c: c != "#" && c != "/") (stringToCharacters str);
+  printerName = (types.addCheck (types.strMatching "[[:graph:]]+") noInvalidChars)
+    // { description = "printable string without spaces, # and /"; };
+
+
+in {
+  options = {
+    hardware.printers = {
+      ensureDefaultPrinter = mkOption {
+        type = types.nullOr printerName;
+        default = null;
+        description = ''
+          Ensures the named printer is the default CUPS printer / printer queue.
+        '';
+      };
+      ensurePrinters = mkOption {
+        description = ''
+          Will regularly ensure that the given CUPS printers are configured as declared here.
+          If a printer's options are manually changed afterwards, they will be overwritten eventually.
+          This option will never delete any printer, even if removed from this list.
+          You can check existing printers with <command>lpstat -s</command>
+          and remove printers with <command>lpadmin -x &lt;printer-name&gt;</command>.
+          Printers not listed here can still be manually configured.
+        '';
+        default = [];
+        type = types.listOf (types.submodule {
+          options = {
+            name = mkOption {
+              type = printerName;
+              example = "BrotherHL_Workroom";
+              description = ''
+                Name of the printer / printer queue.
+                May contain any printable characters except "/", "#", and space.
+              '';
+            };
+            location = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "Workroom";
+              description = ''
+                Optional human-readable location.
+              '';
+            };
+            description = mkOption {
+              type = types.nullOr types.str;
+              default = null;
+              example = "Brother HL-5140";
+              description = ''
+                Optional human-readable description.
+              '';
+            };
+            deviceUri = mkOption {
+              type = types.str;
+              example = [
+                "ipp://printserver.local/printers/BrotherHL_Workroom"
+                "usb://HP/DESKJET%20940C?serial=CN16E6C364BH"
+              ];
+              description = ''
+                How to reach the printer.
+                <command>lpinfo -v</command> shows a list of supported device URIs and schemes.
+              '';
+            };
+            model = mkOption {
+              type = types.str;
+              example = literalExample ''
+                gutenprint.''${lib.version.majorMinor (lib.getVersion pkgs.cups)}://brother-hl-5140/expert
+              '';
+              description = ''
+                Location of the ppd driver file for the printer.
+                <command>lpinfo -m</command> shows a list of supported models.
+              '';
+            };
+            ppdOptions = mkOption {
+              type = types.attrsOf types.str;
+              example = {
+                PageSize = "A4";
+                Duplex = "DuplexNoTumble";
+              };
+              default = {};
+              description = ''
+                Sets PPD options for the printer.
+                <command>lpoptions [-p printername] -l</command> shows suported PPD options for the given printer.
+              '';
+            };
+          };
+        });
+      };
+    };
+  };
+
+  config = mkIf (cfg.ensurePrinters != [] && config.services.printing.enable) {
+    systemd.services.ensure-printers = let
+      cupsUnit = if config.services.printing.startWhenNeeded then "cups.socket" else "cups.service";
+    in {
+      description = "Ensure NixOS-configured CUPS printers";
+      wantedBy = [ "multi-user.target" ];
+      requires = [ cupsUnit ];
+      # in contrast to cups.socket, for cups.service, this is actually not enough,
+      # as the cups service reports its activation before clients can actually interact with it.
+      # Because of this, commands like `lpinfo -v` will report a bad file descriptor
+      # due to the missing UNIX socket without sufficient sleep time.
+      after = [ cupsUnit ];
+
+      serviceConfig = {
+        Type = "oneshot";
+      };
+
+       # sleep 10 is required to wait until cups.service is actually initialized and has created its UNIX socket file
+      script = (optionalString (!config.services.printing.startWhenNeeded) "sleep 10\n")
+        + (concatMapStringsSep "\n" ensurePrinter cfg.ensurePrinters)
+        + optionalString (cfg.ensureDefaultPrinter != null) (ensureDefaultPrinter cfg.ensureDefaultPrinter);
+    };
+  };
+}
diff --git a/nixos/modules/hardware/raid/hpsa.nix b/nixos/modules/hardware/raid/hpsa.nix
index 3a65cb800a98..4d7af138292c 100644
--- a/nixos/modules/hardware/raid/hpsa.nix
+++ b/nixos/modules/hardware/raid/hpsa.nix
@@ -4,11 +4,11 @@ with lib;
 
 let
   hpssacli = pkgs.stdenv.mkDerivation rec {
-    name = "hpssacli-${version}";
+    pname = "hpssacli";
     version = "2.40-13.0";
 
     src = pkgs.fetchurl {
-      url = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/${name}_amd64.deb";
+      url = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/${pname}-${version}_amd64.deb";
       sha256 = "11w7fwk93lmfw0yya4jpjwdmgjimqxx6412sqa166g1pz4jil4sw";
     };
 
diff --git a/nixos/modules/hardware/steam-hardware.nix b/nixos/modules/hardware/steam-hardware.nix
index 378aeffe71b5..6218c9ffbb9b 100644
--- a/nixos/modules/hardware/steam-hardware.nix
+++ b/nixos/modules/hardware/steam-hardware.nix
@@ -21,5 +21,12 @@ in
     services.udev.packages = [
       pkgs.steamPackages.steam
     ];
+
+    # The uinput module needs to be loaded in order to trigger the udev rules
+    # defined in the steam package for setting permissions on /dev/uinput.
+    #
+    # If the udev rules are not triggered, some controllers won't work with
+    # steam.
+    boot.kernelModules = [ "uinput" ];
   };
 }
diff --git a/nixos/modules/hardware/tuxedo-keyboard.nix b/nixos/modules/hardware/tuxedo-keyboard.nix
new file mode 100644
index 000000000000..898eed244935
--- /dev/null
+++ b/nixos/modules/hardware/tuxedo-keyboard.nix
@@ -0,0 +1,35 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let 
+  cfg = config.hardware.tuxedo-keyboard;
+  tuxedo-keyboard = config.boot.kernelPackages.tuxedo-keyboard;
+in
+  {
+    options.hardware.tuxedo-keyboard = {
+      enable = mkEnableOption ''
+          Enables the tuxedo-keyboard driver.
+
+          To configure the driver, pass the options to the <option>boot.kernelParams</option> configuration.
+          There are several parameters you can change. It's best to check at the source code description which options are supported.
+          You can find all the supported parameters at: <link xlink:href="https://github.com/tuxedocomputers/tuxedo-keyboard#kernelparam" />
+
+          In order to use the <literal>custom</literal> lighting with the maximumg brightness and a color of <literal>0xff0a0a</literal> one would put pass <option>boot.kernelParams</option> like this:
+
+          <programlisting>
+          boot.kernelParams = [
+           "tuxedo_keyboard.mode=0"
+           "tuxedo_keyboard.brightness=255"
+           "tuxedo_keyboard.color_left=0xff0a0a"
+          ];
+          </programlisting>
+      '';
+    };
+
+    config = mkIf cfg.enable 
+    {
+      boot.kernelModules = ["tuxedo_keyboard"];
+      boot.extraModulePackages = [ tuxedo-keyboard ];
+    };
+  }
diff --git a/nixos/modules/hardware/usb-wwan.nix b/nixos/modules/hardware/usb-wwan.nix
index 2d20421586a7..679a6c6497cb 100644
--- a/nixos/modules/hardware/usb-wwan.nix
+++ b/nixos/modules/hardware/usb-wwan.nix
@@ -21,6 +21,19 @@ with lib;
   ###### implementation
 
   config = mkIf config.hardware.usbWwan.enable {
+    # Attaches device specific handlers.
     services.udev.packages = with pkgs; [ usb-modeswitch-data ];
+
+    # Triggered by udev, usb-modeswitch creates systemd services via a
+    # template unit in the usb-modeswitch package.
+    systemd.packages = with pkgs; [ usb-modeswitch ];
+
+    # The systemd service requires the usb-modeswitch-data. The
+    # usb-modeswitch package intends to discover this via the
+    # filesystem at /usr/share/usb_modeswitch, and merge it with user
+    # configuration in /etc/usb_modeswitch.d. Configuring the correct
+    # path in the package is difficult, as it would cause a cyclic
+    # dependency.
+    environment.etc."usb_modeswitch.d".source = "${pkgs.usb-modeswitch-data}/share/usb_modeswitch";
   };
 }
diff --git a/nixos/modules/hardware/video/amdgpu-pro.nix b/nixos/modules/hardware/video/amdgpu-pro.nix
index ab9e0c92020e..8e91e9d2baa9 100644
--- a/nixos/modules/hardware/video/amdgpu-pro.nix
+++ b/nixos/modules/hardware/video/amdgpu-pro.nix
@@ -30,10 +30,11 @@ in
     nixpkgs.config.xorg.abiCompat = "1.19";
 
     services.xserver.drivers = singleton
-      { name = "amdgpu"; modules = [ package ]; libPath = [ package ]; };
+      { name = "amdgpu"; modules = [ package ]; };
 
     hardware.opengl.package = package;
     hardware.opengl.package32 = package32;
+    hardware.opengl.setLdLibraryPath = true;
 
     boot.extraModulePackages = [ package ];
 
diff --git a/nixos/modules/hardware/video/ati.nix b/nixos/modules/hardware/video/ati.nix
index 6102919f0155..0aab7bd6b92c 100644
--- a/nixos/modules/hardware/video/ati.nix
+++ b/nixos/modules/hardware/video/ati.nix
@@ -21,10 +21,11 @@ in
     nixpkgs.config.xorg.abiCompat = "1.17";
 
     services.xserver.drivers = singleton
-      { name = "fglrx"; modules = [ ati_x11 ]; libPath = [ "${ati_x11}/lib" ]; };
+      { name = "fglrx"; modules = [ ati_x11 ]; };
 
     hardware.opengl.package = ati_x11;
     hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.ati_drivers_x11.override { libsOnly = true; kernel = null; };
+    hardware.opengl.setLdLibraryPath = true;
 
     environment.systemPackages = [ ati_x11 ];
 
@@ -32,7 +33,7 @@ in
 
     boot.blacklistedKernelModules = [ "radeon" ];
 
-    environment.etc."ati".source = "${ati_x11}/etc/ati";
+    environment.etc.ati.source = "${ati_x11}/etc/ati";
 
   };
 
diff --git a/nixos/modules/hardware/video/displaylink.nix b/nixos/modules/hardware/video/displaylink.nix
index 669ac849cbad..912f53da836a 100644
--- a/nixos/modules/hardware/video/displaylink.nix
+++ b/nixos/modules/hardware/video/displaylink.nix
@@ -19,6 +19,21 @@ in
   config = mkIf enabled {
 
     boot.extraModulePackages = [ evdi ];
+    boot.kernelModules = [ "evdi" ];
+
+    environment.etc."X11/xorg.conf.d/40-displaylink.conf".text = ''
+      Section "OutputClass"
+        Identifier  "DisplayLink"
+        MatchDriver "evdi"
+        Driver      "modesetting"
+        Option      "AccelMethod" "none"
+      EndSection
+    '';
+
+    # make the device available
+    services.xserver.displayManager.sessionCommands = ''
+      ${lib.getBin pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource 1 0
+    '';
 
     # Those are taken from displaylink-installer.sh and from Arch Linux AUR package.
 
@@ -47,18 +62,13 @@ in
       description = "DisplayLink Manager Service";
       after = [ "display-manager.service" ];
       conflicts = [ "getty@tty7.service" ];
-      path = [ pkgs.kmod ];
 
       serviceConfig = {
         ExecStart = "${displaylink}/bin/DisplayLinkManager";
         Restart = "always";
         RestartSec = 5;
+        LogsDirectory = "displaylink";
       };
-
-      preStart = ''
-        mkdir -p /var/log/displaylink
-        modprobe evdi
-      '';
     };
 
   };
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index 80ea7bc5d5c9..1794bb4b433d 100644
--- a/nixos/modules/hardware/video/nvidia.nix
+++ b/nixos/modules/hardware/video/nvidia.nix
@@ -1,6 +1,6 @@
 # This module provides the proprietary NVIDIA X11 / OpenGL drivers.
 
-{ stdenv, config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -75,12 +75,20 @@ in
 
         Note that this configuration will only be successful when a display manager
         for which the <option>services.xserver.displayManager.setupCommands</option>
-        option is supported is used; notably, SLiM is not supported.
+        option is supported is used.
+      '';
+    };
+
+    hardware.nvidia.optimus_prime.allowExternalGpu = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Configure X to allow external NVIDIA GPUs when using optimus.
       '';
     };
 
     hardware.nvidia.optimus_prime.nvidiaBusId = lib.mkOption {
-      type = lib.types.string;
+      type = lib.types.str;
       default = "";
       example = "PCI:1:0:0";
       description = ''
@@ -90,7 +98,7 @@ in
     };
 
     hardware.nvidia.optimus_prime.intelBusId = lib.mkOption {
-      type = lib.types.string;
+      type = lib.types.str;
       default = "";
       example = "PCI:0:2:0";
       description = ''
@@ -103,9 +111,10 @@ in
   config = mkIf enabled {
     assertions = [
       {
-        assertion = with config.services.xserver.displayManager; gdm.enable -> !gdm.wayland;
-        message = "NVIDIA drivers don't support wayland, set services.xserver.displayManager.gdm.wayland=false";
+        assertion = with config.services.xserver.displayManager; gdm.nvidiaWayland -> cfg.modesetting.enable;
+        message = "You cannot use wayland with GDM without modesetting enabled for NVIDIA drivers, set `hardware.nvidia.modesetting.enable = true`";
       }
+
       {
         assertion = !optimusCfg.enable ||
           (optimusCfg.nvidiaBusId != "" && optimusCfg.intelBusId != "");
@@ -130,10 +139,10 @@ in
     services.xserver.drivers = singleton {
       name = "nvidia";
       modules = [ nvidia_x11.bin ];
-      libPath = [ nvidia_x11 ];
       deviceSection = optionalString optimusCfg.enable
         ''
           BusID "${optimusCfg.nvidiaBusId}"
+          ${optionalString optimusCfg.allowExternalGpu "Option \"AllowExternalGpus\""}
         '';
       screenSection =
         ''
@@ -189,10 +198,11 @@ in
     # Create /dev/nvidia-uvm when the nvidia-uvm module is loaded.
     services.udev.extraRules =
       ''
-        KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c $(grep nvidia-frontend /proc/devices | cut -d \  -f 1) 255'"
-        KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c $(grep nvidia-frontend /proc/devices | cut -d \  -f 1) 254'"
-        KERNEL=="card*", SUBSYSTEM=="drm", DRIVERS=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia%n c $(grep nvidia-frontend /proc/devices | cut -d \  -f 1) %n'"
-        KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 0'"
+        KERNEL=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidiactl c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) 255'"
+        KERNEL=="nvidia_modeset", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-modeset c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) 254'"
+        KERNEL=="card*", SUBSYSTEM=="drm", DRIVERS=="nvidia", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia%n c $$(grep nvidia-frontend /proc/devices | cut -d \  -f 1) %n'"
+        KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 0'"
+        KERNEL=="nvidia_uvm", RUN+="${pkgs.runtimeShell} -c 'mknod -m 666 /dev/nvidia-uvm-tools c $$(grep nvidia-uvm /proc/devices | cut -d \  -f 1) 0'"
       '';
 
     boot.blacklistedKernelModules = [ "nouveau" "nvidiafb" ];
diff --git a/nixos/modules/i18n/input-method/ibus.nix b/nixos/modules/i18n/input-method/ibus.nix
index f8e021f551e8..a3d97619fc45 100644
--- a/nixos/modules/i18n/input-method/ibus.nix
+++ b/nixos/modules/i18n/input-method/ibus.nix
@@ -27,6 +27,10 @@ let
   };
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "programs" "ibus" "plugins" ] [ "i18n" "inputMethod" "ibus" "engines" ])
+  ];
+
   options = {
     i18n.inputMethod.ibus = {
       engines = mkOption {
@@ -53,9 +57,17 @@ in
   config = mkIf (config.i18n.inputMethod.enabled == "ibus") {
     i18n.inputMethod.package = ibusPackage;
 
+    environment.systemPackages = [
+      ibusAutostart
+    ];
+
     # Without dconf enabled it is impossible to use IBus
-    environment.systemPackages = with pkgs; [
-      ibus-qt gnome3.dconf ibusAutostart
+    programs.dconf.enable = true;
+
+    programs.dconf.profiles.ibus = "${ibusPackage}/etc/dconf/profile/ibus";
+
+    services.dbus.packages = [
+      ibusAutostart
     ];
 
     environment.variables = {
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
index 917b3758d384..e0b558dcb0d8 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
@@ -1,27 +1,28 @@
 # This module contains the basic configuration for building a graphical NixOS
 # installation CD.
 
-{ config, lib, pkgs, ... }:
+{ lib, pkgs, ... }:
 
 with lib;
 
 {
   imports = [ ./installation-cd-base.nix ];
 
-  services.xserver = {
-    enable = true;
-
-    # Don't start the X server by default.
-    autorun = mkForce false;
-
-    # Automatically login as root.
-    displayManager.slim = {
-      enable = true;
-      defaultUser = "root";
-      autoLogin = true;
-    };
-
-  };
+  # Whitelist wheel users to do anything
+  # This is useful for things like pkexec
+  #
+  # WARNING: this is dangerous for systems
+  # outside the installation-cd and shouldn't
+  # be used anywhere else.
+  security.polkit.extraConfig = ''
+    polkit.addRule(function(action, subject) {
+      if (subject.isInGroup("wheel")) {
+        return polkit.Result.YES;
+      }
+    });
+  '';
+
+  services.xserver.enable = true;
 
   # Provide networkmanager for easy wireless configuration.
   networking.networkmanager.enable = true;
@@ -33,7 +34,6 @@ with lib;
 
   # Enable sound in graphical iso's.
   hardware.pulseaudio.enable = true;
-  hardware.pulseaudio.systemWide = true; # Needed since we run plasma as root.
 
   environment.systemPackages = [
     # Include gparted for partitioning disks.
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
index 42b5ec882272..23c3426bff08 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
@@ -1,7 +1,7 @@
 # This module defines a NixOS installation CD that contains X11 and
 # GNOME 3.
 
-{ config, lib, pkgs, ... }:
+{ lib, ... }:
 
 with lib;
 
@@ -10,8 +10,6 @@ with lib;
 
   services.xserver.desktopManager.gnome3.enable = true;
 
-  services.xserver.displayManager.slim.enable = mkForce false;
-
   # Auto-login as root.
   services.xserver.displayManager.gdm.autoLogin = {
     enable = true;
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
deleted file mode 100644
index 1c3c9cb30b41..000000000000
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
+++ /dev/null
@@ -1,44 +0,0 @@
-# This module defines a NixOS installation CD that contains X11 and
-# Plasma5.
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-{
-  imports = [ ./installation-cd-graphical-base.nix ];
-
-  services.xserver = {
-    desktopManager.plasma5 = {
-      enable = true;
-      enableQt4Support = false;
-    };
-
-    # Enable touchpad support for many laptops.
-    synaptics.enable = true;
-  };
-
-  environment.systemPackages = with pkgs; [
-    # Graphical text editor
-    kate
-  ];
-
-  system.activationScripts.installerDesktop = let
-
-    manualDesktopFile = pkgs.writeScript "nixos-manual.desktop" ''
-      [Desktop Entry]
-      Version=1.0
-      Type=Application
-      Name=NixOS Manual
-      Exec=firefox ${config.system.build.manual.manualHTMLIndex}
-      Icon=text-html
-    '';
-
-  in ''
-    mkdir -p /root/Desktop
-    ln -sfT ${manualDesktopFile} /root/Desktop/nixos-manual.desktop
-    ln -sfT ${pkgs.konsole}/share/applications/org.kde.konsole.desktop /root/Desktop/org.kde.konsole.desktop
-    ln -sfT ${pkgs.gparted}/share/applications/gparted.desktop /root/Desktop/gparted.desktop
-  '';
-
-}
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde-new-kernel.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5-new-kernel.nix
index 3336d512cfd8..d98325a99ac2 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde-new-kernel.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5-new-kernel.nix
@@ -1,7 +1,7 @@
 { pkgs, ... }:
 
 {
-  imports = [ ./installation-cd-graphical-kde.nix ];
+  imports = [ ./installation-cd-graphical-plasma5.nix ];
 
   boot.kernelPackages = pkgs.linuxPackages_latest;
 }
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix
new file mode 100644
index 000000000000..e00d3f7535b2
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-plasma5.nix
@@ -0,0 +1,48 @@
+# This module defines a NixOS installation CD that contains X11 and
+# Plasma 5.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  imports = [ ./installation-cd-graphical-base.nix ];
+
+  services.xserver = {
+    desktopManager.plasma5 = {
+      enable = true;
+    };
+
+    # Automatically login as nixos.
+    displayManager.sddm = {
+      enable = true;
+      autoLogin = {
+        enable = true;
+        user = "nixos";
+      };
+    };
+  };
+
+  environment.systemPackages = with pkgs; [
+    # Graphical text editor
+    kate
+  ];
+
+  system.activationScripts.installerDesktop = let
+
+    # Comes from documentation.nix when xserver and nixos.enable are true.
+    manualDesktopFile = "/run/current-system/sw/share/applications/nixos-manual.desktop";
+
+    homeDir = "/home/nixos/";
+    desktopDir = homeDir + "Desktop/";
+
+  in ''
+    mkdir -p ${desktopDir}
+    chown nixos ${homeDir} ${desktopDir}
+
+    ln -sfT ${manualDesktopFile} ${desktopDir + "nixos-manual.desktop"}
+    ln -sfT ${pkgs.gparted}/share/applications/gparted.desktop ${desktopDir + "gparted.desktop"}
+    ln -sfT ${pkgs.konsole}/share/applications/org.kde.konsole.desktop ${desktopDir + "org.kde.konsole.desktop"}
+  '';
+
+}
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index fd780be20825..11319e5f4f82 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -24,7 +24,7 @@ let
         # Name appended to menuentry defaults to params if no specific name given.
         option.name or (if option ? params then "(${option.params})" else "")
         }' ${if option ? class then " --class ${option.class}" else ""} {
-          linux ${defaults.image} ${defaults.params} ${
+          linux ${defaults.image} \''${isoboot} ${defaults.params} ${
             option.params or ""
           }
           initrd ${defaults.initrd}
@@ -165,8 +165,8 @@ let
     else
       "# No refind for ${targetArch}"
   ;
-  
-  grubPkgs = if config.boot.loader.grub.forcei686 then pkgs.pkgsi686Linux else pkgs; 
+
+  grubPkgs = if config.boot.loader.grub.forcei686 then pkgs.pkgsi686Linux else pkgs;
 
   grubMenuCfg = ''
     #
@@ -198,7 +198,7 @@ let
     fi
 
     ${ # When there is a theme configured, use it, otherwise use the background image.
-    if (!isNull config.isoImage.grubTheme) then ''
+    if config.isoImage.grubTheme != null then ''
       # Sets theme.
       set theme=(hd0)/EFI/boot/grub-theme/theme.txt
       # Load theme fonts
@@ -268,6 +268,12 @@ let
     set timeout=10
     ${grubMenuCfg}
 
+    # If the parameter iso_path is set, append the findiso parameter to the kernel
+    # line. We need this to allow the nixos iso to be booted from grub directly.
+    if [ \''${iso_path} ] ; then
+      set isoboot="findiso=\''${iso_path}"
+    fi
+
     #
     # Menu entries
     #
@@ -284,6 +290,14 @@ let
         ${buildMenuAdditionalParamsGrub2 config "video=1920x1080@60"}
       }
 
+      # If we boot into a graphical environment where X is autoran
+      # and always crashes, it makes the media unusable. Allow the user
+      # to disable this.
+      submenu "Disable display-manager" --class quirk-disable-displaymanager {
+        ${grubMenuCfg}
+        ${buildMenuAdditionalParamsGrub2 config "systemd.mask=display-manager.service"}
+      }
+
       # Some laptop and convertibles have the panel installed in an
       # inconvenient way, rotated away from the keyboard.
       # Those entries makes it easier to use the installer.
@@ -562,8 +576,6 @@ in
 
     boot.initrd.availableKernelModules = [ "squashfs" "iso9660" "uas" ];
 
-    boot.blacklistedKernelModules = [ "nouveau" ];
-
     boot.initrd.kernelModules = [ "loop" ];
 
     # Closures to be copied to the Nix store on the CD, namely the init
@@ -591,9 +603,6 @@ in
         { source = config.system.build.squashfsStore;
           target = "/nix-store.squashfs";
         }
-        { source = config.isoImage.efiSplashImage;
-          target = "/EFI/boot/efi-background.png";
-        }
         { source = config.isoImage.splashImage;
           target = "/isolinux/background.png";
         }
@@ -618,14 +627,21 @@ in
         { source = "${efiDir}/EFI";
           target = "/EFI";
         }
+        { source = (pkgs.writeTextDir "grub/loopback.cfg" "source /EFI/boot/grub.cfg") + "/grub";
+          target = "/boot/grub";
+        }
       ] ++ optionals (config.boot.loader.grub.memtest86.enable && canx86BiosBoot) [
         { source = "${pkgs.memtest86plus}/memtest.bin";
           target = "/boot/memtest.bin";
         }
-      ] ++ optionals (!isNull config.isoImage.grubTheme) [
+      ] ++ optionals (config.isoImage.grubTheme != null) [
         { source = config.isoImage.grubTheme;
           target = "/EFI/boot/grub-theme";
         }
+      ] ++ [
+        { source = config.isoImage.efiSplashImage;
+          target = "/EFI/boot/efi-background.png";
+        }
       ];
 
     boot.loader.timeout = 10;
diff --git a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
index 5f7194e92a36..2d34406a0320 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
@@ -26,8 +26,15 @@ in
   # Also increase the amount of CMA to ensure the virtual console on the RPi3 works.
   boot.kernelParams = ["cma=32M" "console=ttyS0,115200n8" "console=ttyAMA0,115200n8" "console=tty0"];
 
+  boot.initrd.availableKernelModules = [
+    # Allows early (earlier) modesetting for the Raspberry Pi
+    "vc4" "bcm2835_dma" "i2c_bcm2835"
+    # Allows early (earlier) modesetting for Allwinner SoCs
+    "sun4i_drm" "sun8i_drm_hdmi" "sun8i_mixer"
+  ];
+
   sdImage = {
-    populateBootCommands = let
+    populateFirmwareCommands = let
       configTxt = pkgs.writeText "config.txt" ''
         kernel=u-boot-rpi3.bin
 
@@ -43,10 +50,17 @@ in
         avoid_warnings=1
       '';
       in ''
-        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/boot/)
-        cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin boot/u-boot-rpi3.bin
-        cp ${configTxt} boot/config.txt
-        ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./boot
+        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
+        cp ${pkgs.ubootRaspberryPi3_64bit}/u-boot.bin firmware/u-boot-rpi3.bin
+        cp ${configTxt} firmware/config.txt
       '';
+    populateRootCommands = ''
+      mkdir -p ./files/boot
+      ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./files/boot
+    '';
   };
+
+  # the installation media is also the installation target,
+  # so we don't want to provide the installation configuration.nix.
+  installer.cloneConfig = false;
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
index 71448f74c361..651d1a36dc11 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
@@ -29,7 +29,7 @@ in
   boot.kernelParams = ["console=ttyS0,115200n8" "console=ttymxc0,115200n8" "console=ttyAMA0,115200n8" "console=ttyO0,115200n8" "console=ttySAC2,115200n8" "console=tty0"];
 
   sdImage = {
-    populateBootCommands = let
+    populateFirmwareCommands = let
       configTxt = pkgs.writeText "config.txt" ''
         # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
         # when attempting to show low-voltage or overtemperature warnings.
@@ -46,11 +46,18 @@ in
         enable_uart=1
       '';
       in ''
-        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/boot/)
-        cp ${pkgs.ubootRaspberryPi2}/u-boot.bin boot/u-boot-rpi2.bin
-        cp ${pkgs.ubootRaspberryPi3_32bit}/u-boot.bin boot/u-boot-rpi3.bin
-        cp ${configTxt} boot/config.txt
-        ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./boot
+        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
+        cp ${pkgs.ubootRaspberryPi2}/u-boot.bin firmware/u-boot-rpi2.bin
+        cp ${pkgs.ubootRaspberryPi3_32bit}/u-boot.bin firmware/u-boot-rpi3.bin
+        cp ${configTxt} firmware/config.txt
       '';
+    populateRootCommands = ''
+      mkdir -p ./files/boot
+      ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./files/boot
+    '';
   };
+
+  # the installation media is also the installation target,
+  # so we don't want to provide the installation configuration.nix.
+  installer.cloneConfig = false;
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
index 96e06670694e..ba4127eaa0e8 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
@@ -19,10 +19,10 @@ in
   boot.loader.generic-extlinux-compatible.enable = true;
 
   boot.consoleLogLevel = lib.mkDefault 7;
-  boot.kernelPackages = pkgs.linuxPackages_rpi;
+  boot.kernelPackages = pkgs.linuxPackages_rpi1;
 
   sdImage = {
-    populateBootCommands = let
+    populateFirmwareCommands = let
       configTxt = pkgs.writeText "config.txt" ''
         # Prevent the firmware from smashing the framebuffer setup done by the mainline kernel
         # when attempting to show low-voltage or overtemperature warnings.
@@ -35,11 +35,18 @@ in
         kernel=u-boot-rpi1.bin
       '';
       in ''
-        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/boot/)
-        cp ${pkgs.ubootRaspberryPiZero}/u-boot.bin boot/u-boot-rpi0.bin
-        cp ${pkgs.ubootRaspberryPi}/u-boot.bin boot/u-boot-rpi1.bin
-        cp ${configTxt} boot/config.txt
-        ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./boot
+        (cd ${pkgs.raspberrypifw}/share/raspberrypi/boot && cp bootcode.bin fixup*.dat start*.elf $NIX_BUILD_TOP/firmware/)
+        cp ${pkgs.ubootRaspberryPiZero}/u-boot.bin firmware/u-boot-rpi0.bin
+        cp ${pkgs.ubootRaspberryPi}/u-boot.bin firmware/u-boot-rpi1.bin
+        cp ${configTxt} firmware/config.txt
       '';
+    populateRootCommands = ''
+      mkdir -p ./files/boot
+      ${extlinux-conf-builder} -t 3 -c ${config.system.build.toplevel} -d ./files/boot
+    '';
   };
+
+  # the installation media is also the installation target,
+  # so we don't want to provide the installation configuration.nix.
+  installer.cloneConfig = false;
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix
new file mode 100644
index 000000000000..c545a1e7e242
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix
@@ -0,0 +1,31 @@
+# To build, use:
+# nix-build nixos -I nixos-config=nixos/modules/installer/cd-dvd/sd-image-raspberrypi4.nix -A config.system.build.sdImage
+{ config, lib, pkgs, ... }:
+
+{
+  imports = [
+    ../../profiles/base.nix
+    ../../profiles/installation-device.nix
+    ./sd-image.nix
+  ];
+
+  boot.loader.grub.enable = false;
+  boot.loader.raspberryPi.enable = true;
+  boot.loader.raspberryPi.version = 4;
+  boot.kernelPackages = pkgs.linuxPackages_rpi4;
+
+  boot.consoleLogLevel = lib.mkDefault 7;
+
+  sdImage = {
+    firmwareSize = 128;
+    # This is a hack to avoid replicating config.txt from boot.loader.raspberryPi
+    populateFirmwareCommands =
+      "${config.system.build.installBootLoader} ${config.system.build.toplevel} -d ./firmware";
+    # As the boot process is done entirely in the firmware partition.
+    populateRootCommands = "";
+  };
+
+  # the installation media is also the installation target,
+  # so we don't want to provide the installation configuration.nix.
+  installer.cloneConfig = false;
+}
diff --git a/nixos/modules/installer/cd-dvd/sd-image.nix b/nixos/modules/installer/cd-dvd/sd-image.nix
index 69746a8e9799..901c60befb6c 100644
--- a/nixos/modules/installer/cd-dvd/sd-image.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image.nix
@@ -1,8 +1,12 @@
 # This module creates a bootable SD card image containing the given NixOS
-# configuration. The generated image is MBR partitioned, with a FAT /boot
-# partition, and ext4 root partition. The generated image is sized to fit
-# its contents, and a boot script automatically resizes the root partition
-# to fit the device on the first boot.
+# configuration. The generated image is MBR partitioned, with a FAT
+# /boot/firmware partition, and ext4 root partition. The generated image
+# is sized to fit its contents, and a boot script automatically resizes
+# the root partition to fit the device on the first boot.
+#
+# The firmware partition is built with expectation to hold the Raspberry
+# Pi firmware and bootloader, and be removed and replaced with a firmware
+# build for the target SoC for other board families.
 #
 # The derivation for the SD image will be placed in
 # config.system.build.sdImage
@@ -14,12 +18,19 @@ with lib;
 let
   rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({
     inherit (config.sdImage) storePaths;
+    compressImage = true;
+    populateImageCommands = config.sdImage.populateRootCommands;
     volumeLabel = "NIXOS_SD";
   } // optionalAttrs (config.sdImage.rootPartitionUUID != null) {
     uuid = config.sdImage.rootPartitionUUID;
   });
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "sdImage" "bootPartitionID" ] "The FAT partition for SD image now only holds the Raspberry Pi firmware files. Use firmwarePartitionID to configure that partition's ID.")
+    (mkRemovedOptionModule [ "sdImage" "bootSize" ] "The boot files for SD image have been moved to the main ext4 partition. The FAT partition now only holds the Raspberry Pi firmware files. Changing its size may not be required.")
+  ];
+
   options.sdImage = {
     imageName = mkOption {
       default = "${config.sdImage.imageBaseName}-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.img";
@@ -43,17 +54,17 @@ in
       '';
     };
 
-    bootPartitionID = mkOption {
-      type = types.string;
+    firmwarePartitionID = mkOption {
+      type = types.str;
       default = "0x2178694e";
       description = ''
-        Volume ID for the /boot partition on the SD card. This value must be a
-        32-bit hexadecimal number.
+        Volume ID for the /boot/firmware partition on the SD card. This value
+        must be a 32-bit hexadecimal number.
       '';
     };
 
     rootPartitionUUID = mkOption {
-      type = types.nullOr types.string;
+      type = types.nullOr types.str;
       default = null;
       example = "14e19a7b-0ae0-484d-9d54-43bd6fdc20c7";
       description = ''
@@ -61,29 +72,54 @@ in
       '';
     };
 
-    bootSize = mkOption {
+    firmwareSize = mkOption {
       type = types.int;
-      default = 120;
+      # As of 2019-08-18 the Raspberry pi firmware + u-boot takes ~18MiB
+      default = 30;
       description = ''
-        Size of the /boot partition, in megabytes.
+        Size of the /boot/firmware partition, in megabytes.
+      '';
+    };
+
+    populateFirmwareCommands = mkOption {
+      example = literalExample "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''";
+      description = ''
+        Shell commands to populate the ./firmware directory.
+        All files in that directory are copied to the
+        /boot/firmware partition on the SD image.
       '';
     };
 
-    populateBootCommands = mkOption {
-      example = literalExample "'' cp \${pkgs.myBootLoader}/u-boot.bin boot/ ''";
+    populateRootCommands = mkOption {
+      example = literalExample "''\${extlinux-conf-builder} -t 3 -c \${config.system.build.toplevel} -d ./files/boot''";
       description = ''
-        Shell commands to populate the ./boot directory.
+        Shell commands to populate the ./files directory.
         All files in that directory are copied to the
-        /boot partition on the SD image.
+        root (/) partition on the SD image. Use this to
+        populate the ./files/boot (/boot) directory.
       '';
     };
+
+    compressImage = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether the SD image should be compressed using
+        <command>bzip2</command>.
+      '';
+    };
+
   };
 
   config = {
     fileSystems = {
-      "/boot" = {
-        device = "/dev/disk/by-label/NIXOS_BOOT";
+      "/boot/firmware" = {
+        device = "/dev/disk/by-label/FIRMWARE";
         fsType = "vfat";
+        # Alternatively, this could be removed from the configuration.
+        # The filesystem is not needed at runtime, it could be treated
+        # as an opaque blob instead of a discrete FAT32 filesystem.
+        options = [ "nofail" "noauto" ];
       };
       "/" = {
         device = "/dev/disk/by-label/NIXOS_SD";
@@ -93,59 +129,79 @@ in
 
     sdImage.storePaths = [ config.system.build.toplevel ];
 
-    system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs, mtools, libfaketime, utillinux }: stdenv.mkDerivation {
+    system.build.sdImage = pkgs.callPackage ({ stdenv, dosfstools, e2fsprogs,
+    mtools, libfaketime, utillinux, bzip2, zstd }: stdenv.mkDerivation {
       name = config.sdImage.imageName;
 
-      nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime utillinux ];
+      nativeBuildInputs = [ dosfstools e2fsprogs mtools libfaketime utillinux bzip2 zstd ];
+
+      inherit (config.sdImage) compressImage;
 
       buildCommand = ''
         mkdir -p $out/nix-support $out/sd-image
         export img=$out/sd-image/${config.sdImage.imageName}
 
         echo "${pkgs.stdenv.buildPlatform.system}" > $out/nix-support/system
-        echo "file sd-image $img" >> $out/nix-support/hydra-build-products
-
-        # Create the image file sized to fit /boot and /, plus 20M of slack
-        rootSizeBlocks=$(du -B 512 --apparent-size ${rootfsImage} | awk '{ print $1 }')
-        bootSizeBlocks=$((${toString config.sdImage.bootSize} * 1024 * 1024 / 512))
-        imageSize=$((rootSizeBlocks * 512 + bootSizeBlocks * 512 + 20 * 1024 * 1024))
+        if test -n "$compressImage"; then
+          echo "file sd-image $img.bz2" >> $out/nix-support/hydra-build-products
+        else
+          echo "file sd-image $img" >> $out/nix-support/hydra-build-products
+        fi
+
+        echo "Decompressing rootfs image"
+        zstd -d --no-progress "${rootfsImage}" -o ./root-fs.img
+
+        # Gap in front of the first partition, in MiB
+        gap=8
+
+        # Create the image file sized to fit /boot/firmware and /, plus slack for the gap.
+        rootSizeBlocks=$(du -B 512 --apparent-size ./root-fs.img | awk '{ print $1 }')
+        firmwareSizeBlocks=$((${toString config.sdImage.firmwareSize} * 1024 * 1024 / 512))
+        imageSize=$((rootSizeBlocks * 512 + firmwareSizeBlocks * 512 + gap * 1024 * 1024))
         truncate -s $imageSize $img
 
         # type=b is 'W95 FAT32', type=83 is 'Linux'.
+        # The "bootable" partition is where u-boot will look file for the bootloader
+        # information (dtbs, extlinux.conf file).
         sfdisk $img <<EOF
             label: dos
-            label-id: ${config.sdImage.bootPartitionID}
+            label-id: ${config.sdImage.firmwarePartitionID}
 
-            start=8M, size=$bootSizeBlocks, type=b, bootable
-            start=${toString (8 + config.sdImage.bootSize)}M, type=83
+            start=''${gap}M, size=$firmwareSizeBlocks, type=b
+            start=$((gap + ${toString config.sdImage.firmwareSize}))M, type=83, bootable
         EOF
 
         # Copy the rootfs into the SD image
         eval $(partx $img -o START,SECTORS --nr 2 --pairs)
-        dd conv=notrunc if=${rootfsImage} of=$img seek=$START count=$SECTORS
+        dd conv=notrunc if=./root-fs.img of=$img seek=$START count=$SECTORS
 
-        # Create a FAT32 /boot partition of suitable size into bootpart.img
+        # Create a FAT32 /boot/firmware partition of suitable size into firmware_part.img
         eval $(partx $img -o START,SECTORS --nr 1 --pairs)
-        truncate -s $((SECTORS * 512)) bootpart.img
-        faketime "1970-01-01 00:00:00" mkfs.vfat -i ${config.sdImage.bootPartitionID} -n NIXOS_BOOT bootpart.img
+        truncate -s $((SECTORS * 512)) firmware_part.img
+        faketime "1970-01-01 00:00:00" mkfs.vfat -i ${config.sdImage.firmwarePartitionID} -n FIRMWARE firmware_part.img
 
-        # Populate the files intended for /boot
-        mkdir boot
-        ${config.sdImage.populateBootCommands}
+        # Populate the files intended for /boot/firmware
+        mkdir firmware
+        ${config.sdImage.populateFirmwareCommands}
 
-        # Copy the populated /boot into the SD image
-        (cd boot; mcopy -psvm -i ../bootpart.img ./* ::)
+        # Copy the populated /boot/firmware into the SD image
+        (cd firmware; mcopy -psvm -i ../firmware_part.img ./* ::)
         # Verify the FAT partition before copying it.
-        fsck.vfat -vn bootpart.img
-        dd conv=notrunc if=bootpart.img of=$img seek=$START count=$SECTORS
+        fsck.vfat -vn firmware_part.img
+        dd conv=notrunc if=firmware_part.img of=$img seek=$START count=$SECTORS
+        if test -n "$compressImage"; then
+            bzip2 $img
+        fi
       '';
     }) {};
 
     boot.postBootCommands = ''
       # On the first boot do some maintenance tasks
       if [ -f /nix-path-registration ]; then
+        set -euo pipefail
+        set -x
         # Figure out device names for the boot device and root filesystem.
-        rootPart=$(readlink -f /dev/disk/by-label/NIXOS_SD)
+        rootPart=$(${pkgs.utillinux}/bin/findmnt -n -o SOURCE /)
         bootDevice=$(lsblk -npo PKNAME $rootPart)
 
         # Resize the root partition and the filesystem to fit the disk
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix b/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
index 5da5df81ede1..f2af7dcde3d5 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-pc.nix
@@ -122,14 +122,13 @@ in
 
   /* fake entry, just to have a happy stage-1. Users
      may boot without having stage-1 though */
-  fileSystems = [
+  fileSystems.fake =
     { mountPoint = "/";
       device = "/dev/something";
-      }
-  ];
+    };
 
   nixpkgs.config = {
-    packageOverrides = p: rec {
+    packageOverrides = p: {
       linux_3_4 = p.linux_3_4.override {
         extraConfig = ''
           # Enable drivers in kernel for most NICs.
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
index 90a5128c02a5..8408f56f94f9 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
@@ -117,11 +117,10 @@ in
 
   /* fake entry, just to have a happy stage-1. Users
      may boot without having stage-1 though */
-  fileSystems = [
+  fileSystems.fake =
     { mountPoint = "/";
       device = "/dev/something";
-      }
-  ];
+    };
 
   services.mingetty = {
     # Some more help text.
diff --git a/nixos/modules/installer/cd-dvd/system-tarball.nix b/nixos/modules/installer/cd-dvd/system-tarball.nix
index b84096861f56..58098c45535d 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball.nix
@@ -41,7 +41,7 @@ in
 
     # In stage 1 of the boot, mount the CD/DVD as the root FS by label
     # so that we don't need to know its device.
-    fileSystems = [ ];
+    fileSystems = { };
 
     # boot.initrd.availableKernelModules = [ "mvsdio" "reiserfs" "ext3" "ext4" ];
 
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index 303d9fce3f9a..5146858cccf5 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -18,7 +18,7 @@ with lib;
 
   };
 
-  config = rec {
+  config = {
     # Don't build the GRUB menu builder script, since we don't need it
     # here and it causes a cyclic dependency.
     boot.loader.grub.enable = false;
@@ -65,8 +65,7 @@ with lib;
       [ config.system.build.toplevel ];
 
     # Create the squashfs image that contains the Nix store.
-    system.build.squashfsStore = import ../../../lib/make-squashfs.nix {
-      inherit (pkgs) stdenv squashfsTools closureInfo;
+    system.build.squashfsStore = pkgs.callPackage ../../../lib/make-squashfs.nix {
       storeContents = config.netboot.storeContents;
     };
 
@@ -85,7 +84,7 @@ with lib;
 
     system.build.netbootIpxeScript = pkgs.writeTextDir "netboot.ipxe" ''
       #!ipxe
-      kernel ${pkgs.stdenv.hostPlatform.platform.kernelTarget} init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams}
+      kernel ${pkgs.stdenv.hostPlatform.platform.kernelTarget} init=${config.system.build.toplevel}/init initrd=initrd ${toString config.boot.kernelParams}
       initrd initrd
       boot
     '';
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index b9ab2053c41f..c2f2578733ba 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,6 +1,6 @@
 {
-  x86_64-linux = "/nix/store/hbhdjn5ik3byg642d1m11k3k3s0kn3py-nix-2.2.2";
-  i686-linux = "/nix/store/fz5cikwvj3n0a6zl44h6l2z3cin64mda-nix-2.2.2";
-  aarch64-linux = "/nix/store/2gba4cyl4wvxzfbhmli90jy4n5aj0kjj-nix-2.2.2";
-  x86_64-darwin = "/nix/store/87i4fp46jfw9yl8c7i9gx75m5yph7irl-nix-2.2.2";
+  x86_64-linux = "/nix/store/0q5qnh10m2sfrriszc1ysmggw659q6qm-nix-2.3.2";
+  i686-linux = "/nix/store/i7ad7r5d8a5b3l22hg4a1im2qq05y6vd-nix-2.3.2";
+  aarch64-linux = "/nix/store/bv06pavfw0dbqzr8w3l7s71nx27gnxa0-nix-2.3.2";
+  x86_64-darwin = "/nix/store/x6mnl1nij7y4v5ihlplr4k937ayr403r-nix-2.3.2";
 }
diff --git a/nixos/modules/installer/tools/nixos-enter.sh b/nixos/modules/installer/tools/nixos-enter.sh
index 518dbbbf21e3..4680cd8ae95a 100644
--- a/nixos/modules/installer/tools/nixos-enter.sh
+++ b/nixos/modules/installer/tools/nixos-enter.sh
@@ -16,7 +16,8 @@ fi
 
 mountPoint=/mnt
 system=/nix/var/nix/profiles/system
-command=($system/sw/bin/bash "--login")
+command=("$system/sw/bin/bash" "--login")
+silent=0
 
 while [ "$#" -gt 0 ]; do
     i="$1"; shift 1
@@ -32,9 +33,12 @@ while [ "$#" -gt 0 ]; do
             exit 1
             ;;
         --command|-c)
-            command=($system/sw/bin/bash "-c" "$1")
+            command=("$system/sw/bin/bash" "-c" "$1")
             shift 1
             ;;
+        --silent)
+            silent=1
+            ;;
         --)
             command=("$@")
             break
@@ -51,11 +55,20 @@ if [[ ! -e $mountPoint/etc/NIXOS ]]; then
     exit 126
 fi
 
-mkdir -m 0755 -p "$mountPoint/dev" "$mountPoint/sys"
+mkdir -p "$mountPoint/dev" "$mountPoint/sys"
+chmod 0755 "$mountPoint/dev" "$mountPoint/sys"
 mount --rbind /dev "$mountPoint/dev"
 mount --rbind /sys "$mountPoint/sys"
 
+# If silent, write both stdout and stderr of activation script to /dev/null
+# otherwise, write both streams to stderr of this process
+if [ "$silent" -eq 0 ]; then
+    PIPE_TARGET="/dev/stderr"
+else
+    PIPE_TARGET="/dev/null"
+fi
+
 # Run the activation script. Set $LOCALE_ARCHIVE to supress some Perl locale warnings.
-LOCALE_ARCHIVE=$system/sw/lib/locale/locale-archive chroot "$mountPoint" "$system/activate" >&2 || true
+LOCALE_ARCHIVE="$system/sw/lib/locale/locale-archive" chroot "$mountPoint" "$system/activate" >>$PIPE_TARGET 2>&1 || true
 
 exec chroot "$mountPoint" "${command[@]}"
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 686204ee0342..629c56814a16 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -258,6 +258,16 @@ foreach my $path (glob "/sys/class/{block,mmc_host}/*") {
     }
 }
 
+# Add bcache module, if needed.
+my @bcacheDevices = glob("/dev/bcache*");
+if (scalar @bcacheDevices > 0) {
+    push @initrdAvailableKernelModules, "bcache";
+}
+
+# Prevent unbootable systems if LVM snapshots are present at boot time.
+if (`lsblk -o TYPE` =~ "lvm") {
+    push @initrdKernelModules, "dm-snapshot";
+}
 
 my $virt = `systemd-detect-virt`;
 chomp $virt;
@@ -319,10 +329,22 @@ my @swapDevices;
 if (@swaps) {
     shift @swaps;
     foreach my $swap (@swaps) {
-        $swap =~ /^(\S+)\s/;
-        next unless -e $1;
-        my $dev = findStableDevPath $1;
-        push @swapDevices, "{ device = \"$dev\"; }";
+        my @fields = split ' ', $swap;
+        my $swapFilename = $fields[0];
+        my $swapType = $fields[1];
+        next unless -e $swapFilename;
+        my $dev = findStableDevPath $swapFilename;
+        if ($swapType =~ "partition") {
+            # zram devices are more likely created by configuration.nix, so
+            # ignore them here
+            next if ($swapFilename =~ /^\/dev\/zram/);
+            push @swapDevices, "{ device = \"$dev\"; }";
+        } elsif ($swapType =~ "file") {
+            # swap *files* are more likely specified in configuration.nix, so
+            # ignore them here.
+        } else {
+            die "Unsupported swap type: $swapType\n";
+        }
     }
 }
 
@@ -422,6 +444,10 @@ EOF
         }
     }
 
+    # Don't emit tmpfs entry for /tmp, because it most likely comes from the
+    # boot.tmpOnTmpfs option in configuration.nix (managed declaratively).
+    next if ($mountPoint eq "/tmp" && $fsType eq "tmpfs");
+
     # Emit the filesystem.
     $fileSystems .= <<EOF;
   fileSystems.\"$mountPoint\" =
@@ -464,6 +490,21 @@ EOF
     }
 }
 
+# For lack of a better way to determine it, guess whether we should use a
+# bigger font for the console from the display mode on the first
+# framebuffer. A way based on the physical size/actual DPI reported by
+# the monitor would be nice, but I don't know how to do this without X :)
+my $fb_modes_file = "/sys/class/graphics/fb0/modes";
+if (-f $fb_modes_file && -r $fb_modes_file) {
+    my $modes = read_file($fb_modes_file);
+    $modes =~ m/([0-9]+)x([0-9]+)/;
+    my $console_width = $1, my $console_height = $2;
+    if ($console_width > 1920) {
+        push @attrs, "# High-DPI console";
+        push @attrs, 'console.font = lib.mkDefault "${pkgs.terminus_font}/share/consolefonts/ter-u28n.psf.gz";';
+    }
+}
+
 
 # Generate the hardware configuration file.
 
@@ -497,6 +538,7 @@ sub multiLineList {
 }
 
 my $initrdAvailableKernelModules = toNixStringList(uniq @initrdAvailableKernelModules);
+my $initrdKernelModules = toNixStringList(uniq @initrdKernelModules);
 my $kernelModules = toNixStringList(uniq @kernelModules);
 my $modulePackages = toNixList(uniq @modulePackages);
 
@@ -516,6 +558,7 @@ my $hwConfig = <<EOF;
   imports =${\multiLineList("    ", @imports)};
 
   boot.initrd.availableKernelModules = [$initrdAvailableKernelModules ];
+  boot.initrd.kernelModules = [$initrdKernelModules ];
   boot.kernelModules = [$kernelModules ];
   boot.extraModulePackages = [$modulePackages ];
 $fsAndSwap
@@ -523,6 +566,24 @@ $fsAndSwap
 ${\join "", (map { "  $_\n" } (uniq @attrs))}}
 EOF
 
+sub generateNetworkingDhcpConfig {
+    my $config = <<EOF;
+  # The global useDHCP flag is deprecated, therefore explicitly set to false here.
+  # Per-interface useDHCP will be mandatory in the future, so this generated config
+  # replicates the default behaviour.
+  networking.useDHCP = false;
+EOF
+
+    foreach my $path (glob "/sys/class/net/*") {
+        my $dev = basename($path);
+        if ($dev ne "lo") {
+            $config .= "  networking.interfaces.$dev.useDHCP = true;\n";
+        }
+    }
+
+    return $config;
+}
+
 
 if ($showHardwareConfig) {
     print STDOUT $hwConfig;
@@ -566,91 +627,10 @@ EOF
 EOF
         }
 
-        write_file($fn, <<EOF);
-# Edit this configuration file to define what should be installed on
-# your system.  Help is available in the configuration.nix(5) man page
-# and in the NixOS manual (accessible by running ‘nixos-help’).
-
-{ config, pkgs, ... }:
-
-{
-  imports =
-    [ # Include the results of the hardware scan.
-      ./hardware-configuration.nix
-    ];
-
-$bootLoaderConfig
-  # networking.hostName = "nixos"; # Define your hostname.
-  # networking.wireless.enable = true;  # Enables wireless support via wpa_supplicant.
-
-  # Configure network proxy if necessary
-  # networking.proxy.default = "http://user:password\@proxy:port/";
-  # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
-
-  # Select internationalisation properties.
-  # i18n = {
-  #   consoleFont = "Lat2-Terminus16";
-  #   consoleKeyMap = "us";
-  #   defaultLocale = "en_US.UTF-8";
-  # };
-
-  # Set your time zone.
-  # time.timeZone = "Europe/Amsterdam";
-
-  # List packages installed in system profile. To search, run:
-  # \$ nix search wget
-  # environment.systemPackages = with pkgs; [
-  #   wget vim
-  # ];
-
-  # Some programs need SUID wrappers, can be configured further or are
-  # started in user sessions.
-  # programs.mtr.enable = true;
-  # programs.gnupg.agent = { enable = true; enableSSHSupport = true; };
-
-  # List services that you want to enable:
-
-  # Enable the OpenSSH daemon.
-  # services.openssh.enable = true;
-
-  # Open ports in the firewall.
-  # networking.firewall.allowedTCPPorts = [ ... ];
-  # networking.firewall.allowedUDPPorts = [ ... ];
-  # Or disable the firewall altogether.
-  # networking.firewall.enable = false;
-
-  # Enable CUPS to print documents.
-  # services.printing.enable = true;
-
-  # Enable sound.
-  # sound.enable = true;
-  # hardware.pulseaudio.enable = true;
-
-  # Enable the X11 windowing system.
-  # services.xserver.enable = true;
-  # services.xserver.layout = "us";
-  # services.xserver.xkbOptions = "eurosign:e";
-
-  # Enable touchpad support.
-  # services.xserver.libinput.enable = true;
-
-  # Enable the KDE Desktop Environment.
-  # services.xserver.displayManager.sddm.enable = true;
-  # services.xserver.desktopManager.plasma5.enable = true;
-
-  # Define a user account. Don't forget to set a password with ‘passwd’.
-  # users.users.jane = {
-  #   isNormalUser = true;
-  #   extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
-  # };
-
-  # This value determines the NixOS release with which your system is to be
-  # compatible, in order to avoid breaking some software such as database
-  # servers. You should change this only after NixOS release notes say you
-  # should.
-  system.stateVersion = "${\(qw(@release@))}"; # Did you read the comment?
+        my $networkingDhcpConfig = generateNetworkingDhcpConfig();
 
-}
+        write_file($fn, <<EOF);
+@configuration@
 EOF
     } else {
         print STDERR "warning: not overwriting existing $fn\n";
diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh
index 8685cb345e1e..a3ff3fe2c0c1 100644
--- a/nixos/modules/installer/tools/nixos-install.sh
+++ b/nixos/modules/installer/tools/nixos-install.sh
@@ -14,6 +14,8 @@ extraBuildFlags=()
 mountPoint=/mnt
 channelPath=
 system=
+verbosity=()
+buildLogs=
 
 while [ "$#" -gt 0 ]; do
     i="$1"; shift 1
@@ -55,6 +57,12 @@ while [ "$#" -gt 0 ]; do
         --debug)
             set -x
             ;;
+        -v*|--verbose)
+            verbosity+=("$i")
+            ;;
+        -L|--print-build-logs)
+            buildLogs="$i"
+            ;;
         *)
             echo "$0: unknown option \`$i'"
             exit 1
@@ -94,7 +102,7 @@ if [[ -z $system ]]; then
     outLink="$tmpdir/system"
     nix build --out-link "$outLink" --store "$mountPoint" "${extraBuildFlags[@]}" \
         --extra-substituters "$sub" \
-        -f '<nixpkgs/nixos>' system -I "nixos-config=$NIXOS_CONFIG"
+        -f '<nixpkgs/nixos>' system -I "nixos-config=$NIXOS_CONFIG" ${verbosity[@]} ${buildLogs}
     system=$(readlink -f $outLink)
 fi
 
@@ -103,7 +111,7 @@ fi
 # a progress bar.
 nix-env --store "$mountPoint" "${extraBuildFlags[@]}" \
         --extra-substituters "$sub" \
-        -p $mountPoint/nix/var/nix/profiles/system --set "$system"
+        -p $mountPoint/nix/var/nix/profiles/system --set "$system" ${verbosity[@]}
 
 # Copy the NixOS/Nixpkgs sources to the target as the initial contents
 # of the NixOS channel.
@@ -115,7 +123,8 @@ if [[ -z $noChannelCopy ]]; then
         echo "copying channel..."
         mkdir -p $mountPoint/nix/var/nix/profiles/per-user/root
         nix-env --store "$mountPoint" "${extraBuildFlags[@]}" --extra-substituters "$sub" \
-                -p $mountPoint/nix/var/nix/profiles/per-user/root/channels --set "$channelPath" --quiet
+                -p $mountPoint/nix/var/nix/profiles/per-user/root/channels --set "$channelPath" --quiet \
+                ${verbosity[@]}
         install -m 0700 -d $mountPoint/root/.nix-defexpr
         ln -sfn /nix/var/nix/profiles/per-user/root/channels $mountPoint/root/.nix-defexpr/channels
     fi
diff --git a/nixos/modules/installer/tools/nixos-option.sh b/nixos/modules/installer/tools/nixos-option.sh
deleted file mode 100644
index 4560e9c7403a..000000000000
--- a/nixos/modules/installer/tools/nixos-option.sh
+++ /dev/null
@@ -1,327 +0,0 @@
-#! @shell@ -e
-
-# FIXME: rewrite this in a more suitable language.
-
-usage () {
-    exec man nixos-option
-    exit 1
-}
-
-#####################
-# Process Arguments #
-#####################
-
-xml=false
-verbose=false
-nixPath=""
-
-option=""
-exit_code=0
-
-argfun=""
-for arg; do
-  if test -z "$argfun"; then
-    case $arg in
-      -*)
-        sarg="$arg"
-        longarg=""
-        while test "$sarg" != "-"; do
-          case $sarg in
-            --*) longarg=$arg; sarg="--";;
-            -I) argfun="include_nixpath";;
-            -*) usage;;
-          esac
-          # remove the first letter option
-          sarg="-${sarg#??}"
-        done
-        ;;
-      *) longarg=$arg;;
-    esac
-    for larg in $longarg; do
-      case $larg in
-        --xml) xml=true;;
-        --verbose) verbose=true;;
-        --help) usage;;
-        -*) usage;;
-        *) if test -z "$option"; then
-             option="$larg"
-           else
-             usage
-           fi;;
-      esac
-    done
-  else
-    case $argfun in
-      set_*)
-        var=$(echo $argfun | sed 's,^set_,,')
-        eval $var=$arg
-        ;;
-      include_nixpath)
-        nixPath="-I $arg $nixPath"
-        ;;
-    esac
-    argfun=""
-  fi
-done
-
-if $verbose; then
-  set -x
-else
-  set +x
-fi
-
-#############################
-# Process the configuration #
-#############################
-
-evalNix(){
-  # disable `-e` flag, it's possible that the evaluation of `nix-instantiate` fails (e.g. due to broken pkgs)
-  set +e
-  result=$(nix-instantiate ${nixPath:+$nixPath} - --eval-only "$@" 2>&1)
-  exit_code=$?
-  set -e
-
-  if test $exit_code -eq 0; then
-      sed '/^warning: Nix search path/d' <<EOF
-$result
-EOF
-      return 0;
-  else
-      sed -n '
-  /^error/ { s/, at (string):[0-9]*:[0-9]*//; p; };
-  /^warning: Nix search path/ { p; };
-' >&2 <<EOF
-$result
-EOF
-    exit_code=1
-  fi
-}
-
-header="let
-  nixos = import <nixpkgs/nixos> {};
-  nixpkgs = import <nixpkgs> {};
-in with nixpkgs.lib;
-"
-
-# This function is used for converting the option definition path given by
-# the user into accessors for reaching the definition and the declaration
-# corresponding to this option.
-generateAccessors(){
-  if result=$(evalNix --strict --show-trace <<EOF
-$header
-
-let
-  path = "${option:+$option}";
-  pathList = splitString "." path;
-
-  walkOptions = attrsNames: result:
-    if attrsNames == [] then
-      result
-    else
-      let name = head attrsNames; rest = tail attrsNames; in
-      if isOption result.options then
-        walkOptions rest {
-          options = result.options.type.getSubOptions "";
-          opt = ''(\${result.opt}.type.getSubOptions "")'';
-          cfg = ''\${result.cfg}."\${name}"'';
-        }
-      else
-        walkOptions rest {
-          options = result.options.\${name};
-          opt = ''\${result.opt}."\${name}"'';
-          cfg = ''\${result.cfg}."\${name}"'';
-        }
-    ;
-
-  walkResult = (if path == "" then x: x else walkOptions pathList) {
-    options = nixos.options;
-    opt = ''nixos.options'';
-    cfg = ''nixos.config'';
-  };
-
-in
-  ''let option = \${walkResult.opt}; config = \${walkResult.cfg}; in''
-EOF
-)
-  then
-      echo $result
-  else
-      # In case of error we want to ignore the error message roduced by the
-      # script above, as it is iterating over each attribute, which does not
-      # produce a nice error message.  The following code is a fallback
-      # solution which is cause a nicer error message in the next
-      # evaluation.
-      echo "\"let option = nixos.options${option:+.$option}; config = nixos.config${option:+.$option}; in\""
-  fi
-}
-
-header="$header
-$(eval echo $(generateAccessors))
-"
-
-evalAttr(){
-  local prefix="$1"
-  local strict="$2"
-  local suffix="$3"
-
-  # If strict is set, then set it to "true".
-  test -n "$strict" && strict=true
-
-  evalNix ${strict:+--strict} <<EOF
-$header
-
-let
-  value = $prefix${suffix:+.$suffix};
-  strict = ${strict:-false};
-  cleanOutput = x: with nixpkgs.lib;
-    if isDerivation x then x.outPath
-    else if isFunction x then "<CODE>"
-    else if strict then
-      if isAttrs x then mapAttrs (n: cleanOutput) x
-      else if isList x then map cleanOutput x
-      else x
-    else x;
-in
-  cleanOutput value
-EOF
-}
-
-evalOpt(){
-  evalAttr "option" "" "$@"
-}
-
-evalCfg(){
-  local strict="$1"
-  evalAttr "config" "$strict"
-}
-
-findSources(){
-  local suffix=$1
-  evalNix --strict <<EOF
-$header
-
-option.$suffix
-EOF
-}
-
-# Given a result from nix-instantiate, recover the list of attributes it
-# contains.
-attrNames() {
-  local attributeset=$1
-  # sed is used to replace un-printable subset by 0s, and to remove most of
-  # the inner-attribute set, which reduce the likelyhood to encounter badly
-  # pre-processed input.
-  echo "builtins.attrNames $attributeset" | \
-    sed 's,<[A-Z]*>,0,g; :inner; s/{[^\{\}]*};/0;/g; t inner;' | \
-    evalNix --strict
-}
-
-# map a simple list which contains strings or paths.
-nixMap() {
-  local fun="$1"
-  local list="$2"
-  local elem
-  for elem in $list; do
-    test $elem = '[' -o $elem = ']' && continue;
-    $fun $elem
-  done
-}
-
-# This duplicates the work made below, but it is useful for processing
-# the output of nixos-option with other tools such as nixos-gui.
-if $xml; then
-  evalNix --xml --no-location <<EOF
-$header
-
-let
-  sources = builtins.map (f: f.source);
-  opt = option;
-  cfg = config;
-in
-
-with nixpkgs.lib;
-
-let
-  optStrict = v:
-    let
-      traverse = x :
-        if isAttrs x then
-          if x ? outPath then true
-          else all id (mapAttrsFlatten (n: traverseNoAttrs) x)
-        else traverseNoAttrs x;
-      traverseNoAttrs = x:
-        # do not continue in attribute sets
-        if isAttrs x then true
-        else if isList x then all id (map traverse x)
-        else true;
-    in assert traverse v; v;
-in
-
-if isOption opt then
-  optStrict ({}
-  // optionalAttrs (opt ? default) { inherit (opt) default; }
-  // optionalAttrs (opt ? example) { inherit (opt) example; }
-  // optionalAttrs (opt ? description) { inherit (opt) description; }
-  // optionalAttrs (opt ? type) { typename = opt.type.description; }
-  // optionalAttrs (opt ? options) { inherit (opt) options; }
-  // {
-    # to disambiguate the xml output.
-    _isOption = true;
-    declarations = sources opt.declarations;
-    definitions = sources opt.definitions;
-    value = cfg;
-  })
-else
-  opt
-EOF
-  exit $?
-fi
-
-if test "$(evalOpt "_type" 2> /dev/null)" = '"option"'; then
-  echo "Value:"
-  evalCfg 1
-
-  echo
-
-  echo "Default:"
-  if default=$(evalOpt "default" - 2> /dev/null); then
-    echo "$default"
-  else
-    echo "<None>"
-  fi
-  echo
-  if example=$(evalOpt "example" - 2> /dev/null); then
-    echo "Example:"
-    echo "$example"
-    echo
-  fi
-  echo "Description:"
-  echo
-  echo $(evalOpt "description")
-
-  echo $desc;
-
-  printPath () { echo "  $1"; }
-
-  echo "Declared by:"
-  nixMap printPath "$(findSources "declarations")"
-  echo
-  echo "Defined by:"
-  nixMap printPath "$(findSources "files")"
-  echo
-
-else
-  # echo 1>&2 "Warning: This value is not an option."
-
-  result=$(evalCfg "")
-  if [ ! -z "$result" ]; then
-    names=$(attrNames "$result" 2> /dev/null)
-    echo 1>&2 "This attribute set contains:"
-    escapeQuotes () { eval echo "$1"; }
-    nixMap escapeQuotes "$names"
-  else
-    echo 1>&2 "An error occurred while looking for attribute names. Are you sure that '$option' exists?"
-  fi
-fi
-
-exit $exit_code
diff --git a/nixos/modules/installer/tools/nixos-option/CMakeLists.txt b/nixos/modules/installer/tools/nixos-option/CMakeLists.txt
new file mode 100644
index 000000000000..e5834598c4fd
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/CMakeLists.txt
@@ -0,0 +1,8 @@
+cmake_minimum_required (VERSION 2.6)
+project (nixos-option)
+
+add_executable(nixos-option nixos-option.cc libnix-copy-paste.cc)
+target_link_libraries(nixos-option PRIVATE -lnixmain -lnixexpr -lnixstore -lnixutil)
+target_compile_features(nixos-option PRIVATE cxx_std_17)
+
+install (TARGETS nixos-option DESTINATION bin)
diff --git a/nixos/modules/installer/tools/nixos-option/default.nix b/nixos/modules/installer/tools/nixos-option/default.nix
new file mode 100644
index 000000000000..753fd92c7bbf
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/default.nix
@@ -0,0 +1,11 @@
+{lib, stdenv, boost, cmake, pkgconfig, nix, ... }:
+stdenv.mkDerivation rec {
+  name = "nixos-option";
+  src = ./.;
+  nativeBuildInputs = [ cmake pkgconfig ];
+  buildInputs = [ boost nix ];
+  meta = {
+    license = stdenv.lib.licenses.lgpl2Plus;
+    maintainers = with lib.maintainers; [ chkno ];
+  };
+}
diff --git a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc
new file mode 100644
index 000000000000..875c07da6399
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.cc
@@ -0,0 +1,83 @@
+// These are useful methods inside the nix library that ought to be exported.
+// Since they are not, copy/paste them here.
+// TODO: Delete these and use the ones in the library as they become available.
+
+#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
+
+#include "libnix-copy-paste.hh"
+#include <boost/format/alt_sstream.hpp>           // for basic_altstringbuf...
+#include <boost/format/alt_sstream_impl.hpp>      // for basic_altstringbuf...
+#include <boost/format/format_class.hpp>          // for basic_format
+#include <boost/format/format_fwd.hpp>            // for format
+#include <boost/format/format_implementation.hpp> // for basic_format::basi...
+#include <boost/optional/optional.hpp>            // for get_pointer
+#include <iostream>                               // for operator<<, basic_...
+#include <nix/types.hh>                           // for Strings, Error
+#include <string>                                 // for string, basic_string
+
+using boost::format;
+using nix::Error;
+using nix::Strings;
+using std::string;
+
+// From nix/src/libexpr/attr-path.cc
+Strings parseAttrPath(const string & s)
+{
+    Strings res;
+    string cur;
+    string::const_iterator i = s.begin();
+    while (i != s.end()) {
+        if (*i == '.') {
+            res.push_back(cur);
+            cur.clear();
+        } else if (*i == '"') {
+            ++i;
+            while (1) {
+                if (i == s.end())
+                    throw Error(format("missing closing quote in selection path '%1%'") % s);
+                if (*i == '"')
+                    break;
+                cur.push_back(*i++);
+            }
+        } else
+            cur.push_back(*i);
+        ++i;
+    }
+    if (!cur.empty())
+        res.push_back(cur);
+    return res;
+}
+
+// From nix/src/nix/repl.cc
+bool isVarName(const string & s)
+{
+    if (s.size() == 0)
+        return false;
+    char c = s[0];
+    if ((c >= '0' && c <= '9') || c == '-' || c == '\'')
+        return false;
+    for (auto & i : s)
+        if (!((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == '-' ||
+              i == '\''))
+            return false;
+    return true;
+}
+
+// From nix/src/nix/repl.cc
+std::ostream & printStringValue(std::ostream & str, const char * string)
+{
+    str << "\"";
+    for (const char * i = string; *i; i++)
+        if (*i == '\"' || *i == '\\')
+            str << "\\" << *i;
+        else if (*i == '\n')
+            str << "\\n";
+        else if (*i == '\r')
+            str << "\\r";
+        else if (*i == '\t')
+            str << "\\t";
+        else
+            str << *i;
+    str << "\"";
+    return str;
+}
diff --git a/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh
new file mode 100644
index 000000000000..2274e9a0f853
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/libnix-copy-paste.hh
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <iostream>
+#include <nix/types.hh>
+#include <string>
+
+nix::Strings parseAttrPath(const std::string & s);
+bool isVarName(const std::string & s);
+std::ostream & printStringValue(std::ostream & str, const char * string);
diff --git a/nixos/modules/installer/tools/nixos-option/nixos-option.cc b/nixos/modules/installer/tools/nixos-option/nixos-option.cc
new file mode 100644
index 000000000000..9b92dc829cd1
--- /dev/null
+++ b/nixos/modules/installer/tools/nixos-option/nixos-option.cc
@@ -0,0 +1,618 @@
+#include <nix/config.h> // for nix/globals.hh's reference to SYSTEM
+
+#include <exception>               // for exception_ptr, current_exception
+#include <functional>              // for function
+#include <iostream>                // for operator<<, basic_ostream, ostrin...
+#include <iterator>                // for next
+#include <list>                    // for _List_iterator
+#include <memory>                  // for allocator, unique_ptr, make_unique
+#include <new>                     // for operator new
+#include <nix/args.hh>             // for argvToStrings, UsageError
+#include <nix/attr-path.hh>        // for findAlongAttrPath
+#include <nix/attr-set.hh>         // for Attr, Bindings, Bindings::iterator
+#include <nix/common-eval-args.hh> // for MixEvalArgs
+#include <nix/eval-inline.hh>      // for EvalState::forceValue
+#include <nix/eval.hh>             // for EvalState, initGC, operator<<
+#include <nix/globals.hh>          // for initPlugins, Settings, settings
+#include <nix/nixexpr.hh>          // for Pos
+#include <nix/shared.hh>           // for getArg, LegacyArgs, printVersion
+#include <nix/store-api.hh>        // for openStore
+#include <nix/symbol-table.hh>     // for Symbol, SymbolTable
+#include <nix/types.hh>            // for Error, Path, Strings, PathSet
+#include <nix/util.hh>             // for absPath, baseNameOf
+#include <nix/value.hh>            // for Value, Value::(anonymous), Value:...
+#include <string>                  // for string, operator+, operator==
+#include <utility>                 // for move
+#include <variant>                 // for get, holds_alternative, variant
+#include <vector>                  // for vector<>::iterator, vector
+
+#include "libnix-copy-paste.hh"
+
+using nix::absPath;
+using nix::Bindings;
+using nix::Error;
+using nix::EvalError;
+using nix::EvalState;
+using nix::Path;
+using nix::PathSet;
+using nix::Strings;
+using nix::Symbol;
+using nix::tAttrs;
+using nix::ThrownError;
+using nix::tLambda;
+using nix::tString;
+using nix::UsageError;
+using nix::Value;
+
+// An ostream wrapper to handle nested indentation
+class Out
+{
+  public:
+    class Separator
+    {};
+    const static Separator sep;
+    enum LinePolicy
+    {
+        ONE_LINE,
+        MULTI_LINE
+    };
+    explicit Out(std::ostream & ostream) : ostream(ostream), policy(ONE_LINE), writeSinceSep(true) {}
+    Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy);
+    Out(Out & o, const std::string & start, const std::string & end, int count)
+        : Out(o, start, end, count < 2 ? ONE_LINE : MULTI_LINE)
+    {}
+    Out(const Out &) = delete;
+    Out(Out &&) = default;
+    Out & operator=(const Out &) = delete;
+    Out & operator=(Out &&) = delete;
+    ~Out() { ostream << end; }
+
+  private:
+    std::ostream & ostream;
+    std::string indentation;
+    std::string end;
+    LinePolicy policy;
+    bool writeSinceSep;
+    template <typename T> friend Out & operator<<(Out & o, T thing);
+};
+
+template <typename T> Out & operator<<(Out & o, T thing)
+{
+    if (!o.writeSinceSep && o.policy == Out::MULTI_LINE) {
+        o.ostream << o.indentation;
+    }
+    o.writeSinceSep = true;
+    o.ostream << thing;
+    return o;
+}
+
+template <> Out & operator<<<Out::Separator>(Out & o, Out::Separator /* thing */)
+{
+    o.ostream << (o.policy == Out::ONE_LINE ? " " : "\n");
+    o.writeSinceSep = false;
+    return o;
+}
+
+Out::Out(Out & o, const std::string & start, const std::string & end, LinePolicy policy)
+    : ostream(o.ostream), indentation(policy == ONE_LINE ? o.indentation : o.indentation + "  "),
+      end(policy == ONE_LINE ? end : o.indentation + end), policy(policy), writeSinceSep(true)
+{
+    o << start;
+    *this << Out::sep;
+}
+
+// Stuff needed for evaluation
+struct Context
+{
+    Context(EvalState & state, Bindings & autoArgs, Value optionsRoot, Value configRoot)
+        : state(state), autoArgs(autoArgs), optionsRoot(optionsRoot), configRoot(configRoot),
+          underscoreType(state.symbols.create("_type"))
+    {}
+    EvalState & state;
+    Bindings & autoArgs;
+    Value optionsRoot;
+    Value configRoot;
+    Symbol underscoreType;
+};
+
+Value evaluateValue(Context & ctx, Value & v)
+{
+    ctx.state.forceValue(v);
+    if (ctx.autoArgs.empty()) {
+        return v;
+    }
+    Value called{};
+    ctx.state.autoCallFunction(ctx.autoArgs, v, called);
+    return called;
+}
+
+bool isOption(Context & ctx, const Value & v)
+{
+    if (v.type != tAttrs) {
+        return false;
+    }
+    const auto & atualType = v.attrs->find(ctx.underscoreType);
+    if (atualType == v.attrs->end()) {
+        return false;
+    }
+    try {
+        Value evaluatedType = evaluateValue(ctx, *atualType->value);
+        if (evaluatedType.type != tString) {
+            return false;
+        }
+        return static_cast<std::string>(evaluatedType.string.s) == "option";
+    } catch (Error &) {
+        return false;
+    }
+}
+
+// Add quotes to a component of a path.
+// These are needed for paths like:
+//    fileSystems."/".fsType
+//    systemd.units."dbus.service".text
+std::string quoteAttribute(const std::string & attribute)
+{
+    if (isVarName(attribute)) {
+        return attribute;
+    }
+    std::ostringstream buf;
+    printStringValue(buf, attribute.c_str());
+    return buf.str();
+}
+
+const std::string appendPath(const std::string & prefix, const std::string & suffix)
+{
+    if (prefix.empty()) {
+        return quoteAttribute(suffix);
+    }
+    return prefix + "." + quoteAttribute(suffix);
+}
+
+bool forbiddenRecursionName(std::string name) { return (!name.empty() && name[0] == '_') || name == "haskellPackages"; }
+
+void recurse(const std::function<bool(const std::string & path, std::variant<Value, std::exception_ptr>)> & f,
+             Context & ctx, Value v, const std::string & path)
+{
+    std::variant<Value, std::exception_ptr> evaluated;
+    try {
+        evaluated = evaluateValue(ctx, v);
+    } catch (Error &) {
+        evaluated = std::current_exception();
+    }
+    if (!f(path, evaluated)) {
+        return;
+    }
+    if (std::holds_alternative<std::exception_ptr>(evaluated)) {
+        return;
+    }
+    const Value & evaluated_value = std::get<Value>(evaluated);
+    if (evaluated_value.type != tAttrs) {
+        return;
+    }
+    for (const auto & child : evaluated_value.attrs->lexicographicOrder()) {
+        if (forbiddenRecursionName(child->name)) {
+            continue;
+        }
+        recurse(f, ctx, *child->value, appendPath(path, child->name));
+    }
+}
+
+// Calls f on all the option names
+void mapOptions(const std::function<void(const std::string & path)> & f, Context & ctx, Value root)
+{
+    recurse(
+        [f, &ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) {
+            bool isOpt = std::holds_alternative<std::exception_ptr>(v) || isOption(ctx, std::get<Value>(v));
+            if (isOpt) {
+                f(path);
+            }
+            return !isOpt;
+        },
+        ctx, root, "");
+}
+
+// Calls f on all the config values inside one option.
+// Simple options have one config value inside, like sound.enable = true.
+// Compound options have multiple config values.  For example, the option
+// "users.users" has about 1000 config values inside it:
+//   users.users.avahi.createHome = false;
+//   users.users.avahi.cryptHomeLuks = null;
+//   users.users.avahi.description = "`avahi-daemon' privilege separation user";
+//   ...
+//   users.users.avahi.openssh.authorizedKeys.keyFiles = [ ];
+//   users.users.avahi.openssh.authorizedKeys.keys = [ ];
+//   ...
+//   users.users.avahi.uid = 10;
+//   users.users.avahi.useDefaultShell = false;
+//   users.users.cups.createHome = false;
+//   ...
+//   users.users.cups.useDefaultShell = false;
+//   users.users.gdm = ... ... ...
+//   users.users.messagebus = ... .. ...
+//   users.users.nixbld1 = ... .. ...
+//   ...
+//   users.users.systemd-timesync = ... .. ...
+void mapConfigValuesInOption(
+    const std::function<void(const std::string & path, std::variant<Value, std::exception_ptr> v)> & f,
+    const std::string & path, Context & ctx)
+{
+    Value * option;
+    try {
+        option = findAlongAttrPath(ctx.state, path, ctx.autoArgs, ctx.configRoot);
+    } catch (Error &) {
+        f(path, std::current_exception());
+        return;
+    }
+    recurse(
+        [f, ctx](const std::string & path, std::variant<Value, std::exception_ptr> v) {
+            bool leaf = std::holds_alternative<std::exception_ptr>(v) || std::get<Value>(v).type != tAttrs ||
+                        ctx.state.isDerivation(std::get<Value>(v));
+            if (!leaf) {
+                return true; // Keep digging
+            }
+            f(path, v);
+            return false;
+        },
+        ctx, *option, path);
+}
+
+std::string describeError(const Error & e) { return "«error: " + e.msg() + "»"; }
+
+void describeDerivation(Context & ctx, Out & out, Value v)
+{
+    // Copy-pasted from nix/src/nix/repl.cc  :(
+    Bindings::iterator i = v.attrs->find(ctx.state.sDrvPath);
+    PathSet pathset;
+    try {
+        Path drvPath = i != v.attrs->end() ? ctx.state.coerceToPath(*i->pos, *i->value, pathset) : "???";
+        out << "«derivation " << drvPath << "»";
+    } catch (Error & e) {
+        out << describeError(e);
+    }
+}
+
+Value parseAndEval(EvalState & state, const std::string & expression, const std::string & path)
+{
+    Value v{};
+    state.eval(state.parseExprFromString(expression, absPath(path)), v);
+    return v;
+}
+
+void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path);
+
+void printList(Context & ctx, Out & out, Value & v)
+{
+    Out listOut(out, "[", "]", v.listSize());
+    for (unsigned int n = 0; n < v.listSize(); ++n) {
+        printValue(ctx, listOut, *v.listElems()[n], "");
+        listOut << Out::sep;
+    }
+}
+
+void printAttrs(Context & ctx, Out & out, Value & v, const std::string & path)
+{
+    Out attrsOut(out, "{", "}", v.attrs->size());
+    for (const auto & a : v.attrs->lexicographicOrder()) {
+        std::string name = a->name;
+        attrsOut << name << " = ";
+        printValue(ctx, attrsOut, *a->value, appendPath(path, name));
+        attrsOut << ";" << Out::sep;
+    }
+}
+
+void multiLineStringEscape(Out & out, const std::string & s)
+{
+    int i;
+    for (i = 1; i < s.size(); i++) {
+        if (s[i - 1] == '$' && s[i] == '{') {
+            out << "''${";
+            i++;
+        } else if (s[i - 1] == '\'' && s[i] == '\'') {
+            out << "'''";
+            i++;
+        } else {
+            out << s[i - 1];
+        }
+    }
+    if (i == s.size()) {
+        out << s[i - 1];
+    }
+}
+
+void printMultiLineString(Out & out, const Value & v)
+{
+    std::string s = v.string.s;
+    Out strOut(out, "''", "''", Out::MULTI_LINE);
+    std::string::size_type begin = 0;
+    while (begin < s.size()) {
+        std::string::size_type end = s.find('\n', begin);
+        if (end == std::string::npos) {
+            multiLineStringEscape(strOut, s.substr(begin, s.size() - begin));
+            break;
+        }
+        multiLineStringEscape(strOut, s.substr(begin, end - begin));
+        strOut << Out::sep;
+        begin = end + 1;
+    }
+}
+
+void printValue(Context & ctx, Out & out, std::variant<Value, std::exception_ptr> maybeValue, const std::string & path)
+{
+    try {
+        if (auto ex = std::get_if<std::exception_ptr>(&maybeValue)) {
+            std::rethrow_exception(*ex);
+        }
+        Value v = evaluateValue(ctx, std::get<Value>(maybeValue));
+        if (ctx.state.isDerivation(v)) {
+            describeDerivation(ctx, out, v);
+        } else if (v.isList()) {
+            printList(ctx, out, v);
+        } else if (v.type == tAttrs) {
+            printAttrs(ctx, out, v, path);
+        } else if (v.type == tString && std::string(v.string.s).find('\n') != std::string::npos) {
+            printMultiLineString(out, v);
+        } else {
+            ctx.state.forceValueDeep(v);
+            out << v;
+        }
+    } catch (ThrownError & e) {
+        if (e.msg() == "The option `" + path + "' is used but not defined.") {
+            // 93% of errors are this, and just letting this message through would be
+            // misleading.  These values may or may not actually be "used" in the
+            // config.  The thing throwing the error message assumes that if anything
+            // ever looks at this value, it is a "use" of this value.  But here in
+            // nixos-option, we are looking at this value only to print it.
+            // In order to avoid implying that this undefined value is actually
+            // referenced, eat the underlying error message and emit "«not defined»".
+            out << "«not defined»";
+        } else {
+            out << describeError(e);
+        }
+    } catch (Error & e) {
+        out << describeError(e);
+    }
+}
+
+void printConfigValue(Context & ctx, Out & out, const std::string & path, std::variant<Value, std::exception_ptr> v)
+{
+    out << path << " = ";
+    printValue(ctx, out, std::move(v), path);
+    out << ";\n";
+}
+
+void printAll(Context & ctx, Out & out)
+{
+    mapOptions(
+        [&ctx, &out](const std::string & optionPath) {
+            mapConfigValuesInOption(
+                [&ctx, &out](const std::string & configPath, std::variant<Value, std::exception_ptr> v) {
+                    printConfigValue(ctx, out, configPath, v);
+                },
+                optionPath, ctx);
+        },
+        ctx, ctx.optionsRoot);
+}
+
+void printAttr(Context & ctx, Out & out, const std::string & path, Value & root)
+{
+    try {
+        printValue(ctx, out, *findAlongAttrPath(ctx.state, path, ctx.autoArgs, root), path);
+    } catch (Error & e) {
+        out << describeError(e);
+    }
+}
+
+bool hasExample(Context & ctx, Value & option)
+{
+    try {
+        findAlongAttrPath(ctx.state, "example", ctx.autoArgs, option);
+        return true;
+    } catch (Error &) {
+        return false;
+    }
+}
+
+void printOption(Context & ctx, Out & out, const std::string & path, Value & option)
+{
+    out << "Value:\n";
+    printAttr(ctx, out, path, ctx.configRoot);
+
+    out << "\n\nDefault:\n";
+    printAttr(ctx, out, "default", option);
+
+    out << "\n\nType:\n";
+    printAttr(ctx, out, "type.description", option);
+
+    if (hasExample(ctx, option)) {
+        out << "\n\nExample:\n";
+        printAttr(ctx, out, "example", option);
+    }
+
+    out << "\n\nDescription:\n";
+    printAttr(ctx, out, "description", option);
+
+    out << "\n\nDeclared by:\n";
+    printAttr(ctx, out, "declarations", option);
+
+    out << "\n\nDefined by:\n";
+    printAttr(ctx, out, "files", option);
+    out << "\n";
+}
+
+void printListing(Out & out, Value & v)
+{
+    out << "This attribute set contains:\n";
+    for (const auto & a : v.attrs->lexicographicOrder()) {
+        std::string name = a->name;
+        if (!name.empty() && name[0] != '_') {
+            out << name << "\n";
+        }
+    }
+}
+
+bool optionTypeIs(Context & ctx, Value & v, const std::string & soughtType)
+{
+    try {
+        const auto & typeLookup = v.attrs->find(ctx.state.sType);
+        if (typeLookup == v.attrs->end()) {
+            return false;
+        }
+        Value type = evaluateValue(ctx, *typeLookup->value);
+        if (type.type != tAttrs) {
+            return false;
+        }
+        const auto & nameLookup = type.attrs->find(ctx.state.sName);
+        if (nameLookup == type.attrs->end()) {
+            return false;
+        }
+        Value name = evaluateValue(ctx, *nameLookup->value);
+        if (name.type != tString) {
+            return false;
+        }
+        return name.string.s == soughtType;
+    } catch (Error &) {
+        return false;
+    }
+}
+
+bool isAggregateOptionType(Context & ctx, Value & v)
+{
+    return optionTypeIs(ctx, v, "attrsOf") || optionTypeIs(ctx, v, "listOf") || optionTypeIs(ctx, v, "loaOf");
+}
+
+MakeError(OptionPathError, EvalError);
+
+Value getSubOptions(Context & ctx, Value & option)
+{
+    Value getSubOptions = evaluateValue(ctx, *findAlongAttrPath(ctx.state, "type.getSubOptions", ctx.autoArgs, option));
+    if (getSubOptions.type != tLambda) {
+        throw OptionPathError("Option's type.getSubOptions isn't a function");
+    }
+    Value emptyString{};
+    nix::mkString(emptyString, "");
+    Value v;
+    ctx.state.callFunction(getSubOptions, emptyString, v, nix::Pos{});
+    return v;
+}
+
+// Carefully walk an option path, looking for sub-options when a path walks past
+// an option value.
+Value findAlongOptionPath(Context & ctx, const std::string & path)
+{
+    Strings tokens = parseAttrPath(path);
+    Value v = ctx.optionsRoot;
+    for (auto i = tokens.begin(); i != tokens.end(); i++) {
+        const auto & attr = *i;
+        try {
+            bool lastAttribute = std::next(i) == tokens.end();
+            v = evaluateValue(ctx, v);
+            if (attr.empty()) {
+                throw OptionPathError("empty attribute name");
+            }
+            if (isOption(ctx, v) && optionTypeIs(ctx, v, "submodule")) {
+                v = getSubOptions(ctx, v);
+            }
+            if (isOption(ctx, v) && isAggregateOptionType(ctx, v) && !lastAttribute) {
+                v = getSubOptions(ctx, v);
+                // Note that we've consumed attr, but didn't actually use it.  This is the path component that's looked
+                // up in the list or attribute set that doesn't name an option -- the "root" in "users.users.root.name".
+            } else if (v.type != tAttrs) {
+                throw OptionPathError("Value is %s while a set was expected", showType(v));
+            } else {
+                const auto & next = v.attrs->find(ctx.state.symbols.create(attr));
+                if (next == v.attrs->end()) {
+                    throw OptionPathError("Attribute not found", attr, path);
+                }
+                v = *next->value;
+            }
+        } catch (OptionPathError & e) {
+            throw OptionPathError("At '%s' in path '%s': %s", attr, path, e.msg());
+        }
+    }
+    return v;
+}
+
+void printOne(Context & ctx, Out & out, const std::string & path)
+{
+    try {
+        Value option = findAlongOptionPath(ctx, path);
+        option = evaluateValue(ctx, option);
+        if (isOption(ctx, option)) {
+            printOption(ctx, out, path, option);
+        } else {
+            printListing(out, option);
+        }
+    } catch (Error & e) {
+        std::cerr << "error: " << e.msg()
+                  << "\nAn error occurred while looking for attribute names. Are "
+                     "you sure that '"
+                  << path << "' exists?\n";
+    }
+}
+
+int main(int argc, char ** argv)
+{
+    bool all = false;
+    std::string path = ".";
+    std::string optionsExpr = "(import <nixpkgs/nixos> {}).options";
+    std::string configExpr = "(import <nixpkgs/nixos> {}).config";
+    std::vector<std::string> args;
+
+    struct MyArgs : nix::LegacyArgs, nix::MixEvalArgs
+    {
+        using nix::LegacyArgs::LegacyArgs;
+    };
+
+    MyArgs myArgs(nix::baseNameOf(argv[0]), [&](Strings::iterator & arg, const Strings::iterator & end) {
+        if (*arg == "--help") {
+            nix::showManPage("nixos-option");
+        } else if (*arg == "--version") {
+            nix::printVersion("nixos-option");
+        } else if (*arg == "--all") {
+            all = true;
+        } else if (*arg == "--path") {
+            path = nix::getArg(*arg, arg, end);
+        } else if (*arg == "--options_expr") {
+            optionsExpr = nix::getArg(*arg, arg, end);
+        } else if (*arg == "--config_expr") {
+            configExpr = nix::getArg(*arg, arg, end);
+        } else if (!arg->empty() && arg->at(0) == '-') {
+            return false;
+        } else {
+            args.push_back(*arg);
+        }
+        return true;
+    });
+
+    myArgs.parseCmdline(nix::argvToStrings(argc, argv));
+
+    nix::initPlugins();
+    nix::initGC();
+    nix::settings.readOnlyMode = true;
+    auto store = nix::openStore();
+    auto state = std::make_unique<EvalState>(myArgs.searchPath, store);
+
+    Value optionsRoot = parseAndEval(*state, optionsExpr, path);
+    Value configRoot = parseAndEval(*state, configExpr, path);
+
+    Context ctx{*state, *myArgs.getAutoArgs(*state), optionsRoot, configRoot};
+    Out out(std::cout);
+
+    if (all) {
+        if (!args.empty()) {
+            throw UsageError("--all cannot be used with arguments");
+        }
+        printAll(ctx, out);
+    } else {
+        if (args.empty()) {
+            printOne(ctx, out, "");
+        }
+        for (const auto & arg : args) {
+            printOne(ctx, out, arg);
+        }
+    }
+
+    ctx.state.printStats();
+
+    return 0;
+}
diff --git a/nixos/modules/installer/tools/nixos-rebuild.sh b/nixos/modules/installer/tools/nixos-rebuild.sh
index 6a08c9b4c6c6..61b4af110273 100644
--- a/nixos/modules/installer/tools/nixos-rebuild.sh
+++ b/nixos/modules/installer/tools/nixos-rebuild.sh
@@ -22,6 +22,7 @@ repair=
 profile=/nix/var/nix/profiles/system
 buildHost=
 targetHost=
+maybeSudo=()
 
 while [ "$#" -gt 0 ]; do
     i="$1"; shift 1
@@ -89,6 +90,11 @@ while [ "$#" -gt 0 ]; do
         targetHost="$1"
         shift 1
         ;;
+      --use-remote-sudo)
+        # note the trailing space
+        maybeSudo=(sudo --)
+        shift 1
+        ;;
       *)
         echo "$0: unknown option \`$i'"
         exit 1
@@ -96,6 +102,9 @@ while [ "$#" -gt 0 ]; do
     esac
 done
 
+if [ -n "$SUDO_USER" ]; then
+    maybeSudo=(sudo --)
+fi
 
 if [ -z "$buildHost" -a -n "$targetHost" ]; then
     buildHost="$targetHost"
@@ -111,17 +120,17 @@ buildHostCmd() {
     if [ -z "$buildHost" ]; then
         "$@"
     elif [ -n "$remoteNix" ]; then
-        ssh $SSHOPTS "$buildHost" PATH="$remoteNix:$PATH" "$@"
+        ssh $SSHOPTS "$buildHost" env PATH="$remoteNix:$PATH" "${maybeSudo[@]}" "$@"
     else
-        ssh $SSHOPTS "$buildHost" "$@"
+        ssh $SSHOPTS "$buildHost" "${maybeSudo[@]}" "$@"
     fi
 }
 
 targetHostCmd() {
     if [ -z "$targetHost" ]; then
-        "$@"
+        "${maybeSudo[@]}" "$@"
     else
-        ssh $SSHOPTS "$targetHost" "$@"
+        ssh $SSHOPTS "$targetHost" "${maybeSudo[@]}" "$@"
     fi
 }
 
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 00c4d5018bf5..5df9c23e6b69 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -36,15 +36,12 @@ let
   nixos-generate-config = makeProg {
     name = "nixos-generate-config";
     src = ./nixos-generate-config.pl;
-    path = [ pkgs.btrfs-progs ];
+    path = lib.optionals (lib.elem "btrfs" config.boot.supportedFilesystems) [ pkgs.btrfs-progs ];
     perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix}";
-    inherit (config.system.nixos) release;
+    inherit (config.system.nixos-generate-config) configuration;
   };
 
-  nixos-option = makeProg {
-    name = "nixos-option";
-    src = ./nixos-option.sh;
-  };
+  nixos-option = pkgs.callPackage ./nixos-option { };
 
   nixos-version = makeProg {
     name = "nixos-version";
@@ -61,8 +58,118 @@ in
 
 {
 
+  options.system.nixos-generate-config.configuration = mkOption {
+    internal = true;
+    type = types.str;
+    description = ''
+      The NixOS module that <literal>nixos-generate-config</literal>
+      saves to <literal>/etc/nixos/configuration.nix</literal>.
+
+      This is an internal option. No backward compatibility is guaranteed.
+      Use at your own risk!
+
+      Note that this string gets spliced into a Perl script. The perl
+      variable <literal>$bootLoaderConfig</literal> can be used to
+      splice in the boot loader configuration.
+    '';
+  };
+
   config = {
 
+    system.nixos-generate-config.configuration = mkDefault ''
+      # Edit this configuration file to define what should be installed on
+      # your system.  Help is available in the configuration.nix(5) man page
+      # and in the NixOS manual (accessible by running ‘nixos-help’).
+
+      { config, pkgs, ... }:
+
+      {
+        imports =
+          [ # Include the results of the hardware scan.
+            ./hardware-configuration.nix
+          ];
+
+      $bootLoaderConfig
+        # networking.hostName = "nixos"; # Define your hostname.
+        # networking.wireless.enable = true;  # Enables wireless support via wpa_supplicant.
+
+      $networkingDhcpConfig
+        # Configure network proxy if necessary
+        # networking.proxy.default = "http://user:password\@proxy:port/";
+        # networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
+
+        # Select internationalisation properties.
+        # i18n = {
+        #   consoleFont = "Lat2-Terminus16";
+        #   consoleKeyMap = "us";
+        #   defaultLocale = "en_US.UTF-8";
+        # };
+
+        # Set your time zone.
+        # time.timeZone = "Europe/Amsterdam";
+
+        # List packages installed in system profile. To search, run:
+        # \$ nix search wget
+        # environment.systemPackages = with pkgs; [
+        #   wget vim
+        # ];
+
+        # Some programs need SUID wrappers, can be configured further or are
+        # started in user sessions.
+        # programs.mtr.enable = true;
+        # programs.gnupg.agent = {
+        #   enable = true;
+        #   enableSSHSupport = true;
+        #   pinentryFlavor = "gnome3";
+        # };
+
+        # List services that you want to enable:
+
+        # Enable the OpenSSH daemon.
+        # services.openssh.enable = true;
+
+        # Open ports in the firewall.
+        # networking.firewall.allowedTCPPorts = [ ... ];
+        # networking.firewall.allowedUDPPorts = [ ... ];
+        # Or disable the firewall altogether.
+        # networking.firewall.enable = false;
+
+        # Enable CUPS to print documents.
+        # services.printing.enable = true;
+
+        # Enable sound.
+        # sound.enable = true;
+        # hardware.pulseaudio.enable = true;
+
+        # Enable the X11 windowing system.
+        # services.xserver.enable = true;
+        # services.xserver.layout = "us";
+        # services.xserver.xkbOptions = "eurosign:e";
+
+        # Enable touchpad support.
+        # services.xserver.libinput.enable = true;
+
+        # Enable the KDE Desktop Environment.
+        # services.xserver.displayManager.sddm.enable = true;
+        # services.xserver.desktopManager.plasma5.enable = true;
+
+        # Define a user account. Don't forget to set a password with ‘passwd’.
+        # users.users.jane = {
+        #   isNormalUser = true;
+        #   extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
+        # };
+
+        # This value determines the NixOS release from which the default
+        # settings for stateful data, like file locations and database versions
+        # on your system were taken. It‘s perfectly fine and recommended to leave
+        # this value at the release version of the first install of this system.
+        # Before changing this value read the documentation for this option
+        # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
+        system.stateVersion = "${config.system.nixos.release}"; # Did you read the comment?
+
+      }
+    '';
+
     environment.systemPackages =
       [ nixos-build-vms
         nixos-install
diff --git a/nixos/modules/misc/crashdump.nix b/nixos/modules/misc/crashdump.nix
index 6e0b49fa9af0..3c47e79d0512 100644
--- a/nixos/modules/misc/crashdump.nix
+++ b/nixos/modules/misc/crashdump.nix
@@ -58,7 +58,6 @@ in
        "crashkernel=${crashdump.reservedMemory}"
        "nmi_watchdog=panic"
        "softlockup_panic=1"
-       "idle=poll"
       ];
       kernelPatches = [ {
         name = "crashdump-config";
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index 834ac0de9121..d09afadd6097 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, baseModules, extraModules, modules, ... }:
+{ config, lib, pkgs, baseModules, extraModules, modules, modulesPath, ... }:
 
 with lib;
 
@@ -22,7 +22,10 @@ let
         scrubbedEval = evalModules {
           modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ manualModules;
           args = (config._module.args) // { modules = [ ]; };
-          specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; };
+          specialArgs = {
+            pkgs = scrubDerivations "pkgs" pkgs;
+            inherit modulesPath;
+          };
         };
         scrubDerivations = namePrefix: pkgSet: mapAttrs
           (name: value:
@@ -49,11 +52,7 @@ let
       if [ -z "$browser" ]; then
         browser="$(type -P xdg-open || true)"
         if [ -z "$browser" ]; then
-          browser="$(type -P w3m || true)"
-          if [ -z "$browser" ]; then
-            echo "$0: unable to start a web browser; please set \$BROWSER"
-            exit 1
-          fi
+          browser="${pkgs.w3m-nographics}/bin/w3m"
         fi
       fi
       exec "$browser" ${manual.manualHTMLIndex}
@@ -71,6 +70,11 @@ let
 in
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ])
+    (mkRenamedOptionModule [ "programs" "man"  "enable" ] [ "documentation" "man"  "enable" ])
+    (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ])
+  ];
 
   options = {
 
@@ -187,8 +191,6 @@ in
     })
 
     (mkIf cfg.doc.enable {
-      # TODO(@oxij): put it here and remove from profiles?
-      # environment.systemPackages = [ pkgs.w3m ]; # w3m-nox?
       environment.pathsToLink = [ "/share/doc" ];
       environment.extraOutputsToInstall = [ "doc" ] ++ optional cfg.dev.enable "devdoc";
     })
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index e78673514e3b..bedd87a368eb 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -11,6 +11,9 @@
 
 { lib, ... }:
 
+let
+  inherit (lib) types;
+in
 {
   options = {
 
@@ -19,6 +22,7 @@
       description = ''
         The user IDs used in NixOS.
       '';
+      type = types.attrsOf types.int;
     };
 
     ids.gids = lib.mkOption {
@@ -26,6 +30,7 @@
       description = ''
         The group IDs used in NixOS.
       '';
+      type = types.attrsOf types.int;
     };
 
   };
@@ -44,7 +49,7 @@
       vsftpd = 7;
       ftp = 8;
       bitlbee = 9;
-      avahi = 10;
+      #avahi = 10; # removed 2019-05-22
       nagios = 11;
       atd = 12;
       postfix = 13;
@@ -75,8 +80,8 @@
       #kdm = 39; # dropped in 17.03
       #ghostone = 40; # dropped in 18.03
       git = 41;
-      fourstore = 42;
-      fourstorehttp = 43;
+      #fourstore = 42; # dropped in 20.03
+      #fourstorehttp = 43; # dropped in 20.03
       virtuoso = 44;
       rtkit = 45;
       dovecot2 = 46;
@@ -128,7 +133,7 @@
       tcpcryptd = 93; # tcpcryptd uses a hard-coded uid. We patch it in Nixpkgs to match this choice.
       firebird = 95;
       #keys = 96; # unused
-      haproxy = 97;
+      #haproxy = 97; # DynamicUser as of 2019-11-08
       mongodb = 98;
       openldap = 99;
       #users = 100; # unused
@@ -145,7 +150,7 @@
       #notbit = 111; # unused
       aerospike = 111;
       ngircd = 112;
-      btsync = 113;
+      #btsync = 113; # unused
       minecraft = 114;
       vault = 115;
       rippled = 116;
@@ -251,7 +256,7 @@
       gale = 223;
       matrix-synapse = 224;
       rspamd = 225;
-      rmilter = 226;
+      # rmilter = 226; # unused, removed 2019-08-22
       cfdyndns = 227;
       gammu-smsd = 228;
       pdnsd = 229;
@@ -265,8 +270,8 @@
       syncthing = 237;
       caddy = 239;
       taskd = 240;
-      factorio = 241;
-      emby = 242;
+      # factorio = 241; # DynamicUser = true
+      # emby = 242; # unusued, removed 2019-05-01
       graylog = 243;
       sniproxy = 244;
       nzbget = 245;
@@ -328,7 +333,7 @@
       qemu-libvirtd = 301;
       # kvm = 302; # unused
       # render = 303; # unused
-      zeronet = 304;
+      # zeronet = 304; # removed 2019-01-03
       lirc = 305;
       lidarr = 306;
       slurm = 307;
@@ -339,6 +344,8 @@
       rss2email = 312;
       cockroachdb = 313;
       zoneminder = 314;
+      paperless = 315;
+      #mailman = 316;  # removed 2019-08-30
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -357,7 +364,7 @@
       vsftpd = 7;
       ftp = 8;
       bitlbee = 9;
-      avahi = 10;
+      #avahi = 10; # removed 2019-05-22
       #nagios = 11; # unused
       atd = 12;
       postfix = 13;
@@ -441,7 +448,7 @@
       #tcpcryptd = 93; # unused
       firebird = 95;
       keys = 96;
-      haproxy = 97;
+      #haproxy = 97; # DynamicUser as of 2019-11-08
       #mongodb = 98; # unused
       openldap = 99;
       munin = 102;
@@ -456,7 +463,7 @@
       #notbit = 111; # unused
       aerospike = 111;
       #ngircd = 112; # unused
-      btsync = 113;
+      #btsync = 113; # unused
       #minecraft = 114; # unused
       vault = 115;
       #ripped = 116; # unused
@@ -515,7 +522,7 @@
       tss = 176;
       #memcached = 177; # unused, removed 2018-01-03
       #ntp = 179; # unused
-      #zabbix = 180; # unused
+      zabbix = 180;
       #redis = 181; # unused, removed 2018-01-03
       #unifi = 183; # unused
       #uptimed = 184; # unused
@@ -558,7 +565,7 @@
       gale = 223;
       matrix-synapse = 224;
       rspamd = 225;
-      rmilter = 226;
+      # rmilter = 226; # unused, removed 2019-08-22
       cfdyndns = 227;
       pdnsd = 229;
       octoprint = 230;
@@ -566,8 +573,8 @@
       syncthing = 237;
       caddy = 239;
       taskd = 240;
-      factorio = 241;
-      emby = 242;
+      # factorio = 241; # unused
+      # emby = 242; # unused, removed 2019-05-01
       sniproxy = 244;
       nzbget = 245;
       mosquitto = 246;
@@ -627,7 +634,7 @@
       qemu-libvirtd = 301;
       kvm = 302; # default udev rules from systemd requires these
       render = 303; # default udev rules from systemd requires these
-      zeronet = 304;
+      # zeronet = 304; # removed 2019-01-03
       lirc = 305;
       lidarr = 306;
       slurm = 307;
@@ -638,6 +645,8 @@
       rss2email = 312;
       cockroachdb = 313;
       zoneminder = 314;
+      paperless = 315;
+      #mailman = 316;  # removed 2019-08-30
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/misc/locate.nix b/nixos/modules/misc/locate.nix
index 449149e4bb65..552535c253e6 100644
--- a/nixos/modules/misc/locate.nix
+++ b/nixos/modules/misc/locate.nix
@@ -7,6 +7,11 @@ let
   isMLocate = hasPrefix "mlocate" cfg.locate.name;
   isFindutils = hasPrefix "findutils" cfg.locate.name;
 in {
+  imports = [
+    (mkRenamedOptionModule [ "services" "locate" "period" ] [ "services" "locate" "interval" ])
+    (mkRemovedOptionModule [ "services" "locate" "includeStore" ] "Use services.locate.prunePaths" )
+  ];
+
   options.services.locate = with types; {
     enable = mkOption {
       type = bool;
@@ -128,7 +133,10 @@ in {
 
     # directory creation needs to be separated from main service
     # because ReadWritePaths fails when the directory doesn't already exist
-    systemd.tmpfiles.rules = [ "d ${dirOf cfg.output} 0755 root root -" ];
+    systemd.tmpfiles.rules =
+      let dir = dirOf cfg.output; in
+      mkIf (dir != "/var/cache")
+        [ "d ${dir} 0755 root root -" ];
 
     systemd.services.update-locatedb =
       { description = "Update Locate Database";
diff --git a/nixos/modules/misc/nixops-autoluks.nix b/nixos/modules/misc/nixops-autoluks.nix
new file mode 100644
index 000000000000..20c143286afa
--- /dev/null
+++ b/nixos/modules/misc/nixops-autoluks.nix
@@ -0,0 +1,43 @@
+{ config, options, lib, ... }:
+let
+  path = [ "deployment" "autoLuks" ];
+  hasAutoLuksConfig = lib.hasAttrByPath path config && (lib.attrByPath path {} config) != {};
+
+  inherit (config.nixops) enableDeprecatedAutoLuks;
+in {
+  options.nixops.enableDeprecatedAutoLuks = lib.mkEnableOption "Enable the deprecated NixOps AutoLuks module";
+
+  config = {
+    assertions = [
+      {
+        assertion = if hasAutoLuksConfig then hasAutoLuksConfig && enableDeprecatedAutoLuks else true;
+        message = ''
+          ⚠️  !!! WARNING !!! ⚠️
+
+            NixOps autoLuks is deprecated. The feature was never widely used and the maintenance did outgrow the benefit.
+            If you still want to use the module:
+              a) Please raise your voice in the issue tracking usage of the module:
+                 https://github.com/NixOS/nixpkgs/issues/62211
+              b) make sure you set the `_netdev` option for each of the file
+                 systems referring to block devices provided by the autoLuks module.
+
+                 ⚠️ If you do not set the option your system will not boot anymore! ⚠️
+
+                  {
+                    fileSystems."/secret" = { options = [ "_netdev" ]; };
+                  }
+
+              b) set the option >nixops.enableDeprecatedAutoLuks = true< to remove this error.
+
+
+            For more details read through the following resources:
+              - https://github.com/NixOS/nixops/pull/1156
+              - https://github.com/NixOS/nixpkgs/issues/47550
+              - https://github.com/NixOS/nixpkgs/issues/62211
+              - https://github.com/NixOS/nixpkgs/pull/61321
+        '';
+      }
+    ];
+  };
+
+}
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index 3a717fddaba2..afb74581e239 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -19,7 +19,7 @@ let
       lhs = optCall lhs_ { inherit pkgs; };
       rhs = optCall rhs_ { inherit pkgs; };
     in
-    lhs // rhs //
+    recursiveUpdate lhs rhs //
     optionalAttrs (lhs ? packageOverrides) {
       packageOverrides = pkgs:
         optCall lhs.packageOverrides pkgs //
@@ -127,13 +127,14 @@ in
       default = [];
       example = literalExample
         ''
-          [ (self: super: {
+          [
+            (self: super: {
               openssh = super.openssh.override {
                 hpnSupport = true;
                 kerberos = self.libkrb5;
               };
-            };
-          ) ]
+            })
+          ]
         '';
       type = types.listOf overlayType;
       description = ''
@@ -195,7 +196,6 @@ in
     system = mkOption {
       type = types.str;
       example = "i686-linux";
-      default = { system = builtins.currentSystem; };
       description = ''
         Specifies the Nix platform type on which NixOS should be built.
         It is better to specify <code>nixpkgs.localSystem</code> instead.
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index c576cf4cb925..8a85035ceb7c 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -1,4 +1,4 @@
-{ options, config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -6,10 +6,17 @@ let
   cfg = config.system.nixos;
 
   gitRepo      = "${toString pkgs.path}/.git";
+  gitRepoValid = lib.pathIsGitRepo gitRepo;
   gitCommitId  = lib.substring 0 7 (commitIdFromGitRepo gitRepo);
 in
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "system" "nixosVersion" ] [ "system" "nixos" "version" ])
+    (mkRenamedOptionModule [ "system" "nixosVersionSuffix" ] [ "system" "nixos" "versionSuffix" ])
+    (mkRenamedOptionModule [ "system" "nixosRevision" ] [ "system" "nixos" "revision" ])
+    (mkRenamedOptionModule [ "system" "nixosLabel" ] [ "system" "nixos" "label" ])
+  ];
 
   options.system = {
 
@@ -55,11 +62,18 @@ in
         configuration defaults in a way incompatible with stateful
         data. For instance, if the default version of PostgreSQL
         changes, the new version will probably be unable to read your
-        existing databases. To prevent such breakage, you can set the
+        existing databases. To prevent such breakage, you should set the
         value of this option to the NixOS release with which you want
-        to be compatible. The effect is that NixOS will option
+        to be compatible. The effect is that NixOS will use
         defaults corresponding to the specified release (such as using
         an older version of PostgreSQL).
+        It‘s perfectly fine and recommended to leave this value at the
+        release version of the first install of this system.
+        Changing this option will not upgrade your system. In fact it
+        is meant to stay constant exactly when you upgrade your system.
+        You should only bump this option, if you are sure that you can
+        or have migrated all state on your system which is affected
+        by this option.
       '';
     };
 
@@ -78,23 +92,24 @@ in
       # These defaults are set here rather than up there so that
       # changing them would not rebuild the manual
       version = mkDefault (cfg.release + cfg.versionSuffix);
-      revision      = mkIf (pathIsDirectory gitRepo) (mkDefault            gitCommitId);
-      versionSuffix = mkIf (pathIsDirectory gitRepo) (mkDefault (".git." + gitCommitId));
+      revision      = mkIf gitRepoValid (mkDefault            gitCommitId);
+      versionSuffix = mkIf gitRepoValid (mkDefault (".git." + gitCommitId));
     };
 
     # Generate /etc/os-release.  See
     # https://www.freedesktop.org/software/systemd/man/os-release.html for the
     # format.
-    environment.etc."os-release".text =
+    environment.etc.os-release.text =
       ''
         NAME=NixOS
         ID=nixos
         VERSION="${cfg.version} (${cfg.codeName})"
         VERSION_CODENAME=${toLower cfg.codeName}
         VERSION_ID="${cfg.version}"
-        PRETTY_NAME="NixOS ${cfg.version} (${cfg.codeName})"
+        PRETTY_NAME="NixOS ${cfg.release} (${cfg.codeName})"
         LOGO="nix-snowflake"
         HOME_URL="https://nixos.org/"
+        DOCUMENTATION_URL="https://nixos.org/nixos/manual/index.html"
         SUPPORT_URL="https://nixos.org/nixos/support.html"
         BUG_REPORT_URL="https://github.com/NixOS/nixpkgs/issues"
       '';
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index ba61ac364b1c..3dd414ffb0a5 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1,9 +1,7 @@
 [
   ./config/debug-info.nix
-  ./config/fonts/corefonts.nix
   ./config/fonts/fontconfig.nix
   ./config/fonts/fontconfig-penultimate.nix
-  ./config/fonts/fontconfig-ultimate.nix
   ./config/fonts/fontdir.nix
   ./config/fonts/fonts.nix
   ./config/fonts/ghostscript.nix
@@ -11,7 +9,9 @@
   ./config/xdg/icons.nix
   ./config/xdg/menus.nix
   ./config/xdg/mime.nix
+  ./config/xdg/portal.nix
   ./config/appstream.nix
+  ./config/console.nix
   ./config/xdg/sounds.nix
   ./config/gtk/gtk-icon-cache.nix
   ./config/gnu.nix
@@ -19,41 +19,50 @@
   ./config/iproute2.nix
   ./config/krb5/default.nix
   ./config/ldap.nix
+  ./config/locale.nix
+  ./config/malloc.nix
   ./config/networking.nix
   ./config/no-x-libs.nix
   ./config/nsswitch.nix
   ./config/power-management.nix
   ./config/pulseaudio.nix
+  ./config/qt5.nix
+  ./config/resolvconf.nix
   ./config/shells-environment.nix
   ./config/swap.nix
   ./config/sysctl.nix
   ./config/system-environment.nix
   ./config/system-path.nix
   ./config/terminfo.nix
-  ./config/timezone.nix
   ./config/unix-odbc-drivers.nix
   ./config/users-groups.nix
-  ./config/vpnc.nix
+  ./config/vte.nix
   ./config/zram.nix
   ./hardware/acpilight.nix
   ./hardware/all-firmware.nix
   ./hardware/bladeRF.nix
   ./hardware/brightnessctl.nix
+  ./hardware/brillo.nix
   ./hardware/ckb-next.nix
   ./hardware/cpu/amd-microcode.nix
   ./hardware/cpu/intel-microcode.nix
   ./hardware/digitalbitbox.nix
+  ./hardware/device-tree.nix
   ./hardware/sensor/iio.nix
   ./hardware/ksm.nix
   ./hardware/ledger.nix
   ./hardware/logitech.nix
   ./hardware/mcelog.nix
   ./hardware/network/b43.nix
+  ./hardware/network/intel-2200bg.nix
   ./hardware/nitrokey.nix
   ./hardware/opengl.nix
+  ./hardware/openrazer.nix
   ./hardware/pcmcia.nix
+  ./hardware/printers.nix
   ./hardware/raid/hpsa.nix
   ./hardware/steam-hardware.nix
+  ./hardware/tuxedo-keyboard.nix
   ./hardware/usb-wwan.nix
   ./hardware/onlykey.nix
   ./hardware/video/amdgpu.nix
@@ -83,25 +92,36 @@
   ./misc/nixpkgs.nix
   ./misc/passthru.nix
   ./misc/version.nix
+  ./misc/nixops-autoluks.nix
   ./programs/adb.nix
   ./programs/atop.nix
   ./programs/autojump.nix
+  ./programs/bandwhich.nix
   ./programs/bash/bash.nix
   ./programs/bcc.nix
-  ./programs/blcr.nix
   ./programs/browserpass.nix
+  ./programs/captive-browser.nix
   ./programs/ccache.nix
   ./programs/cdemu.nix
   ./programs/chromium.nix
+  ./programs/clickshare.nix
   ./programs/command-not-found/command-not-found.nix
   ./programs/criu.nix
   ./programs/dconf.nix
   ./programs/digitalbitbox/default.nix
   ./programs/dmrconfig.nix
   ./programs/environment.nix
+  ./programs/evince.nix
+  ./programs/file-roller.nix
   ./programs/firejail.nix
   ./programs/fish.nix
   ./programs/freetds.nix
+  ./programs/fuse.nix
+  ./programs/geary.nix
+  ./programs/gnome-disks.nix
+  ./programs/gnome-documents.nix
+  ./programs/gnome-terminal.nix
+  ./programs/gpaste.nix
   ./programs/gnupg.nix
   ./programs/gphoto2.nix
   ./programs/iftop.nix
@@ -109,6 +129,7 @@
   ./programs/java.nix
   ./programs/kbdlight.nix
   ./programs/less.nix
+  ./programs/liboping.nix
   ./programs/light.nix
   ./programs/mosh.nix
   ./programs/mininet.nix
@@ -121,9 +142,9 @@
   ./programs/qt5ct.nix
   ./programs/screen.nix
   ./programs/sedutil.nix
+  ./programs/seahorse.nix
   ./programs/slock.nix
   ./programs/shadow.nix
-  ./programs/shell.nix
   ./programs/spacefm.nix
   ./programs/singularity.nix
   ./programs/ssh.nix
@@ -131,19 +152,24 @@
   ./programs/sysdig.nix
   ./programs/systemtap.nix
   ./programs/sway.nix
+  ./programs/system-config-printer.nix
   ./programs/thefuck.nix
   ./programs/tmux.nix
+  ./programs/traceroute.nix
+  ./programs/tsm-client.nix
   ./programs/udevil.nix
+  ./programs/usbtop.nix
   ./programs/venus.nix
   ./programs/vim.nix
   ./programs/wavemon.nix
-  ./programs/way-cooler.nix
   ./programs/waybar.nix
   ./programs/wireshark.nix
+  ./programs/x2goserver.nix
   ./programs/xfs_quota.nix
   ./programs/xonsh.nix
   ./programs/xss-lock.nix
   ./programs/yabar.nix
+  ./programs/zmap.nix
   ./programs/zsh/oh-my-zsh.nix
   ./programs/zsh/zsh.nix
   ./programs/zsh/zsh-autoenv.nix
@@ -180,27 +206,31 @@
   ./services/amqp/activemq/default.nix
   ./services/amqp/rabbitmq.nix
   ./services/audio/alsa.nix
+  ./services/audio/jack.nix
   ./services/audio/icecast.nix
   ./services/audio/liquidsoap.nix
   ./services/audio/mpd.nix
   ./services/audio/mopidy.nix
+  ./services/audio/roon-server.nix
   ./services/audio/slimserver.nix
   ./services/audio/snapserver.nix
   ./services/audio/squeezelite.nix
+  ./services/audio/spotifyd.nix
   ./services/audio/ympd.nix
   ./services/backup/automysqlbackup.nix
   ./services/backup/bacula.nix
   ./services/backup/borgbackup.nix
   ./services/backup/duplicati.nix
-  ./services/backup/crashplan.nix
-  ./services/backup/crashplan-small-business.nix
   ./services/backup/duplicity.nix
   ./services/backup/mysql-backup.nix
   ./services/backup/postgresql-backup.nix
+  ./services/backup/postgresql-wal-receiver.nix
   ./services/backup/restic.nix
   ./services/backup/restic-rest-server.nix
   ./services/backup/rsnapshot.nix
   ./services/backup/tarsnap.nix
+  ./services/backup/tsm.nix
+  ./services/backup/zfs-replication.nix
   ./services/backup/znapzend.nix
   ./services/cluster/hadoop/default.nix
   ./services/cluster/kubernetes/addons/dns.nix
@@ -229,8 +259,6 @@
   ./services/continuous-integration/jenkins/default.nix
   ./services/continuous-integration/jenkins/job-builder.nix
   ./services/continuous-integration/jenkins/slave.nix
-  ./services/databases/4store-endpoint.nix
-  ./services/databases/4store.nix
   ./services/databases/aerospike.nix
   ./services/databases/cassandra.nix
   ./services/databases/clickhouse.nix
@@ -256,8 +284,8 @@
   ./services/databases/virtuoso.nix
   ./services/desktops/accountsservice.nix
   ./services/desktops/bamf.nix
-  ./services/desktops/deepin/dde-daemon.nix
-  ./services/desktops/deepin/deepin-menu.nix
+  ./services/desktops/blueman.nix
+  ./services/desktops/deepin/deepin.nix
   ./services/desktops/dleyna-renderer.nix
   ./services/desktops/dleyna-server.nix
   ./services/desktops/pantheon/contractor.nix
@@ -265,47 +293,46 @@
   ./services/desktops/flatpak.nix
   ./services/desktops/geoclue2.nix
   ./services/desktops/gsignond.nix
+  ./services/desktops/gvfs.nix
   ./services/desktops/pipewire.nix
   ./services/desktops/gnome3/at-spi2-core.nix
   ./services/desktops/gnome3/chrome-gnome-shell.nix
-  ./services/desktops/gnome3/evince.nix
   ./services/desktops/gnome3/evolution-data-server.nix
-  ./services/desktops/gnome3/file-roller.nix
   ./services/desktops/gnome3/glib-networking.nix
-  ./services/desktops/gnome3/gnome-disks.nix
-  ./services/desktops/gnome3/gnome-documents.nix
+  ./services/desktops/gnome3/gnome-initial-setup.nix
   ./services/desktops/gnome3/gnome-keyring.nix
   ./services/desktops/gnome3/gnome-online-accounts.nix
-  ./services/desktops/gnome3/gnome-remote-desktop.nix
   ./services/desktops/gnome3/gnome-online-miners.nix
+  ./services/desktops/gnome3/gnome-remote-desktop.nix
   ./services/desktops/gnome3/gnome-settings-daemon.nix
-  ./services/desktops/gnome3/gnome-terminal-server.nix
   ./services/desktops/gnome3/gnome-user-share.nix
-  ./services/desktops/gnome3/gpaste.nix
-  ./services/desktops/gnome3/gvfs.nix
   ./services/desktops/gnome3/rygel.nix
-  ./services/desktops/gnome3/seahorse.nix
   ./services/desktops/gnome3/sushi.nix
   ./services/desktops/gnome3/tracker.nix
   ./services/desktops/gnome3/tracker-miners.nix
+  ./services/desktops/neard.nix
   ./services/desktops/profile-sync-daemon.nix
+  ./services/desktops/system-config-printer.nix
   ./services/desktops/telepathy.nix
   ./services/desktops/tumbler.nix
   ./services/desktops/zeitgeist.nix
   ./services/development/bloop.nix
   ./services/development/hoogle.nix
   ./services/development/jupyter/default.nix
+  ./services/development/lorri.nix
   ./services/editors/emacs.nix
   ./services/editors/infinoted.nix
   ./services/games/factorio.nix
   ./services/games/minecraft-server.nix
   ./services/games/minetest-server.nix
+  ./services/games/openarena.nix
   ./services/games/terraria.nix
   ./services/hardware/acpid.nix
   ./services/hardware/actkbd.nix
   ./services/hardware/bluetooth.nix
   ./services/hardware/bolt.nix
   ./services/hardware/brltty.nix
+  ./services/hardware/fancontrol.nix
   ./services/hardware/freefall.nix
   ./services/hardware/fwupd.nix
   ./services/hardware/illum.nix
@@ -323,6 +350,7 @@
   ./services/hardware/tcsd.nix
   ./services/hardware/tlp.nix
   ./services/hardware/thinkfan.nix
+  ./services/hardware/throttled.nix
   ./services/hardware/trezord.nix
   ./services/hardware/triggerhappy.nix
   ./services/hardware/u2f.nix
@@ -358,6 +386,7 @@
   ./services/mail/mail.nix
   ./services/mail/mailcatcher.nix
   ./services/mail/mailhog.nix
+  ./services/mail/mailman.nix
   ./services/mail/mlmmj.nix
   ./services/mail/offlineimap.nix
   ./services/mail/opendkim.nix
@@ -369,7 +398,6 @@
   ./services/mail/spamassassin.nix
   ./services/mail/rspamd.nix
   ./services/mail/rss2email.nix
-  ./services/mail/rmilter.nix
   ./services/mail/roundcube.nix
   ./services/mail/nullmailer.nix
   ./services/misc/airsonic.nix
@@ -389,12 +417,13 @@
   ./services/misc/couchpotato.nix
   ./services/misc/devmon.nix
   ./services/misc/dictd.nix
+  ./services/misc/dwm-status.nix
   ./services/misc/dysnomia.nix
   ./services/misc/disnix.nix
   ./services/misc/docker-registry.nix
-  ./services/misc/emby.nix
   ./services/misc/errbot.nix
   ./services/misc/etcd.nix
+  ./services/misc/ethminer.nix
   ./services/misc/exhibitor.nix
   ./services/misc/felix.nix
   ./services/misc/folding-at-home.nix
@@ -410,20 +439,23 @@
   ./services/misc/gollum.nix
   ./services/misc/gpsd.nix
   ./services/misc/headphones.nix
+  ./services/misc/greenclip.nix
   ./services/misc/home-assistant.nix
   ./services/misc/ihaskell.nix
   ./services/misc/irkerd.nix
   ./services/misc/jackett.nix
+  ./services/misc/jellyfin.nix
   ./services/misc/logkeys.nix
   ./services/misc/leaps.nix
   ./services/misc/lidarr.nix
-  ./services/misc/mantisbt.nix
+  ./services/misc/mame.nix
   ./services/misc/mathics.nix
   ./services/misc/matrix-synapse.nix
   ./services/misc/mbpfan.nix
   ./services/misc/mediatomb.nix
   ./services/misc/mesos-master.nix
   ./services/misc/mesos-slave.nix
+  ./services/misc/metabase.nix
   ./services/misc/mwlib.nix
   ./services/misc/nix-daemon.nix
   ./services/misc/nix-gc.nix
@@ -435,8 +467,8 @@
   ./services/misc/octoprint.nix
   ./services/misc/osrm.nix
   ./services/misc/packagekit.nix
+  ./services/misc/paperless.nix
   ./services/misc/parsoid.nix
-  ./services/misc/phd.nix
   ./services/misc/plex.nix
   ./services/misc/tautulli.nix
   ./services/misc/pykms.nix
@@ -460,6 +492,7 @@
   ./services/misc/synergy.nix
   ./services/misc/sysprof.nix
   ./services/misc/taskserver
+  ./services/misc/tiddlywiki.nix
   ./services/misc/tzupdate.nix
   ./services/misc/uhub.nix
   ./services/misc/weechat.nix
@@ -475,6 +508,7 @@
   ./services/monitoring/das_watchdog.nix
   ./services/monitoring/datadog-agent.nix
   ./services/monitoring/dd-agent/dd-agent.nix
+  ./services/monitoring/do-agent.nix
   ./services/monitoring/fusion-inventory.nix
   ./services/monitoring/grafana.nix
   ./services/monitoring/grafana-reporter.nix
@@ -483,12 +517,12 @@
   ./services/monitoring/heapster.nix
   ./services/monitoring/incron.nix
   ./services/monitoring/kapacitor.nix
+  ./services/monitoring/loki.nix
   ./services/monitoring/longview.nix
   ./services/monitoring/monit.nix
   ./services/monitoring/munin.nix
   ./services/monitoring/nagios.nix
   ./services/monitoring/netdata.nix
-  ./services/monitoring/osquery.nix
   ./services/monitoring/prometheus/default.nix
   ./services/monitoring/prometheus/alertmanager.nix
   ./services/monitoring/prometheus/exporters.nix
@@ -499,15 +533,15 @@
   ./services/monitoring/scollector.nix
   ./services/monitoring/smartd.nix
   ./services/monitoring/sysstat.nix
-  ./services/monitoring/systemhealth.nix
   ./services/monitoring/teamviewer.nix
   ./services/monitoring/telegraf.nix
+  ./services/monitoring/thanos.nix
   ./services/monitoring/ups.nix
   ./services/monitoring/uptime.nix
   ./services/monitoring/vnstat.nix
   ./services/monitoring/zabbix-agent.nix
+  ./services/monitoring/zabbix-proxy.nix
   ./services/monitoring/zabbix-server.nix
-  ./services/network-filesystems/beegfs.nix
   ./services/network-filesystems/cachefilesd.nix
   ./services/network-filesystems/davfs2.nix
   ./services/network-filesystems/drbd.nix
@@ -518,6 +552,8 @@
   ./services/network-filesystems/nfsd.nix
   ./services/network-filesystems/openafs/client.nix
   ./services/network-filesystems/openafs/server.nix
+  ./services/network-filesystems/orangefs/server.nix
+  ./services/network-filesystems/orangefs/client.nix
   ./services/network-filesystems/rsyncd.nix
   ./services/network-filesystems/samba.nix
   ./services/network-filesystems/tahoe.nix
@@ -526,6 +562,7 @@
   ./services/network-filesystems/yandex-disk.nix
   ./services/network-filesystems/xtreemfs.nix
   ./services/network-filesystems/ceph.nix
+  ./services/networking/3proxy.nix
   ./services/networking/amuled.nix
   ./services/networking/aria2.nix
   ./services/networking/asterisk.nix
@@ -533,17 +570,17 @@
   ./services/networking/avahi-daemon.nix
   ./services/networking/babeld.nix
   ./services/networking/bind.nix
+  ./services/networking/bitcoind.nix
   ./services/networking/autossh.nix
   ./services/networking/bird.nix
   ./services/networking/bitlbee.nix
-  ./services/networking/btsync.nix
   ./services/networking/charybdis.nix
-  ./services/networking/chrony.nix
   ./services/networking/cjdns.nix
   ./services/networking/cntlm.nix
   ./services/networking/connman.nix
   ./services/networking/consul.nix
   ./services/networking/coredns.nix
+  ./services/networking/corerad.nix
   ./services/networking/coturn.nix
   ./services/networking/dante.nix
   ./services/networking/ddclient.nix
@@ -572,6 +609,7 @@
   ./services/networking/gdomap.nix
   ./services/networking/git-daemon.nix
   ./services/networking/gnunet.nix
+  ./services/networking/go-shadowsocks2.nix
   ./services/networking/gogoclient.nix
   ./services/networking/gvpe.nix
   ./services/networking/hans.nix
@@ -605,6 +643,7 @@
   ./services/networking/morty.nix
   ./services/networking/miredo.nix
   ./services/networking/mstpd.nix
+  ./services/networking/mtprotoproxy.nix
   ./services/networking/murmur.nix
   ./services/networking/mxisd.nix
   ./services/networking/namecoind.nix
@@ -619,13 +658,15 @@
   ./services/networking/nntp-proxy.nix
   ./services/networking/nsd.nix
   ./services/networking/ntopng.nix
-  ./services/networking/ntpd.nix
+  ./services/networking/ntp/chrony.nix
+  ./services/networking/ntp/ntpd.nix
+  ./services/networking/ntp/openntpd.nix
   ./services/networking/nullidentdmod.nix
   ./services/networking/nylon.nix
   ./services/networking/ocserv.nix
+  ./services/networking/ofono.nix
   ./services/networking/oidentd.nix
   ./services/networking/openfire.nix
-  ./services/networking/openntpd.nix
   ./services/networking/openvpn.nix
   ./services/networking/ostinato.nix
   ./services/networking/owamp.nix
@@ -633,6 +674,7 @@
   ./services/networking/polipo.nix
   ./services/networking/powerdns.nix
   ./services/networking/pdns-recursor.nix
+  ./services/networking/pppd.nix
   ./services/networking/pptpd.nix
   ./services/networking/prayer.nix
   ./services/networking/privoxy.nix
@@ -654,10 +696,13 @@
   ./services/networking/skydns.nix
   ./services/networking/shadowsocks.nix
   ./services/networking/shairport-sync.nix
+  ./services/networking/shorewall.nix
+  ./services/networking/shorewall6.nix
   ./services/networking/shout.nix
   ./services/networking/sniproxy.nix
   ./services/networking/smokeping.nix
   ./services/networking/softether.nix
+  ./services/networking/spacecookie.nix
   ./services/networking/spiped.nix
   ./services/networking/squid.nix
   ./services/networking/sslh.nix
@@ -671,26 +716,34 @@
   ./services/networking/supybot.nix
   ./services/networking/syncthing.nix
   ./services/networking/syncthing-relay.nix
+  ./services/networking/syncplay.nix
   ./services/networking/tcpcrypt.nix
   ./services/networking/teamspeak3.nix
+  ./services/networking/tedicross.nix
+  ./services/networking/thelounge.nix
   ./services/networking/tinc.nix
   ./services/networking/tinydns.nix
   ./services/networking/tftpd.nix
+  ./services/networking/trickster.nix
   ./services/networking/tox-bootstrapd.nix
   ./services/networking/tox-node.nix
   ./services/networking/toxvpn.nix
   ./services/networking/tvheadend.nix
   ./services/networking/unbound.nix
   ./services/networking/unifi.nix
+  ./services/networking/v2ray.nix
   ./services/networking/vsftpd.nix
   ./services/networking/wakeonlan.nix
   ./services/networking/websockify.nix
+  ./services/networking/wg-quick.nix
   ./services/networking/wicd.nix
   ./services/networking/wireguard.nix
   ./services/networking/wpa_supplicant.nix
+  ./services/networking/xandikos.nix
   ./services/networking/xinetd.nix
   ./services/networking/xl2tpd.nix
   ./services/networking/xrdp.nix
+  ./services/networking/yggdrasil.nix
   ./services/networking/zerobin.nix
   ./services/networking/zeronet.nix
   ./services/networking/zerotierone.nix
@@ -706,6 +759,7 @@
   ./services/search/hound.nix
   ./services/search/kibana.nix
   ./services/search/solr.nix
+  ./services/security/bitwarden_rs/default.nix
   ./services/security/certmgr.nix
   ./services/security/cfssl.nix
   ./services/security/clamav.nix
@@ -740,6 +794,7 @@
   ./services/system/uptimed.nix
   ./services/torrent/deluge.nix
   ./services/torrent/flexget.nix
+  ./services/torrent/magnetico.nix
   ./services/torrent/opentracker.nix
   ./services/torrent/peerflix.nix
   ./services/torrent/transmission.nix
@@ -750,23 +805,37 @@
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
   ./services/web-apps/codimd.nix
+  ./services/web-apps/cryptpad.nix
   ./services/web-apps/documize.nix
+  ./services/web-apps/dokuwiki.nix
   ./services/web-apps/frab.nix
+  ./services/web-apps/gotify-server.nix
   ./services/web-apps/icingaweb2/icingaweb2.nix
   ./services/web-apps/icingaweb2/module-monitoring.nix
+  ./services/web-apps/ihatemoney
+  ./services/web-apps/limesurvey.nix
   ./services/web-apps/mattermost.nix
+  ./services/web-apps/mediawiki.nix
   ./services/web-apps/miniflux.nix
+  ./services/web-apps/moodle.nix
   ./services/web-apps/nextcloud.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/pgpkeyserver-lite.nix
   ./services/web-apps/matomo.nix
+  ./services/web-apps/moinmoin.nix
   ./services/web-apps/restya-board.nix
   ./services/web-apps/tt-rss.nix
+  ./services/web-apps/trac.nix
+  ./services/web-apps/trilium.nix
   ./services/web-apps/selfoss.nix
+  ./services/web-apps/shiori.nix
   ./services/web-apps/virtlyst.nix
+  ./services/web-apps/wordpress.nix
   ./services/web-apps/youtrack.nix
+  ./services/web-apps/zabbix.nix
   ./services/web-servers/apache-httpd/default.nix
   ./services/web-servers/caddy.nix
+  ./services/web-servers/darkhttpd.nix
   ./services/web-servers/fcgiwrap.nix
   ./services/web-servers/hitch/default.nix
   ./services/web-servers/hydron.nix
@@ -785,12 +854,14 @@
   ./services/web-servers/shellinabox.nix
   ./services/web-servers/tomcat.nix
   ./services/web-servers/traefik.nix
+  ./services/web-servers/ttyd.nix
   ./services/web-servers/uwsgi.nix
   ./services/web-servers/varnish/default.nix
-  ./services/web-servers/winstone.nix
   ./services/web-servers/zope2.nix
+  ./services/x11/extra-layouts.nix
+  ./services/x11/clight.nix
   ./services/x11/colord.nix
-  ./services/x11/compton.nix
+  ./services/x11/picom.nix
   ./services/x11/unclutter.nix
   ./services/x11/unclutter-xfixes.nix
   ./services/x11/desktop-managers/default.nix
@@ -804,10 +875,12 @@
   ./services/x11/display-managers/xpra.nix
   ./services/x11/fractalart.nix
   ./services/x11/hardware/libinput.nix
-  ./services/x11/hardware/multitouch.nix
   ./services/x11/hardware/synaptics.nix
   ./services/x11/hardware/wacom.nix
+  ./services/x11/hardware/digimend.nix
+  ./services/x11/hardware/cmt.nix
   ./services/x11/gdk-pixbuf.nix
+  ./services/x11/imwheel.nix
   ./services/x11/redshift.nix
   ./services/x11/urxvtd.nix
   ./services/x11/window-managers/awesome.nix
@@ -828,7 +901,6 @@
   ./system/activation/activation-script.nix
   ./system/activation/top-level.nix
   ./system/boot/binfmt.nix
-  ./system/boot/coredump.nix
   ./system/boot/emergency-mode.nix
   ./system/boot/grow-partition.nix
   ./system/boot/initrd-network.nix
@@ -879,7 +951,6 @@
   ./tasks/filesystems/vfat.nix
   ./tasks/filesystems/xfs.nix
   ./tasks/filesystems/zfs.nix
-  ./tasks/kbd.nix
   ./tasks/lvm.nix
   ./tasks/network-interfaces.nix
   ./tasks/network-interfaces-systemd.nix
@@ -892,6 +963,7 @@
   ./virtualisation/anbox.nix
   ./virtualisation/container-config.nix
   ./virtualisation/containers.nix
+  ./virtualisation/cri-o.nix
   ./virtualisation/docker.nix
   ./virtualisation/docker-containers.nix
   ./virtualisation/ecs-agent.nix
@@ -905,6 +977,7 @@
   ./virtualisation/openvswitch.nix
   ./virtualisation/parallels-guest.nix
   ./virtualisation/qemu-guest-agent.nix
+  ./virtualisation/railcar.nix
   ./virtualisation/rkt.nix
   ./virtualisation/virtualbox-guest.nix
   ./virtualisation/virtualbox-host.nix
diff --git a/nixos/modules/profiles/graphical.nix b/nixos/modules/profiles/graphical.nix
index 649f5564ac61..d80456cede56 100644
--- a/nixos/modules/profiles/graphical.nix
+++ b/nixos/modules/profiles/graphical.nix
@@ -9,14 +9,12 @@
     displayManager.sddm.enable = true;
     desktopManager.plasma5 = {
       enable = true;
-      enableQt4Support = false;
     };
     libinput.enable = true; # for touchpad support on many laptops
   };
 
   # Enable sound in virtualbox appliances.
   hardware.pulseaudio.enable = true;
-  hardware.pulseaudio.systemWide = true; # Needed since we run plasma as root.
 
   environment.systemPackages = [ pkgs.glxinfo pkgs.firefox ];
 }
diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
index 9ab2ee87a19e..f7b2f5c7fc1e 100644
--- a/nixos/modules/profiles/hardened.nix
+++ b/nixos/modules/profiles/hardened.nix
@@ -24,7 +24,9 @@ with lib;
 
   security.allowSimultaneousMultithreading = mkDefault false;
 
-  security.virtualization.flushL1DataCache = mkDefault "always";
+  security.forcePageTableIsolation = mkDefault true;
+
+  security.virtualisation.flushL1DataCache = mkDefault "always";
 
   security.apparmor.enable = mkDefault true;
 
@@ -41,8 +43,8 @@ with lib;
     # Disable legacy virtual syscalls
     "vsyscall=none"
 
-    # Enable PTI even if CPU claims to be safe from meltdown
-    "pti=on"
+    # Enable page allocator randomization
+    "page_alloc.shuffle=1"
   ];
 
   boot.blacklistedKernelModules = [
@@ -50,6 +52,27 @@ with lib;
     "ax25"
     "netrom"
     "rose"
+
+    # Old or rare or insufficiently audited filesystems
+    "adfs"
+    "affs"
+    "bfs"
+    "befs"
+    "cramfs"
+    "efs"
+    "erofs"
+    "exofs"
+    "freevxfs"
+    "f2fs"
+    "hfs"
+    "hpfs"
+    "jfs"
+    "minix"
+    "nilfs2"
+    "qnx4"
+    "qnx6"
+    "sysv"
+    "ufs"
   ];
 
   # Restrict ptrace() usage to processes with a pre-defined relationship
@@ -87,4 +110,34 @@ with lib;
   #
   # The value is taken from the KSPP recommendations (Debian uses 4096).
   boot.kernel.sysctl."vm.mmap_min_addr" = mkDefault 65536;
+
+  # Disable ftrace debugging
+  boot.kernel.sysctl."kernel.ftrace_enabled" = mkDefault false;
+
+  # Enable strict reverse path filtering (that is, do not attempt to route
+  # packets that "obviously" do not belong to the iface's network; dropped
+  # packets are logged as martians).
+  boot.kernel.sysctl."net.ipv4.conf.all.log_martians" = mkDefault true;
+  boot.kernel.sysctl."net.ipv4.conf.all.rp_filter" = mkDefault "1";
+  boot.kernel.sysctl."net.ipv4.conf.default.log_martians" = mkDefault true;
+  boot.kernel.sysctl."net.ipv4.conf.default.rp_filter" = mkDefault "1";
+
+  # Ignore broadcast ICMP (mitigate SMURF)
+  boot.kernel.sysctl."net.ipv4.icmp_echo_ignore_broadcasts" = mkDefault true;
+
+  # Ignore incoming ICMP redirects (note: default is needed to ensure that the
+  # setting is applied to interfaces added after the sysctls are set)
+  boot.kernel.sysctl."net.ipv4.conf.all.accept_redirects" = mkDefault false;
+  boot.kernel.sysctl."net.ipv4.conf.all.secure_redirects" = mkDefault false;
+  boot.kernel.sysctl."net.ipv4.conf.default.accept_redirects" = mkDefault false;
+  boot.kernel.sysctl."net.ipv4.conf.default.secure_redirects" = mkDefault false;
+  boot.kernel.sysctl."net.ipv6.conf.all.accept_redirects" = mkDefault false;
+  boot.kernel.sysctl."net.ipv6.conf.default.accept_redirects" = mkDefault false;
+
+  # Ignore outgoing ICMP redirects (this is ipv4 only)
+  boot.kernel.sysctl."net.ipv4.conf.all.send_redirects" = mkDefault false;
+  boot.kernel.sysctl."net.ipv4.conf.default.send_redirects" = mkDefault false;
+
+  # Restrict userfaultfd syscalls to processes with the SYS_PTRACE capability
+  boot.kernel.sysctl."vm.unprivileged_userfaultfd" = mkDefault false;
 }
diff --git a/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix
index 580ea4a58e5b..4596e163404c 100644
--- a/nixos/modules/profiles/installation-device.nix
+++ b/nixos/modules/profiles/installation-device.nix
@@ -31,21 +31,37 @@ with lib;
     # Let the user play Rogue on TTY 8 during the installation.
     #services.rogue.enable = true;
 
-    # Disable some other stuff we don't need.
-    security.sudo.enable = mkDefault false;
-    services.udisks2.enable = mkDefault false;
+    # Use less privileged nixos user
+    users.users.nixos = {
+      isNormalUser = true;
+      extraGroups = [ "wheel" "networkmanager" "video" ];
+      # Allow the graphical user to login without password
+      initialHashedPassword = "";
+    };
+
+    # Allow the user to log in as root without a password.
+    users.users.root.initialHashedPassword = "";
+
+    # Allow passwordless sudo from nixos user
+    security.sudo = {
+      enable = mkDefault true;
+      wheelNeedsPassword = mkForce false;
+    };
 
     # Automatically log in at the virtual consoles.
-    services.mingetty.autologinUser = "root";
+    services.mingetty.autologinUser = "nixos";
 
     # Some more help text.
-    services.mingetty.helpLine =
-      ''
+    services.mingetty.helpLine = ''
+      The "nixos" and "root" accounts have empty passwords.
 
-        The "root" account has an empty password.  ${
-          optionalString config.services.xserver.enable
-            "Type `systemctl start display-manager' to\nstart the graphical user interface."}
-      '';
+      Type `sudo systemctl start sshd` to start the SSH daemon.
+      You then must set a password for either "root" or "nixos"
+      with `passwd` to be able to login.
+    '' + optionalString config.services.xserver.enable ''
+      Type `sudo systemctl start display-manager' to
+      start the graphical user interface.
+    '';
 
     # Allow sshd to be started manually through "systemctl start sshd".
     services.openssh = {
@@ -86,8 +102,5 @@ with lib;
     # because we have the firewall enabled. This makes installs from the
     # console less cumbersome if the machine has a public IP.
     networking.firewall.logRefusedConnections = mkDefault false;
-
-    # Allow the user to log in as root without a password.
-    users.users.root.initialHashedPassword = "";
   };
 }
diff --git a/nixos/modules/profiles/qemu-guest.nix b/nixos/modules/profiles/qemu-guest.nix
index 315d04093b13..0ea70107f717 100644
--- a/nixos/modules/profiles/qemu-guest.nix
+++ b/nixos/modules/profiles/qemu-guest.nix
@@ -1,7 +1,7 @@
 # Common configuration for virtual machines running under QEMU (using
 # virtio).
 
-{ ... }:
+{ lib, ... }:
 
 {
   boot.initrd.availableKernelModules = [ "virtio_net" "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_scsi" "9p" "9pnet_virtio" ];
@@ -15,5 +15,5 @@
       hwclock -s
     '';
 
-  security.rngd.enable = false;
+  security.rngd.enable = lib.mkDefault false;
 }
diff --git a/nixos/modules/programs/adb.nix b/nixos/modules/programs/adb.nix
index 250d8c252a3b..83bcfe886aa1 100644
--- a/nixos/modules/programs/adb.nix
+++ b/nixos/modules/programs/adb.nix
@@ -23,7 +23,8 @@ with lib;
   ###### implementation
   config = mkIf config.programs.adb.enable {
     services.udev.packages = [ pkgs.android-udev-rules ];
-    environment.systemPackages = [ pkgs.androidenv.androidPkgs_9_0.platform-tools ];
+    # Give platform-tools lower priority so mke2fs+friends are taken from other packages first
+    environment.systemPackages = [ (lowPrio pkgs.androidenv.androidPkgs_9_0.platform-tools) ];
     users.groups.adbusers = {};
   };
 }
diff --git a/nixos/modules/programs/atop.nix b/nixos/modules/programs/atop.nix
index 4651cdb76e0b..7ef8d687ca17 100644
--- a/nixos/modules/programs/atop.nix
+++ b/nixos/modules/programs/atop.nix
@@ -30,7 +30,7 @@ in
   };
 
   config = mkIf (cfg.settings != {}) {
-    environment.etc."atoprc".text =
+    environment.etc.atoprc.text =
       concatStrings (mapAttrsToList (n: v: "${n} ${toString v}\n") cfg.settings);
   };
 }
diff --git a/nixos/modules/programs/bandwhich.nix b/nixos/modules/programs/bandwhich.nix
new file mode 100644
index 000000000000..5413044f4614
--- /dev/null
+++ b/nixos/modules/programs/bandwhich.nix
@@ -0,0 +1,29 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let cfg = config.programs.bandwhich;
+in {
+  meta.maintainers = with maintainers; [ filalex77 ];
+
+  options = {
+    programs.bandwhich = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to add bandwhich to the global environment and configure a
+          setcap wrapper for it.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ bandwhich ];
+    security.wrappers.bandwhich = {
+      source = "${pkgs.bandwhich}/bin/bandwhich";
+      capabilities = "cap_net_raw,cap_net_admin+ep";
+    };
+  };
+}
diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index 27b5f9e4b642..366c07c0a352 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/nixos/modules/programs/bash/bash.nix
@@ -34,12 +34,16 @@ let
 
   bashAliases = concatStringsSep "\n" (
     mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
-      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
+      (filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "programs" "bash" "enable" ] "")
+  ];
+
   options = {
 
     programs.bash = {
@@ -98,7 +102,7 @@ in
           if [ "$TERM" != "dumb" -o -n "$INSIDE_EMACS" ]; then
             PROMPT_COLOR="1;31m"
             let $UID && PROMPT_COLOR="1;32m"
-            if [ -n "$INSIDE_EMACS" ]; then
+            if [ -n "$INSIDE_EMACS" -o "$TERM" == "eterm" -o "$TERM" == "eterm-color" ]; then
               # Emacs term mode doesn't support xterm title escape sequence (\e]0;)
               PS1="\n\[\033[$PROMPT_COLOR\][\u@\h:\w]\\$\[\033[0m\] "
             else
@@ -159,7 +163,7 @@ in
 
     };
 
-    environment.etc."profile".text =
+    environment.etc.profile.text =
       ''
         # /etc/profile: DO NOT EDIT -- this file has been generated automatically.
         # This file is read for login shells.
@@ -184,7 +188,7 @@ in
         fi
       '';
 
-    environment.etc."bashrc".text =
+    environment.etc.bashrc.text =
       ''
         # /etc/bashrc: DO NOT EDIT -- this file has been generated automatically.
 
@@ -212,7 +216,7 @@ in
 
     # Configuration for readline in bash. We use "option default"
     # priority to allow user override using both .text and .source.
-    environment.etc."inputrc".source = mkOptionDefault ./inputrc;
+    environment.etc.inputrc.source = mkOptionDefault ./inputrc;
 
     users.defaultUserShell = mkDefault pkgs.bashInteractive;
 
diff --git a/nixos/modules/programs/blcr.nix b/nixos/modules/programs/blcr.nix
deleted file mode 100644
index 804e1d01f12b..000000000000
--- a/nixos/modules/programs/blcr.nix
+++ /dev/null
@@ -1,27 +0,0 @@
-{ config, lib, ... }:
-
-let
-  inherit (lib) mkOption mkIf;
-  cfg = config.environment.blcr;
-  blcrPkg = config.boot.kernelPackages.blcr;
-in
-
-{
-  ###### interface
-
-  options = {
-    environment.blcr.enable = mkOption {
-      default = false;
-      description =
-        "Whether to enable support for the BLCR checkpointing tool.";
-    };
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-    boot.kernelModules = [ "blcr" "blcr_imports" ];
-    boot.extraModulePackages = [ blcrPkg ];
-    environment.systemPackages = [ blcrPkg ];
-  };
-}
diff --git a/nixos/modules/programs/browserpass.nix b/nixos/modules/programs/browserpass.nix
index 47b9d1ccb1f6..e1456d3c1848 100644
--- a/nixos/modules/programs/browserpass.nix
+++ b/nixos/modules/programs/browserpass.nix
@@ -26,16 +26,6 @@ with lib;
       # brave
       "opt/brave/native-messaging-hosts/${appId}".source = source "hosts/chromium";
       "opt/brave/policies/managed/${appId}".source = source "policies/chromium";
-    }
-    # As with the v2 backwards compatibility in the pkgs.browserpass
-    # declaration, this part can be removed once the browser extension
-    # auto-updates to v3 (planned 2019-04-13, see
-    # https://github.com/browserpass/browserpass-native/issues/31)
-    // {
-      "chromium/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json";
-      "chromium/policies/managed/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-policy.json";
-      "opt/chrome/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json";
-      "opt/chrome/policies/managed/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-policy.json";
     };
     nixpkgs.config.firefox.enableBrowserpass = true;
   };
diff --git a/nixos/modules/programs/captive-browser.nix b/nixos/modules/programs/captive-browser.nix
new file mode 100644
index 000000000000..55d474e5c9db
--- /dev/null
+++ b/nixos/modules/programs/captive-browser.nix
@@ -0,0 +1,122 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.captive-browser;
+in
+{
+  ###### interface
+
+  options = {
+    programs.captive-browser = {
+      enable = mkEnableOption "captive browser";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.captive-browser;
+        defaultText = "pkgs.captive-browser";
+        description = "Which package to use for captive-browser";
+      };
+
+      interface = mkOption {
+        type = types.str;
+        description = "your public network interface (wlp3s0, wlan0, eth0, ...)";
+      };
+
+      # the options below are the same as in "captive-browser.toml"
+      browser = mkOption {
+        type = types.str;
+        default = concatStringsSep " " [ ''${pkgs.chromium}/bin/chromium''
+                                         ''--user-data-dir=$HOME/.chromium-captive''
+                                         ''--proxy-server="socks5://$PROXY"''
+                                         ''--host-resolver-rules="MAP * ~NOTFOUND , EXCLUDE localhost"''
+                                         ''--no-first-run''
+                                         ''--new-window''
+                                         ''--incognito''
+                                         ''http://cache.nixos.org/''
+                                       ];
+        description = ''
+          The shell (/bin/sh) command executed once the proxy starts.
+          When browser exits, the proxy exits. An extra env var PROXY is available.
+
+          Here, we use a separate Chrome instance in Incognito mode, so that
+          it can run (and be waited for) alongside the default one, and that
+          it maintains no state across runs. To configure this browser open a
+          normal window in it, settings will be preserved.
+
+          @volth: chromium is to open a plain HTTP (not HTTPS nor redirect to HTTPS!) website.
+                  upstream uses http://example.com but I have seen captive portals whose DNS server resolves "example.com" to 127.0.0.1
+        '';
+      };
+
+      dhcp-dns = mkOption {
+        type = types.str;
+        description = ''
+          The shell (/bin/sh) command executed to obtain the DHCP
+          DNS server address. The first match of an IPv4 regex is used.
+          IPv4 only, because let's be real, it's a captive portal.
+        '';
+      };
+
+      socks5-addr = mkOption {
+        type = types.str;
+        default = "localhost:1666";
+        description = ''the listen address for the SOCKS5 proxy server'';
+      };
+
+      bindInterface = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Binds <package>captive-browser</package> to the network interface declared in
+          <literal>cfg.interface</literal>. This can be used to avoid collisions
+          with private subnets.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    programs.captive-browser.dhcp-dns = mkOptionDefault (
+      if config.networking.networkmanager.enable then
+        "${pkgs.networkmanager}/bin/nmcli dev show ${escapeShellArg cfg.interface} | ${pkgs.gnugrep}/bin/fgrep IP4.DNS"
+      else if config.networking.dhcpcd.enable then
+        "${pkgs.dhcpcd}/bin/dhcpcd -U ${escapeShellArg cfg.interface} | ${pkgs.gnugrep}/bin/fgrep domain_name_servers"
+      else if config.networking.useNetworkd then
+        "${cfg.package}/bin/systemd-networkd-dns ${escapeShellArg cfg.interface}"
+      else
+        "${config.security.wrapperDir}/udhcpc --quit --now -f -i ${escapeShellArg cfg.interface} -O dns --script ${
+            pkgs.writeScript "udhcp-script" ''
+              #!/bin/sh
+              if [ "$1" = bound ]; then
+                echo "$dns"
+              fi
+            ''}"
+    );
+
+    security.wrappers.udhcpc = {
+      capabilities  = "cap_net_raw+p";
+      source        = "${pkgs.busybox}/bin/udhcpc";
+    };
+
+    security.wrappers.captive-browser = {
+      capabilities  = "cap_net_raw+p";
+      source        = pkgs.writeScript "captive-browser" ''
+                        #!${pkgs.bash}/bin/bash
+                        export XDG_CONFIG_HOME=${pkgs.writeTextDir "captive-browser.toml" ''
+                                                  browser = """${cfg.browser}"""
+                                                  dhcp-dns = """${cfg.dhcp-dns}"""
+                                                  socks5-addr = """${cfg.socks5-addr}"""
+                                                  ${optionalString cfg.bindInterface ''
+                                                    bind-device = """${cfg.interface}"""
+                                                  ''}
+                                                ''}
+                        exec ${cfg.package}/bin/captive-browser
+                      '';
+    };
+  };
+}
diff --git a/nixos/modules/programs/clickshare.nix b/nixos/modules/programs/clickshare.nix
new file mode 100644
index 000000000000..9980a7daf525
--- /dev/null
+++ b/nixos/modules/programs/clickshare.nix
@@ -0,0 +1,21 @@
+{ config, lib, pkgs, ... }:
+
+{
+
+  options.programs.clickshare-csc1.enable =
+    lib.options.mkEnableOption ''
+      Barco ClickShare CSC-1 driver/client.
+      This allows users in the <literal>clickshare</literal>
+      group to access and use a ClickShare USB dongle
+      that is connected to the machine
+    '';
+
+  config = lib.modules.mkIf config.programs.clickshare-csc1.enable {
+    environment.systemPackages = [ pkgs.clickshare-csc1 ];
+    services.udev.packages = [ pkgs.clickshare-csc1 ];
+    users.groups.clickshare = {};
+  };
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix
index 9c9765b06b6f..6702e8efd1cb 100644
--- a/nixos/modules/programs/dconf.nix
+++ b/nixos/modules/programs/dconf.nix
@@ -6,7 +6,10 @@ let
   cfg = config.programs.dconf;
 
   mkDconfProfile = name: path:
-    { source = path; target = "dconf/profile/${name}"; };
+    {
+      name = "dconf/profile/${name}";
+      value.source = path; 
+    };
 
 in
 {
@@ -29,16 +32,16 @@ in
   ###### implementation
 
   config = mkIf (cfg.profiles != {} || cfg.enable) {
-    environment.etc = optionals (cfg.profiles != {})
-      (mapAttrsToList mkDconfProfile cfg.profiles);
+    environment.etc = optionalAttrs (cfg.profiles != {})
+      (mapAttrs' mkDconfProfile cfg.profiles);
+
+    services.dbus.packages = [ pkgs.dconf ];
 
-    services.dbus.packages = [ pkgs.gnome3.dconf ];
+    # For dconf executable
+    environment.systemPackages = [ pkgs.dconf ];
 
-    environment.variables.GIO_EXTRA_MODULES = optional cfg.enable
-      "${pkgs.gnome3.dconf.lib}/lib/gio/modules";
-    # https://github.com/NixOS/nixpkgs/pull/31891
-    #environment.variables.XDG_DATA_DIRS = optional cfg.enable
-    #  "$(echo ${pkgs.gnome3.gsettings-desktop-schemas}/share/gsettings-schemas/gsettings-desktop-schemas-*)";
+    # Needed for unwrapped applications
+    environment.variables.GIO_EXTRA_MODULES = mkIf cfg.enable [ "${pkgs.dconf.lib}/lib/gio/modules" ];
   };
 
 }
diff --git a/nixos/modules/programs/environment.nix b/nixos/modules/programs/environment.nix
index 3c6d356ef998..38bdabb4fa81 100644
--- a/nixos/modules/programs/environment.nix
+++ b/nixos/modules/programs/environment.nix
@@ -20,17 +20,16 @@ in
       { NIXPKGS_CONFIG = "/etc/nix/nixpkgs-config.nix";
         PAGER = mkDefault "less -R";
         EDITOR = mkDefault "nano";
-        XCURSOR_PATH = [ "$HOME/.icons" ];
+        XDG_CONFIG_DIRS = [ "/etc/xdg" ]; # needs to be before profile-relative paths to allow changes through environment.etc
       };
 
-    environment.profiles =
-      [ "$HOME/.nix-profile"
-        "/nix/var/nix/profiles/default"
+    environment.profiles = mkAfter
+      [ "/nix/var/nix/profiles/default"
         "/run/current-system/sw"
       ];
 
     # TODO: move most of these elsewhere
-    environment.profileRelativeEnvVars =
+    environment.profileRelativeSessionVariables =
       { PATH = [ "/bin" ];
         INFOPATH = [ "/info" "/share/info" ];
         KDEDIRS = [ "" ];
diff --git a/nixos/modules/services/desktops/gnome3/evince.nix b/nixos/modules/programs/evince.nix
index 5f040a16f067..473fddb09d02 100644
--- a/nixos/modules/services/desktops/gnome3/evince.nix
+++ b/nixos/modules/programs/evince.nix
@@ -6,14 +6,21 @@ with lib;
 
 {
 
+  # Added 2019-08-09
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "gnome3" "evince" "enable" ]
+      [ "programs" "evince" "enable" ])
+  ];
+
   ###### interface
 
   options = {
 
-    services.gnome3.evince = {
+    programs.evince = {
 
       enable = mkEnableOption
-        "systemd and dbus services for Evince, the GNOME document viewer";
+        "Evince, the GNOME document viewer";
 
     };
 
@@ -22,7 +29,7 @@ with lib;
 
   ###### implementation
 
-  config = mkIf config.services.gnome3.evince.enable {
+  config = mkIf config.programs.evince.enable {
 
     environment.systemPackages = [ pkgs.evince ];
 
diff --git a/nixos/modules/services/desktops/gnome3/file-roller.nix b/nixos/modules/programs/file-roller.nix
index 7fb558a98953..64f6a94e7641 100644
--- a/nixos/modules/services/desktops/gnome3/file-roller.nix
+++ b/nixos/modules/programs/file-roller.nix
@@ -6,11 +6,18 @@ with lib;
 
 {
 
+  # Added 2019-08-09
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "gnome3" "file-roller" "enable" ]
+      [ "programs" "file-roller" "enable" ])
+  ];
+
   ###### interface
 
   options = {
 
-    services.gnome3.file-roller = {
+    programs.file-roller = {
 
       enable = mkEnableOption "File Roller, an archive manager for GNOME";
 
@@ -21,7 +28,7 @@ with lib;
 
   ###### implementation
 
-  config = mkIf config.services.gnome3.file-roller.enable {
+  config = mkIf config.programs.file-roller.enable {
 
     environment.systemPackages = [ pkgs.gnome3.file-roller ];
 
diff --git a/nixos/modules/programs/firejail.nix b/nixos/modules/programs/firejail.nix
index 46ee4bc0f7a0..74c3e4425a7c 100644
--- a/nixos/modules/programs/firejail.nix
+++ b/nixos/modules/programs/firejail.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.programs.firejail;
 
-  wrappedBins = pkgs.stdenv.mkDerivation rec {
+  wrappedBins = pkgs.stdenv.mkDerivation {
     name = "firejail-wrapped-binaries";
     nativeBuildInputs = with pkgs; [ makeWrapper ];
     buildCommand = ''
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index 622d2f96fe41..87f6816e4ac0 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -10,7 +10,7 @@ let
 
   fishAliases = concatStringsSep "\n" (
     mapAttrsFlatten (k: v: "alias ${k} ${escapeShellArg v}")
-      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
+      (filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
 in
diff --git a/nixos/modules/programs/fuse.nix b/nixos/modules/programs/fuse.nix
new file mode 100644
index 000000000000..c15896efbb51
--- /dev/null
+++ b/nixos/modules/programs/fuse.nix
@@ -0,0 +1,37 @@
+{ config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.fuse;
+in {
+  meta.maintainers = with maintainers; [ primeos ];
+
+  options.programs.fuse = {
+    mountMax = mkOption {
+      # In the C code it's an "int" (i.e. signed and at least 16 bit), but
+      # negative numbers obviously make no sense:
+      type = types.ints.between 0 32767; # 2^15 - 1
+      default = 1000;
+      description = ''
+        Set the maximum number of FUSE mounts allowed to non-root users.
+      '';
+    };
+
+    userAllowOther = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Allow non-root users to specify the allow_other or allow_root mount
+        options, see mount.fuse3(8).
+      '';
+    };
+  };
+
+  config =  {
+    environment.etc."fuse.conf".text = ''
+      ${optionalString (!cfg.userAllowOther) "#"}user_allow_other
+      mount_max = ${toString cfg.mountMax}
+    '';
+  };
+}
diff --git a/nixos/modules/programs/geary.nix b/nixos/modules/programs/geary.nix
new file mode 100644
index 000000000000..01803bc411e5
--- /dev/null
+++ b/nixos/modules/programs/geary.nix
@@ -0,0 +1,20 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.geary;
+
+in {
+  options = {
+    programs.geary.enable = mkEnableOption "Geary, a Mail client for GNOME 3";
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.gnome3.geary ];
+    programs.dconf.enable = true;
+    services.gnome3.gnome-keyring.enable = true;
+    services.gnome3.gnome-online-accounts.enable = true;
+  };
+}
+
diff --git a/nixos/modules/services/desktops/gnome3/gnome-disks.nix b/nixos/modules/programs/gnome-disks.nix
index 139534cdb892..1cf839a6ddb0 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-disks.nix
+++ b/nixos/modules/programs/gnome-disks.nix
@@ -1,4 +1,4 @@
-# GNOME Disks daemon.
+# GNOME Disks.
 
 { config, pkgs, lib, ... }:
 
@@ -6,17 +6,24 @@ with lib;
 
 {
 
+  # Added 2019-08-09
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "gnome3" "gnome-disks" "enable" ]
+      [ "programs" "gnome-disks" "enable" ])
+  ];
+
   ###### interface
 
   options = {
 
-    services.gnome3.gnome-disks = {
+    programs.gnome-disks = {
 
       enable = mkOption {
         type = types.bool;
         default = false;
         description = ''
-          Whether to enable GNOME Disks daemon, a service designed to
+          Whether to enable GNOME Disks daemon, a program designed to
           be a UDisks2 graphical front-end.
         '';
       };
@@ -28,7 +35,7 @@ with lib;
 
   ###### implementation
 
-  config = mkIf config.services.gnome3.gnome-disks.enable {
+  config = mkIf config.programs.gnome-disks.enable {
 
     environment.systemPackages = [ pkgs.gnome3.gnome-disk-utility ];
 
diff --git a/nixos/modules/services/desktops/gnome3/gnome-documents.nix b/nixos/modules/programs/gnome-documents.nix
index f6efb6684240..bfa3d409ee30 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-documents.nix
+++ b/nixos/modules/programs/gnome-documents.nix
@@ -1,4 +1,4 @@
-# GNOME Documents daemon.
+# GNOME Documents.
 
 { config, pkgs, lib, ... }:
 
@@ -6,17 +6,24 @@ with lib;
 
 {
 
+  # Added 2019-08-09
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "gnome3" "gnome-documents" "enable" ]
+      [ "programs" "gnome-documents" "enable" ])
+  ];
+
   ###### interface
 
   options = {
 
-    services.gnome3.gnome-documents = {
+    programs.gnome-documents = {
 
       enable = mkOption {
         type = types.bool;
         default = false;
         description = ''
-          Whether to enable GNOME Documents services, a document
+          Whether to enable GNOME Documents, a document
           manager application for GNOME.
         '';
       };
@@ -28,7 +35,7 @@ with lib;
 
   ###### implementation
 
-  config = mkIf config.services.gnome3.gnome-documents.enable {
+  config = mkIf config.programs.gnome-documents.enable {
 
     environment.systemPackages = [ pkgs.gnome3.gnome-documents ];
 
diff --git a/nixos/modules/programs/gnome-terminal.nix b/nixos/modules/programs/gnome-terminal.nix
new file mode 100644
index 000000000000..0036677a1576
--- /dev/null
+++ b/nixos/modules/programs/gnome-terminal.nix
@@ -0,0 +1,36 @@
+# GNOME Terminal.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+
+  cfg = config.programs.gnome-terminal;
+
+in
+
+{
+
+  # Added 2019-08-19
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "gnome3" "gnome-terminal-server" "enable" ]
+      [ "programs" "gnome-terminal" "enable" ])
+  ];
+
+  options = {
+
+    programs.gnome-terminal.enable = mkEnableOption "GNOME Terminal";
+
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.gnome3.gnome-terminal ];
+    services.dbus.packages = [ pkgs.gnome3.gnome-terminal ];
+    systemd.packages = [ pkgs.gnome3.gnome-terminal ];
+
+    programs.bash.vteIntegration = true;
+    programs.zsh.vteIntegration = true;
+  };
+}
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index 9618d61a1f5b..7a3cb588ee71 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -6,6 +6,19 @@ let
 
   cfg = config.programs.gnupg;
 
+  xserverCfg = config.services.xserver;
+
+  defaultPinentryFlavor =
+    if xserverCfg.desktopManager.lxqt.enable
+    || xserverCfg.desktopManager.plasma5.enable then
+      "qt"
+    else if xserverCfg.desktopManager.xfce.enable then
+      "gtk2"
+    else if xserverCfg.enable || config.programs.sway.enable then
+      "gnome3"
+    else
+      null;
+
 in
 
 {
@@ -54,6 +67,20 @@ in
       '';
     };
 
+    agent.pinentryFlavor = mkOption {
+      type = types.nullOr (types.enum pkgs.pinentry.flavors);
+      example = "gnome3";
+      description = ''
+        Which pinentry interface to use. If not null, the path to the
+        pinentry binary will be passed to gpg-agent via commandline and
+        thus overrides the pinentry option in gpg-agent.conf in the user's
+        home directory.
+        If not set at all, it'll pick an appropriate flavor depending on the
+        system configuration (qt flavor for lxqt and plasma5, gtk2 for xfce
+        4.12, gnome3 on all other systems with X enabled, ncurses otherwise).
+      '';
+    };
+
     dirmngr.enable = mkOption {
       type = types.bool;
       default = false;
@@ -64,6 +91,16 @@ in
   };
 
   config = mkIf cfg.agent.enable {
+    programs.gnupg.agent.pinentryFlavor = mkDefault defaultPinentryFlavor;
+
+    # This overrides the systemd user unit shipped with the gnupg package
+    systemd.user.services.gpg-agent = mkIf (cfg.agent.pinentryFlavor != null) {
+      serviceConfig.ExecStart = [ "" ''
+        ${cfg.package}/bin/gpg-agent --supervised \
+          --pinentry-program ${pkgs.pinentry.${cfg.agent.pinentryFlavor}}/bin/pinentry
+      '' ];
+    };
+
     systemd.user.sockets.gpg-agent = {
       wantedBy = [ "sockets.target" ];
     };
@@ -84,6 +121,9 @@ in
       wantedBy = [ "sockets.target" ];
     };
 
+    services.dbus.packages = mkIf (cfg.agent.pinentryFlavor == "gnome3") [ pkgs.gcr ];
+
+    environment.systemPackages = with pkgs; [ cfg.package ];
     systemd.packages = [ cfg.package ];
 
     environment.interactiveShellInit = ''
diff --git a/nixos/modules/services/desktops/gnome3/gpaste.nix b/nixos/modules/programs/gpaste.nix
index 5a8258775e0a..4f6deb77e5eb 100644
--- a/nixos/modules/services/desktops/gnome3/gpaste.nix
+++ b/nixos/modules/programs/gpaste.nix
@@ -1,12 +1,20 @@
-# GPaste daemon.
+# GPaste.
 { config, lib, pkgs, ... }:
 
 with lib;
 
 {
+
+  # Added 2019-08-09
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "gnome3" "gpaste" "enable" ]
+      [ "programs" "gpaste" "enable" ])
+  ];
+
   ###### interface
   options = {
-    services.gnome3.gpaste = {
+     programs.gpaste = {
       enable = mkOption {
         type = types.bool;
         default = false;
@@ -18,10 +26,9 @@ with lib;
   };
 
   ###### implementation
-  config = mkIf config.services.gnome3.gpaste.enable {
+  config = mkIf config.programs.gpaste.enable {
     environment.systemPackages = [ pkgs.gnome3.gpaste ];
     services.dbus.packages = [ pkgs.gnome3.gpaste ];
-    services.xserver.desktopManager.gnome3.sessionPath = [ pkgs.gnome3.gpaste ];
     systemd.packages = [ pkgs.gnome3.gpaste ];
   };
 }
diff --git a/nixos/modules/programs/less.nix b/nixos/modules/programs/less.nix
index 9fdf99e9c694..75b3e707d576 100644
--- a/nixos/modules/programs/less.nix
+++ b/nixos/modules/programs/less.nix
@@ -54,8 +54,8 @@ in
         type = types.attrsOf types.str;
         default = {};
         example = {
-          "h" = "noaction 5\e(";
-          "l" = "noaction 5\e)";
+          h = "noaction 5\\e(";
+          l = "noaction 5\\e)";
         };
         description = "Defines new command keys.";
       };
@@ -74,7 +74,7 @@ in
         type = types.attrsOf types.str;
         default = {};
         example = {
-          "\e" = "abort";
+          e = "abort";
         };
         description = "Defines new line-editing keys.";
       };
@@ -111,11 +111,11 @@ in
     environment.systemPackages = [ pkgs.less ];
 
     environment.variables = {
-      "LESSKEY_SYSTEM" = toString lessKey;
+      LESSKEY_SYSTEM = toString lessKey;
     } // optionalAttrs (cfg.lessopen != null) {
-      "LESSOPEN" = cfg.lessopen;
+      LESSOPEN = cfg.lessopen;
     } // optionalAttrs (cfg.lessclose != null) {
-      "LESSCLOSE" = cfg.lessclose;
+      LESSCLOSE = cfg.lessclose;
     };
 
     warnings = optional (
diff --git a/nixos/modules/programs/liboping.nix b/nixos/modules/programs/liboping.nix
new file mode 100644
index 000000000000..4e4c235ccde4
--- /dev/null
+++ b/nixos/modules/programs/liboping.nix
@@ -0,0 +1,22 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.liboping;
+in {
+  options.programs.liboping = {
+    enable = mkEnableOption "liboping";
+  };
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ liboping ];
+    security.wrappers = mkMerge (map (
+      exec: {
+        "${exec}" = {
+          source = "${pkgs.liboping}/bin/${exec}";
+          capabilities = "cap_net_raw+p";
+        };
+      }
+    ) [ "oping" "noping" ]);
+  };
+}
diff --git a/nixos/modules/programs/mtr.nix b/nixos/modules/programs/mtr.nix
index 1fdec4c04f68..75b710c1584f 100644
--- a/nixos/modules/programs/mtr.nix
+++ b/nixos/modules/programs/mtr.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.programs.mtr;
+
 in {
   options = {
     programs.mtr = {
@@ -15,13 +16,22 @@ in {
           setcap wrapper for it.
         '';
       };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.mtr;
+        description = ''
+          The package to use.
+        '';
+      };
     };
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = with pkgs; [ mtr ];
+    environment.systemPackages = with pkgs; [ cfg.package ];
+
     security.wrappers.mtr-packet = {
-      source = "${pkgs.mtr}/bin/mtr-packet";
+      source = "${cfg.package}/bin/mtr-packet";
       capabilities = "cap_net_raw+p";
     };
   };
diff --git a/nixos/modules/programs/nano.nix b/nixos/modules/programs/nano.nix
index 6a4d46338e19..5837dd46d7cd 100644
--- a/nixos/modules/programs/nano.nix
+++ b/nixos/modules/programs/nano.nix
@@ -35,7 +35,7 @@ in
   ###### implementation
 
   config = lib.mkIf (cfg.nanorc != "" || cfg.syntaxHighlight) {
-    environment.etc."nanorc".text = lib.concatStrings [ cfg.nanorc
+    environment.etc.nanorc.text = lib.concatStrings [ cfg.nanorc
       (lib.optionalString cfg.syntaxHighlight ''${LF}include "${pkgs.nano}/share/nano/*.nanorc"'') ];
   };
 
diff --git a/nixos/modules/programs/nm-applet.nix b/nixos/modules/programs/nm-applet.nix
index e42219e9638c..1b806071c43c 100644
--- a/nixos/modules/programs/nm-applet.nix
+++ b/nixos/modules/programs/nm-applet.nix
@@ -10,5 +10,7 @@
       partOf = [ "graphical-session.target" ];
       serviceConfig.ExecStart = "${pkgs.networkmanagerapplet}/bin/nm-applet";
     };
+
+    services.dbus.packages = [ pkgs.gcr ];
   };
 }
diff --git a/nixos/modules/programs/npm.nix b/nixos/modules/programs/npm.nix
index 5fdd4fa841a1..b351d80c7acf 100644
--- a/nixos/modules/programs/npm.nix
+++ b/nixos/modules/programs/npm.nix
@@ -36,7 +36,7 @@ in
   ###### implementation
 
   config = lib.mkIf cfg.enable {
-    environment.etc."npmrc".text = cfg.npmrc;
+    environment.etc.npmrc.text = cfg.npmrc;
 
     environment.variables.NPM_CONFIG_GLOBALCONFIG = "/etc/npmrc";
 
diff --git a/nixos/modules/programs/nylas-mail.nix b/nixos/modules/programs/nylas-mail.nix
deleted file mode 100644
index 08a6cd0a6049..000000000000
--- a/nixos/modules/programs/nylas-mail.nix
+++ /dev/null
@@ -1,36 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.nylas-mail;
-in {
-  ###### interface
-  options = {
-    services.nylas-mail = {
-
-      enable = mkEnableOption ''
-        nylas-mail - Open-source mail client built on the modern web with Electron, React, and Flux
-      '';
-
-      gnome3-keyring = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Enable gnome3 keyring for nylas-mail.";
-      };
-    };
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    environment.systemPackages = [ pkgs.nylas-mail-bin ];
-
-    services.gnome3.gnome-keyring = mkIf cfg.gnome3-keyring {
-      enable = true;
-    };
-
-  };
-}
diff --git a/nixos/modules/programs/oblogout.nix b/nixos/modules/programs/oblogout.nix
index 720c29b1eaee..a039b0623b52 100644
--- a/nixos/modules/programs/oblogout.nix
+++ b/nixos/modules/programs/oblogout.nix
@@ -1,176 +1,11 @@
-# Global configuration for oblogout.
-
 { config, lib, pkgs, ... }:
 
 with lib;
 
-let cfg = config.programs.oblogout;
-
-in
 {
-  ###### interface
-
-  options = {
-
-    programs.oblogout = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to install OBLogout and create <filename>/etc/oblogout.conf</filename>.
-          See <filename>${pkgs.oblogout}/share/doc/README</filename>.
-        '';
-      };
-
-      opacity = mkOption {
-        type = types.int;
-        default = 70;
-        description = ''
-          Opacity percentage of Cairo rendered backgrounds.
-        '';
-      };
-
-      bgcolor = mkOption {
-        type = types.str;
-        default = "black";
-        description = ''
-          Colour name or hex code (#ffffff) of the background color.
-        '';
-      };
-
-      buttontheme = mkOption {
-        type = types.str;
-        default = "simplistic";
-        description = ''
-          Icon theme for the buttons, must be in the themes folder of
-          the package, or in
-          <filename>~/.themes/&lt;name&gt;/oblogout/</filename>.
-        '';
-      };
-
-      buttons = mkOption {
-        type = types.str;
-        default =  "cancel, logout, restart, shutdown, suspend, hibernate";
-        description = ''
-          List and order of buttons to show.
-        '';
-      };
-
-      cancel = mkOption {
-        type = types.str;
-        default =  "Escape";
-        description = ''
-          Cancel logout/shutdown shortcut.
-        '';
-      };
-
-      shutdown = mkOption {
-        type = types.str;
-        default = "S";
-        description = ''
-          Shutdown shortcut.
-        '';
-      };
-
-      restart = mkOption {
-        type = types.str;
-        default = "R";
-        description = ''
-          Restart shortcut.
-        '';
-      };
-
-      suspend = mkOption {
-        type = types.str;
-        default = "U";
-        description = ''
-          Suspend shortcut.
-        '';
-      };
-
-      logout = mkOption {
-        type = types.str;
-        default = "L";
-        description = ''
-          Logout shortcut.
-        '';
-      };
-
-      lock = mkOption {
-        type = types.str;
-        default = "K";
-        description = ''
-          Lock session shortcut.
-        '';
-      };
-
-      hibernate = mkOption {
-        type = types.str;
-        default =  "H";
-        description = ''
-          Hibernate shortcut.
-        '';
-      };
-
-      clogout = mkOption {
-        type = types.str;
-        default = "openbox --exit";
-        description = ''
-          Command to logout.
-        '';
-      };
-
-      clock = mkOption {
-        type = types.str;
-        default = "";
-        description = ''
-          Command to lock screen.
-        '';
-      };
-
-      cswitchuser = mkOption {
-        type = types.str;
-        default = "";
-        description = ''
-          Command to switch user.
-        '';
-      };
-    };
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.oblogout ];
-
-    environment.etc."oblogout.conf".text = ''
-      [settings]
-      usehal = false
-
-      [looks]
-      opacity = ${toString cfg.opacity}
-      bgcolor = ${cfg.bgcolor}
-      buttontheme = ${cfg.buttontheme}
-      buttons = ${cfg.buttons}
 
-      [shortcuts]
-      cancel = ${cfg.cancel}
-      shutdown = ${cfg.shutdown}
-      restart = ${cfg.restart}
-      suspend = ${cfg.suspend}
-      logout = ${cfg.logout}
-      lock = ${cfg.lock}
-      hibernate = ${cfg.hibernate}
+  imports = [
+    (mkRemovedOptionModule [ "programs" "oblogout" ] "programs.oblogout has been removed from NixOS. This is because the oblogout repository has been archived upstream.")
+  ];
 
-      [commands]
-      shutdown = systemctl poweroff
-      restart = systemctl reboot
-      suspend = systemctl suspend
-      hibernate = systemctl hibernate
-      logout = ${cfg.clogout}
-      lock = ${cfg.clock}
-      switchuser = ${cfg.cswitchuser}
-    '';
-  };
 }
diff --git a/nixos/modules/programs/plotinus.nix b/nixos/modules/programs/plotinus.nix
index 065e72d6c374..e3549c79588b 100644
--- a/nixos/modules/programs/plotinus.nix
+++ b/nixos/modules/programs/plotinus.nix
@@ -18,7 +18,7 @@ in
       enable = mkOption {
         default = false;
         description = ''
-          Whether to enable the Plotinus GTK+3 plugin.  Plotinus provides a
+          Whether to enable the Plotinus GTK 3 plugin. Plotinus provides a
           popup (triggered by Ctrl-Shift-P) to search the menus of a
           compatible application.
         '';
diff --git a/nixos/modules/programs/plotinus.xml b/nixos/modules/programs/plotinus.xml
index 902cd89e0c49..8fc8c22c6d76 100644
--- a/nixos/modules/programs/plotinus.xml
+++ b/nixos/modules/programs/plotinus.xml
@@ -13,10 +13,10 @@
   <link xlink:href="https://github.com/p-e-w/plotinus"/>
  </para>
  <para>
-  Plotinus is a searchable command palette in every modern GTK+ application.
+  Plotinus is a searchable command palette in every modern GTK application.
  </para>
  <para>
-  When in a GTK+3 application and Plotinus is enabled, you can press
+  When in a GTK 3 application and Plotinus is enabled, you can press
   <literal>Ctrl+Shift+P</literal> to open the command palette. The command
   palette provides a searchable list of of all menu items in the application.
  </para>
diff --git a/nixos/modules/programs/screen.nix b/nixos/modules/programs/screen.nix
index c1daaa58f16f..728a0eb8cea5 100644
--- a/nixos/modules/programs/screen.nix
+++ b/nixos/modules/programs/screen.nix
@@ -24,9 +24,10 @@ in
   ###### implementation
 
   config = mkIf (cfg.screenrc != "") {
-    environment.etc."screenrc".text = cfg.screenrc;
+    environment.etc.screenrc.text = cfg.screenrc;
 
     environment.systemPackages = [ pkgs.screen ];
+    security.pam.services.screen = {};
   };
 
 }
diff --git a/nixos/modules/programs/seahorse.nix b/nixos/modules/programs/seahorse.nix
new file mode 100644
index 000000000000..b229d2a2c0db
--- /dev/null
+++ b/nixos/modules/programs/seahorse.nix
@@ -0,0 +1,46 @@
+# Seahorse.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+ # Added 2019-08-27
+  imports = [
+    (mkRenamedOptionModule
+      [ "services" "gnome3" "seahorse" "enable" ]
+      [ "programs" "seahorse" "enable" ])
+  ];
+
+
+  ###### interface
+
+  options = {
+
+    programs.seahorse = {
+
+      enable = mkEnableOption "Seahorse, a GNOME application for managing encryption keys and passwords in the GNOME Keyring";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.programs.seahorse.enable {
+
+    programs.ssh.askPassword = mkDefault "${pkgs.gnome3.seahorse}/libexec/seahorse/ssh-askpass";
+
+    environment.systemPackages = [
+      pkgs.gnome3.seahorse
+    ];
+
+    services.dbus.packages = [
+      pkgs.gnome3.seahorse
+    ];
+
+  };
+
+}
diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix
index 8ec4169207db..fc352795c017 100644
--- a/nixos/modules/programs/shadow.nix
+++ b/nixos/modules/programs/shadow.nix
@@ -6,17 +6,27 @@ with lib;
 
 let
 
+  /*
+  There are three different sources for user/group id ranges, each of which gets
+  used by different programs:
+  - The login.defs file, used by the useradd, groupadd and newusers commands
+  - The update-users-groups.pl file, used by NixOS in the activation phase to
+    decide on which ids to use for declaratively defined users without a static
+    id
+  - Systemd compile time options -Dsystem-uid-max= and -Dsystem-gid-max=, used
+    by systemd for features like ConditionUser=@system and systemd-sysusers
+  */
   loginDefs =
     ''
       DEFAULT_HOME yes
 
       SYS_UID_MIN  400
-      SYS_UID_MAX  499
+      SYS_UID_MAX  999
       UID_MIN      1000
       UID_MAX      29999
 
       SYS_GID_MIN  400
-      SYS_GID_MAX  499
+      SYS_GID_MAX  999
       GID_MIN      1000
       GID_MAX      29999
 
@@ -66,22 +76,18 @@ in
         config.users.defaultUserShell;
 
     environment.etc =
-      [ { # /etc/login.defs: global configuration for pwdutils.  You
-          # cannot login without it!
-          source = pkgs.writeText "login.defs" loginDefs;
-          target = "login.defs";
-        }
-
-        { # /etc/default/useradd: configuration for useradd.
-          source = pkgs.writeText "useradd"
-            ''
-              GROUP=100
-              HOME=/home
-              SHELL=${utils.toShellPath config.users.defaultUserShell}
-            '';
-          target = "default/useradd";
-        }
-      ];
+      { # /etc/login.defs: global configuration for pwdutils.  You
+        # cannot login without it!
+        "login.defs".source = pkgs.writeText "login.defs" loginDefs;
+
+        # /etc/default/useradd: configuration for useradd.
+        "default/useradd".source = pkgs.writeText "useradd"
+          ''
+            GROUP=100
+            HOME=/home
+            SHELL=${utils.toShellPath config.users.defaultUserShell}
+          '';
+      };
 
     security.pam.services =
       { chsh = { rootOK = true; };
diff --git a/nixos/modules/programs/shell.nix b/nixos/modules/programs/shell.nix
deleted file mode 100644
index 9842e2bef643..000000000000
--- a/nixos/modules/programs/shell.nix
+++ /dev/null
@@ -1,54 +0,0 @@
-# This module defines a standard configuration for NixOS shells.
-
-{ config, lib, ... }:
-
-with lib;
-
-{
-
-  config = {
-
-    environment.shellInit =
-      ''
-        # Set up the per-user profile.
-        mkdir -m 0755 -p "$NIX_USER_PROFILE_DIR"
-        if [ "$(stat --printf '%u' "$NIX_USER_PROFILE_DIR")" != "$(id -u)" ]; then
-            echo "WARNING: the per-user profile dir $NIX_USER_PROFILE_DIR should belong to user id $(id -u)" >&2
-        fi
-
-        if [ -w "$HOME" ]; then
-          if ! [ -L "$HOME/.nix-profile" ]; then
-              if [ "$USER" != root ]; then
-                  ln -s "$NIX_USER_PROFILE_DIR/profile" "$HOME/.nix-profile"
-              else
-                  # Root installs in the system-wide profile by default.
-                  ln -s /nix/var/nix/profiles/default "$HOME/.nix-profile"
-              fi
-          fi
-
-          # Subscribe the root user to the NixOS channel by default.
-          if [ "$USER" = root -a ! -e "$HOME/.nix-channels" ]; then
-              echo "${config.system.defaultChannel} nixos" > "$HOME/.nix-channels"
-          fi
-
-          # Create the per-user garbage collector roots directory.
-          NIX_USER_GCROOTS_DIR="/nix/var/nix/gcroots/per-user/$USER"
-          mkdir -m 0755 -p "$NIX_USER_GCROOTS_DIR"
-          if [ "$(stat --printf '%u' "$NIX_USER_GCROOTS_DIR")" != "$(id -u)" ]; then
-              echo "WARNING: the per-user gcroots dir $NIX_USER_GCROOTS_DIR should belong to user id $(id -u)" >&2
-          fi
-
-          # Set up a default Nix expression from which to install stuff.
-          if [ ! -e "$HOME/.nix-defexpr" -o -L "$HOME/.nix-defexpr" ]; then
-              rm -f "$HOME/.nix-defexpr"
-              mkdir -p "$HOME/.nix-defexpr"
-              if [ "$USER" != root ]; then
-                  ln -s /nix/var/nix/profiles/per-user/root/channels "$HOME/.nix-defexpr/channels_root"
-              fi
-          fi
-        fi
-      '';
-
-  };
-
-}
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index 46965dd35b71..80198990ed11 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/nixos/modules/programs/ssh.nix
@@ -21,7 +21,7 @@ let
 
   knownHostsText = (flip (concatMapStringsSep "\n") knownHosts
     (h: assert h.hostNames != [];
-      concatStringsSep "," h.hostNames + " "
+      optionalString h.certAuthority "@cert-authority " + concatStringsSep "," h.hostNames + " "
       + (if h.publicKey != null then h.publicKey else readFile h.publicKeyFile)
     )) + "\n";
 
@@ -115,6 +115,16 @@ in
         '';
       };
 
+      agentPKCS11Whitelist = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "\${pkgs.opensc}/lib/opensc-pkcs11.so";
+        description = ''
+          A pattern-list of acceptable paths for PKCS#11 shared libraries
+          that may be used with the -s option to ssh-add.
+        '';
+      };
+
       package = mkOption {
         type = types.package;
         default = pkgs.openssh;
@@ -128,6 +138,14 @@ in
         default = {};
         type = types.loaOf (types.submodule ({ name, ... }: {
           options = {
+            certAuthority = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                This public key is an SSH certificate authority, rather than an
+                individual host's key.
+              '';
+            };
             hostNames = mkOption {
               type = types.listOf types.str;
               default = [];
@@ -227,11 +245,13 @@ in
     systemd.user.services.ssh-agent = mkIf cfg.startAgent
       { description = "SSH Agent";
         wantedBy = [ "default.target" ];
+        unitConfig.ConditionUser = "!@system";
         serviceConfig =
           { ExecStartPre = "${pkgs.coreutils}/bin/rm -f %t/ssh-agent";
             ExecStart =
                 "${cfg.package}/bin/ssh-agent " +
                 optionalString (cfg.agentTimeout != null) ("-t ${cfg.agentTimeout} ") +
+                optionalString (cfg.agentPKCS11Whitelist != null) ("-P ${cfg.agentPKCS11Whitelist} ") +
                 "-a %t/ssh-agent";
             StandardOutput = "null";
             Type = "forking";
diff --git a/nixos/modules/programs/ssmtp.nix b/nixos/modules/programs/ssmtp.nix
index 44756171b74c..f794eac8af00 100644
--- a/nixos/modules/programs/ssmtp.nix
+++ b/nixos/modules/programs/ssmtp.nix
@@ -8,18 +8,21 @@
 with lib;
 
 let
-
-  cfg = config.networking.defaultMailServer;
+  cfg = config.services.ssmtp;
 
 in
-
 {
 
+  imports = [
+    (mkRenamedOptionModule [ "networking" "defaultMailServer" ] [ "services" "ssmtp" ])
+    (mkRenamedOptionModule [ "services" "ssmtp" "directDelivery" ] [ "services" "ssmtp" "enable" ])
+  ];
+
   options = {
 
-    networking.defaultMailServer = {
+    services.ssmtp = {
 
-      directDelivery = mkOption {
+      enable = mkOption {
         type = types.bool;
         default = false;
         description = ''
@@ -29,7 +32,7 @@ in
           <command>sendmail</command> or <command>postfix</command> on
           your machine, set this option to <literal>true</literal>, and
           set the option
-          <option>networking.defaultMailServer.hostName</option> to the
+          <option>services.ssmtp.hostName</option> to the
           host name of your preferred mail server.
         '';
       };
@@ -129,9 +132,9 @@ in
   };
 
 
-  config = mkIf cfg.directDelivery {
+  config = mkIf cfg.enable {
 
-    networking.defaultMailServer.authPassFile = mkIf (cfg.authPass != "")
+    services.ssmtp.authPassFile = mkIf (cfg.authPass != "")
       (mkDefault (toString (pkgs.writeTextFile {
         name = "ssmtp-authpass";
         text = cfg.authPass;
@@ -148,7 +151,7 @@ in
         UseSTARTTLS=${yesNo cfg.useSTARTTLS}
         #Debug=YES
         ${optionalString (cfg.authUser != "")       "AuthUser=${cfg.authUser}"}
-        ${optionalString (!isNull cfg.authPassFile) "AuthPassFile=${cfg.authPassFile}"}
+        ${optionalString (cfg.authPassFile != null) "AuthPassFile=${cfg.authPassFile}"}
       '';
 
     environment.systemPackages = [pkgs.ssmtp];
diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix
index 457faaa3c102..7e646f8737d6 100644
--- a/nixos/modules/programs/sway.nix
+++ b/nixos/modules/programs/sway.nix
@@ -4,26 +4,33 @@ with lib;
 
 let
   cfg = config.programs.sway;
-  swayPackage = pkgs.sway;
 
-  swayWrapped = pkgs.writeShellScriptBin "sway" ''
-    set -o errexit
-
-    if [ ! "$_SWAY_WRAPPER_ALREADY_EXECUTED" ]; then
-      export _SWAY_WRAPPER_ALREADY_EXECUTED=1
-      ${cfg.extraSessionCommands}
-    fi
+  wrapperOptions = types.submodule {
+    options =
+      let
+        mkWrapperFeature  = default: description: mkOption {
+          type = types.bool;
+          inherit default;
+          example = !default;
+          description = "Whether to make use of the ${description}";
+        };
+      in {
+        base = mkWrapperFeature true ''
+          base wrapper to execute extra session commands and prepend a
+          dbus-run-session to the sway command.
+        '';
+        gtk = mkWrapperFeature false ''
+          wrapGAppsHook wrapper to execute sway with required environment
+          variables for GTK applications.
+        '';
+    };
+  };
 
-    if [ "$DBUS_SESSION_BUS_ADDRESS" ]; then
-      export DBUS_SESSION_BUS_ADDRESS
-      exec ${swayPackage}/bin/sway "$@"
-    else
-      exec ${pkgs.dbus}/bin/dbus-run-session ${swayPackage}/bin/sway "$@"
-    fi
-  '';
-  swayJoined = pkgs.symlinkJoin {
-    name = "sway-joined";
-    paths = [ swayWrapped swayPackage ];
+  swayPackage = pkgs.sway.override {
+    extraSessionCommands = cfg.extraSessionCommands;
+    extraOptions = cfg.extraOptions;
+    withBaseWrapper = cfg.wrapperFeatures.base;
+    withGtkWrapper = cfg.wrapperFeatures.gtk;
   };
 in {
   options.programs.sway = {
@@ -35,6 +42,15 @@ in {
       Please have a look at the "extraSessionCommands" example for running
       programs natively under Wayland'';
 
+    wrapperFeatures = mkOption {
+      type = wrapperOptions;
+      default = { };
+      example = { gtk = true; };
+      description = ''
+        Attribute set of features to enable in the wrapper.
+      '';
+    };
+
     extraSessionCommands = mkOption {
       type = types.lines;
       default = "";
@@ -52,11 +68,27 @@ in {
       '';
     };
 
+    extraOptions = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      example = [
+        "--verbose"
+        "--debug"
+        "--unsupported-gpu"
+        "--my-next-gpu-wont-be-nvidia"
+      ];
+      description = ''
+        Command line arguments passed to launch Sway. Please DO NOT report
+        issues if you use an unsupported GPU (proprietary drivers).
+      '';
+    };
+
     extraPackages = mkOption {
       type = with types; listOf package;
       default = with pkgs; [
         swaylock swayidle
-        xwayland rxvt_unicode dmenu
+        xwayland alacritty dmenu
+        rxvt_unicode # For backward compatibility (old default terminal)
       ];
       defaultText = literalExample ''
         with pkgs; [ swaylock swayidle xwayland rxvt_unicode dmenu ];
@@ -75,18 +107,29 @@ in {
   };
 
   config = mkIf cfg.enable {
+    assertions = [
+      {
+        assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base;
+        message = ''
+          The extraSessionCommands for Sway will not be run if
+          wrapperFeatures.base is disabled.
+        '';
+      }
+    ];
     environment = {
-      systemPackages = [ swayJoined ] ++ cfg.extraPackages;
+      systemPackages = [ swayPackage ] ++ cfg.extraPackages;
       etc = {
-        "sway/config".source = "${swayPackage}/etc/sway/config";
-        #"sway/security.d".source = "${swayPackage}/etc/sway/security.d/";
-        #"sway/config.d".source = "${swayPackage}/etc/sway/config.d/";
+        "sway/config".source = mkOptionDefault "${swayPackage}/etc/sway/config";
+        #"sway/security.d".source = mkOptionDefault "${swayPackage}/etc/sway/security.d/";
+        #"sway/config.d".source = mkOptionDefault "${swayPackage}/etc/sway/config.d/";
       };
     };
     security.pam.services.swaylock = {};
     hardware.opengl.enable = mkDefault true;
     fonts.enableDefaultFonts = mkDefault true;
     programs.dconf.enable = mkDefault true;
+    # To make a Sway session available if a display manager like SDDM is enabled:
+    services.xserver.displayManager.sessionPackages = [ swayPackage ];
   };
 
   meta.maintainers = with lib.maintainers; [ gnidorah primeos colemickens ];
diff --git a/nixos/modules/programs/system-config-printer.nix b/nixos/modules/programs/system-config-printer.nix
new file mode 100644
index 000000000000..34592dd7064b
--- /dev/null
+++ b/nixos/modules/programs/system-config-printer.nix
@@ -0,0 +1,32 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    programs.system-config-printer = {
+
+      enable = mkEnableOption "system-config-printer, a Graphical user interface for CUPS administration";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.programs.system-config-printer.enable {
+
+    environment.systemPackages = [
+      pkgs.system-config-printer
+    ];
+
+    services.system-config-printer.enable = true;
+
+  };
+
+}
diff --git a/nixos/modules/programs/thefuck.nix b/nixos/modules/programs/thefuck.nix
index 21ed6603c1bd..b909916158d3 100644
--- a/nixos/modules/programs/thefuck.nix
+++ b/nixos/modules/programs/thefuck.nix
@@ -17,7 +17,7 @@ in
 
         alias = mkOption {
           default = "fuck";
-          type = types.string;
+          type = types.str;
 
           description = ''
             `thefuck` needs an alias to be configured.
diff --git a/nixos/modules/programs/tmux.nix b/nixos/modules/programs/tmux.nix
index 3d5a37274ae2..ed077e3daa76 100644
--- a/nixos/modules/programs/tmux.nix
+++ b/nixos/modules/programs/tmux.nix
@@ -177,7 +177,7 @@ in {
       systemPackages = [ pkgs.tmux ];
 
       variables = {
-        TMUX_TMPDIR = lib.optional cfg.secureSocket ''''${XDG_RUNTIME_DIR:-"/run/user/\$(id -u)"}'';
+        TMUX_TMPDIR = lib.optional cfg.secureSocket ''''${XDG_RUNTIME_DIR:-"/run/user/$(id -u)"}'';
       };
     };
   };
diff --git a/nixos/modules/programs/traceroute.nix b/nixos/modules/programs/traceroute.nix
new file mode 100644
index 000000000000..4eb0be3f0e0b
--- /dev/null
+++ b/nixos/modules/programs/traceroute.nix
@@ -0,0 +1,26 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.traceroute;
+in {
+  options = {
+    programs.traceroute = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to configure a setcap wrapper for traceroute.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    security.wrappers.traceroute = {
+      source = "${pkgs.traceroute}/bin/traceroute";
+      capabilities = "cap_net_raw+p";
+    };
+  };
+}
diff --git a/nixos/modules/programs/tsm-client.nix b/nixos/modules/programs/tsm-client.nix
new file mode 100644
index 000000000000..eb6f12475286
--- /dev/null
+++ b/nixos/modules/programs/tsm-client.nix
@@ -0,0 +1,287 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (builtins) length map;
+  inherit (lib.attrsets) attrNames filterAttrs hasAttr mapAttrs mapAttrsToList optionalAttrs;
+  inherit (lib.modules) mkDefault mkIf;
+  inherit (lib.options) literalExample mkEnableOption mkOption;
+  inherit (lib.strings) concatStringsSep optionalString toLower;
+  inherit (lib.types) addCheck attrsOf lines loaOf nullOr package path port str strMatching submodule;
+
+  # Checks if given list of strings contains unique
+  # elements when compared without considering case.
+  # Type: checkIUnique :: [string] -> bool
+  # Example: checkIUnique ["foo" "Foo"] => false
+  checkIUnique = lst:
+    let
+      lenUniq = l: length (lib.lists.unique l);
+    in
+      lenUniq lst == lenUniq (map toLower lst);
+
+  # TSM rejects servername strings longer than 64 chars.
+  servernameType = strMatching ".{1,64}";
+
+  serverOptions = { name, config, ... }: {
+    options.name = mkOption {
+      type = servernameType;
+      example = "mainTsmServer";
+      description = ''
+        Local name of the IBM TSM server,
+        must be uncapitalized and no longer than 64 chars.
+        The value will be used for the
+        <literal>server</literal>
+        directive in <filename>dsm.sys</filename>.
+      '';
+    };
+    options.server = mkOption {
+      type = strMatching ".+";
+      example = "tsmserver.company.com";
+      description = ''
+        Host/domain name or IP address of the IBM TSM server.
+        The value will be used for the
+        <literal>tcpserveraddress</literal>
+        directive in <filename>dsm.sys</filename>.
+      '';
+    };
+    options.port = mkOption {
+      type = addCheck port (p: p<=32767);
+      default = 1500;  # official default
+      description = ''
+        TCP port of the IBM TSM server.
+        The value will be used for the
+        <literal>tcpport</literal>
+        directive in <filename>dsm.sys</filename>.
+        TSM does not support ports above 32767.
+      '';
+    };
+    options.node = mkOption {
+      type = strMatching ".+";
+      example = "MY-TSM-NODE";
+      description = ''
+        Target node name on the IBM TSM server.
+        The value will be used for the
+        <literal>nodename</literal>
+        directive in <filename>dsm.sys</filename>.
+      '';
+    };
+    options.genPasswd = mkEnableOption ''
+      automatic client password generation.
+      This option influences the
+      <literal>passwordaccess</literal>
+      directive in <filename>dsm.sys</filename>.
+      The password will be stored in the directory
+      given by the option <option>passwdDir</option>.
+      <emphasis>Caution</emphasis>:
+      If this option is enabled and the server forces
+      to renew the password (e.g. on first connection),
+      a random password will be generated and stored
+    '';
+    options.passwdDir = mkOption {
+      type = path;
+      example = "/home/alice/tsm-password";
+      description = ''
+        Directory that holds the TSM
+        node's password information.
+        The value will be used for the
+        <literal>passworddir</literal>
+        directive in <filename>dsm.sys</filename>.
+      '';
+    };
+    options.includeExclude = mkOption {
+      type = lines;
+      default = "";
+      example = ''
+        exclude.dir     /nix/store
+        include.encrypt /home/.../*
+      '';
+      description = ''
+        <literal>include.*</literal> and
+        <literal>exclude.*</literal> directives to be
+        used when sending files to the IBM TSM server.
+        The lines will be written into a file that the
+        <literal>inclexcl</literal>
+        directive in <filename>dsm.sys</filename> points to.
+      '';
+    };
+    options.extraConfig = mkOption {
+      # TSM option keys are case insensitive;
+      # we have to ensure there are no keys that
+      # differ only by upper and lower case.
+      type = addCheck
+        (attrsOf (nullOr str))
+        (attrs: checkIUnique (attrNames attrs));
+      default = {};
+      example.compression = "yes";
+      example.passwordaccess = null;
+      description = ''
+        Additional key-value pairs for the server stanza.
+        Values must be strings, or <literal>null</literal>
+        for the key not to be used in the stanza
+        (e.g. to overrule values generated by other options).
+      '';
+    };
+    options.text = mkOption {
+      type = lines;
+      example = literalExample
+        ''lib.modules.mkAfter "compression no"'';
+      description = ''
+        Additional text lines for the server stanza.
+        This option can be used if certion configuration keys
+        must be used multiple times or ordered in a certain way
+        as the <option>extraConfig</option> option can't
+        control the order of lines in the resulting stanza.
+        Note that the <literal>server</literal>
+        line at the beginning of the stanza is
+        not part of this option's value.
+      '';
+    };
+    options.stanza = mkOption {
+      type = str;
+      internal = true;
+      visible = false;
+      description = "Server stanza text generated from the options.";
+    };
+    config.name = mkDefault name;
+    # Client system-options file directives are explained here:
+    # https://www.ibm.com/support/knowledgecenter/SSEQVQ_8.1.8/client/c_opt_usingopts.html
+    config.extraConfig =
+      mapAttrs (lib.trivial.const mkDefault) (
+        {
+          commmethod = "v6tcpip";  # uses v4 or v6, based on dns lookup result
+          tcpserveraddress = config.server;
+          tcpport = builtins.toString config.port;
+          nodename = config.node;
+          passwordaccess = if config.genPasswd then "generate" else "prompt";
+          passworddir = ''"${config.passwdDir}"'';
+        } // optionalAttrs (config.includeExclude!="") {
+          inclexcl = ''"${pkgs.writeText "inclexcl.dsm.sys" config.includeExclude}"'';
+        }
+      );
+    config.text =
+      let
+        attrset = filterAttrs (k: v: v!=null) config.extraConfig;
+        mkLine = k: v: k + optionalString (v!="") "  ${v}";
+        lines = mapAttrsToList mkLine attrset;
+      in
+        concatStringsSep "\n" lines;
+    config.stanza = ''
+      server  ${config.name}
+      ${config.text}
+    '';
+  };
+
+  options.programs.tsmClient = {
+    enable = mkEnableOption ''
+      IBM Spectrum Protect (Tivoli Storage Manager, TSM)
+      client command line applications with a
+      client system-options file "dsm.sys"
+    '';
+    servers = mkOption {
+      type = loaOf (submodule [ serverOptions ]);
+      default = {};
+      example.mainTsmServer = {
+        server = "tsmserver.company.com";
+        node = "MY-TSM-NODE";
+        extraConfig.compression = "yes";
+      };
+      description = ''
+        Server definitions ("stanzas")
+        for the client system-options file.
+      '';
+    };
+    defaultServername = mkOption {
+      type = nullOr servernameType;
+      default = null;
+      example = "mainTsmServer";
+      description = ''
+        If multiple server stanzas are declared with
+        <option>programs.tsmClient.servers</option>,
+        this option may be used to name a default
+        server stanza that IBM TSM uses in the absence of
+        a user-defined <filename>dsm.opt</filename> file.
+        This option translates to a
+        <literal>defaultserver</literal> configuration line.
+      '';
+    };
+    dsmSysText = mkOption {
+      type = lines;
+      readOnly = true;
+      description = ''
+        This configuration key contains the effective text
+        of the client system-options file "dsm.sys".
+        It should not be changed, but may be
+        used to feed the configuration into other
+        TSM-depending packages used on the system.
+      '';
+    };
+    package = mkOption {
+      type = package;
+      default = pkgs.tsm-client;
+      defaultText = "pkgs.tsm-client";
+      example = literalExample "pkgs.tsm-client-withGui";
+      description = ''
+        The TSM client derivation to be
+        added to the system environment.
+        It will called with <literal>.override</literal>
+        to add paths to the client system-options file.
+      '';
+    };
+    wrappedPackage = mkOption {
+      type = package;
+      readOnly = true;
+      description = ''
+        The TSM client derivation, wrapped with the path
+        to the client system-options file "dsm.sys".
+        This option is to provide the effective derivation
+        for other modules that want to call TSM executables.
+      '';
+    };
+  };
+
+  cfg = config.programs.tsmClient;
+
+  assertions = [
+    {
+      assertion = checkIUnique (mapAttrsToList (k: v: v.name) cfg.servers);
+      message = ''
+        TSM servernames contain duplicate name
+        (note that case doesn't matter!)
+      '';
+    }
+    {
+      assertion = (cfg.defaultServername!=null)->(hasAttr cfg.defaultServername cfg.servers);
+      message = "TSM defaultServername not found in list of servers";
+    }
+  ];
+
+  dsmSysText = ''
+    ****  IBM Spectrum Protect (Tivoli Storage Manager)
+    ****  client system-options file "dsm.sys".
+    ****  Do not edit!
+    ****  This file is generated by NixOS configuration.
+
+    ${optionalString (cfg.defaultServername!=null) "defaultserver  ${cfg.defaultServername}"}
+
+    ${concatStringsSep "\n" (mapAttrsToList (k: v: v.stanza) cfg.servers)}
+  '';
+
+in
+
+{
+
+  inherit options;
+
+  config = mkIf cfg.enable {
+    inherit assertions;
+    programs.tsmClient.dsmSysText = dsmSysText;
+    programs.tsmClient.wrappedPackage = cfg.package.override rec {
+      dsmSysCli = pkgs.writeText "dsm.sys" cfg.dsmSysText;
+      dsmSysApi = dsmSysCli;
+    };
+    environment.systemPackages = [ cfg.wrappedPackage ];
+  };
+
+  meta.maintainers = [ lib.maintainers.yarny ];
+
+}
diff --git a/nixos/modules/programs/usbtop.nix b/nixos/modules/programs/usbtop.nix
new file mode 100644
index 000000000000..c1b6ee38caa1
--- /dev/null
+++ b/nixos/modules/programs/usbtop.nix
@@ -0,0 +1,21 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.usbtop;
+in {
+  options = {
+    programs.usbtop.enable = mkEnableOption "usbtop and required kernel module";
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [
+      usbtop
+    ];
+
+    boot.kernelModules = [
+      "usbmon"
+    ];
+  };
+}
diff --git a/nixos/modules/programs/way-cooler.nix b/nixos/modules/programs/way-cooler.nix
deleted file mode 100644
index f27bd42bd764..000000000000
--- a/nixos/modules/programs/way-cooler.nix
+++ /dev/null
@@ -1,78 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.programs.way-cooler;
-  way-cooler = pkgs.way-cooler;
-
-  wcWrapped = pkgs.writeShellScriptBin "way-cooler" ''
-    ${cfg.extraSessionCommands}
-    exec ${pkgs.dbus}/bin/dbus-run-session ${way-cooler}/bin/way-cooler
-  '';
-  wcJoined = pkgs.symlinkJoin {
-    name = "way-cooler-wrapped";
-    paths = [ wcWrapped way-cooler ];
-  };
-  configFile = readFile "${way-cooler}/etc/way-cooler/init.lua";
-  spawnBar = ''
-    util.program.spawn_at_startup("lemonbar");
-  '';
-in
-{
-  options.programs.way-cooler = {
-    enable = mkEnableOption "way-cooler";
-
-    extraSessionCommands = mkOption {
-      default     = "";
-      type        = types.lines;
-      example = ''
-        export XKB_DEFAULT_LAYOUT=us,de
-        export XKB_DEFAULT_VARIANT=,nodeadkeys
-        export XKB_DEFAULT_OPTIONS=grp:caps_toggle,
-      '';
-      description = ''
-        Shell commands executed just before way-cooler is started.
-      '';
-    };
-
-    extraPackages = mkOption {
-      type = with types; listOf package;
-      default = with pkgs; [
-        westonLite xwayland dmenu
-      ];
-      example = literalExample ''
-        with pkgs; [
-          westonLite xwayland dmenu
-        ]
-      '';
-      description = ''
-        Extra packages to be installed system wide.
-      '';
-    };
-
-    enableBar = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Whether to enable an unofficial bar.
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ wcJoined ] ++ cfg.extraPackages;
-
-    security.pam.services.wc-lock = {};
-    environment.etc."way-cooler/init.lua".text = ''
-      ${configFile}
-      ${optionalString cfg.enableBar spawnBar}
-    '';
-
-    hardware.opengl.enable = mkDefault true;
-    fonts.enableDefaultFonts = mkDefault true;
-    programs.dconf.enable = mkDefault true;
-  };
-
-  meta.maintainers = with maintainers; [ gnidorah ];
-}
diff --git a/nixos/modules/programs/x2goserver.nix b/nixos/modules/programs/x2goserver.nix
new file mode 100644
index 000000000000..7d74231e956b
--- /dev/null
+++ b/nixos/modules/programs/x2goserver.nix
@@ -0,0 +1,149 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.x2goserver;
+
+  defaults = {
+    superenicer = { enable = cfg.superenicer.enable; };
+  };
+  confText = generators.toINI {} (recursiveUpdate defaults cfg.settings);
+  x2goServerConf = pkgs.writeText "x2goserver.conf" confText;
+
+  x2goAgentOptions = pkgs.writeText "x2goagent.options" ''
+    X2GO_NXOPTIONS=""
+    X2GO_NXAGENT_DEFAULT_OPTIONS="${concatStringsSep " " cfg.nxagentDefaultOptions}"
+  '';
+
+in {
+  options.programs.x2goserver = {
+    enable = mkEnableOption "x2goserver" // {
+      description = ''
+        Enables the x2goserver module.
+        NOTE: This will create a good amount of symlinks in `/usr/local/bin`
+      '';
+    };
+
+    superenicer = {
+      enable = mkEnableOption "superenicer" // {
+        description = ''
+          Enables the SupeReNicer code in x2gocleansessions, this will renice
+          suspended sessions to nice level 19 and renice them to level 0 if the
+          session becomes marked as running again
+        '';
+      };
+    };
+
+    nxagentDefaultOptions = mkOption {
+      type = types.listOf types.str;
+      default = [ "-extension GLX" "-nolisten tcp" ];
+      example = [ "-extension GLX" "-nolisten tcp" ];
+      description = ''
+        List of default nx agent options.
+      '';
+    };
+
+    settings = mkOption {
+      type = types.attrsOf types.attrs;
+      default = {};
+      description = ''
+        x2goserver.conf ini configuration as nix attributes. See
+        `x2goserver.conf(5)` for details
+      '';
+      example = literalExample ''
+        superenicer = {
+          "enable" = "yes";
+          "idle-nice-level" = 19;
+        };
+        telekinesis = { "enable" = "no"; };
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+
+    environment.systemPackages = [ pkgs.x2goserver ];
+
+    users.groups.x2go = {};
+    users.users.x2go = {
+      home = "/var/lib/x2go/db";
+      group = "x2go";
+      isSystemUser = true;
+    };
+
+    security.wrappers.x2gosqliteWrapper = {
+      source = "${pkgs.x2goserver}/lib/x2go/libx2go-server-db-sqlite3-wrapper.pl";
+      owner = "x2go";
+      group = "x2go";
+      setgid = true;
+    };
+    security.wrappers.x2goprintWrapper = {
+      source = "${pkgs.x2goserver}/bin/x2goprint";
+      owner = "x2go";
+      group = "x2go";
+      setgid = true;
+    };
+
+    systemd.tmpfiles.rules = with pkgs; [
+      "d /var/lib/x2go/ - x2go x2go - -"
+      "d /var/lib/x2go/db - x2go x2go - -"
+      "d /var/lib/x2go/conf - x2go x2go - -"
+      "d /run/x2go 0755 x2go x2go - -"
+    ] ++
+    # x2goclient sends SSH commands with preset PATH set to
+    # "/usr/local/bin;/usr/bin;/bin". Since we cannot filter arbitrary ssh
+    # commands, we have to make the following executables available.
+    map (f: "L+ /usr/local/bin/${f} - - - - ${x2goserver}/bin/${f}") [
+      "x2goagent" "x2gobasepath" "x2gocleansessions" "x2gocmdexitmessage"
+      "x2godbadmin" "x2gofeature" "x2gofeaturelist" "x2gofm" "x2gogetapps"
+      "x2gogetservers" "x2golistdesktops" "x2golistmounts" "x2golistsessions"
+      "x2golistsessions_root" "x2golistshadowsessions" "x2gomountdirs"
+      "x2gopath" "x2goprint" "x2goresume-desktopsharing" "x2goresume-session"
+      "x2goruncommand" "x2goserver-run-extensions" "x2gosessionlimit"
+      "x2gosetkeyboard" "x2goshowblocks" "x2gostartagent"
+      "x2gosuspend-desktopsharing" "x2gosuspend-session"
+      "x2goterminate-desktopsharing" "x2goterminate-session"
+      "x2goumount-session" "x2goversion"
+    ] ++ [
+      "L+ /usr/local/bin/awk - - - - ${gawk}/bin/awk"
+      "L+ /usr/local/bin/chmod - - - - ${coreutils}/bin/chmod"
+      "L+ /usr/local/bin/cp - - - - ${coreutils}/bin/cp"
+      "L+ /usr/local/bin/sed - - - - ${gnused}/bin/sed"
+      "L+ /usr/local/bin/setsid - - - - ${utillinux}/bin/setsid"
+      "L+ /usr/local/bin/xrandr - - - - ${xorg.xrandr}/bin/xrandr"
+      "L+ /usr/local/bin/xmodmap - - - - ${xorg.xmodmap}/bin/xmodmap"
+    ];
+
+    systemd.services.x2goserver = {
+      description = "X2Go Server Daemon";
+      wantedBy = [ "multi-user.target" ];
+      unitConfig.Documentation = "man:x2goserver.conf(5)";
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${pkgs.x2goserver}/bin/x2gocleansessions";
+        PIDFile = "/run/x2go/x2goserver.pid";
+        User = "x2go";
+        Group = "x2go";
+        RuntimeDirectory = "x2go";
+        StateDirectory = "x2go";
+      };
+      preStart = ''
+        if [ ! -e /var/lib/x2go/setup_ran ]
+        then
+          mkdir -p /var/lib/x2go/conf
+          cp -r ${pkgs.x2goserver}/etc/x2go/* /var/lib/x2go/conf/
+          ln -sf ${x2goServerConf} /var/lib/x2go/conf/x2goserver.conf
+          ln -sf ${x2goAgentOptions} /var/lib/x2go/conf/x2goagent.options
+          ${pkgs.x2goserver}/bin/x2godbadmin --createdb
+          touch /var/lib/x2go/setup_ran
+        fi
+      '';
+    };
+
+    # https://bugs.x2go.org/cgi-bin/bugreport.cgi?bug=276
+    security.sudo.extraConfig = ''
+      Defaults  env_keep+=QT_GRAPHICSSYSTEM
+    '';
+  };
+}
diff --git a/nixos/modules/programs/xfs_quota.nix b/nixos/modules/programs/xfs_quota.nix
index 648fd9a8a94f..c03e59a5b4ab 100644
--- a/nixos/modules/programs/xfs_quota.nix
+++ b/nixos/modules/programs/xfs_quota.nix
@@ -61,7 +61,7 @@ in
         description = "Setup of xfs_quota projects. Make sure the filesystem is mounted with the pquota option.";
 
         example = {
-          "projname" = {
+          projname = {
             id = 50;
             path = "/xfsprojects/projname";
             sizeHardLimit = "50g";
diff --git a/nixos/modules/programs/xonsh.nix b/nixos/modules/programs/xonsh.nix
index ceab9b5db931..1590020f7b64 100644
--- a/nixos/modules/programs/xonsh.nix
+++ b/nixos/modules/programs/xonsh.nix
@@ -26,6 +26,7 @@ in
 
       package = mkOption {
         type = types.package;
+        default = pkgs.xonsh;
         example = literalExample "pkgs.xonsh.override { configFile = \"/path/to/xonshrc\"; }";
         description = ''
           xonsh package to use.
@@ -44,13 +45,13 @@ in
 
   config = mkIf cfg.enable {
 
-    environment.etc."xonshrc".text = cfg.config;
+    environment.etc.xonshrc.text = cfg.config;
 
-    environment.systemPackages = [ pkgs.xonsh ];
+    environment.systemPackages = [ cfg.package ];
 
     environment.shells =
       [ "/run/current-system/sw/bin/xonsh"
-        "${pkgs.xonsh}/bin/xonsh"
+        "${cfg.package}/bin/xonsh"
       ];
 
   };
diff --git a/nixos/modules/programs/xss-lock.nix b/nixos/modules/programs/xss-lock.nix
index c290df01b960..a7ad9b89db4d 100644
--- a/nixos/modules/programs/xss-lock.nix
+++ b/nixos/modules/programs/xss-lock.nix
@@ -8,12 +8,23 @@ in
 {
   options.programs.xss-lock = {
     enable = mkEnableOption "xss-lock";
+
     lockerCommand = mkOption {
       default = "${pkgs.i3lock}/bin/i3lock";
       example = literalExample ''''${pkgs.i3lock-fancy}/bin/i3lock-fancy'';
-      type = types.string;
+      type = types.separatedString " ";
       description = "Locker to be used with xsslock";
     };
+
+    extraOptions = mkOption {
+      default = [ ];
+      example = [ "--ignore-sleep" ];
+      type = types.listOf types.str;
+      description = ''
+        Additional command-line arguments to pass to
+        <command>xss-lock</command>.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
@@ -21,7 +32,13 @@ in
       description = "XSS Lock Daemon";
       wantedBy = [ "graphical-session.target" ];
       partOf = [ "graphical-session.target" ];
-      serviceConfig.ExecStart = "${pkgs.xss-lock}/bin/xss-lock ${cfg.lockerCommand}";
+      serviceConfig.ExecStart = with lib;
+        strings.concatStringsSep " " ([
+            "${pkgs.xss-lock}/bin/xss-lock"
+          ] ++ (map escapeShellArg cfg.extraOptions) ++ [
+            "--"
+            cfg.lockerCommand
+        ]);
     };
   };
 }
diff --git a/nixos/modules/programs/yabar.nix b/nixos/modules/programs/yabar.nix
index db085211366e..5de9331ac520 100644
--- a/nixos/modules/programs/yabar.nix
+++ b/nixos/modules/programs/yabar.nix
@@ -76,7 +76,7 @@ in
             font = mkOption {
               default = "sans bold 9";
               example = "Droid Sans, FontAwesome Bold 9";
-              type = types.string;
+              type = types.str;
 
               description = ''
                 The font that will be used to draw the status bar.
@@ -95,7 +95,7 @@ in
 
             extra = mkOption {
               default = {};
-              type = types.attrsOf types.string;
+              type = types.attrsOf types.str;
 
               description = ''
                 An attribute set which contains further attributes of a bar.
@@ -107,7 +107,7 @@ in
               type = types.attrsOf(types.submodule {
                 options.exec = mkOption {
                   example = "YABAR_DATE";
-                  type = types.string;
+                  type = types.str;
                   description = ''
                      The type of the indicator to be executed.
                   '';
@@ -125,7 +125,7 @@ in
 
                 options.extra = mkOption {
                   default = {};
-                  type = types.attrsOf (types.either types.string types.int);
+                  type = types.attrsOf (types.either types.str types.int);
 
                   description = ''
                     An attribute set which contains further attributes of a indicator.
diff --git a/nixos/modules/programs/zmap.nix b/nixos/modules/programs/zmap.nix
new file mode 100644
index 000000000000..2e27fce4d7c6
--- /dev/null
+++ b/nixos/modules/programs/zmap.nix
@@ -0,0 +1,18 @@
+{ pkgs, config, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.zmap;
+in {
+  options.programs.zmap = {
+    enable = mkEnableOption "ZMap";
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.zmap ];
+
+    environment.etc."zmap/blacklist.conf".source = "${pkgs.zmap}/etc/zmap/blacklist.conf";
+    environment.etc."zmap/zmap.conf".source = "${pkgs.zmap}/etc/zmap.conf";
+  };
+}
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.nix b/nixos/modules/programs/zsh/oh-my-zsh.nix
index f4df4e983e42..932a780a356a 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.nix
+++ b/nixos/modules/programs/zsh/oh-my-zsh.nix
@@ -29,6 +29,13 @@ let
 
 in
   {
+    imports = [
+      (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "enable" ] [ "programs" "zsh" "ohMyZsh" "enable" ])
+      (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "theme" ] [ "programs" "zsh" "ohMyZsh" "theme" ])
+      (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "custom" ] [ "programs" "zsh" "ohMyZsh" "custom" ])
+      (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "plugins" ] [ "programs" "zsh" "ohMyZsh" "plugins" ])
+    ];
+
     options = {
       programs.zsh.ohMyZsh = {
         enable = mkOption {
diff --git a/nixos/modules/programs/zsh/zsh-autosuggestions.nix b/nixos/modules/programs/zsh/zsh-autosuggestions.nix
index ded17f38a618..037888fdc5a8 100644
--- a/nixos/modules/programs/zsh/zsh-autosuggestions.nix
+++ b/nixos/modules/programs/zsh/zsh-autosuggestions.nix
@@ -6,6 +6,10 @@ let
   cfg = config.programs.zsh.autosuggestions;
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "programs" "zsh" "enableAutosuggestions" ] [ "programs" "zsh" "autosuggestions" "enable" ])
+  ];
+
   options.programs.zsh.autosuggestions = {
 
     enable = mkEnableOption "zsh-autosuggestions";
diff --git a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
index 89087a229eb7..927a904369d5 100644
--- a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
+++ b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
@@ -6,6 +6,13 @@ let
   cfg = config.programs.zsh.syntaxHighlighting;
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
+    (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "enable" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
+    (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "highlighters" ] [ "programs" "zsh" "syntaxHighlighting" "highlighters" ])
+    (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "patterns" ] [ "programs" "zsh" "syntaxHighlighting" "patterns" ])
+  ];
+
   options = {
     programs.zsh.syntaxHighlighting = {
       enable = mkEnableOption "zsh-syntax-highlighting";
@@ -33,7 +40,7 @@ in
 
       patterns = mkOption {
         default = {};
-        type = types.attrsOf types.string;
+        type = types.attrsOf types.str;
 
         example = literalExample ''
           {
@@ -50,7 +57,7 @@ in
       };
       styles = mkOption {
         default = {};
-        type = types.attrsOf types.string;
+        type = types.attrsOf types.str;
 
         example = literalExample ''
           {
@@ -81,7 +88,7 @@ in
     ];
 
     programs.zsh.interactiveShellInit = with pkgs;
-      lib.concatStringsSep "\n" ([
+      lib.mkAfter (lib.concatStringsSep "\n" ([
         "source ${zsh-syntax-highlighting}/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh"
       ] ++ optional (length(cfg.highlighters) > 0)
         "ZSH_HIGHLIGHT_HIGHLIGHTERS=(${concatStringsSep " " cfg.highlighters})"
@@ -95,6 +102,6 @@ in
             styles: design:
             "ZSH_HIGHLIGHT_STYLES[${styles}]='${design}'"
           ) cfg.styles)
-      );
+      ));
   };
 }
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index b7117e5f90d7..c66c29ed45fb 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -12,7 +12,7 @@ let
 
   zshAliases = concatStringsSep "\n" (
     mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
-      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
+      (filterAttrs (k: v: v != null) cfg.shellAliases)
   );
 
 in
@@ -69,9 +69,7 @@ in
 
       promptInit = mkOption {
         default = ''
-          if [ "$TERM" != dumb ]; then
-              autoload -U promptinit && promptinit && prompt walters
-          fi
+          autoload -U promptinit && promptinit && prompt walters && setopt prompt_sp
         '';
         description = ''
           Shell script code used to initialise the zsh prompt.
@@ -135,7 +133,7 @@ in
 
     programs.zsh.shellAliases = mapAttrs (name: mkDefault) cfge.shellAliases;
 
-    environment.etc."zshenv".text =
+    environment.etc.zshenv.text =
       ''
         # /etc/zshenv: DO NOT EDIT -- this file has been generated automatically.
         # This file is read for all shells.
@@ -159,7 +157,7 @@ in
         fi
       '';
 
-    environment.etc."zprofile".text =
+    environment.etc.zprofile.text =
       ''
         # /etc/zprofile: DO NOT EDIT -- this file has been generated automatically.
         # This file is read for login shells.
@@ -178,7 +176,7 @@ in
         fi
       '';
 
-    environment.etc."zshrc".text =
+    environment.etc.zshrc.text =
       ''
         # /etc/zshrc: DO NOT EDIT -- this file has been generated automatically.
         # This file is read for interactive shells.
@@ -194,8 +192,6 @@ in
         HISTSIZE=${toString cfg.histSize}
         HISTFILE=${cfg.histFile}
 
-        ${optionalString (cfg.setOptions != []) "setopt ${concatStringsSep " " cfg.setOptions}"}
-
         HELPDIR="${pkgs.zsh}/share/zsh/$ZSH_VERSION/help"
 
         # Tell zsh how to find installed completions
@@ -209,17 +205,27 @@ in
 
         ${cfg.interactiveShellInit}
 
+        ${optionalString (cfg.setOptions != []) "setopt ${concatStringsSep " " cfg.setOptions}"}
+
         ${zshAliases}
 
         ${cfg.promptInit}
 
+        # Need to disable features to support TRAMP
+        if [ "$TERM" = dumb ]; then
+            unsetopt zle prompt_cr prompt_subst
+            unset RPS1 RPROMPT
+            PS1='$ '
+            PROMPT='$ '
+        fi
+
         # Read system-wide modifications.
         if test -f /etc/zshrc.local; then
             . /etc/zshrc.local
         fi
       '';
 
-    environment.etc."zinputrc".source = ./zinputrc;
+    environment.etc.zinputrc.source = ./zinputrc;
 
     environment.systemPackages = [ pkgs.zsh ]
       ++ optional cfg.enableCompletion pkgs.nix-zsh-completions;
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index f6c112d9cfab..26de8a18d922 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -4,238 +4,37 @@ with lib;
 
 {
   imports = [
-    (mkRenamedOptionModule [ "networking" "enableRT73Firmware" ] [ "hardware" "enableRedistributableFirmware" ])
-    (mkRenamedOptionModule [ "networking" "enableIntel3945ABGFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
-    (mkRenamedOptionModule [ "networking" "enableIntel2100BGFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
-    (mkRenamedOptionModule [ "networking" "enableRalinkFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
-    (mkRenamedOptionModule [ "networking" "enableRTL8192cFirmware" ] [ "hardware" "enableRedistributableFirmware" ])
-    (mkRenamedOptionModule [ "networking" "networkmanager" "useDnsmasq" ] [ "networking" "networkmanager" "dns" ])
-    (mkChangedOptionModule [ "services" "printing" "gutenprint" ] [ "services" "printing" "drivers" ]
-      (config:
-        let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config;
-        in if enabled then [ pkgs.gutenprint ] else [ ]))
-    (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" ] "")
-    (mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ])
-    (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" "addons" "dashboard" "enableRBAC" ] [ "services" "kubernetes" "addons" "dashboard" "rbac" "enable" ])
-    (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "address" ] ["services" "kubernetes" "controllerManager" "bindAddress"])
-    (mkRenamedOptionModule [ "services" "kubernetes" "controllerManager" "port" ] ["services" "kubernetes" "controllerManager" "insecurePort"])
-    (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" ])
-    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "applyManifests" ] "")
-    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "")
-    (mkRenamedOptionModule [ "services" "kubernetes" "proxy" "address" ] ["services" "kubernetes" "proxy" "bindAddress"])
-    (mkRemovedOptionModule [ "services" "kubernetes" "verbose" ] "")
-    (mkRenamedOptionModule [ "services" "logstash" "address" ] [ "services" "logstash" "listenAddress" ])
-    (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.")
-    (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.")
-    (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ])
-    (mkRenamedOptionModule [ "services" "vmwareGuest" ] [ "virtualisation" "vmware" "guest" ])
-    (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ])
+    /*
+    This file defines some renaming/removing options for backwards compatibility
 
-    (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ])
-    (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
-
-    (mkRenamedOptionModule [ "services" "clamav" "updater" "config" ] [ "services" "clamav" "updater" "extraConfig" ])
-
-    (mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead")
-    (mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead")
-
-    # PAM
-    (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
-
-    (mkRemovedOptionModule [ "services" "rmilter" "bindInetSockets" ] "Use services.rmilter.bindSocket.* instead")
-    (mkRemovedOptionModule [ "services" "rmilter" "bindUnixSockets" ] "Use services.rmilter.bindSocket.* instead")
-
-    # Xsession script
-    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "job" "logsXsession" ] [ "services" "xserver" "displayManager" "job" "logToFile" ])
-    (mkRenamedOptionModule [ "services" "xserver" "displayManager" "logToJournal" ] [ "services" "xserver" "displayManager" "job" "logToJournal" ])
-
-    # Old Grub-related options.
-    (mkRenamedOptionModule [ "boot" "loader" "grub" "timeout" ] [ "boot" "loader" "timeout" ])
-    (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "timeout" ] [ "boot" "loader" "timeout" ])
-
-    # OpenSSH
-    (mkAliasOptionModule [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ])
-    (mkAliasOptionModule [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ])
-
-    # libvirtd
-    (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ]
-      "Set the option `virtualisation.libvirtd.qemuPackage' instead.")
-
-    # ibus
-    (mkRenamedOptionModule [ "programs" "ibus" "plugins" ] [ "i18n" "inputMethod" "ibus" "engines" ])
-
-    # sandboxing
-    (mkRenamedOptionModule [ "nix" "useChroot" ] [ "nix" "useSandbox" ])
-    (mkRenamedOptionModule [ "nix" "chrootDirs" ] [ "nix" "sandboxPaths" ])
-
-    (mkRenamedOptionModule [ "services" "xserver" "vaapiDrivers" ] [ "hardware" "opengl" "extraPackages" ])
+    It should ONLY be used when the relevant module can't define these imports
+    itself, such as when the module was removed completely.
+    See https://github.com/NixOS/nixpkgs/pull/61570 for explanation
+    */
 
+    # This alias module can't be where _module.check is defined because it would
+    # be added to submodules as well there
     (mkAliasOptionModule [ "environment" "checkConfigurationOptions" ] [ "_module" "check" ])
 
-    # opendkim
-    (mkRenamedOptionModule [ "services" "opendkim" "keyFile" ] [ "services" "opendkim" "keyPath" ])
-
-    # Enlightenment
-    (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "e19" "enable" ] [ "services" "xserver" "desktopManager" "enlightenment" "enable" ])
-
-    # Iodine
-    (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" ] "")
-
-    # Unity3D
-    (mkRenamedOptionModule [ "programs" "unity3d" "enable" ] [ "security" "chromiumSuidSandbox" "enable" ])
-
-    # murmur
-    (mkRenamedOptionModule [ "services" "murmur" "welcome" ] [ "services" "murmur" "welcometext" ])
-
-    # parsoid
-    (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] [ "services" "parsoid" "wikis" ])
-
-    # plexpy / tautulli
-    (mkRenamedOptionModule [ "services" "plexpy" ] [ "services" "tautulli" ])
-
-    # piwik was renamed to matomo
-    (mkRenamedOptionModule [ "services" "piwik" "enable" ] [ "services" "matomo" "enable" ])
-    (mkRenamedOptionModule [ "services" "piwik" "webServerUser" ] [ "services" "matomo" "webServerUser" ])
-    (mkRenamedOptionModule [ "services" "piwik" "phpfpmProcessManagerConfig" ] [ "services" "matomo" "phpfpmProcessManagerConfig" ])
-    (mkRenamedOptionModule [ "services" "piwik" "nginx" ] [ "services" "matomo" "nginx" ])
-
-    # tarsnap
-    (mkRemovedOptionModule [ "services" "tarsnap" "cachedir" ] "Use services.tarsnap.archives.<name>.cachedir")
-
-    # alsa
-    (mkRenamedOptionModule [ "sound" "enableMediaKeys" ] [ "sound" "mediaKeys" "enable" ])
-
-    # postgrey
-    (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; }
-    ))
-
-    # dhcpd
-    (mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ])
-
-    # locate
-    (mkRenamedOptionModule [ "services" "locate" "period" ] [ "services" "locate" "interval" ])
-    (mkRemovedOptionModule [ "services" "locate" "includeStore" ] "Use services.locate.prunePaths" )
-
-    # nfs
-    (mkRenamedOptionModule [ "services" "nfs" "lockdPort" ] [ "services" "nfs" "server" "lockdPort" ])
-    (mkRenamedOptionModule [ "services" "nfs" "statdPort" ] [ "services" "nfs" "server" "statdPort" ])
-
-    # KDE Plasma 5
-    (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "kde5" ] [ "services" "xserver" "desktopManager" "plasma5" ])
-
-    # Fontconfig
-    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowBitmaps" ] [ "fonts" "fontconfig" "allowBitmaps" ])
-    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "allowType1" ] [ "fonts" "fontconfig" "allowType1" ])
-    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "useEmbeddedBitmaps" ] [ "fonts" "fontconfig" "useEmbeddedBitmaps" ])
-    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "forceAutohint" ] [ "fonts" "fontconfig" "forceAutohint" ])
-    (mkRenamedOptionModule [ "fonts" "fontconfig" "ultimate" "renderMonoTTFAsBitmap" ] [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ])
-
-    # postgresqlBackup
-    (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>.
-    '')
-
-    # Profile splitting
-    (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ])
-
-    # misc/version.nix
-    (mkRenamedOptionModule [ "system" "nixosVersion" ] [ "system" "nixos" "version" ])
-    (mkRenamedOptionModule [ "system" "nixosVersionSuffix" ] [ "system" "nixos" "versionSuffix" ])
-    (mkRenamedOptionModule [ "system" "nixosRevision" ] [ "system" "nixos" "revision" ])
-    (mkRenamedOptionModule [ "system" "nixosLabel" ] [ "system" "nixos" "label" ])
-
-    # Users
-    (mkAliasOptionModule [ "users" "extraUsers" ] [ "users" "users" ])
-    (mkAliasOptionModule [ "users" "extraGroups" ] [ "users" "groups" ])
-
-    # Options that are obsolete and have no replacement.
-    (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
-    (mkRemovedOptionModule [ "programs" "bash" "enable" ] "")
-    (mkRemovedOptionModule [ "services" "samba" "defaultShare" ] "")
-    (mkRemovedOptionModule [ "services" "syslog-ng" "serviceName" ] "")
-    (mkRemovedOptionModule [ "services" "syslog-ng" "listenToJournal" ] "")
-    (mkRemovedOptionModule [ "ec2" "metadata" ] "")
-    (mkRemovedOptionModule [ "services" "openvpn" "enable" ] "")
-    (mkRemovedOptionModule [ "services" "printing" "cupsFilesConf" ] "")
-    (mkRemovedOptionModule [ "services" "printing" "cupsdConf" ] "")
-    (mkRemovedOptionModule [ "services" "tor" "relay" "isBridge" ] "Use services.tor.relay.role instead.")
-    (mkRemovedOptionModule [ "services" "tor" "relay" "isExit" ] "Use services.tor.relay.role instead.")
-    (mkRemovedOptionModule [ "services" "xserver" "startGnuPGAgent" ]
-      "See the 16.09 release notes for more information.")
-    (mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "")
-    (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "")
+    # Completely removed modules
     (mkRemovedOptionModule [ "services" "firefox" "syncserver" "user" ] "")
     (mkRemovedOptionModule [ "services" "firefox" "syncserver" "group" ] "")
-    (mkRemovedOptionModule [ "fonts" "fontconfig" "hinting" "style" ] "")
-    (mkRemovedOptionModule [ "services" "xserver" "displayManager" "sddm" "themes" ]
-      "Set the option `services.xserver.displayManager.sddm.package' instead.")
-    (mkRemovedOptionModule [ "services" "xserver" "desktopManager" "xfce" "screenLock" ] "")
-    (mkRemovedOptionModule [ "fonts" "fontconfig" "forceAutohint" ] "")
-    (mkRemovedOptionModule [ "fonts" "fontconfig" "renderMonoTTFAsBitmap" ] "")
-    (mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.")
-    (mkRemovedOptionModule [ "services" "logstash" "enableWeb" ] "The web interface was removed from logstash")
-    (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.")
-
-    # ZSH
-    (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
-    (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "enable" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
-    (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "highlighters" ] [ "programs" "zsh" "syntaxHighlighting" "highlighters" ])
-    (mkRenamedOptionModule [ "programs" "zsh" "syntax-highlighting" "patterns" ] [ "programs" "zsh" "syntaxHighlighting" "patterns" ])
-    (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "enable" ] [ "programs" "zsh" "ohMyZsh" "enable" ])
-    (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "theme" ] [ "programs" "zsh" "ohMyZsh" "theme" ])
-    (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "custom" ] [ "programs" "zsh" "ohMyZsh" "custom" ])
-    (mkRenamedOptionModule [ "programs" "zsh" "oh-my-zsh" "plugins" ] [ "programs" "zsh" "ohMyZsh" "plugins" ])
-
-    (mkRenamedOptionModule [ "programs" "zsh" "enableAutosuggestions" ] [ "programs" "zsh" "autosuggestions" "enable" ])
-
-    # Xen
-    (mkRenamedOptionModule [ "virtualisation" "xen" "qemu-package" ] [ "virtualisation" "xen" "package-qemu" ])
-
-    (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ])
-    (mkRenamedOptionModule [ "programs" "man"  "enable" ] [ "documentation" "man"  "enable" ])
-    (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ])
-
-    # ckb
-    (mkRenamedOptionModule [ "hardware" "ckb" "enable" ] [ "hardware" "ckb-next" "enable" ])
-    (mkRenamedOptionModule [ "hardware" "ckb" "package" ] [ "hardware" "ckb-next" "package" ])
+    (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.")
+    (mkRemovedOptionModule [ "networking" "vpnc" ] "Use environment.etc.\"vpnc/service.conf\" instead.")
+    (mkRemovedOptionModule [ "environment.blcr.enable" ] "The BLCR module has been removed")
+    (mkRemovedOptionModule [ "services.beegfsEnable" ] "The BeeGFS module has been removed")
+    (mkRemovedOptionModule [ "services.beegfs" ] "The BeeGFS module has been removed")
+    (mkRemovedOptionModule [ "services.osquery" ] "The osquery module has been removed")
+    (mkRemovedOptionModule [ "services.fourStore" ] "The fourStore module has been removed")
+    (mkRemovedOptionModule [ "services.fourStoreEndpoint" ] "The fourStoreEndpoint module has been removed")
+    (mkRemovedOptionModule [ "programs" "way-cooler" ] ("way-cooler is abandoned by its author: " +
+      "https://way-cooler.org/blog/2020/01/09/way-cooler-post-mortem.html"))
+    (mkRemovedOptionModule [ "services" "xserver" "multitouch" ] ''
+      services.xserver.multitouch (which uses xf86_input_mtrack) has been removed
+      as the underlying package isn't being maintained. Working alternatives are
+      libinput and synaptics.
+    '')
 
-  ] ++ (flip map [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
-                   "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
-                   "snmpExporter" "unifiExporter" "varnishExporter" ]
-       (opt: mkRemovedOptionModule [ "services" "prometheus" "${opt}" ] ''
-         The prometheus exporters are now configured using `services.prometheus.exporters'.
-         See the 18.03 release notes for more information.
-       '' ));
+    # Do NOT add any option renames here, see top of the file
+  ];
 }
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index 092704c6fc3f..890c421b0ea9 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -20,6 +20,16 @@ let
         '';
       };
 
+      server = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          ACME Directory Resource URI. Defaults to let's encrypt
+          production endpoint,
+          https://acme-v02.api.letsencrypt.org/directory, if unset.
+        '';
+      };
+
       domain = mkOption {
         type = types.str;
         default = name;
@@ -69,9 +79,9 @@ let
       plugins = mkOption {
         type = types.listOf (types.enum [
           "cert.der" "cert.pem" "chain.pem" "external.sh"
-          "fullchain.pem" "full.pem" "key.der" "key.pem" "account_key.json"
+          "fullchain.pem" "full.pem" "key.der" "key.pem" "account_key.json" "account_reg.json"
         ]);
-        default = [ "fullchain.pem" "full.pem" "key.pem" "account_key.json" ];
+        default = [ "fullchain.pem" "full.pem" "key.pem" "account_key.json" "account_reg.json" ];
         description = ''
           Plugins to enable. With default settings simp_le will
           store public certificate bundle in <filename>fullchain.pem</filename>,
@@ -80,25 +90,11 @@ let
         '';
       };
 
-      activationDelay = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Systemd time span expression to delay copying new certificates to main
-          state directory. See <citerefentry><refentrytitle>systemd.time</refentrytitle>
-          <manvolnum>7</manvolnum></citerefentry>.
-        '';
-      };
-
-      preDelay = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Commands to run after certificates are re-issued but before they are
-          activated. Typically the new certificate is published to DNS.
-
-          Executed in the same directory with the new certificate.
-        '';
+      directory = mkOption {
+        type = types.str;
+        readOnly = true;
+        default = "/var/lib/acme/${name}";
+        description = "Directory where certificate and other state is stored.";
       };
 
       extraDomains = mkOption {
@@ -123,16 +119,20 @@ in
 {
 
   ###### interface
-
+  imports = [
+    (mkRemovedOptionModule [ "security" "acme" "production" ] ''
+      Use security.acme.server to define your staging ACME server URL instead.
+
+      To use the let's encrypt staging server, use security.acme.server =
+      "https://acme-staging-v02.api.letsencrypt.org/directory".
+    ''
+    )
+    (mkRemovedOptionModule [ "security" "acme" "directory"] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.")
+    (mkRemovedOptionModule [ "security" "acme" "preDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal")
+    (mkRemovedOptionModule [ "security" "acme" "activationDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal")
+  ];
   options = {
     security.acme = {
-      directory = mkOption {
-        default = "/var/lib/acme";
-        type = types.str;
-        description = ''
-          Directory where certs and other state will be stored by default.
-        '';
-      };
 
       validMin = mkOption {
         type = types.int;
@@ -150,6 +150,16 @@ in
         '';
       };
 
+      server = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          ACME Directory Resource URI. Defaults to let's encrypt
+          production endpoint,
+          <literal>https://acme-v02.api.letsencrypt.org/directory</literal>, if unset.
+        '';
+      };
+
       preliminarySelfsigned = mkOption {
         type = types.bool;
         default = true;
@@ -163,25 +173,15 @@ in
         '';
       };
 
-      production = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          If set to true, use Let's Encrypt's production environment
-          instead of the staging environment. The main benefit of the
-          staging environment is to get much higher rate limits.
-
-          See
-          <literal>https://letsencrypt.org/docs/staging-environment</literal>
-          for more detail.
-        '';
-      };
-
       certs = mkOption {
         default = { };
         type = with types; attrsOf (submodule certOpts);
         description = ''
-          Attribute set of certificates to get signed and renewed.
+          Attribute set of certificates to get signed and renewed. Creates
+          <literal>acme-''${cert}.{service,timer}</literal> systemd units for
+          each certificate defined here. Other services can add dependencies
+          to those units if they rely on the certificates being present,
+          or trigger restarts of the service if certificates get renewed.
         '';
         example = literalExample ''
           {
@@ -209,94 +209,52 @@ in
           servicesLists = mapAttrsToList certToServices cfg.certs;
           certToServices = cert: data:
               let
-                cpath = lpath + optionalString (data.activationDelay != null) ".staging";
-                lpath = "${cfg.directory}/${cert}";
+                lpath = "acme/${cert}";
                 rights = if data.allowKeysForGroup then "750" else "700";
                 cmdline = [ "-v" "-d" data.domain "--default_root" data.webroot "--valid_min" cfg.validMin ]
                           ++ optionals (data.email != null) [ "--email" data.email ]
                           ++ concatMap (p: [ "-f" p ]) data.plugins
                           ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains)
-                          ++ optionals (!cfg.production) ["--server" "https://acme-staging.api.letsencrypt.org/directory"];
+                          ++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)];
                 acmeService = {
                   description = "Renew ACME Certificate for ${cert}";
                   after = [ "network.target" "network-online.target" ];
                   wants = [ "network-online.target" ];
+                  # simp_le uses requests, which uses certifi under the hood,
+                  # which doesn't respect the system trust store.
+                  # At least in the acme test, we provision a fake CA, impersonating the LE endpoint.
+                  # REQUESTS_CA_BUNDLE is a way to teach python requests to use something else
+                  environment.REQUESTS_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
                   serviceConfig = {
                     Type = "oneshot";
+                    # With RemainAfterExit the service is considered active even
+                    # after the main process having exited, which means when it
+                    # gets changed, the activation phase restarts it, meaning
+                    # the permissions of the StateDirectory get adjusted
+                    # according to the specified group
+                    RemainAfterExit = true;
                     SuccessExitStatus = [ "0" "1" ];
-                    PermissionsStartOnly = true;
                     User = data.user;
                     Group = data.group;
                     PrivateTmp = true;
+                    StateDirectory = lpath;
+                    StateDirectoryMode = rights;
+                    WorkingDirectory = "/var/lib/${lpath}";
+                    ExecStart = "${pkgs.simp_le}/bin/simp_le ${escapeShellArgs cmdline}";
+                    ExecStartPost =
+                      let
+                        script = pkgs.writeScript "acme-post-start" ''
+                          #!${pkgs.runtimeShell} -e
+                          ${data.postRun}
+                        '';
+                      in
+                        "+${script}";
                   };
-                  path = with pkgs; [ simp_le systemd ];
-                  preStart = ''
-                    mkdir -p '${cfg.directory}'
-                    chown 'root:root' '${cfg.directory}'
-                    chmod 755 '${cfg.directory}'
-                    if [ ! -d '${cpath}' ]; then
-                      mkdir '${cpath}'
-                    fi
-                    chmod ${rights} '${cpath}'
-                    chown -R '${data.user}:${data.group}' '${cpath}'
-                    mkdir -p '${data.webroot}/.well-known/acme-challenge'
-                    chown -R '${data.user}:${data.group}' '${data.webroot}/.well-known/acme-challenge'
-                  '';
-                  script = ''
-                    cd '${cpath}'
-                    set +e
-                    simp_le ${escapeShellArgs cmdline}
-                    EXITCODE=$?
-                    set -e
-                    echo "$EXITCODE" > /tmp/lastExitCode
-                    exit "$EXITCODE"
-                  '';
-                  postStop = ''
-                    cd '${cpath}'
-
-                    if [ -e /tmp/lastExitCode ] && [ "$(cat /tmp/lastExitCode)" = "0" ]; then
-                      ${if data.activationDelay != null then ''
-
-                      ${data.preDelay}
-
-                      if [ -d '${lpath}' ]; then
-                        systemd-run --no-block --on-active='${data.activationDelay}' --unit acme-setlive-${cert}.service
-                      else
-                        systemctl --wait start acme-setlive-${cert}.service
-                      fi
-                      '' else data.postRun}
-
-                      # noop ensuring that the "if" block is non-empty even if
-                      # activationDelay == null and postRun == ""
-                      true
-                    fi
-                  '';
-
-                  before = [ "acme-certificates.target" ];
-                  wantedBy = [ "acme-certificates.target" ];
-                };
-                delayService = {
-                  description = "Set certificate for ${cert} live";
-                  path = with pkgs; [ rsync ];
-                  serviceConfig = {
-                    Type = "oneshot";
-                  };
-                  script = ''
-                    rsync -a --delete-after '${cpath}/' '${lpath}'
-                  '';
-                  postStop = data.postRun;
+
                 };
                 selfsignedService = {
                   description = "Create preliminary self-signed certificate for ${cert}";
                   path = [ pkgs.openssl ];
-                  preStart = ''
-                      if [ ! -d '${cpath}' ]
-                      then
-                        mkdir -p '${cpath}'
-                        chmod ${rights} '${cpath}'
-                        chown '${data.user}:${data.group}' '${cpath}'
-                      fi
-                  '';
                   script =
                     ''
                       workdir="$(mktemp -d)"
@@ -318,50 +276,41 @@ in
                         -out $workdir/server.crt
 
                       # Copy key to destination
-                      cp $workdir/server.key ${cpath}/key.pem
+                      cp $workdir/server.key /var/lib/${lpath}/key.pem
 
                       # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates)
-                      cat $workdir/{server.crt,ca.crt} > "${cpath}/fullchain.pem"
+                      cat $workdir/{server.crt,ca.crt} > "/var/lib/${lpath}/fullchain.pem"
 
                       # Create full.pem for e.g. lighttpd
-                      cat $workdir/{server.key,server.crt,ca.crt} > "${cpath}/full.pem"
+                      cat $workdir/{server.key,server.crt,ca.crt} > "/var/lib/${lpath}/full.pem"
 
                       # Give key acme permissions
-                      chown '${data.user}:${data.group}' "${cpath}/"{key,fullchain,full}.pem
-                      chmod ${rights} "${cpath}/"{key,fullchain,full}.pem
+                      chown '${data.user}:${data.group}' "/var/lib/${lpath}/"{key,fullchain,full}.pem
+                      chmod ${rights} "/var/lib/${lpath}/"{key,fullchain,full}.pem
                     '';
                   serviceConfig = {
                     Type = "oneshot";
-                    PermissionsStartOnly = true;
                     PrivateTmp = true;
+                    StateDirectory = lpath;
                     User = data.user;
                     Group = data.group;
                   };
                   unitConfig = {
                     # Do not create self-signed key when key already exists
-                    ConditionPathExists = "!${cpath}/key.pem";
+                    ConditionPathExists = "!/var/lib/${lpath}/key.pem";
                   };
-                  before = [
-                    "acme-selfsigned-certificates.target"
-                  ];
-                  wantedBy = [
-                    "acme-selfsigned-certificates.target"
-                  ];
                 };
               in (
                 [ { name = "acme-${cert}"; value = acmeService; } ]
                 ++ optional cfg.preliminarySelfsigned { name = "acme-selfsigned-${cert}"; value = selfsignedService; }
-                ++ optional (data.activationDelay != null) { name = "acme-setlive-${cert}"; value = delayService; }
               );
           servicesAttr = listToAttrs services;
-          injectServiceDep = {
-            after = [ "acme-selfsigned-certificates.target" ];
-            wants = [ "acme-selfsigned-certificates.target" "acme-certificates.target" ];
-          };
         in
-          servicesAttr //
-          (if config.services.nginx.enable then { nginx = injectServiceDep; } else {}) //
-          (if config.services.lighttpd.enable then { lighttpd = injectServiceDep; } else {});
+          servicesAttr;
+
+      systemd.tmpfiles.rules =
+        flip mapAttrsToList cfg.certs
+        (cert: data: "d ${data.webroot}/.well-known/acme-challenge - ${data.user} ${data.group}");
 
       systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair
         ("acme-${cert}")
@@ -378,8 +327,8 @@ in
         })
       );
 
-      systemd.targets."acme-selfsigned-certificates" = mkIf cfg.preliminarySelfsigned {};
-      systemd.targets."acme-certificates" = {};
+      systemd.targets.acme-selfsigned-certificates = mkIf cfg.preliminarySelfsigned {};
+      systemd.targets.acme-certificates = {};
     })
 
   ];
diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml
index ef71fe53d0c7..9d0a1995e0ff 100644
--- a/nixos/modules/security/acme.xml
+++ b/nixos/modules/security/acme.xml
@@ -59,10 +59,8 @@ http {
   <para>
    The private key <filename>key.pem</filename> and certificate
    <filename>fullchain.pem</filename> will be put into
-   <filename>/var/lib/acme/foo.example.com</filename>. The target directory can
-   be configured with the option <xref linkend="opt-security.acme.directory"/>.
+   <filename>/var/lib/acme/foo.example.com</filename>.
   </para>
-
   <para>
    Refer to <xref linkend="ch-options" /> for all available configuration
    options for the <link linkend="opt-security.acme.certs">security.acme</link>
diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix
index 498c2f25d1c0..3c93f5440ab5 100644
--- a/nixos/modules/security/apparmor-suid.nix
+++ b/nixos/modules/security/apparmor-suid.nix
@@ -4,6 +4,9 @@ let
 in
 with lib;
 {
+  imports = [
+    (mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ])
+  ];
 
   options.security.apparmor.confineSUIDApplications = mkOption {
     default = true;
diff --git a/nixos/modules/security/apparmor.nix b/nixos/modules/security/apparmor.nix
index d323a158a4df..cfc65b347bc6 100644
--- a/nixos/modules/security/apparmor.nix
+++ b/nixos/modules/security/apparmor.nix
@@ -29,11 +29,18 @@ in
    config = mkIf cfg.enable {
      environment.systemPackages = [ pkgs.apparmor-utils ];
 
+     boot.kernelParams = [ "apparmor=1" "security=apparmor" ];
+
      systemd.services.apparmor = let
        paths = concatMapStrings (s: " -I ${s}/etc/apparmor.d")
          ([ pkgs.apparmor-profiles ] ++ cfg.packages);
      in {
-       wantedBy = [ "local-fs.target" ];
+       after = [ "local-fs.target" ];
+       before = [ "sysinit.target" ];
+       wantedBy = [ "multi-user.target" ];
+       unitConfig = {
+         DefaultDependencies = "no";
+       };
        serviceConfig = {
          Type = "oneshot";
          RemainAfterExit = "yes";
@@ -43,6 +50,9 @@ in
          ExecStop = map (p:
            ''${pkgs.apparmor-parser}/bin/apparmor_parser -Rv "${p}"''
          ) cfg.profiles;
+         ExecReload = map (p:
+           ''${pkgs.apparmor-parser}/bin/apparmor_parser --reload ${paths} "${p}"''
+         ) cfg.profiles;
        };
      };
    };
diff --git a/nixos/modules/security/auditd.nix b/nixos/modules/security/auditd.nix
index 6abac244dac2..9d26cfbcfb10 100644
--- a/nixos/modules/security/auditd.nix
+++ b/nixos/modules/security/auditd.nix
@@ -6,6 +6,10 @@ with lib;
   options.security.auditd.enable = mkEnableOption "the Linux Audit daemon";
 
   config = mkIf config.security.auditd.enable {
+    boot.kernelParams = [ "audit=1" ];
+
+    environment.systemPackages = [ pkgs.audit ];
+
     systemd.services.auditd = {
       description = "Linux Audit daemon";
       wantedBy = [ "basic.target" ];
diff --git a/nixos/modules/security/chromium-suid-sandbox.nix b/nixos/modules/security/chromium-suid-sandbox.nix
index be6acb3f1f53..b83dbc4202a8 100644
--- a/nixos/modules/security/chromium-suid-sandbox.nix
+++ b/nixos/modules/security/chromium-suid-sandbox.nix
@@ -7,6 +7,10 @@ let
   sandbox = pkgs.chromium.sandbox;
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "programs" "unity3d" "enable" ] [ "security" "chromiumSuidSandbox" "enable" ])
+  ];
+
   options.security.chromiumSuidSandbox.enable = mkOption {
     type = types.bool;
     default = false;
@@ -24,6 +28,6 @@ in
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ sandbox ];
-    security.wrappers."${sandbox.passthru.sandboxExecutableName}".source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}";
+    security.wrappers.${sandbox.passthru.sandboxExecutableName}.source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}";
   };
 }
diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix
index 997328ad9e6a..78a82b7154e7 100644
--- a/nixos/modules/security/duosec.nix
+++ b/nixos/modules/security/duosec.nix
@@ -25,19 +25,21 @@ let
     accept_env_factor=${boolToStr cfg.acceptEnvFactor}
   '';
 
-  loginCfgFile = optional cfg.ssh.enable
-    { source = pkgs.writeText "login_duo.conf" configFileLogin;
-      mode   = "0600";
-      user   = "sshd";
-      target = "duo/login_duo.conf";
-    };
+  loginCfgFile = optionalAttrs cfg.ssh.enable {
+    "duo/login_duo.conf" =
+      { source = pkgs.writeText "login_duo.conf" configFileLogin;
+        mode   = "0600";
+        user   = "sshd";
+      };
+  };
 
-  pamCfgFile = optional cfg.pam.enable
-    { source = pkgs.writeText "pam_duo.conf" configFilePam;
-      mode   = "0600";
-      user   = "sshd";
-      target = "duo/pam_duo.conf";
-    };
+  pamCfgFile = optional cfg.pam.enable {
+    "duo/pam_duo.conf" =
+      { source = pkgs.writeText "pam_duo.conf" configFilePam;
+        mode   = "0600";
+        user   = "sshd";
+      };
+  };
 in
 {
   options = {
@@ -186,7 +188,7 @@ in
      environment.systemPackages = [ pkgs.duo-unix ];
 
      security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo";
-     environment.etc = loginCfgFile ++ pamCfgFile;
+     environment.etc = loginCfgFile // pamCfgFile;
 
      /* If PAM *and* SSH are enabled, then don't do anything special.
      If PAM isn't used, set the default SSH-only options. */
diff --git a/nixos/modules/security/misc.nix b/nixos/modules/security/misc.nix
index ecf22bf81c59..16e3bfb14199 100644
--- a/nixos/modules/security/misc.nix
+++ b/nixos/modules/security/misc.nix
@@ -13,23 +13,17 @@ with lib;
       default = true;
       description = ''
         Whether to allow creation of user namespaces.
-        </para>
 
-        <para>
         The motivation for disabling user namespaces is the potential
         presence of code paths where the kernel's permission checking
         logic fails to account for namespacing, instead permitting a
         namespaced process to act outside the namespace with the same
         privileges as it would have inside it.  This is particularly
         damaging in the common case of running as root within the namespace.
-        </para>
 
-        <para>
         When user namespace creation is disallowed, attempting to create a
         user namespace fails with "no space left on device" (ENOSPC).
         root may re-enable user namespace creation at runtime.
-        </para>
-        <para>
       '';
     };
 
@@ -48,35 +42,39 @@ with lib;
         Whether to allow SMT/hyperthreading.  Disabling SMT means that only
         physical CPU cores will be usable at runtime, potentially at
         significant performance cost.
-        </para>
 
-        <para>
         The primary motivation for disabling SMT is to mitigate the risk of
         leaking data between threads running on the same CPU core (due to
         e.g., shared caches).  This attack vector is unproven.
-        </para>
 
-        <para>
         Disabling SMT is a supplement to the L1 data cache flushing mitigation
-        (see <xref linkend="opt-security.virtualization.flushL1DataCache"/>)
+        (see <xref linkend="opt-security.virtualisation.flushL1DataCache"/>)
         versus malicious VM guests (SMT could "bring back" previously flushed
         data).
-        </para>
-        <para>
       '';
     };
 
-    security.virtualization.flushL1DataCache = mkOption {
+    security.forcePageTableIsolation = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to force-enable the Page Table Isolation (PTI) Linux kernel
+        feature even on CPU models that claim to be safe from Meltdown.
+
+        This hardening feature is most beneficial to systems that run untrusted
+        workloads that rely on address space isolation for security.
+      '';
+    };
+
+    security.virtualisation.flushL1DataCache = mkOption {
       type = types.nullOr (types.enum [ "never" "cond" "always" ]);
       default = null;
       description = ''
         Whether the hypervisor should flush the L1 data cache before
         entering guests.
         See also <xref linkend="opt-security.allowSimultaneousMultithreading"/>.
-        </para>
 
-        <para>
-          <variablelist>
+        <variablelist>
           <varlistentry>
             <term><literal>null</literal></term>
             <listitem><para>uses the kernel default</para></listitem>
@@ -98,7 +96,7 @@ with lib;
             enters the guest.  May incur significant performance cost.
             </para></listitem>
           </varlistentry>
-          </variablelist>
+        </variablelist>
       '';
     };
   };
@@ -128,8 +126,12 @@ with lib;
       boot.kernelParams = [ "nosmt" ];
     })
 
-    (mkIf (config.security.virtualization.flushL1DataCache != null) {
-      boot.kernelParams = [ "kvm-intel.vmentry_l1d_flush=${config.security.virtualization.flushL1DataCache}" ];
+    (mkIf config.security.forcePageTableIsolation {
+      boot.kernelParams = [ "pti=on" ];
+    })
+
+    (mkIf (config.security.virtualisation.flushL1DataCache != null) {
+      boot.kernelParams = [ "kvm-intel.vmentry_l1d_flush=${config.security.virtualisation.flushL1DataCache}" ];
     })
   ];
 }
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 89e71c5136e4..bfc2a881387f 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -351,7 +351,7 @@ let
           ${let oath = config.security.pam.oath; in optionalString cfg.oathAuth
               "auth requisite ${pkgs.oathToolkit}/lib/security/pam_oath.so window=${toString oath.window} usersfile=${toString oath.usersFile} digits=${toString oath.digits}"}
           ${let yubi = config.security.pam.yubico; in optionalString cfg.yubicoAuth
-              "auth ${yubi.control} ${pkgs.yubico-pam}/lib/security/pam_yubico.so id=${toString yubi.id} ${optionalString yubi.debug "debug"}"}
+              "auth ${yubi.control} ${pkgs.yubico-pam}/lib/security/pam_yubico.so mode=${toString yubi.mode} ${optionalString (yubi.mode == "client") "id=${toString yubi.id}"} ${optionalString yubi.debug "debug"}"}
         '' +
           # Modules in this block require having the password set in PAM_AUTHTOK.
           # pam_unix is marked as 'sufficient' on NixOS which means nothing will run
@@ -415,7 +415,7 @@ let
 
           # Session management.
           ${optionalString cfg.setEnvironment ''
-            session required pam_env.so envfile=${config.system.build.pamEnvironment}
+            session required pam_env.so conffile=${config.system.build.pamEnvironment} readenv=0
           ''}
           session required pam_unix.so
           ${optionalString cfg.setLoginUid
@@ -475,15 +475,19 @@ let
 
   motd = pkgs.writeText "motd" config.users.motd;
 
-  makePAMService = pamService:
-    { source = pkgs.writeText "${pamService.name}.pam" pamService.text;
-      target = "pam.d/${pamService.name}";
+  makePAMService = name: service:
+    { name = "pam.d/${name}";
+      value.source = pkgs.writeText "${name}.pam" service.text;
     };
 
 in
 
 {
 
+  imports = [
+    (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
+  ];
+
   ###### interface
 
   options = {
@@ -685,7 +689,7 @@ in
       };
       id = mkOption {
         example = "42";
-        type = types.string;
+        type = types.str;
         description = "client id";
       };
 
@@ -696,6 +700,23 @@ in
           Debug output to stderr.
         '';
       };
+      mode = mkOption {
+        default = "client";
+        type = types.enum [ "client" "challenge-response" ];
+        description = ''
+          Mode of operation.
+
+          Use "client" for online validation with a YubiKey validation service such as
+          the YubiCloud.
+
+          Use "challenge-response" for offline validation using YubiKeys with HMAC-SHA-1
+          Challenge-Response configurations. See the man-page ykpamcfg(1) for further
+          details on how to configure offline Challenge-Response validation.
+
+          More information can be found <link
+          xlink:href="https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html">here</link>.
+        '';
+      };
     };
 
     security.pam.enableEcryptfs = mkOption {
@@ -739,15 +760,7 @@ in
       };
     };
 
-    environment.etc =
-      mapAttrsToList (n: v: makePAMService v) config.security.pam.services;
-
-    systemd.tmpfiles.rules = optionals
-      (any (s: s.updateWtmp) (attrValues config.security.pam.services))
-      [
-        "f /var/log/wtmp"
-        "f /var/log/lastlog"
-      ];
+    environment.etc = mapAttrs' makePAMService config.security.pam.services;
 
     security.pam.services =
       { other.text =
@@ -763,11 +776,8 @@ in
           '';
 
         # Most of these should be moved to specific modules.
-        cups = {};
-        ftp = {};
         i3lock = {};
         i3lock-color = {};
-        screen = {};
         vlock = {};
         xlock = {};
         xscreensaver = {};
diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix
index 8b131c54a2a5..77e22a96b553 100644
--- a/nixos/modules/security/pam_mount.nix
+++ b/nixos/modules/security/pam_mount.nix
@@ -36,8 +36,7 @@ in
   config = mkIf (cfg.enable || anyPamMount) {
 
     environment.systemPackages = [ pkgs.pam_mount ];
-    environment.etc = [{
-      target = "security/pam_mount.conf.xml";
+    environment.etc."security/pam_mount.conf.xml" = {
       source =
         let
           extraUserVolumes = filterAttrs (n: u: u.cryptHomeLuks != null) config.users.users;
@@ -50,9 +49,6 @@ in
           <pam_mount>
           <debug enable="0" />
 
-          ${concatStrings (map userVolumeEntry (attrValues extraUserVolumes))}
-          ${concatStringsSep "\n" cfg.extraVolumes}
-
           <!-- if activated, requires ofl from hxtools to be present -->
           <logout wait="0" hup="no" term="no" kill="no" />
           <!-- set PATH variable for pam_mount module -->
@@ -64,9 +60,12 @@ in
           <cryptmount>${pkgs.pam_mount}/bin/mount.crypt %(VOLUME) %(MNTPT)</cryptmount>
           <cryptumount>${pkgs.pam_mount}/bin/umount.crypt %(MNTPT)</cryptumount>
           <pmvarrun>${pkgs.pam_mount}/bin/pmvarrun -u %(USER) -o %(OPERATION)</pmvarrun>
+
+          ${concatStrings (map userVolumeEntry (attrValues extraUserVolumes))}
+          ${concatStringsSep "\n" cfg.extraVolumes}
           </pam_mount>
           '';
-    }];
+    };
 
   };
 }
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index 7f1de81d5b70..a6724bd75832 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -42,15 +42,14 @@ in
 
     security.polkit.adminIdentities = mkOption {
       type = types.listOf types.str;
-      default = [ "unix-user:0" "unix-group:wheel" ];
+      default = [ "unix-group:wheel" ];
       example = [ "unix-user:alice" "unix-group:admin" ];
       description =
         ''
           Specifies which users are considered “administrators”, for those
           actions that require the user to authenticate as an
           administrator (i.e. have an <literal>auth_admin</literal>
-          value).  By default, this is the <literal>root</literal>
-          user and all users in the <literal>wheel</literal> group.
+          value).  By default, this is all users in the <literal>wheel</literal> group.
         '';
     };
 
@@ -85,7 +84,7 @@ in
 
     security.wrappers = {
       pkexec.source = "${pkgs.polkit.bin}/bin/pkexec";
-      "polkit-agent-helper-1".source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1";
+      polkit-agent-helper-1.source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1";
     };
 
     systemd.tmpfiles.rules = [
diff --git a/nixos/modules/security/prey.nix b/nixos/modules/security/prey.nix
index 1c643f2e1a57..b899ccb6c3e2 100644
--- a/nixos/modules/security/prey.nix
+++ b/nixos/modules/security/prey.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg = config.services.prey;
-  myPrey = pkgs."prey-bash-client".override {
+  myPrey = pkgs.prey-bash-client.override {
     apiKey = cfg.apiKey;
     deviceKey = cfg.deviceKey;
   };
diff --git a/nixos/modules/security/rngd.nix b/nixos/modules/security/rngd.nix
index a54ef2e6fcad..d9d6d9c9f253 100644
--- a/nixos/modules/security/rngd.nix
+++ b/nixos/modules/security/rngd.nix
@@ -2,20 +2,30 @@
 
 with lib;
 
+let
+  cfg = config.security.rngd;
+in
 {
   options = {
-    security.rngd.enable = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Whether to enable the rng daemon, which adds entropy from
-        hardware sources of randomness to the kernel entropy pool when
-        available.
-      '';
+    security.rngd = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable the rng daemon, which adds entropy from
+          hardware sources of randomness to the kernel entropy pool when
+          available.
+        '';
+      };
+      debug = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable debug output (-d).";
+      };
     };
   };
 
-  config = mkIf config.security.rngd.enable {
+  config = mkIf cfg.enable {
     services.udev.extraRules = ''
       KERNEL=="random", TAG+="systemd"
       SUBSYSTEM=="cpu", ENV{MODALIAS}=="cpu:type:x86,*feature:*009E*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"
@@ -29,7 +39,15 @@ with lib;
 
       description = "Hardware RNG Entropy Gatherer Daemon";
 
-      serviceConfig.ExecStart = "${pkgs.rng-tools}/sbin/rngd -f";
+      serviceConfig = {
+        ExecStart = "${pkgs.rng-tools}/sbin/rngd -f"
+          + optionalString cfg.debug " -d";
+        NoNewPrivileges = true;
+        PrivateNetwork = true;
+        PrivateTmp = true;
+        ProtectSystem = "full";
+        ProtectHome = true;
+      };
     };
   };
 }
diff --git a/nixos/modules/security/rtkit.nix b/nixos/modules/security/rtkit.nix
index f6dda21c6006..a7b27cbcf215 100644
--- a/nixos/modules/security/rtkit.nix
+++ b/nixos/modules/security/rtkit.nix
@@ -34,9 +34,8 @@ with lib;
 
     services.dbus.packages = [ pkgs.rtkit ];
 
-    users.users = singleton
-      { name = "rtkit";
-        uid = config.ids.uids.rtkit;
+    users.users.rtkit =
+      { uid = config.ids.uids.rtkit;
         description = "RealtimeKit daemon";
       };
 
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index 573588aaeecc..d899806ef058 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -91,7 +91,7 @@ in
       type = with types; listOf (submodule {
         options = {
           users = mkOption {
-            type = with types; listOf (either string int);
+            type = with types; listOf (either str int);
             description = ''
               The usernames / UIDs this rule should apply for.
             '';
@@ -99,7 +99,7 @@ in
           };
 
           groups = mkOption {
-            type = with types; listOf (either string int);
+            type = with types; listOf (either str int);
             description = ''
               The groups / GIDs this rule should apply for.
             '';
@@ -107,7 +107,7 @@ in
           };
 
           host = mkOption {
-            type = types.string;
+            type = types.str;
             default = "ALL";
             description = ''
               For what host this rule should apply.
@@ -115,7 +115,7 @@ in
           };
 
           runAs = mkOption {
-            type = with types; string;
+            type = with types; str;
             default = "ALL:ALL";
             description = ''
               Under which user/group the specified command is allowed to run.
@@ -130,11 +130,11 @@ in
             description = ''
               The commands for which the rule should apply.
             '';
-            type = with types; listOf (either string (submodule {
+            type = with types; listOf (either str (submodule {
 
               options = {
                 command = mkOption {
-                  type = with types; string;
+                  type = with types; str;
                   description = ''
                     A command being either just a path to a binary to allow any arguments,
                     the full command with arguments pre-set or with <code>""</code> used as the argument,
@@ -212,7 +212,7 @@ in
 
     security.pam.services.sudo = { sshAgentAuth = true; };
 
-    environment.etc = singleton
+    environment.etc.sudoers =
       { source =
           pkgs.runCommand "sudoers"
           {
@@ -222,7 +222,6 @@ in
           # Make sure that the sudoers file is syntactically valid.
           # (currently disabled - NIXOS-66)
           "${pkgs.buildPackages.sudo}/sbin/visudo -f $src -c && cp $src $out";
-        target = "sudoers";
         mode = "0440";
       };
 
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index dcb9c8d4ed5f..a0fadb018eca 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -7,7 +7,7 @@ let
 
   programs =
     (lib.mapAttrsToList
-      (n: v: (if v ? "program" then v else v // {program=n;}))
+      (n: v: (if v ? program then v else v // {program=n;}))
       wrappers);
 
   securityWrapper = pkgs.stdenv.mkDerivation {
@@ -74,15 +74,15 @@ let
 
   mkWrappedPrograms =
     builtins.map
-      (s: if (s ? "capabilities")
+      (s: if (s ? capabilities)
           then mkSetcapProgram
                  ({ owner = "root";
                     group = "root";
                   } // s)
           else if
-             (s ? "setuid" && s.setuid) ||
-             (s ? "setgid" && s.setgid) ||
-             (s ? "permissions")
+             (s ? setuid && s.setuid) ||
+             (s ? setgid && s.setgid) ||
+             (s ? permissions)
           then mkSetuidProgram s
           else mkSetuidProgram
                  ({ owner  = "root";
@@ -94,6 +94,10 @@ let
       ) programs;
 in
 {
+  imports = [
+    (lib.mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead")
+    (lib.mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead")
+  ];
 
   ###### interface
 
diff --git a/nixos/modules/services/admin/oxidized.nix b/nixos/modules/services/admin/oxidized.nix
index 687cdfb5ba57..94b44630ba6c 100644
--- a/nixos/modules/services/admin/oxidized.nix
+++ b/nixos/modules/services/admin/oxidized.nix
@@ -89,6 +89,7 @@ in
       group = cfg.group;
       home = cfg.dataDir;
       createHome = true;
+      isSystemUser = true;
     };
 
     systemd.services.oxidized = {
@@ -97,8 +98,8 @@ in
 
       preStart = ''
         mkdir -p ${cfg.dataDir}/.config/oxidized
-        cp -v ${cfg.routerDB} ${cfg.dataDir}/.config/oxidized/router.db
-        cp -v ${cfg.configFile} ${cfg.dataDir}/.config/oxidized/config
+        ln -f -s ${cfg.routerDB} ${cfg.dataDir}/.config/oxidized/router.db
+        ln -f -s ${cfg.configFile} ${cfg.dataDir}/.config/oxidized/config
       '';
 
       serviceConfig = {
@@ -110,6 +111,7 @@ in
         Restart  = "always";
         WorkingDirectory = cfg.dataDir;
         KillSignal = "SIGKILL";
+        PIDFile = "${cfg.dataDir}/.config/oxidized/pid";
       };
     };
   };
diff --git a/nixos/modules/services/amqp/activemq/default.nix b/nixos/modules/services/amqp/activemq/default.nix
index 27bfd91cd2d5..7729da27304b 100644
--- a/nixos/modules/services/amqp/activemq/default.nix
+++ b/nixos/modules/services/amqp/activemq/default.nix
@@ -40,7 +40,7 @@ in {
         '';
       };
       configurationURI = mkOption {
-        type = types.string;
+        type = types.str;
         default = "xbean:activemq.xml";
         description = ''
           The URI that is passed along to the BrokerFactory to
@@ -51,7 +51,7 @@ in {
         '';
       };
       baseDir = mkOption {
-        type = types.string;
+        type = types.str;
         default = "/var/activemq";
         description = ''
           The base directory where ActiveMQ stores its persistent data and logs.
@@ -81,7 +81,7 @@ in {
         '';
       };
       extraJavaOptions = mkOption {
-        type = types.string;
+        type = types.separatedString " ";
         default = "";
         example = "-Xmx2G -Xms2G -XX:MaxPermSize=512M";
         description = ''
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix
index 302b94de1965..f80d6b3f1ba5 100644
--- a/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixos/modules/services/amqp/rabbitmq.nix
@@ -80,7 +80,7 @@ in {
       configItems = mkOption {
         default = {};
         type = types.attrsOf types.str;
-        example = ''
+        example = literalExample ''
           {
             "auth_backends.1.authn" = "rabbit_auth_backend_ldap";
             "auth_backends.1.authz" = "rabbit_auth_backend_internal";
@@ -98,8 +98,8 @@ in {
           will be merged into these options by RabbitMQ at runtime to
           form the final configuration.
 
-          See http://www.rabbitmq.com/configure.html#config-items
-          For the distinct formats, see http://www.rabbitmq.com/configure.html#config-file-formats
+          See https://www.rabbitmq.com/configure.html#config-items
+          For the distinct formats, see https://www.rabbitmq.com/configure.html#config-file-formats
         '';
       };
 
@@ -116,8 +116,8 @@ in {
           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 http://www.rabbitmq.com/configure.html#config-items
-          For the distinct formats, see http://www.rabbitmq.com/configure.html#config-file-formats
+          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
         '';
       };
 
@@ -165,7 +165,10 @@ in {
       after = [ "network.target" "epmd.socket" ];
       wants = [ "network.target" "epmd.socket" ];
 
-      path = [ cfg.package pkgs.procps ];
+      path = [
+        cfg.package
+        pkgs.coreutils # mkdir/chown/chmod for preStart
+      ];
 
       environment = {
         RABBITMQ_MNESIA_BASE = "${cfg.dataDir}/mnesia";
diff --git a/nixos/modules/services/audio/alsa.nix b/nixos/modules/services/audio/alsa.nix
index 376aad66e236..990398e65463 100644
--- a/nixos/modules/services/audio/alsa.nix
+++ b/nixos/modules/services/audio/alsa.nix
@@ -12,6 +12,9 @@ let
 in
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "sound" "enableMediaKeys" ] [ "sound" "mediaKeys" "enable" ])
+  ];
 
   ###### interface
 
@@ -64,7 +67,7 @@ in
         };
 
         volumeStep = mkOption {
-          type = types.string;
+          type = types.str;
           default = "1";
           example = "1%";
           description = ''
@@ -99,7 +102,7 @@ in
 
     boot.kernelModules = optional config.sound.enableOSSEmulation "snd_pcm_oss";
 
-    systemd.services."alsa-store" =
+    systemd.services.alsa-store =
       { description = "Store Sound Card State";
         wantedBy = [ "multi-user.target" ];
         unitConfig.RequiresMountsFor = "/var/lib/alsa";
diff --git a/nixos/modules/services/audio/jack.nix b/nixos/modules/services/audio/jack.nix
new file mode 100644
index 000000000000..ceff366d0bbb
--- /dev/null
+++ b/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/nixos/modules/services/audio/mopidy.nix b/nixos/modules/services/audio/mopidy.nix
index e2f4ec39f94c..a534b692f177 100644
--- a/nixos/modules/services/audio/mopidy.nix
+++ b/nixos/modules/services/audio/mopidy.nix
@@ -70,25 +70,25 @@ in {
 
   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";
-      preStart = "mkdir -p ${cfg.dataDir} && chown -R mopidy:mopidy  ${cfg.dataDir}";
       serviceConfig = {
         ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)}";
         User = "mopidy";
-        PermissionsStartOnly = true;
       };
     };
 
     systemd.services.mopidy-scan = {
       description = "mopidy local files scanner";
-      preStart = "mkdir -p ${cfg.dataDir} && chown -R mopidy:mopidy  ${cfg.dataDir}";
       serviceConfig = {
         ExecStart = "${mopidyEnv}/bin/mopidy --config ${concatStringsSep ":" ([mopidyConf] ++ cfg.extraConfigFiles)} local scan";
         User = "mopidy";
-        PermissionsStartOnly = true;
         Type = "oneshot";
       };
     };
@@ -98,7 +98,7 @@ in {
       group = "mopidy";
       extraGroups = [ "audio" ];
       description = "Mopidy daemon user";
-      home = "${cfg.dataDir}";
+      home = cfg.dataDir;
     };
 
     users.groups.mopidy.gid = gid;
diff --git a/nixos/modules/services/audio/mpd.nix b/nixos/modules/services/audio/mpd.nix
index 0df8f9688d25..e20591b5beb4 100644
--- a/nixos/modules/services/audio/mpd.nix
+++ b/nixos/modules/services/audio/mpd.nix
@@ -18,7 +18,6 @@ let
     ''}
     state_file          "${cfg.dataDir}/state"
     sticker_file        "${cfg.dataDir}/sticker.sql"
-    log_file            "syslog"
     user                "${cfg.user}"
     group               "${cfg.group}"
 
@@ -181,22 +180,23 @@ in {
         ProtectKernelModules = true;
         RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX AF_NETLINK";
         RestrictNamespaces = true;
+        Restart = "always";
       };
     };
 
-    users.users = optionalAttrs (cfg.user == name) (singleton {
-      inherit uid;
-      inherit name;
-      group = cfg.group;
-      extraGroups = [ "audio" ];
-      description = "Music Player Daemon user";
-      home = "${cfg.dataDir}";
-    });
-
-    users.groups = optionalAttrs (cfg.group == name) (singleton {
-      inherit name;
-      gid = gid;
-    });
+    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/nixos/modules/services/audio/roon-server.nix b/nixos/modules/services/audio/roon-server.nix
new file mode 100644
index 000000000000..6aed485638cc
--- /dev/null
+++ b/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/nixos/modules/services/audio/slimserver.nix b/nixos/modules/services/audio/slimserver.nix
index 640403d2c97d..8f94a2b49404 100644
--- a/nixos/modules/services/audio/slimserver.nix
+++ b/nixos/modules/services/audio/slimserver.nix
@@ -42,15 +42,17 @@ in {
 
   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" ];
 
-      preStart = "mkdir -p ${cfg.dataDir} && chown -R slimserver:slimserver ${cfg.dataDir}";
       serviceConfig = {
         User = "slimserver";
-        PermissionsStartOnly = true;
         # 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";
       };
diff --git a/nixos/modules/services/audio/snapserver.nix b/nixos/modules/services/audio/snapserver.nix
index f709dd7fe16b..b0b9264e8166 100644
--- a/nixos/modules/services/audio/snapserver.nix
+++ b/nixos/modules/services/audio/snapserver.nix
@@ -4,7 +4,6 @@ with lib;
 
 let
 
-  package = "snapcast";
   name = "snapserver";
 
   cfg = config.services.snapserver;
diff --git a/nixos/modules/services/audio/spotifyd.nix b/nixos/modules/services/audio/spotifyd.nix
new file mode 100644
index 000000000000..4b74e7532795
--- /dev/null
+++ b/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/nixos/modules/services/audio/ympd.nix b/nixos/modules/services/audio/ympd.nix
index 919b76622510..551bd941fe68 100644
--- a/nixos/modules/services/audio/ympd.nix
+++ b/nixos/modules/services/audio/ympd.nix
@@ -23,7 +23,7 @@ in {
 
       mpd = {
         host = mkOption {
-          type = types.string;
+          type = types.str;
           default = "localhost";
           description = "The host where MPD is listening.";
           example = "localhost";
diff --git a/nixos/modules/services/backup/automysqlbackup.nix b/nixos/modules/services/backup/automysqlbackup.nix
index b845f370fb70..e3a8d1f79934 100644
--- a/nixos/modules/services/backup/automysqlbackup.nix
+++ b/nixos/modules/services/backup/automysqlbackup.nix
@@ -41,7 +41,7 @@ in
       };
 
       config = mkOption {
-        type = with types; attrsOf (either (either str (either int bool)) (listOf str));
+        type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
         default = {};
         description = ''
           automysqlbackup configuration. Refer to
@@ -99,7 +99,10 @@ in
 
     environment.systemPackages = [ pkg ];
 
-    users.users.${user}.group = group;
+    users.users.${user} = {
+      group = group;
+      isSystemUser = true;
+    };
     users.groups.${group} = { };
 
     systemd.tmpfiles.rules = [
diff --git a/nixos/modules/services/backup/bacula.nix b/nixos/modules/services/backup/bacula.nix
index 41bda7893a75..cef304734aee 100644
--- a/nixos/modules/services/backup/bacula.nix
+++ b/nixos/modules/services/backup/bacula.nix
@@ -44,7 +44,17 @@ let
         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}";
@@ -103,7 +113,19 @@ let
       password = mkOption {
         # TODO: required?
         description = ''
-           Specifies the password that must be supplied for a Director to b
+          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. 
         '';
       };
       
@@ -111,26 +133,133 @@ let
         default = "no";
         example = "yes";
         description = ''
-           If Monitor is set to no (default), this director will have full 
+          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 " /dev/nst0" or "/dev/rmt/0mbn". For a DVD-writer, it will be for example /dev/hdc. It may also be a directory name if you are archiving to disk storage.
+          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, "DLT7000". 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.
+          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.
         '';
       };
 
@@ -166,8 +295,8 @@ in {
         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
+          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.
         '';
       };
@@ -232,7 +361,8 @@ in {
         default = 9103;
         type = types.int;
         description = ''
-          Specifies port number on which the Storage daemon listens for Director connections. The default is 9103.
+          Specifies port number on which the Storage daemon listens for
+          Director connections.
         '';
       };
 
@@ -251,7 +381,15 @@ in {
         '';
         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 = ''
@@ -287,7 +425,8 @@ in {
       name = mkOption {
         default = "${config.networking.hostName}-dir";
         description = ''
-          The director name used by the system administrator. This directive is required.
+          The director name used by the system administrator. This directive is
+          required.
         '';
       };
  
@@ -295,7 +434,12 @@ in {
         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.
+          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.
         '';
       };
  
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index 2ad116a7872a..a2eb80c55a8c 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -8,7 +8,7 @@ let
     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);
@@ -68,7 +68,7 @@ let
       { BORG_PASSPHRASE = passphrase; }
     else { };
 
-  mkBackupService = name: cfg: 
+  mkBackupService = name: cfg:
     let
       userHome = config.users.users.${cfg.user}.home;
     in nameValuePair "borgbackup-job-${name}" {
@@ -98,18 +98,35 @@ let
       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" ] (''
-        # Eensure that the home directory already exists
+        # 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}"                                                                                                         
+        cd "${config.users.users.${cfg.user}.home}"
         ${install} -d .config/borg
         ${install} -d .cache/borg
-      '' + optionalString (isLocalPath cfg.repo) ''
+      '' + optionalString (isLocalPath cfg.repo && !cfg.removableDevice) ''
         ${install} -d ${escapeShellArg cfg.repo}
       ''));
 
@@ -163,13 +180,24 @@ let
       + " 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 ];
 
   ###### interface
 
   options.services.borgbackup.jobs = mkOption {
-    description = "Deduplicating backups using BorgBackup.";
+    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.
+    '';
     default = { };
     example = literalExample ''
       {
@@ -202,6 +230,12 @@ in {
             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}";
@@ -511,7 +545,6 @@ in {
     type = types.attrsOf (types.submodule (
       { ... }: {
         options = {
-          
           path = mkOption {
             type = types.path;
             description = ''
@@ -598,7 +631,8 @@ in {
     (with config.services.borgbackup; {
       assertions =
         mapAttrsToList mkPassAssertion jobs
-        ++ mapAttrsToList mkKeysAssertion repos;
+        ++ mapAttrsToList mkKeysAssertion repos
+        ++ mapAttrsToList mkRemovableDeviceAssertions jobs;
 
       system.activationScripts = mapAttrs' mkActivationScript jobs;
 
@@ -610,6 +644,6 @@ in {
 
       users = mkMerge (mapAttrsToList mkUsersConfig repos);
 
-      environment.systemPackages = with pkgs; [ borgbackup ];
+      environment.systemPackages = with pkgs; [ borgbackup ] ++ (mapAttrsToList mkBorgWrapper jobs);
     });
 }
diff --git a/nixos/modules/services/backup/crashplan-small-business.nix b/nixos/modules/services/backup/crashplan-small-business.nix
deleted file mode 100644
index 790dafefe66f..000000000000
--- a/nixos/modules/services/backup/crashplan-small-business.nix
+++ /dev/null
@@ -1,73 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  cfg = config.services.crashplansb;
-  crashplansb = pkgs.crashplansb.override { maxRam = cfg.maxRam; };
-in
-
-with lib;
-
-{
-  options = {
-    services.crashplansb = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Starts crashplan for small business background service.
-        '';
-      };
-      maxRam = mkOption {
-        default = "1024m";
-        example = "2G";
-        type = types.str;
-        description = ''
-          Maximum amount of ram that the crashplan engine should use.
-        '';
-      };
-      openPorts = mkOption {
-        description = "Open ports in the firewall for crashplan.";
-        default = true;
-        type = types.bool;
-      };
-      ports =  mkOption {
-        # https://support.code42.com/Administrator/6/Planning_and_installing/TCP_and_UDP_ports_used_by_the_Code42_platform
-        # used ports can also be checked in the desktop app console using the command connection.info
-        description = "which ports to open.";
-        default = [ 4242 4243 4244 4247 ];
-        type = types.listOf types.int;
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ crashplansb ];
-    networking.firewall.allowedTCPPorts = mkIf cfg.openPorts cfg.ports;
-
-    systemd.services.crashplansb = {
-      description = "CrashPlan Backup Engine";
-
-      wantedBy = [ "multi-user.target" ];
-      after    = [ "network.target" "local-fs.target" ];
-
-      preStart = ''
-        install -d -m 755 ${crashplansb.vardir}
-        install -d -m 700 ${crashplansb.vardir}/conf
-        install -d -m 700 ${crashplansb.manifestdir}
-        install -d -m 700 ${crashplansb.vardir}/cache
-        install -d -m 700 ${crashplansb.vardir}/backupArchives
-        install -d -m 777 ${crashplansb.vardir}/log
-        cp -avn ${crashplansb}/conf.template/* ${crashplansb.vardir}/conf
-      '';
-
-      serviceConfig = {
-        Type = "forking";
-        EnvironmentFile = "${crashplansb}/bin/run.conf";
-        ExecStart = "${crashplansb}/bin/CrashPlanEngine start";
-        ExecStop = "${crashplansb}/bin/CrashPlanEngine stop";
-        PIDFile = "${crashplansb.vardir}/CrashPlanEngine.pid";
-        WorkingDirectory = crashplansb;
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/backup/crashplan.nix b/nixos/modules/services/backup/crashplan.nix
deleted file mode 100644
index c540cc6e2aee..000000000000
--- a/nixos/modules/services/backup/crashplan.nix
+++ /dev/null
@@ -1,67 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  cfg = config.services.crashplan;
-  crashplan = pkgs.crashplan;
-in
-
-with lib;
-
-{
-  options = {
-    services.crashplan = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Starts crashplan background service.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment.systemPackages = [ crashplan ];
-
-    systemd.services.crashplan = {
-      description = "CrashPlan Backup Engine";
-
-      wantedBy = [ "multi-user.target" ];
-      after    = [ "network.target" "local-fs.target" ];
-
-      preStart = ''
-        ensureDir() {
-          dir=$1
-          mode=$2
-
-          if ! test -e $dir; then
-            ${pkgs.coreutils}/bin/mkdir -m $mode -p $dir
-          elif [ "$(${pkgs.coreutils}/bin/stat -c %a $dir)" != "$mode" ]; then
-            ${pkgs.coreutils}/bin/chmod $mode $dir
-          fi
-        }
-
-        ensureDir ${crashplan.vardir} 755
-        ensureDir ${crashplan.vardir}/conf 700
-        ensureDir ${crashplan.manifestdir} 700
-        ensureDir ${crashplan.vardir}/cache 700
-        ensureDir ${crashplan.vardir}/backupArchives 700
-        ensureDir ${crashplan.vardir}/log 777
-        cp -avn ${crashplan}/conf.template/* ${crashplan.vardir}/conf
-        for x in app.asar bin install.vars lang lib libc42archive64.so libc52archive.so libjniwrap64.so libjniwrap.so libjtux64.so libjtux.so libleveldb64.so libleveldb.so libmd564.so libmd5.so share skin upgrade; do
-          rm -f ${crashplan.vardir}/$x;
-          ln -sf ${crashplan}/$x ${crashplan.vardir}/$x;
-        done
-      '';
-
-      serviceConfig = {
-        Type = "forking";
-        EnvironmentFile = "${crashplan}/bin/run.conf";
-        ExecStart = "${crashplan}/bin/CrashPlanEngine start";
-        ExecStop = "${crashplan}/bin/CrashPlanEngine stop";
-        PIDFile = "${crashplan.vardir}/CrashPlanEngine.pid";
-        WorkingDirectory = crashplan;
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/backup/duplicati.nix b/nixos/modules/services/backup/duplicati.nix
index 80287f30b813..0ff720c5897d 100644
--- a/nixos/modules/services/backup/duplicati.nix
+++ b/nixos/modules/services/backup/duplicati.nix
@@ -19,13 +19,22 @@ in
       };
 
       interface = mkOption {
-        default = "lo";
+        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.
+        '';
+      };
     };
   };
 
@@ -37,14 +46,15 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        User = "duplicati";
+        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 = {
+    users.users.duplicati = lib.optionalAttrs (cfg.user == "duplicati") {
       uid = config.ids.uids.duplicati;
       home = "/var/lib/duplicati";
       createHome = true;
diff --git a/nixos/modules/services/backup/mysql-backup.nix b/nixos/modules/services/backup/mysql-backup.nix
index ba6e154f6b3d..f58af82773f3 100644
--- a/nixos/modules/services/backup/mysql-backup.nix
+++ b/nixos/modules/services/backup/mysql-backup.nix
@@ -84,13 +84,14 @@ in
   };
 
   config = mkIf cfg.enable {
-    users.users = optionalAttrs (cfg.user == defaultUser) (singleton
-      { name = defaultUser;
+    users.users = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} = {
         isSystemUser = true;
         createHome = false;
         home = cfg.location;
         group = "nogroup";
-      });
+      };
+    };
 
     services.mysql.ensureUsers = [{
       name = cfg.user;
@@ -103,7 +104,7 @@ in
     }];
 
     systemd = {
-      timers."mysql-backup" = {
+      timers.mysql-backup = {
         description = "Mysql backup timer";
         wantedBy = [ "timers.target" ];
         timerConfig = {
@@ -112,7 +113,7 @@ in
           Unit = "mysql-backup.service";
         };
       };
-      services."mysql-backup" = {
+      services.mysql-backup = {
         description = "Mysql backup service";
         enable = true;
         serviceConfig = {
diff --git a/nixos/modules/services/backup/postgresql-backup.nix b/nixos/modules/services/backup/postgresql-backup.nix
index 17b410a97f3e..580c7ce68f1d 100644
--- a/nixos/modules/services/backup/postgresql-backup.nix
+++ b/nixos/modules/services/backup/postgresql-backup.nix
@@ -35,6 +35,13 @@ let
 
 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 = mkOption {
@@ -81,8 +88,8 @@ in {
       };
 
       pgdumpOptions = mkOption {
-        type = types.string;
-        default = "-Cbo";
+        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.
diff --git a/nixos/modules/services/backup/postgresql-wal-receiver.nix b/nixos/modules/services/backup/postgresql-wal-receiver.nix
new file mode 100644
index 000000000000..3d9869d53431
--- /dev/null
+++ b/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/nixos/modules/services/backup/rsnapshot.nix b/nixos/modules/services/backup/rsnapshot.nix
index bb5dcab1dcf2..6635a51ec2c6 100644
--- a/nixos/modules/services/backup/rsnapshot.nix
+++ b/nixos/modules/services/backup/rsnapshot.nix
@@ -2,7 +2,7 @@
 
 with lib;
 
-let 
+let
   cfg = config.services.rsnapshot;
   cfgfile = pkgs.writeText "rsnapshot.conf" ''
     config_version	1.2
@@ -52,7 +52,7 @@ in
       cronIntervals = mkOption {
         default = {};
         example = { hourly = "0 * * * *"; daily = "50 21 * * *"; };
-        type = types.attrsOf types.string;
+        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
diff --git a/nixos/modules/services/backup/tarsnap.nix b/nixos/modules/services/backup/tarsnap.nix
index 4fc7c24813a5..6d99a1efb613 100644
--- a/nixos/modules/services/backup/tarsnap.nix
+++ b/nixos/modules/services/backup/tarsnap.nix
@@ -23,6 +23,10 @@ let
   '';
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "tarsnap" "cachedir" ] "Use services.tarsnap.archives.<name>.cachedir")
+  ];
+
   options = {
     services.tarsnap = {
       enable = mkOption {
diff --git a/nixos/modules/services/backup/tsm.nix b/nixos/modules/services/backup/tsm.nix
new file mode 100644
index 000000000000..6c238745797e
--- /dev/null
+++ b/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/nixos/modules/services/backup/zfs-replication.nix b/nixos/modules/services/backup/zfs-replication.nix
new file mode 100644
index 000000000000..5a64304275d5
--- /dev/null
+++ b/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/nixos/modules/services/backup/znapzend.nix b/nixos/modules/services/backup/znapzend.nix
index fc8a424190f7..203631a577f0 100644
--- a/nixos/modules/services/backup/znapzend.nix
+++ b/nixos/modules/services/backup/znapzend.nix
@@ -7,28 +7,23 @@ let
 
   planDescription = ''
       The znapzend backup plan to use for the source.
-    </para>
-    <para>
+
       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:
-    </para>
-    <para>
+
       <literal>
         retA=>intA,retB=>intB,...
       </literal>
-    </para>
-    <para>
-    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:
-    </para>
-    <para>
+
+      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>
-    </para>
-    <para>
+
       See <citerefentry><refentrytitle>znapzendzetup</refentrytitle><manvolnum>1</manvolnum></citerefentry> for more info.
   '';
   planExample = "1h=>10min,1d=>1h,1w=>1d,1m=>1w,1y=>1m";
@@ -39,6 +34,8 @@ let
     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
@@ -139,12 +136,10 @@ let
           type = nullOr ints.u16;
           description = ''
               Port to use for <command>mbuffer</command>.
-            </para>
-            <para>
+
               If this is null, it will run <command>mbuffer</command> through
               ssh.
-            </para>
-            <para>
+
               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.
@@ -255,7 +250,7 @@ let
   cfg = config.services.znapzend;
 
   onOff = b: if b then "on" else "off";
-  nullOff = b: if isNull b then "off" else toString b;
+  nullOff = b: if b == null then "off" else toString b;
   stripSlashes = replaceStrings [ "/" ] [ "." ];
 
   attrsToFile = config: concatStringsSep "\n" (builtins.attrValues (
@@ -263,7 +258,7 @@ let
 
   mkDestAttrs = dst: with dst;
     mapAttrs' (n: v: nameValuePair "dst_${label}${n}" v) ({
-      "" = optionalString (! isNull host) "${host}:" + dataset;
+      "" = optionalString (host != null) "${host}:" + dataset;
       _plan = plan;
     } // optionalAttrs (presend != null) {
       _precmd = presend;
@@ -361,6 +356,22 @@ in
         '';
         default = false;
       };
+
+      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.
+      '';
     };
   };
 
@@ -368,7 +379,7 @@ in
     environment.systemPackages = [ pkgs.znapzend ];
 
     systemd.services = {
-      "znapzend" = {
+      znapzend = {
         description = "ZnapZend - ZFS Backup System";
         wantedBy    = [ "zfs.target" ];
         after       = [ "zfs.target" ];
@@ -382,16 +393,28 @@ in
             | 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);
+          ${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";
diff --git a/nixos/modules/services/cluster/hadoop/hdfs.nix b/nixos/modules/services/cluster/hadoop/hdfs.nix
index a38b6a78d3a5..4f4b0a92108f 100644
--- a/nixos/modules/services/cluster/hadoop/hdfs.nix
+++ b/nixos/modules/services/cluster/hadoop/hdfs.nix
@@ -24,7 +24,7 @@ with lib;
 
   config = mkMerge [
     (mkIf cfg.hdfs.namenode.enabled {
-      systemd.services."hdfs-namenode" = {
+      systemd.services.hdfs-namenode = {
         description = "Hadoop HDFS NameNode";
         wantedBy = [ "multi-user.target" ];
 
@@ -44,7 +44,7 @@ with lib;
       };
     })
     (mkIf cfg.hdfs.datanode.enabled {
-      systemd.services."hdfs-datanode" = {
+      systemd.services.hdfs-datanode = {
         description = "Hadoop HDFS DataNode";
         wantedBy = [ "multi-user.target" ];
 
diff --git a/nixos/modules/services/cluster/hadoop/yarn.nix b/nixos/modules/services/cluster/hadoop/yarn.nix
index 5345a2732d7e..c92020637e47 100644
--- a/nixos/modules/services/cluster/hadoop/yarn.nix
+++ b/nixos/modules/services/cluster/hadoop/yarn.nix
@@ -35,7 +35,7 @@ with lib;
     })
 
     (mkIf cfg.yarn.resourcemanager.enabled {
-      systemd.services."yarn-resourcemanager" = {
+      systemd.services.yarn-resourcemanager = {
         description = "Hadoop YARN ResourceManager";
         wantedBy = [ "multi-user.target" ];
 
@@ -53,7 +53,7 @@ with lib;
     })
 
     (mkIf cfg.yarn.nodemanager.enabled {
-      systemd.services."yarn-nodemanager" = {
+      systemd.services.yarn-nodemanager = {
         description = "Hadoop YARN NodeManager";
         wantedBy = [ "multi-user.target" ];
 
diff --git a/nixos/modules/services/cluster/kubernetes/addon-manager.nix b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
index ad7d17c9c283..17f2dde31a71 100644
--- a/nixos/modules/services/cluster/kubernetes/addon-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/addon-manager.nix
@@ -62,50 +62,19 @@ in
       '';
     };
 
-    enable = mkEnableOption "Kubernetes addon manager";
-
-    kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes addon manager";
-    bootstrapAddonsKubeconfig = top.lib.mkKubeConfigOptions "Kubernetes addon manager bootstrap";
+    enable = mkEnableOption "Whether to enable Kubernetes addon manager.";
   };
 
   ###### implementation
-  config = let
-
-    addonManagerPaths = filter (a: a != null) [
-      cfg.kubeconfig.caFile
-      cfg.kubeconfig.certFile
-      cfg.kubeconfig.keyFile
-    ];
-    bootstrapAddonsPaths = filter (a: a != null) [
-      cfg.bootstrapAddonsKubeconfig.caFile
-      cfg.bootstrapAddonsKubeconfig.certFile
-      cfg.bootstrapAddonsKubeconfig.keyFile
-    ];
-
-  in mkIf cfg.enable {
+  config = mkIf cfg.enable {
     environment.etc."kubernetes/addons".source = "${addons}/";
 
-    #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 = {
       description = "Kubernetes addon manager";
       wantedBy = [ "kubernetes.target" ];
-      after = [ "kube-node-online.target" ];
-      before = [ "kubernetes.target" ];
-      environment = {
-        ADDON_PATH = "/etc/kubernetes/addons/";
-        KUBECONFIG = top.lib.mkKubeConfig "kube-addon-manager" cfg.kubeconfig;
-      };
-      path = with pkgs; [ gawk kubectl ];
-      preStart = ''
-        until kubectl -n kube-system get serviceaccounts/default 2>/dev/null; do
-          echo kubectl -n kube-system get serviceaccounts/default: exit status $?
-          sleep 2
-        done
-      '';
+      after = [ "kube-apiserver.service" ];
+      environment.ADDON_PATH = "/etc/kubernetes/addons/";
+      path = [ pkgs.gawk ];
       serviceConfig = {
         Slice = "kubernetes.slice";
         ExecStart = "${top.package}/bin/kube-addons";
@@ -115,52 +84,8 @@ in
         Restart = "on-failure";
         RestartSec = 10;
       };
-      unitConfig.ConditionPathExists = addonManagerPaths;
     };
 
-    systemd.paths.kube-addon-manager = {
-      wantedBy = [ "kube-addon-manager.service" ];
-      pathConfig = {
-        PathExists = addonManagerPaths;
-        PathChanged = addonManagerPaths;
-      };
-    };
-
-    services.kubernetes.addonManager.kubeconfig.server = mkDefault top.apiserverAddress;
-
-    systemd.services.kube-addon-manager-bootstrap = mkIf (top.apiserver.enable && top.addonManager.bootstrapAddons != {}) {
-      wantedBy = [ "kube-control-plane-online.target" ];
-      after = [ "kube-apiserver.service" ];
-      before = [ "kube-control-plane-online.target" ];
-      path = [ pkgs.kubectl ];
-      environment = {
-        KUBECONFIG = top.lib.mkKubeConfig "kube-addon-manager-bootstrap" cfg.bootstrapAddonsKubeconfig;
-      };
-      preStart = with pkgs; let
-        files = mapAttrsToList (n: v: writeText "${n}.json" (builtins.toJSON v))
-          cfg.bootstrapAddons;
-      in ''
-        until kubectl auth can-i '*' '*' -q 2>/dev/null; do
-          echo kubectl auth can-i '*' '*': exit status $?
-          sleep 2
-        done
-
-        kubectl apply -f ${concatStringsSep " \\\n -f " files}
-      '';
-      script = "echo Ok";
-      unitConfig.ConditionPathExists = bootstrapAddonsPaths;
-    };
-
-    systemd.paths.kube-addon-manager-bootstrap = {
-      wantedBy = [ "kube-addon-manager-bootstrap.service" ];
-      pathConfig = {
-        PathExists = bootstrapAddonsPaths;
-        PathChanged = bootstrapAddonsPaths;
-      };
-    };
-
-    services.kubernetes.addonManager.bootstrapAddonsKubeconfig.server = mkDefault top.apiserverAddress;
-
     services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled
     (let
       name = system:kube-addon-manager;
diff --git a/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix b/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
index 2295694ffc74..2ed7742eda09 100644
--- a/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
+++ b/nixos/modules/services/cluster/kubernetes/addons/dashboard.nix
@@ -5,6 +5,10 @@ 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";
 
@@ -74,7 +78,7 @@ in {
         spec = {
           replicas = 1;
           revisionHistoryLimit = 10;
-          selector.matchLabels."k8s-app" = "kubernetes-dashboard";
+          selector.matchLabels.k8s-app = "kubernetes-dashboard";
           template = {
             metadata = {
               labels = {
@@ -169,23 +173,6 @@ in {
         };
       };
 
-      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";
-        };
-      };
-    };
-
-    services.kubernetes.addonManager.bootstrapAddons = mkMerge [{
-
       kubernetes-dashboard-sa = {
         apiVersion = "v1";
         kind = "ServiceAccount";
@@ -227,9 +214,20 @@ in {
         };
         type = "Opaque";
       };
-    }
-
-    (optionalAttrs cfg.rbac.enable
+      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";
@@ -329,6 +327,6 @@ in {
             inherit subjects;
           };
         })
-    ))];
+    ));
   };
 }
diff --git a/nixos/modules/services/cluster/kubernetes/addons/dns.nix b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
index 4368159ea6e3..f12e866930da 100644
--- a/nixos/modules/services/cluster/kubernetes/addons/dns.nix
+++ b/nixos/modules/services/cluster/kubernetes/addons/dns.nix
@@ -3,7 +3,7 @@
 with lib;
 
 let
-  version = "1.3.1";
+  version = "1.6.4";
   cfg = config.services.kubernetes.addons.dns;
   ports = {
     dns = 10053;
@@ -55,9 +55,9 @@ in {
       type = types.attrs;
       default = {
         imageName = "coredns/coredns";
-        imageDigest = "sha256:02382353821b12c21b062c59184e227e001079bb13ebd01f9d3270ba0fcbf1e4";
+        imageDigest = "sha256:493ee88e1a92abebac67cbd4b5658b4730e0f33512461442d8d9214ea6734a9b";
         finalImageTag = version;
-        sha256 = "0vbylgyxv2jm2mnzk6f28jbsj305zsxmx3jr6ngjq461czcl5fi5";
+        sha256 = "0fm9zdjavpf5hni8g7fkdd3csjbhd7n7py7llxjc66sbii087028";
       };
     };
   };
@@ -68,12 +68,12 @@ in {
 
     services.kubernetes.addonManager.bootstrapAddons = {
       coredns-cr = {
-        apiVersion = "rbac.authorization.k8s.io/v1beta1";
+        apiVersion = "rbac.authorization.k8s.io/v1";
         kind = "ClusterRole";
         metadata = {
           labels = {
             "addonmanager.kubernetes.io/mode" = "Reconcile";
-            "k8s-app" = "kube-dns";
+            k8s-app = "kube-dns";
             "kubernetes.io/cluster-service" = "true";
             "kubernetes.io/bootstrapping" = "rbac-defaults";
           };
@@ -94,7 +94,7 @@ in {
       };
 
       coredns-crb = {
-        apiVersion = "rbac.authorization.k8s.io/v1beta1";
+        apiVersion = "rbac.authorization.k8s.io/v1";
         kind = "ClusterRoleBinding";
         metadata = {
           annotations = {
@@ -102,7 +102,7 @@ in {
           };
           labels = {
             "addonmanager.kubernetes.io/mode" = "Reconcile";
-            "k8s-app" = "kube-dns";
+            k8s-app = "kube-dns";
             "kubernetes.io/cluster-service" = "true";
             "kubernetes.io/bootstrapping" = "rbac-defaults";
           };
@@ -130,7 +130,7 @@ in {
         metadata = {
           labels = {
             "addonmanager.kubernetes.io/mode" = "Reconcile";
-            "k8s-app" = "kube-dns";
+            k8s-app = "kube-dns";
             "kubernetes.io/cluster-service" = "true";
           };
           name = "coredns";
@@ -144,7 +144,7 @@ in {
         metadata = {
           labels = {
             "addonmanager.kubernetes.io/mode" = cfg.reconcileMode;
-            "k8s-app" = "kube-dns";
+            k8s-app = "kube-dns";
             "kubernetes.io/cluster-service" = "true";
           };
           name = "coredns";
@@ -160,7 +160,7 @@ in {
               fallthrough in-addr.arpa ip6.arpa
             }
             prometheus :${toString ports.metrics}
-            proxy . /etc/resolv.conf
+            forward . /etc/resolv.conf
             cache 30
             loop
             reload
@@ -170,12 +170,12 @@ in {
       };
 
       coredns-deploy = {
-        apiVersion = "extensions/v1beta1";
+        apiVersion = "apps/v1";
         kind = "Deployment";
         metadata = {
           labels = {
             "addonmanager.kubernetes.io/mode" = cfg.reconcileMode;
-            "k8s-app" = "kube-dns";
+            k8s-app = "kube-dns";
             "kubernetes.io/cluster-service" = "true";
             "kubernetes.io/name" = "CoreDNS";
           };
@@ -301,7 +301,7 @@ in {
           };
           labels = {
             "addonmanager.kubernetes.io/mode" = "Reconcile";
-            "k8s-app" = "kube-dns";
+            k8s-app = "kube-dns";
             "kubernetes.io/cluster-service" = "true";
             "kubernetes.io/name" = "CoreDNS";
           };
diff --git a/nixos/modules/services/cluster/kubernetes/apiserver.nix b/nixos/modules/services/cluster/kubernetes/apiserver.nix
index 0c04648355b4..95bdb4c0d14e 100644
--- a/nixos/modules/services/cluster/kubernetes/apiserver.nix
+++ b/nixos/modules/services/cluster/kubernetes/apiserver.nix
@@ -13,6 +13,18 @@ let
   )) + ".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; {
 
@@ -184,6 +196,12 @@ in
       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;
@@ -284,32 +302,11 @@ in
   ###### implementation
   config = mkMerge [
 
-    (let
-
-      apiserverPaths = filter (a: a != null) [
-        cfg.clientCaFile
-        cfg.etcd.caFile
-        cfg.etcd.certFile
-        cfg.etcd.keyFile
-        cfg.kubeletClientCaFile
-        cfg.kubeletClientCertFile
-        cfg.kubeletClientKeyFile
-        cfg.serviceAccountKeyFile
-        cfg.tlsCertFile
-        cfg.tlsKeyFile
-      ];
-      etcdPaths = filter (a: a != null) [
-        config.services.etcd.trustedCaFile
-        config.services.etcd.certFile
-        config.services.etcd.keyFile
-      ];
-
-    in mkIf cfg.enable {
+    (mkIf cfg.enable {
         systemd.services.kube-apiserver = {
           description = "Kubernetes APIServer Service";
-          wantedBy = [ "kube-control-plane-online.target" ];
-          after = [ "certmgr.service" ];
-          before = [ "kube-control-plane-online.target" ];
+          wantedBy = [ "kubernetes.target" ];
+          after = [ "network.target" ];
           serviceConfig = {
             Slice = "kubernetes.slice";
             ExecStart = ''${top.package}/bin/kube-apiserver \
@@ -349,6 +346,8 @@ in
                 "--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)
@@ -378,15 +377,6 @@ in
             Restart = "on-failure";
             RestartSec = 5;
           };
-          unitConfig.ConditionPathExists = apiserverPaths;
-        };
-
-        systemd.paths.kube-apiserver = mkIf top.apiserver.enable {
-          wantedBy = [ "kube-apiserver.service" ];
-          pathConfig = {
-            PathExists = apiserverPaths;
-            PathChanged = apiserverPaths;
-          };
         };
 
         services.etcd = {
@@ -400,18 +390,6 @@ in
           initialAdvertisePeerUrls = mkDefault ["https://${top.masterAddress}:2380"];
         };
 
-        systemd.services.etcd = {
-          unitConfig.ConditionPathExists = etcdPaths;
-        };
-
-        systemd.paths.etcd = {
-          wantedBy = [ "etcd.service" ];
-          pathConfig = {
-            PathExists = etcdPaths;
-            PathChanged = etcdPaths;
-          };
-        };
-
         services.kubernetes.addonManager.bootstrapAddons = mkIf isRBACEnabled {
 
           apiserver-kubelet-api-admin-crb = {
diff --git a/nixos/modules/services/cluster/kubernetes/controller-manager.nix b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
index b94e8bd86d4c..a99ef6640e97 100644
--- a/nixos/modules/services/cluster/kubernetes/controller-manager.nix
+++ b/nixos/modules/services/cluster/kubernetes/controller-manager.nix
@@ -7,6 +7,11 @@ let
   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; {
 
@@ -104,31 +109,11 @@ in
   };
 
   ###### implementation
-  config = let
-
-    controllerManagerPaths = filter (a: a != null) [
-      cfg.kubeconfig.caFile
-      cfg.kubeconfig.certFile
-      cfg.kubeconfig.keyFile
-      cfg.rootCaFile
-      cfg.serviceAccountKeyFile
-      cfg.tlsCertFile
-      cfg.tlsKeyFile
-    ];
-
-  in mkIf cfg.enable {
-    systemd.services.kube-controller-manager = rec {
+  config = mkIf cfg.enable {
+    systemd.services.kube-controller-manager = {
       description = "Kubernetes Controller Manager Service";
-      wantedBy = [ "kube-control-plane-online.target" ];
+      wantedBy = [ "kubernetes.target" ];
       after = [ "kube-apiserver.service" ];
-      before = [ "kube-control-plane-online.target" ];
-      environment.KUBECONFIG = top.lib.mkKubeConfig "kube-controller-manager" cfg.kubeconfig;
-      preStart = ''
-        until kubectl auth can-i get /api -q 2>/dev/null; do
-          echo kubectl auth can-i get /api: exit status $?
-          sleep 2
-        done
-      '';
       serviceConfig = {
         RestartSec = "30s";
         Restart = "on-failure";
@@ -140,7 +125,7 @@ in
             "--cluster-cidr=${cfg.clusterCidr}"} \
           ${optionalString (cfg.featureGates != [])
             "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
-          --kubeconfig=${environment.KUBECONFIG} \
+          --kubeconfig=${top.lib.mkKubeConfig "kube-controller-manager" cfg.kubeconfig} \
           --leader-elect=${boolToString cfg.leaderElect} \
           ${optionalString (cfg.rootCaFile!=null)
             "--root-ca-file=${cfg.rootCaFile}"} \
@@ -161,16 +146,7 @@ in
         User = "kubernetes";
         Group = "kubernetes";
       };
-      path = top.path ++ [ pkgs.kubectl ];
-      unitConfig.ConditionPathExists = controllerManagerPaths;
-    };
-
-    systemd.paths.kube-controller-manager = {
-      wantedBy = [ "kube-controller-manager.service" ];
-      pathConfig = {
-        PathExists = controllerManagerPaths;
-        PathChanged = controllerManagerPaths;
-      };
+      path = top.path;
     };
 
     services.kubernetes.pki.certs = with top.lib; {
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 192c893f8a16..3a11a6513a49 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -72,15 +72,12 @@ let
       default = null;
     };
   };
-
-  kubeConfigDefaults = {
-    server = mkDefault cfg.kubeconfig.server;
-    caFile = mkDefault cfg.kubeconfig.caFile;
-    certFile = mkDefault cfg.kubeconfig.certFile;
-    keyFile = mkDefault cfg.kubeconfig.keyFile;
-  };
 in {
 
+  imports = [
+    (mkRemovedOptionModule [ "services" "kubernetes" "verbose" ] "")
+  ];
+
   ###### interface
 
   options.services.kubernetes = {
@@ -263,38 +260,13 @@ in {
         wantedBy = [ "multi-user.target" ];
       };
 
-      systemd.targets.kube-control-plane-online = {
-        wantedBy = [ "kubernetes.target" ];
-        before = [ "kubernetes.target" ];
-      };
-
-      systemd.services.kube-control-plane-online = rec {
-        description = "Kubernetes control plane is online";
-        wantedBy = [ "kube-control-plane-online.target" ];
-        after = [ "kube-scheduler.service" "kube-controller-manager.service" ];
-        before = [ "kube-control-plane-online.target" ];
-        environment.KUBECONFIG = cfg.lib.mkKubeConfig "default" cfg.kubeconfig;
-        path = [ pkgs.kubectl ];
-        preStart = ''
-          until kubectl get --raw=/healthz 2>/dev/null; do
-            echo kubectl get --raw=/healthz: exit status $?
-            sleep 3
-          done
-        '';
-        script = "echo Ok";
-        serviceConfig = {
-          TimeoutSec = "500";
-        };
-      };
-
       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 = singleton {
-        name = "kubernetes";
+      users.users.kubernetes = {
         uid = config.ids.uids.kubernetes;
         description = "Kubernetes user";
         extraGroups = [ "docker" ];
@@ -310,8 +282,6 @@ in {
       services.kubernetes.apiserverAddress = mkDefault ("https://${if cfg.apiserver.advertiseAddress != null
                           then cfg.apiserver.advertiseAddress
                           else "${cfg.masterAddress}:${toString cfg.apiserver.securePort}"}");
-
-      services.kubernetes.kubeconfig.server = mkDefault cfg.apiserverAddress;
     })
   ];
 }
diff --git a/nixos/modules/services/cluster/kubernetes/flannel.nix b/nixos/modules/services/cluster/kubernetes/flannel.nix
index d9437427d6d1..548ffed1ddb5 100644
--- a/nixos/modules/services/cluster/kubernetes/flannel.nix
+++ b/nixos/modules/services/cluster/kubernetes/flannel.nix
@@ -14,36 +14,25 @@ let
     buildInputs = [ pkgs.makeWrapper ];
   } ''
     mkdir -p $out
-    cp ${pkgs.kubernetes.src}/cluster/centos/node/bin/mk-docker-opts.sh $out/mk-docker-opts.sh
 
     # bashInteractive needed for `compgen`
-    makeWrapper ${pkgs.bashInteractive}/bin/bash $out/mk-docker-opts --add-flags "$out/mk-docker-opts.sh"
+    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 "flannel networking";
-    kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes flannel";
+    enable = mkEnableOption "enable flannel networking";
   };
 
   ###### implementation
-  config = let
-
-    flannelPaths = filter (a: a != null) [
-      cfg.kubeconfig.caFile
-      cfg.kubeconfig.certFile
-      cfg.kubeconfig.keyFile
-    ];
-    kubeconfig = top.lib.mkKubeConfig "flannel" cfg.kubeconfig;
-
-  in mkIf cfg.enable {
+  config = mkIf cfg.enable {
     services.flannel = {
 
       enable = mkDefault true;
       network = mkDefault top.clusterCidr;
-      inherit storageBackend kubeconfig;
-      nodeName = top.kubelet.hostname;
+      inherit storageBackend;
+      nodeName = config.services.kubernetes.kubelet.hostname;
     };
 
     services.kubernetes.kubelet = {
@@ -51,6 +40,7 @@ in
       cni.config = mkDefault [{
         name = "mynet";
         type = "flannel";
+        cniVersion = "0.3.1";
         delegate = {
           isDefaultGateway = true;
           bridge = "docker0";
@@ -60,64 +50,22 @@ in
 
     systemd.services.mk-docker-opts = {
       description = "Pre-Docker Actions";
-      wantedBy = [ "flannel.target" ];
-      before = [ "flannel.target" ];
       path = with pkgs; [ gawk gnugrep ];
       script = ''
         ${mkDockerOpts}/mk-docker-opts -d /run/flannel/docker
         systemctl restart docker
       '';
-      unitConfig.ConditionPathExists = [ "/run/flannel/subnet.env" ];
       serviceConfig.Type = "oneshot";
     };
 
     systemd.paths.flannel-subnet-env = {
-      wantedBy = [ "mk-docker-opts.service" ];
-      pathConfig = {
-        PathExists = [ "/run/flannel/subnet.env" ];
-        PathChanged = [ "/run/flannel/subnet.env" ];
-        Unit = "mk-docker-opts.service";
-      };
-    };
-
-    systemd.targets.flannel = {
-      wantedBy = [ "kube-node-online.target" ];
-      before = [ "kube-node-online.target" ];
-    };
-
-    systemd.services.flannel = {
-      wantedBy = [ "flannel.target" ];
-      after = [ "kubelet.target" ];
-      before = [ "flannel.target" ];
-      path = with pkgs; [ iptables kubectl ];
-      environment.KUBECONFIG = kubeconfig;
-      preStart = let
-        args = [
-          "--selector=kubernetes.io/hostname=${top.kubelet.hostname}"
-          # flannel exits if node is not registered yet, before that there is no podCIDR
-          "--output=jsonpath={.items[0].spec.podCIDR}"
-          # if jsonpath cannot be resolved exit with status 1
-          "--allow-missing-template-keys=false"
-        ];
-      in ''
-        until kubectl get nodes ${concatStringsSep " " args} 2>/dev/null; do
-          echo Waiting for ${top.kubelet.hostname} to be RegisteredNode
-          sleep 1
-        done
-      '';
-      unitConfig.ConditionPathExists = flannelPaths;
-    };
-
-    systemd.paths.flannel = {
       wantedBy = [ "flannel.service" ];
       pathConfig = {
-        PathExists = flannelPaths;
-        PathChanged = flannelPaths;
+        PathModified = "/run/flannel/subnet.env";
+        Unit = "mk-docker-opts.service";
       };
     };
 
-    services.kubernetes.flannel.kubeconfig.server = mkDefault top.apiserverAddress;
-
     systemd.services.docker = {
       environment.DOCKER_OPTS = "-b none";
       serviceConfig.EnvironmentFile = "-/run/flannel/docker";
@@ -144,6 +92,7 @@ in
 
     # 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";
@@ -179,6 +128,7 @@ in
           name = "flannel-client";
         }];
       };
+
     };
   };
 }
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index 2a4a0624555d..c3d67552cc8c 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -7,9 +7,9 @@ let
   cfg = top.kubelet;
 
   cniConfig =
-    if cfg.cni.config != [] && !(isNull cfg.cni.configDir) then
+    if cfg.cni.config != [] && cfg.cni.configDir != null then
       throw "Verbatim CNI-config and CNI configDir cannot both be set."
-    else if !(isNull cfg.cni.configDir) then
+    else if cfg.cni.configDir != null then
       cfg.cni.configDir
     else
       (pkgs.buildEnv {
@@ -28,13 +28,6 @@ let
 
   kubeconfig = top.lib.mkKubeConfig "kubelet" cfg.kubeconfig;
 
-  manifests = pkgs.buildEnv {
-    name = "kubernetes-manifests";
-    paths = mapAttrsToList (name: manifest:
-      pkgs.writeTextDir "${name}.json" (builtins.toJSON manifest)
-    ) cfg.manifests;
-  };
-
   manifestPath = "kubernetes/manifests";
 
   taintOptions = with lib.types; { name, ... }: {
@@ -59,6 +52,12 @@ let
   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; {
 
@@ -68,12 +67,6 @@ in
       type = str;
     };
 
-    allowPrivileged = mkOption {
-      description = "Whether to allow Kubernetes containers to request privileged mode.";
-      default = false;
-      type = bool;
-    };
-
     clusterDns = mkOption {
       description = "Use alternative DNS.";
       default = "10.1.0.1";
@@ -105,7 +98,7 @@ in
         default = [];
         example = literalExample ''
           [{
-            "cniVersion": "0.2.0",
+            "cniVersion": "0.3.1",
             "name": "mynet",
             "type": "bridge",
             "bridge": "cni0",
@@ -119,7 +112,7 @@ in
                 ]
             }
           } {
-            "cniVersion": "0.2.0",
+            "cniVersion": "0.3.1",
             "type": "loopback"
           }]
         '';
@@ -241,28 +234,21 @@ in
 
   ###### implementation
   config = mkMerge [
-    (let
-
-      kubeletPaths = filter (a: a != null) [
-        cfg.kubeconfig.caFile
-        cfg.kubeconfig.certFile
-        cfg.kubeconfig.keyFile
-        cfg.clientCaFile
-        cfg.tlsCertFile
-        cfg.tlsKeyFile
-      ];
-
-    in mkIf cfg.enable {
+    (mkIf cfg.enable {
       services.kubernetes.kubelet.seedDockerImages = [infraContainer];
 
       systemd.services.kubelet = {
         description = "Kubernetes Kubelet Service";
-        wantedBy = [ "kubelet.target" ];
-        after = [ "kube-control-plane-online.target" ];
-        before = [ "kubelet.target" ];
+        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 = ''
-          rm -f /opt/cni/bin/* || true
+          ${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
@@ -276,7 +262,6 @@ in
           RestartSec = "1000ms";
           ExecStart = ''${top.package}/bin/kubelet \
             --address=${cfg.address} \
-            --allow-privileged=${boolToString cfg.allowPrivileged} \
             --authentication-token-webhook \
             --authentication-token-webhook-cache-ttl="10s" \
             --authorization-mode=Webhook \
@@ -315,56 +300,6 @@ in
           '';
           WorkingDirectory = top.dataDir;
         };
-        unitConfig.ConditionPathExists = kubeletPaths;
-      };
-
-      systemd.paths.kubelet = {
-        wantedBy =  [ "kubelet.service" ];
-        pathConfig = {
-          PathExists = kubeletPaths;
-          PathChanged = kubeletPaths;
-        };
-      };
-
-      systemd.services.docker.before = [ "kubelet.service" ];
-
-      systemd.services.docker-seed-images = {
-        wantedBy = [ "docker.service" ];
-        after = [ "docker.service" ];
-        before = [ "kubelet.service" ];
-        path = with pkgs; [ docker ];
-        preStart = ''
-          ${concatMapStrings (img: ''
-            echo "Seeding docker image: ${img}"
-            docker load <${img}
-          '') cfg.seedDockerImages}
-        '';
-        script = "echo Ok";
-        serviceConfig.Type = "oneshot";
-        serviceConfig.RemainAfterExit = true;
-        serviceConfig.Slice = "kubernetes.slice";
-      };
-
-      systemd.services.kubelet-online = {
-        wantedBy = [ "kube-node-online.target" ];
-        after = [ "flannel.target" "kubelet.target" ];
-        before = [ "kube-node-online.target" ];
-        # it is complicated. flannel needs kubelet to run the pause container before
-        # it discusses the node CIDR with apiserver and afterwards configures and restarts
-        # dockerd. Until then prevent creating any pods because they have to be recreated anyway
-        # because the network of docker0 has been changed by flannel.
-        script = let
-          docker-env = "/run/flannel/docker";
-          flannel-date = "stat --print=%Y ${docker-env}";
-          docker-date = "systemctl show --property=ActiveEnterTimestamp --value docker";
-        in ''
-          until test -f ${docker-env} ; do sleep 1 ; done
-          while test `${flannel-date}` -gt `date +%s --date="$(${docker-date})"` ; do
-            sleep 1
-          done
-        '';
-        serviceConfig.Type = "oneshot";
-        serviceConfig.Slice = "kubernetes.slice";
       };
 
       # Allways include cni plugins
@@ -373,7 +308,7 @@ in
       boot.kernelModules = ["br_netfilter"];
 
       services.kubernetes.kubelet.hostname = with config.networking;
-        mkDefault (hostName + optionalString (!isNull domain) ".${domain}");
+        mkDefault (hostName + optionalString (domain != null) ".${domain}");
 
       services.kubernetes.pki.certs = with top.lib; {
         kubelet = mkCert {
@@ -411,16 +346,5 @@ in
       };
     })
 
-    {
-      systemd.targets.kubelet = {
-        wantedBy = [ "kube-node-online.target" ];
-        before = [ "kube-node-online.target" ];
-      };
-
-      systemd.targets.kube-node-online = {
-        wantedBy = [ "kubernetes.target" ];
-        before = [ "kubernetes.target" ];
-      };
-    }
   ];
 }
diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index 32eacad9025f..4275563f1a36 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -20,6 +20,7 @@ let
         size = 2048;
     };
     CN = top.masterAddress;
+    hosts = cfg.cfsslAPIExtraSANs;
   });
 
   cfsslAPITokenBaseName = "apitoken.secret";
@@ -27,11 +28,12 @@ let
   certmgrAPITokenPath = "${top.secretsPath}/${cfsslAPITokenBaseName}";
   cfsslAPITokenLength = 32;
 
-  clusterAdminKubeconfig = with cfg.certs.clusterAdmin; {
-    server = top.apiserverAddress;
-    certFile = cert;
-    keyFile = key;
-  };
+  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
@@ -65,6 +67,15 @@ in
       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,
@@ -118,12 +129,6 @@ in
     cfsslCertPathPrefix = "${config.services.cfssl.dataDir}/cfssl";
     cfsslCert = "${cfsslCertPathPrefix}.pem";
     cfsslKey = "${cfsslCertPathPrefix}-key.pem";
-    cfsslPort = toString config.services.cfssl.port;
-
-    certmgrPaths = [
-      top.caFile
-      certmgrAPITokenPath
-    ];
   in
   {
 
@@ -173,40 +178,13 @@ in
         chown cfssl "${cfsslAPITokenPath}" && chmod 400 "${cfsslAPITokenPath}"
       '')]);
 
-    systemd.targets.cfssl-online = {
-      wantedBy = [ "network-online.target" ];
-      after = [ "cfssl.service" "network-online.target" "cfssl-online.service" ];
-    };
-
-    systemd.services.cfssl-online = {
-      description = "Wait for ${remote} to be reachable.";
-      wantedBy = [ "cfssl-online.target" ];
-      before = [ "cfssl-online.target" ];
-      path = [ pkgs.curl ];
-      preStart = ''
-        until curl --fail-early -fskd '{}' ${remote}/api/v1/cfssl/info -o /dev/null; do
-          echo curl ${remote}/api/v1/cfssl/info: exit status $?
-          sleep 2
-        done
-      '';
-      script = "echo Ok";
-      serviceConfig = {
-        TimeoutSec = "300";
-      };
-    };
-
     systemd.services.kube-certmgr-bootstrap = {
       description = "Kubernetes certmgr bootstrapper";
-      wantedBy = [ "cfssl-online.target" ];
-      after = [ "cfssl-online.target" ];
-      before = [ "certmgr.service" ];
-      path = with pkgs; [ curl cfssl ];
+      wantedBy = [ "certmgr.service" ];
+      after = [ "cfssl.target" ];
       script = concatStringsSep "\n" [''
         set -e
 
-        mkdir -p $(dirname ${certmgrAPITokenPath})
-        mkdir -p $(dirname ${top.caFile})
-
         # 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
@@ -218,18 +196,15 @@ in
         fi
       ''
       (optionalString (cfg.pkiTrustOnBootstrap) ''
-        if [ ! -s "${top.caFile}" ]; then
-          until test -s ${top.caFile}.json; do
-            sleep 2
-            curl --fail-early -fskd '{}' ${remote}/api/v1/cfssl/info -o ${top.caFile}.json
-          done
-          cfssljson -f ${top.caFile}.json -stdout >${top.caFile}
-          rm ${top.caFile}.json
+        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 = {
-        TimeoutSec = "500";
+        RestartSec = "10s";
+        Restart = "on-failure";
       };
     };
 
@@ -265,28 +240,35 @@ in
           mapAttrs mkSpec cfg.certs;
       };
 
-      systemd.services.certmgr = {
-        wantedBy = [ "cfssl-online.target" ];
-        after = [ "cfssl-online.target" "kube-certmgr-bootstrap.service" ];
-        preStart = ''
-          while ! test -s ${certmgrAPITokenPath} ; do
-            sleep 1
-            echo Waiting for ${certmgrAPITokenPath}
-          done
-        '';
-        unitConfig.ConditionPathExists = certmgrPaths;
-      };
-
-      systemd.paths.certmgr = {
-        wantedBy = [ "certmgr.service" ];
-        pathConfig = {
-          PathExists = certmgrPaths;
-          PathChanged = certmgrPaths;
-        };
-      };
+      #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)
-        (top.lib.mkKubeConfig "cluster-admin" clusterAdminKubeconfig);
+        clusterAdminKubeconfig;
 
       environment.systemPackages = mkIf (top.kubelet.enable || top.proxy.enable) [
       (pkgs.writeScriptBin "nixos-kubernetes-node-join" ''
@@ -312,22 +294,38 @@ in
           exit 1
         fi
 
-        do_restart=$(test -s ${certmgrAPITokenPath} && echo -n y || echo -n n)
-
         echo $token > ${certmgrAPITokenPath}
         chmod 600 ${certmgrAPITokenPath}
 
-        if [ y = $do_restart ]; then
-          echo "Restarting certmgr..." >&1
-          systemctl restart certmgr
-        fi
+        echo "Restarting certmgr..." >&1
+        systemctl restart certmgr
+
+        echo "Waiting for certs to appear..." >&1
 
-        echo "Node joined succesfully" >&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 = mkIf top.apiserver.enable (with cfg.certs.etcd; {
+      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"];
@@ -336,11 +334,19 @@ in
         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; {
@@ -360,13 +366,6 @@ in
           proxyClientCertFile = mkDefault cfg.certs.apiserverProxyClient.cert;
           proxyClientKeyFile = mkDefault cfg.certs.apiserverProxyClient.key;
         });
-        addonManager = mkIf top.addonManager.enable {
-          kubeconfig = with cfg.certs.addonManager; {
-            certFile = mkDefault cert;
-            keyFile = mkDefault key;
-          };
-          bootstrapAddonsKubeconfig = clusterAdminKubeconfig;
-        };
         controllerManager = mkIf top.controllerManager.enable {
           serviceAccountKeyFile = mkDefault cfg.certs.serviceAccount.key;
           rootCaFile = cfg.certs.controllerManagerClient.caCert;
@@ -375,12 +374,6 @@ in
             keyFile = mkDefault key;
           };
         };
-        flannel = mkIf top.flannel.enable {
-          kubeconfig = with cfg.certs.flannelClient; {
-            certFile = cert;
-            keyFile = key;
-          };
-        };
         scheduler = mkIf top.scheduler.enable {
           kubeconfig = with cfg.certs.schedulerClient; {
             certFile = mkDefault cert;
diff --git a/nixos/modules/services/cluster/kubernetes/proxy.nix b/nixos/modules/services/cluster/kubernetes/proxy.nix
index 23f4d97b7030..86d1dc2439bd 100644
--- a/nixos/modules/services/cluster/kubernetes/proxy.nix
+++ b/nixos/modules/services/cluster/kubernetes/proxy.nix
@@ -7,6 +7,9 @@ let
   cfg = top.proxy;
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "kubernetes" "proxy" "address" ] ["services" "kubernetes" "proxy" "bindAddress"])
+  ];
 
   ###### interface
   options.services.kubernetes.proxy = with lib.types; {
@@ -31,6 +34,12 @@ in
       type = listOf str;
     };
 
+    hostname = mkOption {
+      description = "Kubernetes proxy hostname override.";
+      default = config.networking.hostName;
+      type = str;
+    };
+
     kubeconfig = top.lib.mkKubeConfigOptions "Kubernetes proxy";
 
     verbosity = mkOption {
@@ -45,28 +54,12 @@ in
   };
 
   ###### implementation
-  config = let
-
-    proxyPaths = filter (a: a != null) [
-      cfg.kubeconfig.caFile
-      cfg.kubeconfig.certFile
-      cfg.kubeconfig.keyFile
-    ];
-
-  in mkIf cfg.enable {
-    systemd.services.kube-proxy = rec {
+  config = mkIf cfg.enable {
+    systemd.services.kube-proxy = {
       description = "Kubernetes Proxy Service";
-      wantedBy = [ "kube-node-online.target" ];
-      after = [ "kubelet-online.service" ];
-      before = [ "kube-node-online.target" ];
-      environment.KUBECONFIG = top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig;
-      path = with pkgs; [ iptables conntrack_tools kubectl ];
-      preStart = ''
-        until kubectl auth can-i get nodes/${top.kubelet.hostname} -q 2>/dev/null; do
-          echo kubectl auth can-i get nodes/${top.kubelet.hostname}: exit status $?
-          sleep 2
-        done
-      '';
+      wantedBy = [ "kubernetes.target" ];
+      after = [ "kube-apiserver.service" ];
+      path = with pkgs; [ iptables conntrack_tools ];
       serviceConfig = {
         Slice = "kubernetes.slice";
         ExecStart = ''${top.package}/bin/kube-proxy \
@@ -75,7 +68,8 @@ in
             "--cluster-cidr=${top.clusterCidr}"} \
           ${optionalString (cfg.featureGates != [])
             "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.featureGates}"} \
-          --kubeconfig=${environment.KUBECONFIG} \
+          --hostname-override=${cfg.hostname} \
+          --kubeconfig=${top.lib.mkKubeConfig "kube-proxy" cfg.kubeconfig} \
           ${optionalString (cfg.verbosity != null) "--v=${toString cfg.verbosity}"} \
           ${cfg.extraOpts}
         '';
@@ -83,16 +77,9 @@ in
         Restart = "on-failure";
         RestartSec = 5;
       };
-      unitConfig.ConditionPathExists = proxyPaths;
     };
 
-    systemd.paths.kube-proxy = {
-      wantedBy = [ "kube-proxy.service" ];
-      pathConfig = {
-        PathExists = proxyPaths;
-        PathChanged = proxyPaths;
-      };
-    };
+    services.kubernetes.proxy.hostname = with config.networking; mkDefault hostName;
 
     services.kubernetes.pki.certs = {
       kubeProxyClient = top.lib.mkCert {
diff --git a/nixos/modules/services/cluster/kubernetes/scheduler.nix b/nixos/modules/services/cluster/kubernetes/scheduler.nix
index a0e484542951..5f6113227d9d 100644
--- a/nixos/modules/services/cluster/kubernetes/scheduler.nix
+++ b/nixos/modules/services/cluster/kubernetes/scheduler.nix
@@ -56,35 +56,18 @@ in
   };
 
   ###### implementation
-  config =  let
-
-    schedulerPaths = filter (a: a != null) [
-      cfg.kubeconfig.caFile
-      cfg.kubeconfig.certFile
-      cfg.kubeconfig.keyFile
-    ];
-
-  in mkIf cfg.enable {
-    systemd.services.kube-scheduler = rec {
+  config = mkIf cfg.enable {
+    systemd.services.kube-scheduler = {
       description = "Kubernetes Scheduler Service";
-      wantedBy = [ "kube-control-plane-online.target" ];
+      wantedBy = [ "kubernetes.target" ];
       after = [ "kube-apiserver.service" ];
-      before = [ "kube-control-plane-online.target" ];
-      environment.KUBECONFIG = top.lib.mkKubeConfig "kube-scheduler" cfg.kubeconfig;
-      path = [ pkgs.kubectl ];
-      preStart = ''
-        until kubectl auth can-i get /api -q 2>/dev/null; do
-          echo kubectl auth can-i get /api: exit status $?
-          sleep 2
-        done
-      '';
       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=${environment.KUBECONFIG} \
+          --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}"} \
@@ -96,15 +79,6 @@ in
         Restart = "on-failure";
         RestartSec = 5;
       };
-      unitConfig.ConditionPathExists = schedulerPaths;
-    };
-
-    systemd.paths.kube-scheduler = {
-      wantedBy = [ "kube-scheduler.service" ];
-      pathConfig = {
-        PathExists = schedulerPaths;
-        PathChanged = schedulerPaths;
-      };
     };
 
     services.kubernetes.pki.certs = {
diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix
index 8abe3c5b8c9b..a7edac025384 100644
--- a/nixos/modules/services/computing/boinc/client.nix
+++ b/nixos/modules/services/computing/boinc/client.nix
@@ -105,19 +105,18 @@ in
         isSystemUser = true;
       };
 
+      systemd.tmpfiles.rules = [
+        "d '${cfg.dataDir}' - boinc - - -"
+      ];
+
       systemd.services.boinc = {
         description = "BOINC Client";
-        after = ["network.target" "local-fs.target"];
+        after = ["network.target"];
         wantedBy = ["multi-user.target"];
-        preStart = ''
-          mkdir -p ${cfg.dataDir}
-          chown boinc ${cfg.dataDir}
-        '';
         script = ''
           ${fhsEnvExecutable} --dir ${cfg.dataDir} --redirectio ${allowRemoteGuiRpcFlag}
         '';
         serviceConfig = {
-          PermissionsStartOnly = true; # preStart must be run as root
           User = "boinc";
           Nice = 10;
         };
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index d1a1383e45b0..c70d999ca96d 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -18,7 +18,7 @@ let
       ${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}
+      PlugStackConfig=${plugStackConfig}/plugstack.conf
       ProctrackType=${cfg.procTrackType}
       ${cfg.extraConfig}
     '';
@@ -39,6 +39,8 @@ let
      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}
    '';
 
@@ -48,7 +50,6 @@ let
     name = "etc-slurm";
     paths = [ configFile cgroupConfig plugStackConfig ] ++ cfg.extraConfigPaths;
   };
-
 in
 
 {
@@ -86,6 +87,37 @@ in
           '';
         };
 
+        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 = "";
@@ -112,7 +144,7 @@ in
 
       package = mkOption {
         type = types.package;
-        default = pkgs.slurm;
+        default = pkgs.slurm.override { enableX11 = ! cfg.enableSrunX11; };
         defaultText = "pkgs.slurm";
         example = literalExample "pkgs.slurm-full";
         description = ''
@@ -178,9 +210,14 @@ in
           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
-          'services.openssh.forwardX11' on the client.
+          <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.
         '';
       };
 
@@ -356,7 +393,11 @@ in
       requires = [ "munged.service" "mysql.service" ];
 
       # slurm strips the last component off the path
-      environment.SLURM_CONF = "${slurmdbdConf}/slurm.conf";
+      environment.SLURM_CONF =
+        if (cfg.dbdserver.configFile == null) then
+          "${slurmdbdConf}/slurm.conf"
+        else
+          cfg.dbdserver.configFile;
 
       serviceConfig = {
         Type = "forking";
diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index 9c615fbe885f..e3da3092d459 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -222,19 +222,20 @@ in {
   };
 
   config = mkIf cfg.enable {
-    users.groups = optional (cfg.group == "buildbot") {
-      name = "buildbot";
+    users.groups = optionalAttrs (cfg.group == "buildbot") {
+      buildbot = { };
     };
 
-    users.users = optional (cfg.user == "buildbot") {
-      name = "buildbot";
-      description = "Buildbot User.";
-      isNormalUser = true;
-      createHome = true;
-      home = cfg.home;
-      group = cfg.group;
-      extraGroups = cfg.extraGroups;
-      useDefaultShell = true;
+    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 = {
diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix
index 49e04ca36228..52f24b8cee3c 100644
--- a/nixos/modules/services/continuous-integration/buildbot/worker.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -136,19 +136,20 @@ in {
   config = mkIf cfg.enable {
     services.buildbot-worker.workerPassFile = mkDefault (pkgs.writeText "buildbot-worker-password" cfg.workerPass);
 
-    users.groups = optional (cfg.group == "bbworker") {
-      name = "bbworker";
+    users.groups = optionalAttrs (cfg.group == "bbworker") {
+      bbworker = { };
     };
 
-    users.users = optional (cfg.user == "bbworker") {
-      name = "bbworker";
-      description = "Buildbot Worker User.";
-      isNormalUser = true;
-      createHome = true;
-      home = cfg.home;
-      group = cfg.group;
-      extraGroups = cfg.extraGroups;
-      useDefaultShell = true;
+    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 = {
diff --git a/nixos/modules/services/continuous-integration/buildkite-agent.nix b/nixos/modules/services/continuous-integration/buildkite-agent.nix
index 2136778aff47..58bce6549414 100644
--- a/nixos/modules/services/continuous-integration/buildkite-agent.nix
+++ b/nixos/modules/services/continuous-integration/buildkite-agent.nix
@@ -50,8 +50,8 @@ in
       };
 
       runtimePackages = mkOption {
-        default = [ pkgs.bash pkgs.nix ];
-        defaultText = "[ pkgs.bash pkgs.nix ]";
+        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;
       };
@@ -74,13 +74,12 @@ in
         '';
       };
 
-      meta-data = mkOption {
-        type = types.str;
-        default = "";
-        example = "queue=default,docker=true,ruby2=true";
+      tags = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        example = { queue = "default"; docker = "true"; ruby2 ="true"; };
         description = ''
-          Meta data for the agent. This is a comma-separated list of
-          <code>key=value</code> pairs.
+          Tags for the agent.
         '';
       };
 
@@ -93,26 +92,20 @@ in
         '';
       };
 
-      openssh =
-        { privateKeyPath = mkOption {
-            type = types.path;
-            description = ''
-              Private agent key.
+      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;
 
-              A run-time path to the key file, which is supposed to be provisioned
-              outside of Nix store.
-            '';
-          };
-          publicKeyPath = mkOption {
-            type = types.path;
-            description = ''
-              Public agent key.
-
-              A run-time path to the key file, which is supposed to be provisioned
-              outside of Nix store.
-            '';
-          };
-        };
+        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";
@@ -181,17 +174,26 @@ in
           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.
+        '';
+      };
     };
   };
 
   config = mkIf config.services.buildkite-agent.enable {
-    users.users.buildkite-agent =
-      { name = "buildkite-agent";
-        home = cfg.dataDir;
-        createHome = true;
-        description = "Buildkite agent user";
-        extraGroups = [ "keys" ];
-      };
+    users.users.buildkite-agent = {
+      name = "buildkite-agent";
+      home = cfg.dataDir;
+      createHome = true;
+      description = "Buildkite agent user";
+      extraGroups = [ "keys" ];
+      isSystemUser = true;
+    };
 
     environment.systemPackages = [ cfg.package ];
 
@@ -209,17 +211,18 @@ in
         ##     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.openssh.privateKeyPath}" "${sshDir}/id_rsa"
-            cp -f "${toString cfg.openssh.publicKeyPath}"  "${sshDir}/id_rsa.pub"
-            chmod 600 "${sshDir}"/id_rsa*
-
+            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}"
-            meta-data="${cfg.meta-data}"
+            shell="${cfg.shell}"
+            tags="${tagStr}"
             build-path="${cfg.dataDir}/builds"
             hooks-path="${cfg.hooksPath}"
             ${cfg.extraConfig}
@@ -227,16 +230,19 @@ in
           '';
 
         serviceConfig =
-          { ExecStart = "${pkgs.buildkite-agent}/bin/buildkite-agent start --config /var/lib/buildkite-agent/buildkite-agent.cfg";
+          { ExecStart = "${cfg.package}/bin/buildkite-agent start --config /var/lib/buildkite-agent/buildkite-agent.cfg";
             User = "buildkite-agent";
             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";
           };
       };
 
     assertions = [
-      { assertion = cfg.hooksPath == hooksDir || all isNull (attrValues cfg.hooks);
+      { assertion = cfg.hooksPath == hooksDir || all (v: v == null) (attrValues cfg.hooks);
         message = ''
           Options `services.buildkite-agent.hooksPath' and
           `services.buildkite-agent.hooks.<name>' are mutually exclusive.
@@ -245,8 +251,11 @@ in
     ];
   };
   imports = [
-    (mkRenamedOptionModule [ "services" "buildkite-agent" "token" ]                [ "services" "buildkite-agent" "tokenPath" ])
-    (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "privateKey" ] [ "services" "buildkite-agent" "openssh" "privateKeyPath" ])
-    (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "publicKey" ]  [ "services" "buildkite-agent" "openssh" "publicKeyPath" ])
+    (mkRenamedOptionModule [ "services" "buildkite-agent" "token" ]                    [ "services" "buildkite-agent" "tokenPath" ])
+    (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "privateKey" ]     [ "services" "buildkite-agent" "privateSshKeyPath" ])
+    (mkRenamedOptionModule [ "services" "buildkite-agent" "openssh" "privateKeyPath" ] [ "services" "buildkite-agent" "privateSshKeyPath" ])
+    (mkRemovedOptionModule [ "services" "buildkite-agent" "openssh" "publicKey" ]      "SSH public keys aren't necessary to clone private repos.")
+    (mkRemovedOptionModule [ "services" "buildkite-agent" "openssh" "publicKeyPath" ]  "SSH public keys aren't necessary to clone private repos.")
+    (mkRenamedOptionModule [ "services" "buildkite-agent" "meta-data"]                 [ "services" "buildkite-agent" "tags" ])
   ];
 }
diff --git a/nixos/modules/services/continuous-integration/gitlab-runner.nix b/nixos/modules/services/continuous-integration/gitlab-runner.nix
index 3ceaa6f5ff3e..3d307b1abcf8 100644
--- a/nixos/modules/services/continuous-integration/gitlab-runner.nix
+++ b/nixos/modules/services/continuous-integration/gitlab-runner.nix
@@ -111,7 +111,10 @@ in
   config = mkIf cfg.enable {
     systemd.services.gitlab-runner = {
       path = cfg.packages;
-      environment = config.networking.proxy.envVars;
+      environment = config.networking.proxy.envVars // {
+        # Gitlab runner will not start if the HOME variable is not set
+        HOME = cfg.workDir;
+      };
       description = "Gitlab Runner";
       after = [ "network.target" ]
         ++ optional hasDocker "docker.service";
diff --git a/nixos/modules/services/continuous-integration/gocd-agent/default.nix b/nixos/modules/services/continuous-integration/gocd-agent/default.nix
index 8126f27c2b0c..2e9e1c94857a 100644
--- a/nixos/modules/services/continuous-integration/gocd-agent/default.nix
+++ b/nixos/modules/services/continuous-integration/gocd-agent/default.nix
@@ -135,20 +135,20 @@ in {
   };
 
   config = mkIf cfg.enable {
-    users.groups = optional (cfg.group == "gocd-agent") {
-      name = "gocd-agent";
-      gid = config.ids.gids.gocd-agent;
+    users.groups = optionalAttrs (cfg.group == "gocd-agent") {
+      gocd-agent.gid = config.ids.gids.gocd-agent;
     };
 
-    users.users = optional (cfg.user == "gocd-agent") {
-      name = "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;
+    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 = {
diff --git a/nixos/modules/services/continuous-integration/gocd-server/default.nix b/nixos/modules/services/continuous-integration/gocd-server/default.nix
index 8f177da129e5..4fa41ac49edf 100644
--- a/nixos/modules/services/continuous-integration/gocd-server/default.nix
+++ b/nixos/modules/services/continuous-integration/gocd-server/default.nix
@@ -143,20 +143,20 @@ in {
   };
 
   config = mkIf cfg.enable {
-    users.groups = optional (cfg.group == "gocd-server") {
-      name = "gocd-server";
-      gid = config.ids.gids.gocd-server;
+    users.groups = optionalAttrs (cfg.group == "gocd-server") {
+      gocd-server.gid = config.ids.gids.gocd-server;
     };
 
-    users.users = optional (cfg.user == "gocd-server") {
-      name = "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;
+    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 = {
diff --git a/nixos/modules/services/continuous-integration/hydra/default.nix b/nixos/modules/services/continuous-integration/hydra/default.nix
index c7fe4eeeab99..8b56207590a1 100644
--- a/nixos/modules/services/continuous-integration/hydra/default.nix
+++ b/nixos/modules/services/continuous-integration/hydra/default.nix
@@ -43,7 +43,7 @@ in
   ###### interface
   options = {
 
-    services.hydra = rec {
+    services.hydra = {
 
       enable = mkOption {
         type = types.bool;
@@ -167,7 +167,7 @@ in
 
       buildMachinesFiles = mkOption {
         type = types.listOf types.path;
-        default = [ "/etc/nix/machines" ];
+        default = optional (config.nix.buildMachines != []) "/etc/nix/machines";
         example = [ "/etc/nix/machines" "/var/lib/hydra/provisioner/machines" ];
         description = "List of files containing build machines.";
       };
@@ -242,8 +242,8 @@ in
     environment.variables = hydraEnv;
 
     nix.extraOptions = ''
-      gc-keep-outputs = true
-      gc-keep-derivations = true
+      keep-outputs = true
+      keep-derivations = true
 
       # The default (`true') slows Nix down a lot since the build farm
       # has so many GC roots.
@@ -275,6 +275,7 @@ in
               ${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
@@ -332,7 +333,7 @@ in
           IN_SYSTEMD = "1"; # to get log severity levels
         };
         serviceConfig =
-          { ExecStart = "@${cfg.package}/bin/hydra-queue-runner hydra-queue-runner -v --option build-use-substitutes ${boolToString cfg.useSubstitutes}";
+          { ExecStart = "@${cfg.package}/bin/hydra-queue-runner hydra-queue-runner -v";
             ExecStopPost = "${cfg.package}/bin/hydra-queue-runner --unlock";
             User = "hydra-queue-runner";
             Restart = "always";
@@ -379,6 +380,23 @@ in
           };
       };
 
+    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 = "@${cfg.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 =
@@ -416,6 +434,8 @@ in
         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
diff --git a/nixos/modules/services/continuous-integration/jenkins/default.nix b/nixos/modules/services/continuous-integration/jenkins/default.nix
index 1eca45fbd570..1477c471f8ab 100644
--- a/nixos/modules/services/continuous-integration/jenkins/default.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/default.nix
@@ -150,20 +150,20 @@ in {
       pkgs.dejavu_fonts
     ];
 
-    users.groups = optional (cfg.group == "jenkins") {
-      name = "jenkins";
-      gid = config.ids.gids.jenkins;
+    users.groups = optionalAttrs (cfg.group == "jenkins") {
+      jenkins.gid = config.ids.gids.jenkins;
     };
 
-    users.users = optional (cfg.user == "jenkins") {
-      name = "jenkins";
-      description = "jenkins user";
-      createHome = true;
-      home = cfg.home;
-      group = cfg.group;
-      extraGroups = cfg.extraGroups;
-      useDefaultShell = true;
-      uid = config.ids.uids.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 = {
@@ -189,11 +189,11 @@ in {
 
       preStart =
         let replacePlugins =
-              if isNull cfg.plugins
+              if cfg.plugins == null
               then ""
               else
                 let pluginCmds = lib.attrsets.mapAttrsToList
-                      (n: v: "cp ${v} ${cfg.home}/plugins/${n}.hpi")
+                      (n: v: "cp ${v} ${cfg.home}/plugins/${n}.jpi")
                       cfg.plugins;
                 in ''
                   rm -r ${cfg.home}/plugins || true
diff --git a/nixos/modules/services/continuous-integration/jenkins/slave.nix b/nixos/modules/services/continuous-integration/jenkins/slave.nix
index 92deabc3dd3b..3c0e6f78e74c 100644
--- a/nixos/modules/services/continuous-integration/jenkins/slave.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/slave.nix
@@ -50,19 +50,19 @@ in {
   };
 
   config = mkIf (cfg.enable && !masterCfg.enable) {
-    users.groups = optional (cfg.group == "jenkins") {
-      name = "jenkins";
-      gid = config.ids.gids.jenkins;
+    users.groups = optionalAttrs (cfg.group == "jenkins") {
+      jenkins.gid = config.ids.gids.jenkins;
     };
 
-    users.users = optional (cfg.user == "jenkins") {
-      name = "jenkins";
-      description = "jenkins user";
-      createHome = true;
-      home = cfg.home;
-      group = cfg.group;
-      useDefaultShell = true;
-      uid = config.ids.uids.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/nixos/modules/services/databases/4store-endpoint.nix b/nixos/modules/services/databases/4store-endpoint.nix
deleted file mode 100644
index 59ed0e5f0afd..000000000000
--- a/nixos/modules/services/databases/4store-endpoint.nix
+++ /dev/null
@@ -1,74 +0,0 @@
-{ config, lib, pkgs, ... }:
-let
-  cfg = config.services.fourStoreEndpoint;
-  endpointUser = "fourstorehttp";
-  run = "${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${endpointUser} -c";
-in
-with lib;
-{
-
-  ###### interface
-
-  options = {
-
-    services.fourStoreEndpoint = {
-
-      enable = mkOption {
-        default = false;
-        description = "Whether to enable 4Store SPARQL endpoint.";
-      };
-
-      database = mkOption {
-        default = config.services.fourStore.database;
-        description = "RDF database name to expose via the endpoint. Defaults to local 4Store database name.";
-      };
-
-      listenAddress = mkOption {
-        default = null;
-        description = "IP address to listen on.";
-      };
-
-      port = mkOption {
-        default = 8080;
-        description = "port to listen on.";
-      };
-
-      options = mkOption {
-        default = "";
-        description = "Extra CLI options to pass to 4Store's 4s-httpd process.";
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    assertions = singleton
-      { assertion = cfg.enable -> cfg.database != "";
-        message = "Must specify 4Store database name";
-      };
-
-    users.users = singleton
-      { name = endpointUser;
-        uid = config.ids.uids.fourstorehttp;
-        description = "4Store SPARQL endpoint user";
-      };
-
-    services.avahi.enable = true;
-
-    systemd.services."4store-endpoint" = {
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      script = ''
-        ${run} '${pkgs.rdf4store}/bin/4s-httpd -D ${cfg.options} ${if cfg.listenAddress!=null then "-H ${cfg.listenAddress}" else "" } -p ${toString cfg.port} ${cfg.database}'
-      '';
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/databases/4store.nix b/nixos/modules/services/databases/4store.nix
deleted file mode 100644
index be4351c1c38f..000000000000
--- a/nixos/modules/services/databases/4store.nix
+++ /dev/null
@@ -1,72 +0,0 @@
-{ config, lib, pkgs, ... }:
-let
-  cfg = config.services.fourStore;
-  stateDir = "/var/lib/4store";
-  fourStoreUser = "fourstore";
-  run = "${pkgs.su}/bin/su -s ${pkgs.runtimeShell} ${fourStoreUser}";
-in
-with lib;
-{
-
-  ###### interface
-
-  options = {
-
-    services.fourStore = {
-
-      enable = mkOption {
-        default = false;
-        description = "Whether to enable 4Store RDF database server.";
-      };
-
-      database = mkOption {
-        default = "";
-        description = "RDF database name. If it doesn't exist, it will be created. Databases are stored in ${stateDir}.";
-      };
-
-      options = mkOption {
-        default = "";
-        description = "Extra CLI options to pass to 4Store.";
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    assertions = singleton
-      { assertion = cfg.enable -> cfg.database != "";
-        message = "Must specify 4Store database name.";
-      };
-
-    users.users = singleton
-      { name = fourStoreUser;
-        uid = config.ids.uids.fourstore;
-        description = "4Store database user";
-        home = stateDir;
-      };
-
-    services.avahi.enable = true;
-
-    systemd.services."4store" = {
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-
-      preStart = ''
-        mkdir -p ${stateDir}/
-        chown ${fourStoreUser} ${stateDir}
-        if ! test -e "${stateDir}/${cfg.database}"; then
-          ${run} -c '${pkgs.rdf4store}/bin/4s-backend-setup ${cfg.database}'
-        fi
-      '';
-
-      script = ''
-        ${run} -c '${pkgs.rdf4store}/bin/4s-backend -D ${cfg.options} ${cfg.database}'
-      '';
-    };
-  };
-}
diff --git a/nixos/modules/services/databases/cassandra.nix b/nixos/modules/services/databases/cassandra.nix
index d741ee48c48f..90c094f68b61 100644
--- a/nixos/modules/services/databases/cassandra.nix
+++ b/nixos/modules/services/databases/cassandra.nix
@@ -8,25 +8,28 @@ let
   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";
-       seed_provider =
-         [{ class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
-            parameters = [ { seeds = "127.0.0.1"; } ];
-         }];
        data_file_directories = [ "${cfg.homeDir}/data" ];
        commitlog_directory = "${cfg.homeDir}/commitlog";
        saved_caches_directory = "${cfg.homeDir}/saved_caches";
-     } // (if builtins.compareVersions cfg.package.version "3" >= 0
-             then { hints_directory = "${cfg.homeDir}/hints"; }
-             else {})
+     } // (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 isNull cfg.listenAddress
+    ( if cfg.listenAddress == null
         then { listen_interface = cfg.listenInterface; }
         else { listen_address = cfg.listenAddress; }
     ) // (
-      if isNull cfg.rpcAddress
+      if cfg.rpcAddress == null
         then { rpc_interface = cfg.rpcInterface; }
         else { rpc_address = cfg.rpcAddress; }
     );
@@ -39,15 +42,42 @@ let
         mkdir -p "$out"
 
         echo "$cassandraYaml" > "$out/cassandra.yaml"
-        ln -s "$cassandraEnvPkg" "$out/cassandra-env.sh"
         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;
@@ -162,6 +192,28 @@ in {
         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 = {};
@@ -178,11 +230,11 @@ in {
       example = literalExample "null";
       description = ''
           Set the interval how often full repairs are run, i.e.
-          `nodetool repair --full` is executed. See
+          <literal>nodetool repair --full</literal> is executed. See
           https://cassandra.apache.org/doc/latest/operating/repair.html
           for more information.
 
-          Set to `null` to disable full repairs.
+          Set to <literal>null</literal> to disable full repairs.
         '';
     };
     fullRepairOptions = mkOption {
@@ -199,51 +251,160 @@ in {
       example = literalExample "null";
       description = ''
           Set the interval how often incremental repairs are run, i.e.
-          `nodetool repair` is executed. See
+          <literal>nodetool repair</literal> is executed. See
           https://cassandra.apache.org/doc/latest/operating/repair.html
           for more information.
 
-          Set to `null` to disable incremental repairs.
+          Set to <literal>null</literal> to disable incremental repairs.
         '';
     };
     incrementalRepairOptions = mkOption {
-      type = types.listOf types.string;
+      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 =
-            ((isNull cfg.listenAddress)
-             || (isNull cfg.listenInterface)
-            ) && !((isNull cfg.listenAddress)
-                   && (isNull cfg.listenInterface)
-                  );
+      [ { assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null);
           message = "You have to set either listenAddress or listenInterface";
         }
-        { assertion =
-            ((isNull cfg.rpcAddress)
-             || (isNull cfg.rpcInterface)
-            ) && !((isNull cfg.rpcAddress)
-                   && (isNull cfg.rpcInterface)
-                  );
+        { 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}" =
+      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;
+      extraGroups.${defaultUser}.gid = config.ids.gids.cassandra;
     };
 
     systemd.services.cassandra =
@@ -251,7 +412,12 @@ in {
         after = [ "network.target" ];
         environment =
           { CASSANDRA_CONF = "${cassandraEtc}";
-            JVM_OPTS = builtins.concatStringsSep " " cfg.jvmOpts;
+            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 =
@@ -276,7 +442,7 @@ in {
           };
       };
     systemd.timers.cassandra-full-repair =
-      mkIf (!isNull cfg.fullRepairInterval) {
+      mkIf (cfg.fullRepairInterval != null) {
         description = "Schedule full repairs on Cassandra";
         wantedBy = [ "timers.target" ];
         timerConfig =
@@ -300,7 +466,7 @@ in {
           };
       };
     systemd.timers.cassandra-incremental-repair =
-      mkIf (!isNull cfg.incrementalRepairInterval) {
+      mkIf (cfg.incrementalRepairInterval != null) {
         description = "Schedule incremental repairs on Cassandra";
         wantedBy = [ "timers.target" ];
         timerConfig =
diff --git a/nixos/modules/services/databases/cockroachdb.nix b/nixos/modules/services/databases/cockroachdb.nix
index e977751b21ef..b6f94a4881a0 100644
--- a/nixos/modules/services/databases/cockroachdb.nix
+++ b/nixos/modules/services/databases/cockroachdb.nix
@@ -7,7 +7,7 @@ let
   crdb = cfg.package;
 
   escape    = builtins.replaceStrings ["%"] ["%%"];
-  ifNotNull = v: s: optionalString (!isNull v) s;
+  ifNotNull = v: s: optionalString (v != null) s;
 
   startupCommand = lib.concatStringsSep " "
     [ # Basic startup
@@ -164,24 +164,24 @@ in
 
   config = mkIf config.services.cockroachdb.enable {
     assertions = [
-      { assertion = !cfg.insecure -> !(isNull cfg.certsDir);
+      { 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") (singleton
-      { name        = "cockroachdb";
+    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") (singleton
-      { name = "cockroachdb";
-        gid  = config.ids.gids.cockroachdb;
-      });
+    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 ];
diff --git a/nixos/modules/services/databases/couchdb.nix b/nixos/modules/services/databases/couchdb.nix
index 5ddf8ba4bfbd..53224db1d896 100644
--- a/nixos/modules/services/databases/couchdb.nix
+++ b/nixos/modules/services/databases/couchdb.nix
@@ -56,7 +56,7 @@ in {
 
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "couchdb";
         description = ''
           User account under which couchdb runs.
@@ -64,7 +64,7 @@ in {
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "couchdb";
         description = ''
           Group account under which couchdb runs.
@@ -106,7 +106,7 @@ in {
       };
 
       bindAddress = mkOption {
-        type = types.string;
+        type = types.str;
         default = "127.0.0.1";
         description = ''
           Defines the IP address by which CouchDB will be accessible.
@@ -138,7 +138,7 @@ in {
       };
 
       configFile = mkOption {
-        type = types.string;
+        type = types.path;
         description = ''
           Configuration file for persisting runtime changes. File
           needs to be readable and writable from couchdb user/group.
@@ -160,7 +160,7 @@ in {
 
     systemd.tmpfiles.rules = [
       "d '${dirOf cfg.uriFile}' - ${cfg.user} ${cfg.group} - -"
-      "d '${dirOf cfg.logFile}' - ${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} - -"
     ];
@@ -169,11 +169,9 @@ in {
       description = "CouchDB Server";
       wantedBy = [ "multi-user.target" ];
 
-      preStart =
-        ''
+      preStart = ''
         touch ${cfg.configFile}
-        touch -a ${cfg.logFile}
-        '';
+      '';
 
       environment = mkIf useVersion2 {
         # we are actually specifying 4 configuration files:
diff --git a/nixos/modules/services/databases/firebird.nix b/nixos/modules/services/databases/firebird.nix
index cc81b440450b..042c9841df54 100644
--- a/nixos/modules/services/databases/firebird.nix
+++ b/nixos/modules/services/databases/firebird.nix
@@ -95,6 +95,11 @@ in
 
     environment.systemPackages = [cfg.package];
 
+    systemd.tmpfiles.rules = [
+      "d '${dataDir}' 0700 ${cfg.user} - - -"
+      "d '${systemDir}' 0700 ${cfg.user} - - -"
+    ];
+
     systemd.services.firebird =
       { description = "Firebird Super-Server";
 
@@ -104,21 +109,16 @@ in
         # is a better way
         preStart =
           ''
-            mkdir -m 0700 -p \
-              "${dataDir}" \
-              "${systemDir}" \
-              /var/log/firebird
-
             if ! test -e "${systemDir}/security2.fdb"; then
                 cp ${firebird}/security2.fdb "${systemDir}"
             fi
 
-            chown -R ${cfg.user} "${dataDir}" "${systemDir}" /var/log/firebird
             chmod -R 700         "${dataDir}" "${systemDir}" /var/log/firebird
           '';
 
-        serviceConfig.PermissionsStartOnly = true; # preStart must be run as root
         serviceConfig.User = cfg.user;
+        serviceConfig.LogsDirectory = "firebird";
+        serviceConfig.LogsDirectoryMode = "0700";
         serviceConfig.ExecStart = ''${firebird}/bin/fbserver -d'';
 
         # TODO think about shutdown
diff --git a/nixos/modules/services/databases/foundationdb.nix b/nixos/modules/services/databases/foundationdb.nix
index ad24f9f4b0fd..18727acc7c75 100644
--- a/nixos/modules/services/databases/foundationdb.nix
+++ b/nixos/modules/services/databases/foundationdb.nix
@@ -36,6 +36,10 @@ let
     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}
@@ -136,7 +140,7 @@ in
     };
 
     logSize = mkOption {
-      type        = types.string;
+      type        = types.str;
       default     = "10MiB";
       description = ''
         Roll over to a new log file after the current log file
@@ -145,7 +149,7 @@ in
     };
 
     maxLogSize = mkOption {
-      type        = types.string;
+      type        = types.str;
       default     = "100MiB";
       description = ''
         Delete the oldest log file when the total size of all log
@@ -167,7 +171,7 @@ in
     };
 
     memory = mkOption {
-      type        = types.string;
+      type        = types.str;
       default     = "8GiB";
       description = ''
         Maximum memory used by the process. The default value is
@@ -189,7 +193,7 @@ in
     };
 
     storageMemory = mkOption {
-      type        = types.string;
+      type        = types.str;
       default     = "1GiB";
       description = ''
         Maximum memory used for data storage. The default value is
@@ -317,22 +321,37 @@ in
       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") (singleton
-      { name        = "foundationdb";
+    users.users = optionalAttrs (cfg.user == "foundationdb") {
+      foundationdb = {
         description = "FoundationDB User";
         uid         = config.ids.uids.foundationdb;
         group       = cfg.group;
-      });
+      };
+    };
 
-    users.groups = optionalAttrs (cfg.group == "foundationdb") (singleton
-      { name = "foundationdb";
-        gid  = config.ids.gids.foundationdb;
-      });
+    users.groups = optionalAttrs (cfg.group == "foundationdb") {
+      foundationdb.gid = config.ids.gids.foundationdb;
+    };
 
     networking.firewall.allowedTCPPortRanges = mkIf cfg.openFirewall
       [ { from = cfg.listenPortStart;
@@ -340,6 +359,13 @@ in
         }
       ];
 
+    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";
 
@@ -377,25 +403,12 @@ in
       path = [ pkg pkgs.coreutils ];
 
       preStart = ''
-        rm -f ${cfg.pidfile}   && \
-          touch ${cfg.pidfile} && \
-          chown -R ${cfg.user}:${cfg.group} ${cfg.pidfile}
-
-        for x in "${cfg.logDir}" "${cfg.dataDir}"; do
-          [ ! -d "$x" ] && mkdir -m 0700 -vp "$x";
-          chown -R ${cfg.user}:${cfg.group} "$x";
-        done
-
-        [ ! -d /etc/foundationdb ] && \
-          mkdir -m 0775 -vp /etc/foundationdb && \
-          chown -R ${cfg.user}:${cfg.group} "/etc/foundationdb"
-
         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 && chown -R ${cfg.user}:${cfg.group} $cf
+            chmod 0664 $cf
             touch "${cfg.dataDir}/.first_startup"
         fi
       '';
@@ -404,7 +417,7 @@ in
 
       postStart = ''
         if [ -e "${cfg.dataDir}/.first_startup" ]; then
-          fdbcli --exec "configure new single memory"
+          fdbcli --exec "configure new single ssd"
           rm -f "${cfg.dataDir}/.first_startup";
         fi
       '';
diff --git a/nixos/modules/services/databases/foundationdb.xml b/nixos/modules/services/databases/foundationdb.xml
index bf4b644c9b86..b0b1ebeab45f 100644
--- a/nixos/modules/services/databases/foundationdb.xml
+++ b/nixos/modules/services/databases/foundationdb.xml
@@ -47,14 +47,14 @@ services.foundationdb.package = pkgs.foundationdb52; # FoundationDB 5.2.x
    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>):
-<programlisting>
-$ sudo -u foundationdb fdbcli
+<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'.
-fdb> status
+<prompt>fdb> </prompt>status
 
 Using cluster file `/etc/foundationdb/fdb.cluster'.
 
@@ -72,8 +72,8 @@ Cluster:
 
 ...
 
-fdb>
-</programlisting>
+<prompt>fdb></prompt>
+</screen>
   </para>
 
   <para>
@@ -82,8 +82,8 @@ fdb>
    cluster status, as a quick example. (This example uses
    <command>nix-shell</command> shebang support to automatically supply the
    necessary Python modules).
-<programlisting>
-a@link> cat fdb-status.py
+<screen>
+<prompt>a@link> </prompt>cat fdb-status.py
 #! /usr/bin/env nix-shell
 #! nix-shell -i python -p python pythonPackages.foundationdb52
 
@@ -103,11 +103,11 @@ def main():
 
 if __name__ == "__main__":
     main()
-a@link> chmod +x fdb-status.py
-a@link> ./fdb-status.py
+<prompt>a@link> </prompt>chmod +x fdb-status.py
+<prompt>a@link> </prompt>./fdb-status.py
 FoundationDB available: True
-a@link>
-</programlisting>
+<prompt>a@link></prompt>
+</screen>
   </para>
 
   <para>
@@ -266,10 +266,10 @@ services.foundationdb.dataDir = "/data/fdb";
    <emphasis>every</emphasis> node a coordinator automatically:
   </para>
 
-<programlisting>
-fdbcli> configure double ssd
-fdbcli> coordinators auto
-</programlisting>
+<screen>
+<prompt>fdbcli> </prompt>configure double ssd
+<prompt>fdbcli> </prompt>coordinators auto
+</screen>
 
   <para>
    This will transparently update all the servers within seconds, and
@@ -386,10 +386,10 @@ services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
    You can now perform a backup:
   </para>
 
-<programlisting>
-$ sudo -u foundationdb fdbbackup start  -t default -d file:///opt/fdb-backups
-$ sudo -u foundationdb fdbbackup status -t default
-</programlisting>
+<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>
diff --git a/nixos/modules/services/databases/hbase.nix b/nixos/modules/services/databases/hbase.nix
index 52f2d95b4e00..2d1a47bbaa31 100644
--- a/nixos/modules/services/databases/hbase.nix
+++ b/nixos/modules/services/databases/hbase.nix
@@ -53,7 +53,7 @@ in {
 
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "hbase";
         description = ''
           User account under which HBase runs.
@@ -61,7 +61,7 @@ in {
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "hbase";
         description = ''
           Group account under which HBase runs.
@@ -94,6 +94,11 @@ in {
 
   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" ];
@@ -103,19 +108,7 @@ in {
         HBASE_LOG_DIR = cfg.logDir;
       };
 
-      preStart =
-        ''
-        mkdir -p ${cfg.dataDir};
-        mkdir -p ${cfg.logDir};
-
-        if [ "$(id -u)" = 0 ]; then
-          chown ${cfg.user}:${cfg.group} ${cfg.dataDir}
-          chown ${cfg.user}:${cfg.group} ${cfg.logDir}
-        fi
-        '';
-
       serviceConfig = {
-        PermissionsStartOnly = true;
         User = cfg.user;
         Group = cfg.group;
         ExecStart = "${cfg.package}/bin/hbase --config ${configDir} master start";
diff --git a/nixos/modules/services/databases/influxdb.nix b/nixos/modules/services/databases/influxdb.nix
index 6868050c8446..dd5d69b1147a 100644
--- a/nixos/modules/services/databases/influxdb.nix
+++ b/nixos/modules/services/databases/influxdb.nix
@@ -129,13 +129,13 @@ in
       user = mkOption {
         default = "influxdb";
         description = "User account under which influxdb runs";
-        type = types.string;
+        type = types.str;
       };
 
       group = mkOption {
         default = "influxdb";
         description = "Group under which influxdb runs";
-        type = types.string;
+        type = types.str;
       };
 
       dataDir = mkOption {
@@ -182,15 +182,15 @@ in
         '';
     };
 
-    users.users = optional (cfg.user == "influxdb") {
-      name = "influxdb";
-      uid = config.ids.uids.influxdb;
-      description = "Influxdb daemon user";
+    users.users = optionalAttrs (cfg.user == "influxdb") {
+      influxdb = {
+        uid = config.ids.uids.influxdb;
+        description = "Influxdb daemon user";
+      };
     };
 
-    users.groups = optional (cfg.group == "influxdb") {
-      name = "influxdb";
-      gid = config.ids.gids.influxdb;
+    users.groups = optionalAttrs (cfg.group == "influxdb") {
+      influxdb.gid = config.ids.gids.influxdb;
     };
   };
 
diff --git a/nixos/modules/services/databases/memcached.nix b/nixos/modules/services/databases/memcached.nix
index 052ff1f308eb..89ff957babf5 100644
--- a/nixos/modules/services/databases/memcached.nix
+++ b/nixos/modules/services/databases/memcached.nix
@@ -64,9 +64,9 @@ in
 
   config = mkIf config.services.memcached.enable {
 
-    users.users = optional (cfg.user == "memcached") {
-      name = "memcached";
-      description = "Memcached server user";
+    users.users = optionalAttrs (cfg.user == "memcached") {
+      memcached.description = "Memcached server user";
+      memcached.isSystemUser = true;
     };
 
     environment.systemPackages = [ memcached ];
@@ -86,7 +86,24 @@ in
         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;
       };
     };
   };
diff --git a/nixos/modules/services/databases/mongodb.nix b/nixos/modules/services/databases/mongodb.nix
index 3fe4af2f2619..12879afed477 100644
--- a/nixos/modules/services/databases/mongodb.nix
+++ b/nixos/modules/services/databases/mongodb.nix
@@ -8,12 +8,13 @@ let
 
   mongodb = cfg.package;
 
-  mongoCnf = pkgs.writeText "mongodb.conf"
+  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}
   '';
@@ -59,6 +60,18 @@ in
         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";
@@ -84,6 +97,14 @@ in
         '';
         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.
+        '';
+      };
     };
 
   };
@@ -92,6 +113,11 @@ in
   ###### 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";
@@ -108,7 +134,7 @@ in
         after = [ "network.target" ];
 
         serviceConfig = {
-          ExecStart = "${mongodb}/bin/mongod --config ${mongoCnf} --fork --pidfilepath ${cfg.pidFile}";
+          ExecStart = "${mongodb}/bin/mongod --config ${mongoCnf cfg} --fork --pidfilepath ${cfg.pidFile}";
           User = cfg.user;
           PIDFile = cfg.pidFile;
           Type = "forking";
@@ -116,15 +142,50 @@ in
           PermissionsStartOnly = true;
         };
 
-        preStart = ''
+        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/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 89291d4438ff..8d520b82fb55 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -8,30 +8,26 @@ let
 
   mysql = cfg.package;
 
-  isMariaDB =
-    let
-      pName = _p: (builtins.parseDrvName (_p.name)).name;
-    in pName mysql == pName pkgs.mariadb;
-  isMysqlAtLeast57 =
-    let
-      pName = _p: (builtins.parseDrvName (_p.name)).name;
-    in (pName mysql == pName pkgs.mysql57)
-       && ((builtins.compareVersions mysql.version "5.7") >= 0);
+  isMariaDB = lib.getName mysql == lib.getName pkgs.mariadb;
 
-  pidFile = "${cfg.pidDir}/mysqld.pid";
+  isMysqlAtLeast57 =
+    (lib.getName mysql == lib.getName pkgs.mysql57)
+     && (builtins.compareVersions mysql.version "5.7" >= 0);
 
-  mysqldAndInstallOptions =
-    "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
   mysqldOptions =
-    "${mysqldAndInstallOptions} --pid-file=${pidFile}";
+    "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
   # For MySQL 5.7+, --insecure creates the root user without password
   # (earlier versions and MariaDB do this by default).
   installOptions =
-    "${mysqldAndInstallOptions} ${lib.optionalString isMysqlAtLeast57 "--insecure"}";
+    "${mysqldOptions} ${lib.optionalString isMysqlAtLeast57 "--insecure"}";
 
 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
 
@@ -80,11 +76,6 @@ in
         description = "Location where MySQL stores its table files";
       };
 
-      pidDir = mkOption {
-        default = "/run/mysqld";
-        description = "Location of the file which stores the PID of the MySQL server";
-      };
-
       extraOptions = mkOption {
         type = types.lines;
         default = "";
@@ -133,7 +124,7 @@ in
       };
 
       initialScript = mkOption {
-        type = types.nullOr types.lines;
+        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";
       };
@@ -213,12 +204,6 @@ in
         '';
       };
 
-      # FIXME: remove this option; it's a really bad idea.
-      rootPassword = mkOption {
-        default = null;
-        description = "Path to a file containing the root password, modified on the first startup. Not specifying a root password will leave the root password empty.";
-      };
-
       replication = {
         role = mkOption {
           type = types.enum [ "master" "slave" "none" ];
@@ -287,8 +272,13 @@ in
       port = ${toString cfg.port}
       datadir = ${cfg.dataDir}
       ${optionalString (cfg.bind != null) "bind-address = ${cfg.bind}" }
-      ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "log-bin=mysql-bin"}
-      ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "server-id = ${toString cfg.replication.serverId}"}
+      ${optionalString (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 = ${toString cfg.replication.serverId}
+      ''}
       ${optionalString (cfg.ensureUsers != [])
       ''
         plugin-load-add = auth_socket.so
@@ -296,6 +286,10 @@ in
       ${cfg.extraOptions}
     '';
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 0700 ${cfg.user} mysql -"
+    ];
+
     systemd.services.mysql = let
       hasNotify = (cfg.package == pkgs.mariadb);
     in {
@@ -313,126 +307,125 @@ in
           pkgs.nettools
         ];
 
-        preStart =
-          ''
-            if ! test -e ${cfg.dataDir}/mysql; then
-                mkdir -m 0700 -p ${cfg.dataDir}
-                chown -R ${cfg.user} ${cfg.dataDir}
-                ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${installOptions}
-                touch /tmp/mysql_init
-            fi
-
-            mkdir -m 0755 -p ${cfg.pidDir}
-            chown -R ${cfg.user} ${cfg.pidDir}
-          '';
+        preStart = ''
+          if ! test -e ${cfg.dataDir}/mysql; then
+            ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${installOptions}
+            touch /tmp/mysql_init
+          fi
+        '';
 
         serviceConfig = {
+          User = cfg.user;
+          Group = "mysql";
           Type = if hasNotify then "notify" else "simple";
           RuntimeDirectory = "mysqld";
+          RuntimeDirectoryMode = "0755";
+          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";
-        };
-
-        postStart = ''
-          ${lib.optionalString (!hasNotify) ''
-            # Wait until the MySQL server is available for use
-            count=0
-            while [ ! -e /run/mysqld/mysqld.sock ]
-            do
-                if [ $count -eq 30 ]
+          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 /tmp/mysql_init ]
                 then
-                    echo "Tried 30 times, giving up..."
-                    exit 1
-                fi
+                    ${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
+                      ''}
 
-                echo "MySQL daemon not yet started. Waiting for 1 second..."
-                count=$((count++))
-                sleep 1
-            done
-          ''}
+                    ${optionalString (cfg.replication.role == "slave")
+                      ''
+                        # Set up the replication slave
 
-            if [ -f /tmp/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") ''
-                          echo 'use `${database.name}`;'
-
-                          if [ -f "${database.schema}" ]
-                          then
-                              cat ${database.schema}
-                          elif [ -d "${database.schema}" ]
-                          then
-                              cat ${database.schema}/mysql-databases/*.sql
-                          fi
-                          ''}
+                        ( echo "stop slave;"
+                          echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
+                          echo "set global slave_exec_mode='IDEMPOTENT';"
+                          echo "start slave;"
                         ) | ${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.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
+                      ''}
 
-                ${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
-                  ''}
+                    rm /tmp/mysql_init
+                fi
 
-                ${optionalString (cfg.initialScript != null)
-                  ''
-                    # Execute initial script
-                    cat ${cfg.initialScript} | ${mysql}/bin/mysql -u root -N
-                  ''}
+                ${optionalString (cfg.ensureDatabases != []) ''
+                  (
+                  ${concatMapStrings (database: ''
+                    echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
+                  '') cfg.ensureDatabases}
+                  ) | ${mysql}/bin/mysql -u root -N
+                ''}
 
-                ${optionalString (cfg.rootPassword != null)
+                ${concatMapStrings (user:
                   ''
-                    # Change root password
-
-                    ( echo "use mysql;"
-                      echo "update user set Password=password('$(cat ${cfg.rootPassword})') where User='root';"
-                      echo "flush privileges;"
+                    ( 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
-                  ''}
-
-              rm /tmp/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}
-
-          ''; # */
+                  '') cfg.ensureUsers}
+              '';
+            in
+              # ensureDatbases & ensureUsers depends on this script being run as root
+              # when the user has secured their mysql install
+              "+${setupScript}";
+        };
       };
 
   };
diff --git a/nixos/modules/services/databases/neo4j.nix b/nixos/modules/services/databases/neo4j.nix
index 5533182c3116..09b453e75845 100644
--- a/nixos/modules/services/databases/neo4j.nix
+++ b/nixos/modules/services/databases/neo4j.nix
@@ -103,6 +103,18 @@ let
 
 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 = {
@@ -638,8 +650,7 @@ in {
 
       environment.systemPackages = [ cfg.package ];
 
-      users.users = singleton {
-        name = "neo4j";
+      users.users.neo4j = {
         uid = config.ids.uids.neo4j;
         description = "Neo4j daemon user";
         home = cfg.directories.home;
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index c101e7375af9..809f61cfa818 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -18,7 +18,11 @@ let
     database ${cfg.database}
     suffix ${cfg.suffix}
     rootdn ${cfg.rootdn}
-    rootpw ${cfg.rootpw}
+    ${if (cfg.rootpw != null) then ''
+      rootpw ${cfg.rootpw}
+    '' else ''
+      include ${cfg.rootpwFile}
+    ''}
     directory ${cfg.dataDir}
     ${cfg.extraDatabaseConfig}
   '');
@@ -43,26 +47,26 @@ in
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "openldap";
         description = "User account under which slapd runs.";
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "openldap";
         description = "Group account under which slapd runs.";
       };
 
       urlList = mkOption {
-        type = types.listOf types.string;
+        type = types.listOf types.str;
         default = [ "ldap:///" ];
         description = "URL list slapd should listen on.";
         example = [ "ldaps:///" ];
       };
 
       dataDir = mkOption {
-        type = types.string;
+        type = types.path;
         default = "/var/db/openldap";
         description = "The database directory.";
       };
@@ -106,10 +110,23 @@ in
       };
 
       rootpw = mkOption {
-        type = types.str;
+        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>
         '';
       };
 
@@ -140,9 +157,9 @@ in
             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 
+            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
@@ -218,6 +235,12 @@ in
   ###### 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 ];
 
@@ -236,6 +259,8 @@ in
           ${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}' " +
diff --git a/nixos/modules/services/databases/opentsdb.nix b/nixos/modules/services/databases/opentsdb.nix
index b26fa9093ef4..c4bd71f3d60e 100644
--- a/nixos/modules/services/databases/opentsdb.nix
+++ b/nixos/modules/services/databases/opentsdb.nix
@@ -34,7 +34,7 @@ in {
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "opentsdb";
         description = ''
           User account under which OpenTSDB runs.
@@ -42,7 +42,7 @@ in {
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "opentsdb";
         description = ''
           Group account under which OpenTSDB runs.
diff --git a/nixos/modules/services/databases/pgmanage.nix b/nixos/modules/services/databases/pgmanage.nix
index 1a34c7f5ecee..0f8634dab319 100644
--- a/nixos/modules/services/databases/pgmanage.nix
+++ b/nixos/modules/services/databases/pgmanage.nix
@@ -16,7 +16,7 @@ let
 
       super_only = ${builtins.toJSON cfg.superOnly}
 
-      ${optionalString (!isNull cfg.loginGroup) "login_group = ${cfg.loginGroup}"}
+      ${optionalString (cfg.loginGroup != null) "login_group = ${cfg.loginGroup}"}
 
       login_timeout = ${toString cfg.loginTimeout}
 
@@ -24,7 +24,7 @@ let
 
       sql_root = ${cfg.sqlRoot}
 
-      ${optionalString (!isNull cfg.tls) ''
+      ${optionalString (cfg.tls != null) ''
       tls_cert = ${cfg.tls.cert}
       tls_key = ${cfg.tls.key}
       ''}
@@ -59,8 +59,8 @@ in {
       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";
+        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.
@@ -192,13 +192,13 @@ in {
       };
     };
     users = {
-      users."${pgmanage}" = {
+      users.${pgmanage} = {
         name  = pgmanage;
         group = pgmanage;
         home  = cfg.sqlRoot;
         createHome = true;
       };
-      groups."${pgmanage}" = {
+      groups.${pgmanage} = {
         name = pgmanage;
       };
     };
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 87b236dd5fd1..c8fdd89d0d8f 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -6,23 +6,10 @@ let
 
   cfg = config.services.postgresql;
 
-  # see description of extraPlugins
-  postgresqlAndPlugins = pg:
-    if cfg.extraPlugins == [] then pg
-    else pkgs.buildEnv {
-      name = "postgresql-and-plugins-${(builtins.parseDrvName pg.name).version}";
-      paths = [ pg pg.lib ] ++ cfg.extraPlugins;
-      buildInputs = [ pkgs.makeWrapper ];
-      postBuild =
-        ''
-          mkdir -p $out/bin
-          rm $out/bin/{pg_config,postgres,pg_ctl}
-          cp --target-directory=$out/bin ${pg}/bin/{postgres,pg_config,pg_ctl}
-          wrapProgram $out/bin/postgres --set NIX_PGLIBDIR $out/lib
-        '';
-    };
-
-  postgresql = postgresqlAndPlugins cfg.package;
+  postgresql =
+    if cfg.extraPlugins == []
+      then cfg.package
+      else cfg.package.withPackages (_: cfg.extraPlugins);
 
   # The main PostgreSQL configuration file.
   configFile = pkgs.writeText "postgresql.conf"
@@ -55,7 +42,7 @@ in
 
       package = mkOption {
         type = types.package;
-        example = literalExample "pkgs.postgresql_9_6";
+        example = literalExample "pkgs.postgresql_11";
         description = ''
           PostgreSQL package to use.
         '';
@@ -71,7 +58,7 @@ in
 
       dataDir = mkOption {
         type = types.path;
-        example = "/var/lib/postgresql/9.6";
+        example = "/var/lib/postgresql/11";
         description = ''
           Data directory for PostgreSQL.
         '';
@@ -94,6 +81,10 @@ in
         default = "";
         description = ''
           Defines the mapping from system users to database users.
+
+          The general form is:
+
+          map-name system-username database-username
         '';
       };
 
@@ -105,6 +96,80 @@ in
         '';
       };
 
+      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;
@@ -118,17 +183,11 @@ in
       extraPlugins = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ (pkgs.postgis.override { postgresql = pkgs.postgresql_9_4; }) ]";
+        example = literalExample "with pkgs.postgresql_11.pkgs; [ postgis pg_repack ]";
         description = ''
-          When this list contains elements a new store path is created.
-          PostgreSQL and the elements are symlinked into it. Then pg_config,
-          postgres and pg_ctl are copied to make them use the new
-          $out/lib directory as pkglibdir. This makes it possible to use postgis
-          without patching the .sql files which reference $libdir/postgis-1.5.
+          List of PostgreSQL plugins. PostgreSQL version for each plugin should
+          match version for <literal>services.postgresql.package</literal> value.
         '';
-        # Note: the duplication of executables is about 4MB size.
-        # So a nicer solution was patching postgresql to allow setting the
-        # libdir explicitely.
       };
 
       extraConfig = mkOption {
@@ -167,9 +226,10 @@ in
       # Note: when changing the default, make it conditional on
       # ‘system.stateVersion’ to maintain compatibility with existing
       # systems!
-      mkDefault (if versionAtLeast config.system.stateVersion "17.09" then pkgs.postgresql_9_6
+      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 pkgs.postgresql_9_4);
+            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/${config.services.postgresql.package.psqlSchema}"
@@ -196,6 +256,10 @@ in
 
     environment.systemPackages = [ postgresql ];
 
+    environment.pathsToLink = [
+     "/share/postgresql"
+    ];
+
     systemd.services.postgresql =
       { description = "PostgreSQL Server";
 
@@ -256,17 +320,30 @@ in
         # Wait for PostgreSQL to be ready to accept connections.
         postStart =
           ''
-            while ! ${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql --port=${toString cfg.port} -d postgres -c "" 2> /dev/null; do
+            PSQL="${pkgs.sudo}/bin/sudo -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) ''
-                ${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql -f "${cfg.initialScript}" --port=${toString cfg.port} -d postgres
+                $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}";
diff --git a/nixos/modules/services/databases/postgresql.xml b/nixos/modules/services/databases/postgresql.xml
index 14f4d4909bc0..72d4a8249a32 100644
--- a/nixos/modules/services/databases/postgresql.xml
+++ b/nixos/modules/services/databases/postgresql.xml
@@ -27,10 +27,10 @@
    <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-services.postgresql.enable"/> = true;
-<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_9_4;
+<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_9_4</literal>). Since upgrading your
+   (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
@@ -42,17 +42,17 @@
 whether PostgreSQL works by running <command>psql</command>:
 
 <screen>
-$ psql
+<prompt>$ </prompt>psql
 psql (9.2.9)
 Type "help" for help.
 
-alice=>
+<prompt>alice=></prompt>
 </screen>
 -->
 
   <para>
    By default, PostgreSQL stores its databases in
-   <filename>/var/db/postgresql</filename>. You can override this using
+   <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";
@@ -74,4 +74,70 @@ alice=>
    <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/nixos/modules/services/databases/redis.nix b/nixos/modules/services/databases/redis.nix
index c04cc1283b2e..70895fa53e45 100644
--- a/nixos/modules/services/databases/redis.nix
+++ b/nixos/modules/services/databases/redis.nix
@@ -8,17 +8,19 @@ let
   condOption = name: value: if value != null then "${name} ${toString value}" else "";
 
   redisConfig = pkgs.writeText "redis.conf" ''
-    pidfile ${cfg.pidFile}
     port ${toString cfg.port}
     ${condOption "bind" cfg.bind}
     ${condOption "unixsocket" cfg.unixSocket}
+    daemonize yes
+    supervised systemd
     loglevel ${cfg.logLevel}
     logfile ${cfg.logfile}
     syslog-enabled ${redisBool cfg.syslog}
+    pidfile /run/redis/redis.pid
     databases ${toString cfg.databases}
     ${concatMapStrings (d: "save ${toString (builtins.elemAt d 0)} ${toString (builtins.elemAt d 1)}\n") cfg.save}
-    dbfilename ${cfg.dbFilename}
-    dir ${toString cfg.dbpath}
+    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}
@@ -30,6 +32,13 @@ let
   '';
 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
 
@@ -40,7 +49,12 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the Redis server.";
+        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 {
@@ -50,18 +64,6 @@ in
         description = "Which Redis derivation to use.";
       };
 
-      user = mkOption {
-        type = types.str;
-        default = "redis";
-        description = "User account under which Redis runs.";
-      };
-
-      pidFile = mkOption {
-        type = types.path;
-        default = "/var/lib/redis/redis.pid";
-        description = "";
-      };
-
       port = mkOption {
         type = types.int;
         default = 6379;
@@ -95,7 +97,7 @@ in
         type = with types; nullOr path;
         default = null;
         description = "The path to the socket to bind to.";
-        example = "/run/redis.sock";
+        example = "/run/redis/redis.sock";
       };
 
       logLevel = mkOption {
@@ -131,18 +133,6 @@ in
         example = [ [900 1] [300 10] [60 10000] ];
       };
 
-      dbFilename = mkOption {
-        type = types.str;
-        default = "dump.rdb";
-        description = "The filename where to dump the DB.";
-      };
-
-      dbpath = mkOption {
-        type = types.path;
-        default = "/var/lib/redis";
-        description = "The DB will be written inside this directory, with the filename specified using the 'dbFilename' configuration.";
-      };
-
       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.";
@@ -170,12 +160,6 @@ in
         description = "By default data is only periodically persisted to disk, enable this option to use an append-only file for improved persistence.";
       };
 
-      appendOnlyFilename = mkOption {
-        type = types.str;
-        default = "appendonly.aof";
-        description = "Filename for the append-only file (stored inside of dbpath)";
-      };
-
       appendFsync = mkOption {
         type = types.str;
         default = "everysec"; # no, always, everysec
@@ -208,36 +192,22 @@ in
   ###### implementation
 
   config = mkIf config.services.redis.enable {
-
-    boot.kernel.sysctl = mkIf cfg.vmOverCommit {
-      "vm.overcommit_memory" = "1";
-    };
+    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 =
-      { name = cfg.user;
-        description = "Redis database user";
-      };
+    users.users.redis = {
+      description = "Redis database user";
+      isSystemUser = true;
+    };
 
     environment.systemPackages = [ cfg.package ];
 
-    systemd.services.redis_init =
-      { description = "Redis Server Initialisation";
-
-        wantedBy = [ "redis.service" ];
-        before = [ "redis.service" ];
-
-        serviceConfig.Type = "oneshot";
-
-        script = ''
-          install -d -m0700 -o ${cfg.user} ${cfg.dbpath}
-          chown -R ${cfg.user} ${cfg.dbpath}
-        '';
-      };
-
     systemd.services.redis =
       { description = "Redis Server";
 
@@ -246,7 +216,10 @@ in
 
         serviceConfig = {
           ExecStart = "${cfg.package}/bin/redis-server ${redisConfig}";
-          User = cfg.user;
+          RuntimeDirectory = "redis";
+          StateDirectory = "redis";
+          Type = "notify";
+          User = "redis";
         };
       };
 
diff --git a/nixos/modules/services/databases/rethinkdb.nix b/nixos/modules/services/databases/rethinkdb.nix
index 4828e594b328..f18fbaf5b062 100644
--- a/nixos/modules/services/databases/rethinkdb.nix
+++ b/nixos/modules/services/databases/rethinkdb.nix
@@ -99,6 +99,7 @@ in
     users.users.rethinkdb = mkIf (cfg.user == "rethinkdb")
       { name = "rethinkdb";
         description = "RethinkDB server user";
+        isSystemUser = true;
       };
 
     users.groups = optionalAttrs (cfg.group == "rethinkdb") (singleton
diff --git a/nixos/modules/services/databases/riak.nix b/nixos/modules/services/databases/riak.nix
index ac086cf55996..885215209bdf 100644
--- a/nixos/modules/services/databases/riak.nix
+++ b/nixos/modules/services/databases/riak.nix
@@ -29,7 +29,7 @@ in
       };
 
       nodeName = mkOption {
-        type = types.string;
+        type = types.str;
         default = "riak@127.0.0.1";
         description = ''
           Name of the Erlang node.
@@ -37,7 +37,7 @@ in
       };
 
       distributedCookie = mkOption {
-        type = types.string;
+        type = types.str;
         default = "riak";
         description = ''
           Cookie for distributed node communication.  All nodes in the
diff --git a/nixos/modules/services/databases/virtuoso.nix b/nixos/modules/services/databases/virtuoso.nix
index 6ffc44a5274e..0cc027cb1d74 100644
--- a/nixos/modules/services/databases/virtuoso.nix
+++ b/nixos/modules/services/databases/virtuoso.nix
@@ -54,9 +54,8 @@ with lib;
 
   config = mkIf cfg.enable {
 
-    users.users = singleton
-      { name = virtuosoUser;
-        uid = config.ids.uids.virtuoso;
+    users.users.${virtuosoUser} =
+      { uid = config.ids.uids.virtuoso;
         description = "virtuoso user";
         home = stateDir;
       };
diff --git a/nixos/modules/services/desktops/blueman.nix b/nixos/modules/services/desktops/blueman.nix
new file mode 100644
index 000000000000..18ad610247ed
--- /dev/null
+++ b/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/nixos/modules/services/desktops/deepin/dde-daemon.nix b/nixos/modules/services/desktops/deepin/dde-daemon.nix
deleted file mode 100644
index 057da4e2d7f2..000000000000
--- a/nixos/modules/services/desktops/deepin/dde-daemon.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-# dde-daemon
-
-{ config, pkgs, lib, ... }:
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.deepin.dde-daemon = {
-
-      enable = lib.mkEnableOption
-        "A daemon for handling Deepin Desktop Environment session settings";
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = lib.mkIf config.services.deepin.dde-daemon.enable {
-
-    environment.systemPackages = [ pkgs.deepin.dde-daemon ];
-
-    services.dbus.packages = [ pkgs.deepin.dde-daemon ];
-
-    systemd.packages = [ pkgs.deepin.dde-daemon ];
-
-    users.groups.dde-daemon = { };
-
-    users.users.dde-daemon = {
-      description = "Deepin daemon user";
-      group = "dde-daemon";
-      isSystemUser = true;
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/desktops/deepin/deepin-menu.nix b/nixos/modules/services/desktops/deepin/deepin-menu.nix
deleted file mode 100644
index 23fe5a741c42..000000000000
--- a/nixos/modules/services/desktops/deepin/deepin-menu.nix
+++ /dev/null
@@ -1,29 +0,0 @@
-# deepin-menu
-
-{ config, pkgs, lib, ... }:
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.deepin.deepin-menu = {
-
-      enable = lib.mkEnableOption
-        "DBus service for unified menus in Deepin Desktop Environment";
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = lib.mkIf config.services.deepin.deepin-menu.enable {
-
-    services.dbus.packages = [ pkgs.deepin.deepin-menu ];
-
-  };
-
-}
diff --git a/nixos/modules/services/desktops/deepin/deepin.nix b/nixos/modules/services/desktops/deepin/deepin.nix
new file mode 100644
index 000000000000..931bac58aceb
--- /dev/null
+++ b/nixos/modules/services/desktops/deepin/deepin.nix
@@ -0,0 +1,125 @@
+# 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
+        pkgs.deepin.deepin-screenshot
+      ];
+
+      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
+        pkgs.deepin.deepin-screenshot
+      ];
+
+      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/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index cfca1893bd82..7fb0024f37dc 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -15,38 +15,39 @@ in {
   options = {
     services.flatpak = {
       enable = mkEnableOption "flatpak";
-
-      extraPortals = mkOption {
-        type = types.listOf types.package;
-        default = [];
-        description = ''
-          List of additional portals to add to path. Portals allow interaction
-          with system, like choosing files or taking screenshots. At minimum,
-          a desktop portal implementation should be listed. GNOME already
-          adds <package>xdg-desktop-portal-gtk</package>; for KDE, there
-          is <package>xdg-desktop-portal-kde</package>. Other desktop
-          environments will probably want to do the same.
-        '';
-      };
     };
   };
 
 
   ###### 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 pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
+    services.dbus.packages = [ pkgs.flatpak ];
 
-    systemd.packages = [ pkgs.flatpak pkgs.xdg-desktop-portal ] ++ cfg.extraPortals;
+    systemd.packages = [ pkgs.flatpak ];
 
     environment.profiles = [
       "$HOME/.local/share/flatpak/exports"
       "/var/lib/flatpak/exports"
     ];
 
-    environment.variables = {
-      XDG_DESKTOP_PORTAL_PATH = map (p: "${p}/share/xdg-desktop-portal/portals") cfg.extraPortals;
+    # 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/nixos/modules/services/desktops/flatpak.xml b/nixos/modules/services/desktops/flatpak.xml
index 8045d5fa14f8..8f080b250228 100644
--- a/nixos/modules/services/desktops/flatpak.xml
+++ b/nixos/modules/services/desktops/flatpak.xml
@@ -21,7 +21,7 @@
   <filename>configuration.nix</filename>:
 <programlisting>
   <xref linkend="opt-services.flatpak.enable"/> = true;
-  </programlisting>
+</programlisting>
  </para>
  <para>
   For the sandboxed apps to work correctly, desktop integration portals need to
@@ -29,28 +29,28 @@
   in other cases, you will need to add something like the following to your
   <filename>configuration.nix</filename>:
 <programlisting>
-  <xref linkend="opt-services.flatpak.extraPortals"/> = [ pkgs.xdg-desktop-portal-gtk ];
-  </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:
-<programlisting>
-  flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
-  flatpak update
-  </programlisting>
+<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:
-<programlisting>
-  flatpak search bustle
-  flatpak install flathub org.freedesktop.Bustle
-  flatpak run org.freedesktop.Bustle
-  </programlisting>
+<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/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix
index 840aa5294ed0..542b2ead4104 100644
--- a/nixos/modules/services/desktops/geoclue2.nix
+++ b/nixos/modules/services/desktops/geoclue2.nix
@@ -7,6 +7,56 @@ 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
 {
 
@@ -35,34 +85,184 @@ in
         '';
       };
 
+      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 config.services.geoclue2.enable {
+  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 config.services.geoclue2.enableDemoAgent { 
-      "geoclue-agent" = {
+    systemd.user.services = mkIf cfg.enableDemoAgent {
+      geoclue-agent = {
         description = "Geoclue agent";
-        script = "${package}/libexec/geoclue-2.0/demos/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;
+        };
       };
     };
 
-    environment.etc."geoclue/geoclue.conf".source = "${package}/etc/geoclue/geoclue.conf";
+    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/nixos/modules/services/desktops/gnome3/at-spi2-core.nix b/nixos/modules/services/desktops/gnome3/at-spi2-core.nix
index cca98c43dc7a..8fa108c4f9df 100644
--- a/nixos/modules/services/desktops/gnome3/at-spi2-core.nix
+++ b/nixos/modules/services/desktops/gnome3/at-spi2-core.nix
@@ -18,6 +18,9 @@ with lib;
         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>.
         '';
       };
 
diff --git a/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix b/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix
index 2740a22c7ca0..3d2b3ed85e3a 100644
--- a/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix
+++ b/nixos/modules/services/desktops/gnome3/chrome-gnome-shell.nix
@@ -23,5 +23,7 @@ with lib;
     environment.systemPackages = [ pkgs.chrome-gnome-shell ];
 
     services.dbus.packages = [ pkgs.chrome-gnome-shell ];
+
+    nixpkgs.config.firefox.enableGnomeExtensions = true;
   };
 }
diff --git a/nixos/modules/services/desktops/gnome3/glib-networking.nix b/nixos/modules/services/desktops/gnome3/glib-networking.nix
index 186668d7d385..fcd58509d6fc 100644
--- a/nixos/modules/services/desktops/gnome3/glib-networking.nix
+++ b/nixos/modules/services/desktops/gnome3/glib-networking.nix
@@ -22,11 +22,11 @@ with lib;
 
   config = mkIf config.services.gnome3.glib-networking.enable {
 
-    services.dbus.packages = [ pkgs.gnome3.glib-networking ];
+    services.dbus.packages = [ pkgs.glib-networking ];
 
-    systemd.packages = [ pkgs.gnome3.glib-networking ];
+    systemd.packages = [ pkgs.glib-networking ];
 
-    environment.variables.GIO_EXTRA_MODULES = [ "${pkgs.gnome3.glib-networking.out}/lib/gio/modules" ];
+    environment.variables.GIO_EXTRA_MODULES = [ "${pkgs.glib-networking.out}/lib/gio/modules" ];
 
   };
 
diff --git a/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix b/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix
new file mode 100644
index 000000000000..d715d52c2d06
--- /dev/null
+++ b/nixos/modules/services/desktops/gnome3/gnome-initial-setup.nix
@@ -0,0 +1,86 @@
+# 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
+
+{
+
+  ###### 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/nixos/modules/services/desktops/gnome3/gnome-keyring.nix b/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
index 4c350d8bb1c6..db60445ef773 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
+++ b/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
@@ -37,6 +37,11 @@ with lib;
 
     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/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix b/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
index 4286251357f7..748a025414a7 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
+++ b/nixos/modules/services/desktops/gnome3/gnome-online-accounts.nix
@@ -30,9 +30,9 @@ with lib;
 
   config = mkIf config.services.gnome3.gnome-online-accounts.enable {
 
-    environment.systemPackages = [ pkgs.gnome3.gnome-online-accounts ];
+    environment.systemPackages = [ pkgs.gnome-online-accounts ];
 
-    services.dbus.packages = [ pkgs.gnome3.gnome-online-accounts ];
+    services.dbus.packages = [ pkgs.gnome-online-accounts ];
 
   };
 
diff --git a/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix b/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
index 7f7adcf26acf..2f83fd653bde 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
+++ b/nixos/modules/services/desktops/gnome3/gnome-settings-daemon.nix
@@ -12,6 +12,12 @@ in
 
 {
 
+  imports = [
+    (mkRemovedOptionModule
+      ["services" "gnome3" "gnome-settings-daemon" "package"]
+      "")
+  ];
+
   ###### interface
 
   options = {
@@ -20,13 +26,6 @@ in
 
       enable = mkEnableOption "GNOME Settings Daemon";
 
-      # There are many forks of gnome-settings-daemon
-      package = mkOption {
-        type = types.package;
-        default = pkgs.gnome3.gnome-settings-daemon;
-        description = "Which gnome-settings-daemon package to use.";
-      };
-
     };
 
   };
@@ -36,9 +35,39 @@ in
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ cfg.package ];
-
-    services.udev.packages = [ cfg.package ];
+    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/nixos/modules/services/desktops/gnome3/gnome-terminal-server.nix b/nixos/modules/services/desktops/gnome3/gnome-terminal-server.nix
deleted file mode 100644
index fd14efee5f2e..000000000000
--- a/nixos/modules/services/desktops/gnome3/gnome-terminal-server.nix
+++ /dev/null
@@ -1,41 +0,0 @@
-# GNOME Documents daemon.
-
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.gnome3.gnome-terminal-server = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable GNOME Terminal server service,
-          needed for gnome-terminal.
-        '';
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.gnome3.gnome-terminal-server.enable {
-
-    environment.systemPackages = [ pkgs.gnome3.gnome-terminal ];
-
-    services.dbus.packages = [ pkgs.gnome3.gnome-terminal ];
-
-    systemd.packages = [ pkgs.gnome3.gnome-terminal ];
-
-  };
-
-}
diff --git a/nixos/modules/services/desktops/gnome3/gnome-user-share.nix b/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
index 1f6ce2ae968e..f83962877700 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
+++ b/nixos/modules/services/desktops/gnome3/gnome-user-share.nix
@@ -12,14 +12,7 @@ with lib;
 
     services.gnome3.gnome-user-share = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable GNOME User Share, a service that exports the
-          contents of the Public folder in your home directory on the local network.
-        '';
-      };
+      enable = mkEnableOption "GNOME User Share, a user-level file sharing service for GNOME";
 
     };
 
@@ -30,12 +23,13 @@ with lib;
 
   config = mkIf config.services.gnome3.gnome-user-share.enable {
 
-    environment.systemPackages = [ pkgs.gnome3.gnome-user-share ];
+    environment.systemPackages = [
+      pkgs.gnome3.gnome-user-share
+    ];
 
-    services.xserver.displayManager.sessionCommands = with pkgs.gnome3; ''
-      # Don't let gnome-control-center depend upon gnome-user-share
-      export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${gnome-user-share}/share/gsettings-schemas/${gnome-user-share.name}
-    '';
+    systemd.packages = [
+      pkgs.gnome3.gnome-user-share
+    ];
 
   };
 
diff --git a/nixos/modules/services/desktops/gnome3/gvfs.nix b/nixos/modules/services/desktops/gnome3/gvfs.nix
deleted file mode 100644
index 4e99d191f18c..000000000000
--- a/nixos/modules/services/desktops/gnome3/gvfs.nix
+++ /dev/null
@@ -1,43 +0,0 @@
-# gvfs backends
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.gnome3.gvfs = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable gvfs backends, userspace virtual filesystem used
-          by GNOME components via D-Bus.
-        '';
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.gnome3.gvfs.enable {
-
-    environment.systemPackages = [ pkgs.gnome3.gvfs ];
-
-    services.dbus.packages = [ pkgs.gnome3.gvfs ];
-
-    systemd.packages = [ pkgs.gnome3.gvfs ];
-
-    services.udev.packages = [ pkgs.libmtp.bin ];
-
-  };
-
-}
diff --git a/nixos/modules/services/desktops/gnome3/seahorse.nix b/nixos/modules/services/desktops/gnome3/seahorse.nix
deleted file mode 100644
index 9631157934f9..000000000000
--- a/nixos/modules/services/desktops/gnome3/seahorse.nix
+++ /dev/null
@@ -1,38 +0,0 @@
-# Seahorse daemon.
-
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.gnome3.seahorse = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable Seahorse search provider for the GNOME Shell activity search.
-        '';
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.gnome3.seahorse.enable {
-
-    environment.systemPackages = [ pkgs.gnome3.seahorse pkgs.gnome3.dconf ];
-
-    services.dbus.packages = [ pkgs.gnome3.seahorse ];
-
-  };
-
-}
diff --git a/nixos/modules/services/desktops/gnome3/tracker-miners.nix b/nixos/modules/services/desktops/gnome3/tracker-miners.nix
index 20154fc2fed3..b390d8368c65 100644
--- a/nixos/modules/services/desktops/gnome3/tracker-miners.nix
+++ b/nixos/modules/services/desktops/gnome3/tracker-miners.nix
@@ -30,11 +30,11 @@ with lib;
 
   config = mkIf config.services.gnome3.tracker-miners.enable {
 
-    environment.systemPackages = [ pkgs.gnome3.tracker-miners ];
+    environment.systemPackages = [ pkgs.tracker-miners ];
 
-    services.dbus.packages = [ pkgs.gnome3.tracker-miners ];
+    services.dbus.packages = [ pkgs.tracker-miners ];
 
-    systemd.packages = [ pkgs.gnome3.tracker-miners ];
+    systemd.packages = [ pkgs.tracker-miners ];
 
   };
 
diff --git a/nixos/modules/services/desktops/gnome3/tracker.nix b/nixos/modules/services/desktops/gnome3/tracker.nix
index c061f7890499..2e8292742264 100644
--- a/nixos/modules/services/desktops/gnome3/tracker.nix
+++ b/nixos/modules/services/desktops/gnome3/tracker.nix
@@ -30,11 +30,11 @@ with lib;
 
   config = mkIf config.services.gnome3.tracker.enable {
 
-    environment.systemPackages = [ pkgs.gnome3.tracker ];
+    environment.systemPackages = [ pkgs.tracker ];
 
-    services.dbus.packages = [ pkgs.gnome3.tracker ];
+    services.dbus.packages = [ pkgs.tracker ];
 
-    systemd.packages = [ pkgs.gnome3.tracker ];
+    systemd.packages = [ pkgs.tracker ];
 
   };
 
diff --git a/nixos/modules/services/desktops/gvfs.nix b/nixos/modules/services/desktops/gvfs.nix
new file mode 100644
index 000000000000..1d002eac41de
--- /dev/null
+++ b/nixos/modules/services/desktops/gvfs.nix
@@ -0,0 +1,59 @@
+# GVfs
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.gvfs;
+
+in
+
+{
+
+  # 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/nixos/modules/services/desktops/neard.nix b/nixos/modules/services/desktops/neard.nix
new file mode 100644
index 000000000000..9b0f8d1b3a77
--- /dev/null
+++ b/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/nixos/modules/services/desktops/pantheon/contractor.nix b/nixos/modules/services/desktops/pantheon/contractor.nix
index 2638a21df733..c76145191a70 100644
--- a/nixos/modules/services/desktops/pantheon/contractor.nix
+++ b/nixos/modules/services/desktops/pantheon/contractor.nix
@@ -6,35 +6,12 @@ with lib;
 
 {
 
-  meta.maintainers = pkgs.pantheon.maintainers;
-
-  ###### interface
-
-  options = {
-
-    services.pantheon.contractor = {
-
-      enable = mkEnableOption "contractor, a desktop-wide extension service used by pantheon";
-
-    };
-
-  };
-
 
   ###### implementation
 
   config = mkIf config.services.pantheon.contractor.enable {
 
-    environment.systemPackages = with  pkgs.pantheon; [
-      contractor
-      extra-elementary-contracts
-    ];
-
-    services.dbus.packages = [ pkgs.pantheon.contractor ];
-
-    environment.pathsToLink = [
-      "/share/contractor"
-    ];
+    
 
   };
 
diff --git a/nixos/modules/services/desktops/pantheon/files.nix b/nixos/modules/services/desktops/pantheon/files.nix
index 577aad6c2987..8cee9f42b62f 100644
--- a/nixos/modules/services/desktops/pantheon/files.nix
+++ b/nixos/modules/services/desktops/pantheon/files.nix
@@ -6,33 +6,8 @@ with lib;
 
 {
 
-  meta.maintainers = pkgs.pantheon.maintainers;
-
-  ###### interface
-
-  options = {
-
-    services.pantheon.files = {
-
-      enable = mkEnableOption "pantheon files daemon";
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.pantheon.files.enable {
-
-    environment.systemPackages = [
-      pkgs.pantheon.elementary-files
-    ];
-
-    services.dbus.packages = [
-      pkgs.pantheon.elementary-files
-    ];
-
-  };
+  imports = [
+    (mkRemovedOptionModule [ "services" "pantheon" "files" "enable" ] "Use `environment.systemPackages [ pkgs.pantheon.elementary-files ];`")
+  ];
 
 }
diff --git a/nixos/modules/services/desktops/profile-sync-daemon.nix b/nixos/modules/services/desktops/profile-sync-daemon.nix
index e4e47cfbd438..a8ac22ac1276 100644
--- a/nixos/modules/services/desktops/profile-sync-daemon.nix
+++ b/nixos/modules/services/desktops/profile-sync-daemon.nix
@@ -34,7 +34,7 @@ in {
           psd = {
             enable = true;
             description = "Profile Sync daemon";
-            wants = [ "psd-resync.service" "local-fs.target" ];
+            wants = [ "psd-resync.service" ];
             wantedBy = [ "default.target" ];
             path = with pkgs; [ rsync kmod gawk nettools utillinux profile-sync-daemon ];
             unitConfig = {
diff --git a/nixos/modules/services/desktops/system-config-printer.nix b/nixos/modules/services/desktops/system-config-printer.nix
new file mode 100644
index 000000000000..09c68c587b43
--- /dev/null
+++ b/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/nixos/modules/services/desktops/tumbler.nix b/nixos/modules/services/desktops/tumbler.nix
index ccbb6d1434d9..a833e99ff8c6 100644
--- a/nixos/modules/services/desktops/tumbler.nix
+++ b/nixos/modules/services/desktops/tumbler.nix
@@ -7,12 +7,17 @@ with lib;
 let
 
   cfg = config.services.tumbler;
-  tumbler = cfg.package;
 
 in
 
 {
 
+  imports = [
+    (mkRemovedOptionModule
+      [ "services" "tumbler" "package" ]
+      "")
+  ];
+
   ###### interface
 
   options = {
@@ -21,13 +26,6 @@ in
 
       enable = mkEnableOption "Tumbler, A D-Bus thumbnailer service";
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.xfce4-13.tumbler;
-        description = "Which tumbler package to use";
-        example = pkgs.xfce4-12.tumbler;
-      };
-
     };
 
   };
@@ -37,11 +35,11 @@ in
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [
+    environment.systemPackages = with pkgs.xfce; [
       tumbler
     ];
 
-    services.dbus.packages = [
+    services.dbus.packages = with pkgs.xfce; [
       tumbler
     ];
 
diff --git a/nixos/modules/services/development/bloop.nix b/nixos/modules/services/development/bloop.nix
index 56904b7c40e6..226718a9e80a 100644
--- a/nixos/modules/services/development/bloop.nix
+++ b/nixos/modules/services/development/bloop.nix
@@ -9,6 +9,20 @@ let
 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;
@@ -25,10 +39,13 @@ in {
     systemd.user.services.bloop = {
       description = "Bloop Scala build server";
 
+      environment = {
+        PATH = mkForce "${makeBinPath [ config.programs.java.package ]}";
+      };
       serviceConfig = {
-        Type      = "simple";
-        ExecStart = ''${pkgs.bloop}/bin/blp-server'';
-        Restart   = "always";
+        Type        = "simple";
+        ExecStart   = ''${pkgs.bloop}/bin/bloop server'';
+        Restart     = "always";
       };
     };
 
diff --git a/nixos/modules/services/development/lorri.nix b/nixos/modules/services/development/lorri.nix
new file mode 100644
index 000000000000..c843aa56d133
--- /dev/null
+++ b/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/nixos/modules/services/editors/emacs.nix b/nixos/modules/services/editors/emacs.nix
index ba7ec967919e..d791b387665f 100644
--- a/nixos/modules/services/editors/emacs.nix
+++ b/nixos/modules/services/editors/emacs.nix
@@ -95,13 +95,7 @@ in {
 
     environment.systemPackages = [ cfg.package editorScript desktopApplicationFile ];
 
-    environment.variables = {
-      # This is required so that GTK applications launched from Emacs
-      # get properly themed:
-      GTK_DATA_PREFIX = "${config.system.path}";
-    } // (if cfg.defaultEditor then {
-        EDITOR = mkOverride 900 "${editorScript}/bin/emacseditor";
-      } else {});
+    environment.variables.EDITOR = mkIf cfg.defaultEditor (mkOverride 900 "${editorScript}/bin/emacseditor");
   };
 
   meta.doc = ./emacs.xml;
diff --git a/nixos/modules/services/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml
index 1ac53c818a7f..03483f69fa2f 100644
--- a/nixos/modules/services/editors/emacs.xml
+++ b/nixos/modules/services/editors/emacs.xml
@@ -9,6 +9,7 @@
       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
@@ -58,7 +59,7 @@
        <para>
         The latest stable version of Emacs 25 using the
         <link
-                xlink:href="http://www.gtk.org">GTK+ 2</link>
+                xlink:href="http://www.gtk.org">GTK 2</link>
         widget toolkit.
        </para>
       </listitem>
@@ -95,7 +96,8 @@
     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="http://yi-editor.github.io/">Yi</link>,
+    <link xlink:href="https://joe-editor.sourceforge.io/">jmacs</link>.
    </para>
   </section>
 
@@ -129,15 +131,6 @@
     Emacs packages through nixpkgs.
    </para>
 
-   <note>
-    <para>
-     This documentation describes the new Emacs packages framework in NixOS
-     16.03 (<varname>emacsPackagesNg</varname>) which should not be confused
-     with the previous and deprecated framework
-     (<varname>emacs24Packages</varname>).
-    </para>
-   </note>
-
    <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
@@ -163,7 +156,7 @@ $ ./result/bin/emacs
 
 let
   myEmacs = pkgs.emacs; <co xml:id="ex-emacsNix-2" />
-  emacsWithPackages = (pkgs.emacsPackagesNgGen myEmacs).emacsWithPackages; <co xml:id="ex-emacsNix-3" />
+  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;
@@ -238,8 +231,8 @@ in
    <para>
     You can check that it works by executing this in a terminal:
 <screen>
-$ nix-build emacs.nix
-$ ./result/bin/emacs -q
+<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
@@ -261,10 +254,10 @@ $ ./result/bin/emacs -q
     <example xml:id="module-services-emacs-querying-packages">
      <title>Querying Emacs packages</title>
 <programlisting><![CDATA[
-nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.elpaPackages
-nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.melpaPackages
-nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.melpaStablePackages
-nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.orgPackages
+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>
@@ -328,7 +321,7 @@ https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides
    <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
+    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:
@@ -356,7 +349,7 @@ in [...]
 
    <para>
     After building this file as shown in <xref linkend="ex-emacsNix" />, you
-    will get an GTK3-based Emacs binary pre-loaded with your favorite packages.
+    will get an GTK 3-based Emacs binary pre-loaded with your favorite packages.
    </para>
   </section>
  </section>
@@ -403,9 +396,9 @@ in [...]
    <para>
     To start the daemon, execute the following:
 <screen>
-$ nixos-rebuild switch  # to activate the new configuration.nix
-$ systemctl --user daemon-reload        # to force systemd reload
-$ systemctl --user start emacs.service  # to start the Emacs daemon
+<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>
@@ -551,7 +544,7 @@ emacsclient --create-frame --tty  # opens a new frame on the current terminal
     <xref linkend="opt-environment.systemPackages"/>
     (<link
       linkend="sec-declarative-package-mgmt">NixOS</link>), or run
-    <literal>nix-env -i pkgs.docbook5</literal>
+    <literal>nix-env -f '&lt;nixpkgs&gt;' -iA docbook5</literal>
     (<link linkend="sec-ad-hoc-packages">Nix</link>).
    </para>
 
diff --git a/nixos/modules/services/editors/infinoted.nix b/nixos/modules/services/editors/infinoted.nix
index 9cc8d421270e..8b997ccbf66e 100644
--- a/nixos/modules/services/editors/infinoted.nix
+++ b/nixos/modules/services/editors/infinoted.nix
@@ -111,13 +111,15 @@ in {
   };
 
   config = mkIf (cfg.enable) {
-    users.users = optional (cfg.user == "infinoted")
-      { name = "infinoted";
-        description = "Infinoted user";
-        group = cfg.group;
+    users.users = optionalAttrs (cfg.user == "infinoted")
+      { infinoted = {
+          description = "Infinoted user";
+          group = cfg.group;
+          isSystemUser = true;
+        };
       };
-    users.groups = optional (cfg.group == "infinoted")
-      { name = "infinoted";
+    users.groups = optionalAttrs (cfg.group == "infinoted")
+      { infinoted = { };
       };
 
     systemd.services.infinoted =
diff --git a/nixos/modules/services/games/factorio.nix b/nixos/modules/services/games/factorio.nix
index 3f6bf9de8931..f3831156f453 100644
--- a/nixos/modules/services/games/factorio.nix
+++ b/nixos/modules/services/games/factorio.nix
@@ -6,7 +6,7 @@ let
   cfg = config.services.factorio;
   factorio = pkgs.factorio-headless;
   name = "Factorio";
-  stateDir = cfg.stateDir;
+  stateDir = "/var/lib/${cfg.stateDirName}";
   mkSavePath = name: "${stateDir}/saves/${name}.zip";
   configFile = pkgs.writeText "factorio.conf" ''
     use-system-read-write-data-directories=true
@@ -55,7 +55,7 @@ in
         '';
       };
       saveName = mkOption {
-        type = types.string;
+        type = types.str;
         default = "default";
         description = ''
           The name of the savegame that will be used by the server.
@@ -80,11 +80,11 @@ in
           customizations.
         '';
       };
-      stateDir = mkOption {
-        type = types.path;
-        default = "/var/lib/factorio";
+      stateDirName = mkOption {
+        type = types.str;
+        default = "factorio";
         description = ''
-          The server's data directory.
+          Name of the directory under /var/lib holding the server's data.
 
           The configuration and map will be stored here.
         '';
@@ -102,14 +102,14 @@ in
         '';
       };
       game-name = mkOption {
-        type = types.nullOr types.string;
+        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.string;
+        type = types.nullOr types.str;
         default = "";
         description = ''
           Description of the game that will appear in the listing.
@@ -130,28 +130,28 @@ in
         '';
       };
       username = mkOption {
-        type = types.nullOr types.string;
+        type = types.nullOr types.str;
         default = null;
         description = ''
           Your factorio.com login credentials. Required for games with visibility public.
         '';
       };
       password = mkOption {
-        type = types.nullOr types.string;
+        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.string;
+        type = types.nullOr types.str;
         default = null;
         description = ''
           Authentication token. May be used instead of 'password' above.
         '';
       };
       game-password = mkOption {
-        type = types.nullOr types.string;
+        type = types.nullOr types.str;
         default = null;
         description = ''
           Game password.
@@ -176,20 +176,6 @@ in
   };
 
   config = mkIf cfg.enable {
-    users = {
-      users.factorio = {
-        uid             = config.ids.uids.factorio;
-        description     = "Factorio server user";
-        group           = "factorio";
-        home            = stateDir;
-        createHome      = true;
-      };
-
-      groups.factorio = {
-        gid = config.ids.gids.factorio;
-      };
-    };
-
     systemd.services.factorio = {
       description   = "Factorio headless server";
       wantedBy      = [ "multi-user.target" ];
@@ -205,12 +191,10 @@ in
       ];
 
       serviceConfig = {
-        User = "factorio";
-        Group = "factorio";
         Restart = "always";
         KillSignal = "SIGINT";
-        WorkingDirectory = stateDir;
-        PrivateTmp = true;
+        DynamicUser = true;
+        StateDirectory = cfg.stateDirName;
         UMask = "0007";
         ExecStart = toString [
           "${factorio}/bin/factorio"
@@ -220,6 +204,20 @@ in
           "--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;
       };
     };
 
diff --git a/nixos/modules/services/games/minecraft-server.nix b/nixos/modules/services/games/minecraft-server.nix
index 7d26d1501650..eb9288fca586 100644
--- a/nixos/modules/services/games/minecraft-server.nix
+++ b/nixos/modules/services/games/minecraft-server.nix
@@ -118,7 +118,7 @@ in {
       };
 
       serverProperties = mkOption {
-        type = with types; attrsOf (either bool (either int str));
+        type = with types; attrsOf (oneOf [ bool int str ]);
         default = {};
         example = literalExample ''
           {
@@ -215,8 +215,8 @@ in {
     networking.firewall = mkIf cfg.openFirewall (if cfg.declarative then {
       allowedUDPPorts = [ serverPort ];
       allowedTCPPorts = [ serverPort ]
-        ++ optional (! isNull queryPort) queryPort
-        ++ optional (! isNull rconPort) rconPort;
+        ++ optional (queryPort != null) queryPort
+        ++ optional (rconPort != null) rconPort;
     } else {
       allowedUDPPorts = [ defaultServerPort ];
       allowedTCPPorts = [ defaultServerPort ];
diff --git a/nixos/modules/services/games/openarena.nix b/nixos/modules/services/games/openarena.nix
new file mode 100644
index 000000000000..8c014d78809b
--- /dev/null
+++ b/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/nixos/modules/services/games/terraria.nix b/nixos/modules/services/games/terraria.nix
index 31f8edca20ce..a59b74c0b4c4 100644
--- a/nixos/modules/services/games/terraria.nix
+++ b/nixos/modules/services/games/terraria.nix
@@ -4,7 +4,7 @@ with lib;
 
 let
   cfg   = config.services.terraria;
-  worldSizeMap = { "small" = 1; "medium" = 2; "large" = 3; };
+  worldSizeMap = { small = 1; medium = 2; large = 3; };
   valFlag = name: val: optionalString (val != null) "-${name} \"${escape ["\\" "\""] (toString val)}\"";
   boolFlag = name: val: optionalString val "-${name}";
   flags = [ 
diff --git a/nixos/modules/services/hardware/80-net-setup-link.rules b/nixos/modules/services/hardware/80-net-setup-link.rules
deleted file mode 100644
index 18547f170a3f..000000000000
--- a/nixos/modules/services/hardware/80-net-setup-link.rules
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copied from systemd 203.
-ACTION=="remove", GOTO="net_name_slot_end"
-SUBSYSTEM!="net", GOTO="net_name_slot_end"
-NAME!="", GOTO="net_name_slot_end"
-
-IMPORT{cmdline}="net.ifnames"
-ENV{net.ifnames}=="0", GOTO="net_name_slot_end"
-
-NAME=="", ENV{ID_NET_NAME_ONBOARD}!="", NAME="$env{ID_NET_NAME_ONBOARD}"
-NAME=="", ENV{ID_NET_NAME_SLOT}!="", NAME="$env{ID_NET_NAME_SLOT}"
-NAME=="", ENV{ID_NET_NAME_PATH}!="", NAME="$env{ID_NET_NAME_PATH}"
-
-LABEL="net_name_slot_end"
diff --git a/nixos/modules/services/hardware/actkbd.nix b/nixos/modules/services/hardware/actkbd.nix
index 4168140b287a..daa407ca1f0e 100644
--- a/nixos/modules/services/hardware/actkbd.nix
+++ b/nixos/modules/services/hardware/actkbd.nix
@@ -83,7 +83,7 @@ in
 
           See <command>actkbd</command> <filename>README</filename> for documentation.
 
-          The example shows a piece of what <option>sound.enableMediaKeys</option> does when enabled.
+          The example shows a piece of what <option>sound.mediaKeys.enable</option> does when enabled.
         '';
       };
 
diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix
index c5f9d1f9b725..dfa39e7f6024 100644
--- a/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixos/modules/services/hardware/bluetooth.nix
@@ -36,17 +36,25 @@ in {
         '';
       };
 
+      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 = types.lines;
-        default = "";
+        type = with types; nullOr lines;
+        default = null;
         example = ''
           [General]
           ControllerMode = bredr
         '';
         description = ''
           Set additional configuration for system-wide bluetooth (/etc/bluetooth/main.conf).
-
-          NOTE: We already include [Policy], so any configuration under the Policy group should come first.
         '';
       };
     };
@@ -56,17 +64,19 @@ in {
   ###### implementation
 
   config = mkIf cfg.enable {
+    warnings = optional (cfg.extraConfig != null) "hardware.bluetooth.`extraConfig` is deprecated, please use hardware.bluetooth.`config`.";
 
-    environment.systemPackages = [ bluez-bluetooth pkgs.openobex pkgs.obexftp ];
+    hardware.bluetooth.config = {
+      Policy = {
+        AutoEnable = mkDefault cfg.powerOnBoot;
+      };
+    };
 
-    environment.etc = singleton {
-      source = pkgs.writeText "main.conf" ''
-        [Policy]
-        AutoEnable=${lib.boolToString cfg.powerOnBoot}
+    environment.systemPackages = [ bluez-bluetooth ];
 
-        ${cfg.extraConfig}
-      '';
-      target = "bluetooth/main.conf";
+    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 ];
diff --git a/nixos/modules/services/hardware/fancontrol.nix b/nixos/modules/services/hardware/fancontrol.nix
new file mode 100644
index 000000000000..bb4541a784da
--- /dev/null
+++ b/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/nixos/modules/services/hardware/freefall.nix b/nixos/modules/services/hardware/freefall.nix
index 066ccaa4d7cf..83f1e8c84f28 100644
--- a/nixos/modules/services/hardware/freefall.nix
+++ b/nixos/modules/services/hardware/freefall.nix
@@ -28,7 +28,7 @@ in {
     };
 
     devices = mkOption {
-      type = types.listOf types.string;
+      type = types.listOf types.str;
       default = [ "/dev/sda" ];
       description = ''
         Device paths to all internal spinning hard drives.
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index cad9fa20de0f..51877970a8bc 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -8,8 +8,8 @@ let
   cfg = config.services.fwupd;
   originalEtc =
     let
-      mkEtcFile = n: nameValuePair n { source = "${pkgs.fwupd}/etc/${n}"; };
-    in listToAttrs (map mkEtcFile pkgs.fwupd.filesInstalledToEtc);
+      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)}";
@@ -24,7 +24,7 @@ let
     "fwupd/remotes.d/fwupd-tests.conf" = {
       source = pkgs.runCommand "fwupd-tests-enabled.conf" {} ''
         sed "s,^Enabled=false,Enabled=true," \
-        "${pkgs.fwupd.installedTests}/etc/fwupd/remotes.d/fwupd-tests.conf" > "$out"
+        "${cfg.package.installedTests}/etc/fwupd/remotes.d/fwupd-tests.conf" > "$out"
       '';
     };
   } else {};
@@ -43,7 +43,7 @@ in {
       };
 
       blacklistDevices = mkOption {
-        type = types.listOf types.string;
+        type = types.listOf types.str;
         default = [];
         example = [ "2082b5e0-7a64-478a-b1b2-e3404fab6dad" ];
         description = ''
@@ -52,7 +52,7 @@ in {
       };
 
       blacklistPlugins = mkOption {
-        type = types.listOf types.string;
+        type = types.listOf types.str;
         default = [ "test" ];
         example = [ "udev" ];
         description = ''
@@ -74,7 +74,15 @@ in {
         default = false;
         description = ''
           Whether to enable test remote. This is used by
-          <link xlink:href="https://github.com/hughsie/fwupd/blob/master/data/installed-tests/README.md">installed tests</link>.
+          <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.
         '';
       };
     };
@@ -83,7 +91,7 @@ in {
 
   ###### implementation
   config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs.fwupd ];
+    environment.systemPackages = [ cfg.package ];
 
     environment.etc = {
       "fwupd/daemon.conf" = {
@@ -102,15 +110,11 @@ in {
 
     } // originalEtc // extraTrustedKeys // testRemote;
 
-    services.dbus.packages = [ pkgs.fwupd ];
-
-    services.udev.packages = [ pkgs.fwupd ];
+    services.dbus.packages = [ cfg.package ];
 
-    systemd.packages = [ pkgs.fwupd ];
+    services.udev.packages = [ cfg.package ];
 
-    systemd.tmpfiles.rules = [
-      "d /var/lib/fwupd 0755 root root -"
-    ];
+    systemd.packages = [ cfg.package ];
   };
 
   meta = {
diff --git a/nixos/modules/services/hardware/sane.nix b/nixos/modules/services/hardware/sane.nix
index fe05c5a5c06f..b344dfc20610 100644
--- a/nixos/modules/services/hardware/sane.nix
+++ b/nixos/modules/services/hardware/sane.nix
@@ -76,7 +76,7 @@ in
     };
 
     hardware.sane.configDir = mkOption {
-      type = types.string;
+      type = types.str;
       internal = true;
       description = "The value of SANE_CONFIG_DIR.";
     };
@@ -124,7 +124,7 @@ in
       environment.sessionVariables = env;
       services.udev.packages = backends;
 
-      users.groups."scanner".gid = config.ids.gids.scanner;
+      users.groups.scanner.gid = config.ids.gids.scanner;
     })
 
     (mkIf config.services.saned.enable {
@@ -152,7 +152,7 @@ in
         };
       };
 
-      users.users."scanner" = {
+      users.users.scanner = {
         uid = config.ids.uids.scanner;
         group = "scanner";
       };
diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
index f6ed4e25e9cb..6f49a1ab6d40 100644
--- a/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
+++ b/nixos/modules/services/hardware/sane_extra_backends/brscan4.nix
@@ -67,11 +67,11 @@ in
 {
   options = {
 
-    hardware.sane.brscan4.enable = 
+    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. 
+        backend and bring configuration files to their expected location.
       '';
     };
 
@@ -95,14 +95,11 @@ in
       pkgs.brscan4
     ];
 
-    environment.etc = singleton {
-      target = "opt/brother/scanner/brscan4";
-      source = "${etcFiles}/etc/opt/brother/scanner/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`
diff --git a/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
index bd114f0d2cca..6bf31982b71a 100644
--- a/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
+++ b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
@@ -19,7 +19,7 @@ nix-shell -E 'with import <nixpkgs> { }; brscan4-etc-files.override{netDevices=[
 
 */
 
-with lib; 
+with lib;
 
 let
 
@@ -33,14 +33,14 @@ let
   addAllNetDev = xs: concatStringsSep "\n" (map addNetDev xs);
 in
 
-stdenv.mkDerivation rec {
+stdenv.mkDerivation {
 
   name = "brscan4-etc-files-0.4.3-3";
   src = "${brscan4}/opt/brother/scanner/brscan4";
 
   nativeBuildInputs = [ brscan4 ];
 
-  configurePhase = ":";
+  dontConfigure = true;
 
   buildPhase = ''
     TARGET_DIR="$out/etc/opt/brother/scanner/brscan4"
diff --git a/nixos/modules/services/hardware/tcsd.nix b/nixos/modules/services/hardware/tcsd.nix
index d4b0a9495d75..68cb5d791aa3 100644
--- a/nixos/modules/services/hardware/tcsd.nix
+++ b/nixos/modules/services/hardware/tcsd.nix
@@ -49,13 +49,13 @@ in
 
       user = mkOption {
         default = "tss";
-        type = types.string;
+        type = types.str;
         description = "User account under which tcsd runs.";
       };
 
       group = mkOption {
         default = "tss";
-        type = types.string;
+        type = types.str;
         description = "Group account under which tcsd runs.";
       };
 
@@ -65,19 +65,19 @@ in
         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. 
+          restarts of the TCSD and system reboots.
         '';
       };
 
       firmwarePCRs = mkOption {
         default = "0,1,2,3,4,5,6,7";
-        type = types.string;
+        type = types.str;
         description = "PCR indices used in the TPM for firmware measurements.";
       };
 
       kernelPCRs = mkOption {
         default = "8,9,10,11,12";
-        type = types.string;
+        type = types.str;
         description = "PCR indices used in the TPM for kernel measurements.";
       };
 
@@ -137,15 +137,15 @@ in
       serviceConfig.ExecStart = "${pkgs.trousers}/sbin/tcsd -f -c ${tcsdConf}";
     };
 
-    users.users = optionalAttrs (cfg.user == "tss") (singleton
-      { name = "tss";
+    users.users = optionalAttrs (cfg.user == "tss") {
+      tss = {
         group = "tss";
         uid = config.ids.uids.tss;
-      });
+      };
+    };
 
-    users.groups = optionalAttrs (cfg.group == "tss") (singleton
-      { name = "tss";
-        gid = config.ids.gids.tss;
-      });
+    users.groups = optionalAttrs (cfg.group == "tss") {
+      tss.gid = config.ids.gids.tss;
+    };
   };
 }
diff --git a/nixos/modules/services/hardware/throttled.nix b/nixos/modules/services/hardware/throttled.nix
new file mode 100644
index 000000000000..7617c4492d7c
--- /dev/null
+++ b/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/nixos/modules/services/hardware/tlp.nix b/nixos/modules/services/hardware/tlp.nix
index 092ff051a042..955a60677997 100644
--- a/nixos/modules/services/hardware/tlp.nix
+++ b/nixos/modules/services/hardware/tlp.nix
@@ -37,7 +37,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "Whether to enable the TLP daemon.";
+        description = "Whether to enable the TLP power management daemon.";
       };
 
       extraConfig = mkOption {
@@ -60,11 +60,11 @@ in
     powerManagement.cpufreq.max = null;
     powerManagement.cpufreq.min = null;
 
-    systemd.sockets."systemd-rfkill".enable = false;
+    systemd.sockets.systemd-rfkill.enable = false;
 
     systemd.services = {
       "systemd-rfkill@".enable = false;
-      "systemd-rfkill".enable = false;
+      systemd-rfkill.enable = false;
 
       tlp = {
         description = "TLP system startup/shutdown";
@@ -103,13 +103,14 @@ in
 
     services.udev.packages = [ tlp ];
 
-    environment.etc = [{ source = confFile;
-                         target = "default/tlp";
-                       }
-                      ] ++ optional enableRDW {
-                        source = "${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm";
-                        target = "NetworkManager/dispatcher.d/99tlp-rdw-nm";
-                      };
+    environment.etc =
+      {
+        "default/tlp".source = confFile;
+      } // optionalAttrs enableRDW {
+        "NetworkManager/dispatcher.d/99tlp-rdw-nm" = {
+          source = "${tlp}/etc/NetworkManager/dispatcher.d/99tlp-rdw-nm";
+        };
+      };
 
     environment.systemPackages = [ tlp ];
 
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index c06a0665d02f..c517e9fbb2bd 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -4,6 +4,12 @@ with lib;
 let
   cfg = config.services.trezord;
 in {
+
+  ### docs
+
+  meta = {
+    doc = ./trezord.xml;
+  };
   
   ### interface
 
@@ -16,26 +22,29 @@ in {
           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 = lib.singleton (pkgs.writeTextFile {
-      name = "trezord-udev-rules";
-      destination = "/etc/udev/rules.d/51-trezor.rules";
-      text = ''
-        # TREZOR v1 (One)
-        SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
-        KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess"
-
-        # TREZOR v2 (T)
-        SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
-        SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
-        KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess"
-      '';
-    });
+    services.udev.packages = [ pkgs.trezor-udev-rules ];
 
     systemd.services.trezord = {
       description = "TREZOR Bridge";
@@ -44,7 +53,7 @@ in {
       path = [];
       serviceConfig = {
         Type = "simple";
-        ExecStart = "${pkgs.trezord}/bin/trezord-go";
+        ExecStart = "${pkgs.trezord}/bin/trezord-go ${optionalString cfg.emulator.enable "-e ${builtins.toString cfg.emulator.port}"}";
         User = "trezord";
       };
     };
@@ -52,6 +61,7 @@ in {
     users.users.trezord = {
       group = "trezord";
       description = "Trezor bridge daemon user";
+      isSystemUser = true;
     };
 
     users.groups.trezord = {};
diff --git a/nixos/modules/services/hardware/trezord.xml b/nixos/modules/services/hardware/trezord.xml
new file mode 100644
index 000000000000..972d409d9d0e
--- /dev/null
+++ b/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/nixos/modules/services/hardware/triggerhappy.nix b/nixos/modules/services/hardware/triggerhappy.nix
index bffe7353b10e..f9f5234bdc3f 100644
--- a/nixos/modules/services/hardware/triggerhappy.nix
+++ b/nixos/modules/services/hardware/triggerhappy.nix
@@ -17,7 +17,7 @@ let
     ${cfg.extraConfig}
   '';
 
-  bindingCfg = { config, ... }: {
+  bindingCfg = { ... }: {
     options = {
 
       keys = mkOption {
@@ -102,7 +102,6 @@ in
 
     systemd.services.triggerhappy = {
       wantedBy = [ "multi-user.target" ];
-      after = [ "local-fs.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*";
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 0266286aaacf..168056a475e5 100644
--- a/nixos/modules/services/hardware/udev.nix
+++ b/nixos/modules/services/hardware/udev.nix
@@ -85,7 +85,7 @@ let
       for i in $import_progs $run_progs; do
         if [[ ! -x $i ]]; then
           echo "FAIL"
-          echo "$i is called in udev rules but not installed by udev"
+          echo "$i is called in udev rules but is not executable or does not exist"
           exit 1
         fi
       done
@@ -116,10 +116,6 @@ let
         exit 1
       fi
 
-      ${optionalString config.networking.usePredictableInterfaceNames ''
-        cp ${./80-net-setup-link.rules} $out/80-net-setup-link.rules
-      ''}
-
       # If auto-configuration is disabled, then remove
       # udev's 80-drivers.rules file, which contains rules for
       # automatically calling modprobe.
@@ -225,8 +221,8 @@ in
         type = types.lines;
         description = ''
           Additional <command>hwdb</command> files. They'll be written
-          into file <filename>10-local.hwdb</filename>. Thus they are
-          read before all other files.
+          into file <filename>99-local.hwdb</filename>. Thus they are
+          read after all other files.
         '';
       };
 
@@ -282,14 +278,13 @@ in
 
     services.udev.path = [ pkgs.coreutils pkgs.gnused pkgs.gnugrep pkgs.utillinux udev ];
 
+    boot.kernelParams = mkIf (!config.networking.usePredictableInterfaceNames) [ "net.ifnames=0" ];
+
     environment.etc =
-      [ { source = udevRules;
-          target = "udev/rules.d";
-        }
-        { source = hwdbBin;
-          target = "udev/hwdb.bin";
-        }
-      ];
+      {
+        "udev/rules.d".source = udevRules;
+        "udev/hwdb.bin".source = hwdbBin;
+      };
 
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isEnabled "UNIX")
diff --git a/nixos/modules/services/hardware/udisks2.nix b/nixos/modules/services/hardware/udisks2.nix
index ed8703be921c..e898f3260585 100644
--- a/nixos/modules/services/hardware/udisks2.nix
+++ b/nixos/modules/services/hardware/udisks2.nix
@@ -34,10 +34,7 @@ with lib;
 
     services.dbus.packages = [ pkgs.udisks2 ];
 
-    system.activationScripts.udisks2 =
-      ''
-        mkdir -m 0755 -p /var/lib/udisks2
-      '';
+    systemd.tmpfiles.rules = [ "d /var/lib/udisks2 0755 root root -" ];
 
     services.udev.packages = [ pkgs.udisks2 ];
 
diff --git a/nixos/modules/services/hardware/upower.nix b/nixos/modules/services/hardware/upower.nix
index 1da47349c077..449810b53150 100644
--- a/nixos/modules/services/hardware/upower.nix
+++ b/nixos/modules/services/hardware/upower.nix
@@ -5,8 +5,11 @@
 with lib;
 
 let
+
   cfg = config.services.upower;
+
 in
+
 {
 
   ###### interface
@@ -34,6 +37,172 @@ in
         '';
       };
 
+      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
+        '';
+      };
+
     };
 
   };
@@ -49,56 +218,23 @@ in
 
     services.udev.packages = [ cfg.package ];
 
-    systemd.services.upower =
-      { description = "Power Management Daemon";
-        path = [ pkgs.glib.out ]; # needed for gdbus
-        serviceConfig =
-          { Type = "dbus";
-            BusName = "org.freedesktop.UPower";
-            ExecStart = "@${cfg.package}/libexec/upowerd upowerd";
-            Restart = "on-failure";
-            # Upstream lockdown:
-            # Filesystem lockdown
-            ProtectSystem = "strict";
-            # Needed by keyboard backlight support
-            ProtectKernelTunables = false;
-            ProtectControlGroups = true;
-            ReadWritePaths = "/var/lib/upower";
-            ProtectHome = true;
-            PrivateTmp = true;
-
-            # Network
-            # PrivateNetwork=true would block udev's netlink socket
-            RestrictAddressFamilies = "AF_UNIX AF_NETLINK";
-
-            # Execute Mappings
-            MemoryDenyWriteExecute = true;
-
-            # Modules
-            ProtectKernelModules = true;
-
-            # Real-time
-            RestrictRealtime = true;
-
-            # Privilege escalation
-            NoNewPrivileges = true;
-          };
+    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;
       };
-
-    system.activationScripts.upower =
-      ''
-        mkdir -m 0755 -p /var/lib/upower
-      '';
-
-    # The upower daemon seems to get stuck after doing a suspend
-    # (i.e. subsequent suspend requests will say "Sleep has already
-    # been requested and is pending").  So as a workaround, restart
-    # the daemon.
-    powerManagement.resumeCommands =
-      ''
-        ${config.systemd.package}/bin/systemctl try-restart upower
-      '';
-
+    };
   };
 
 }
diff --git a/nixos/modules/services/hardware/usbmuxd.nix b/nixos/modules/services/hardware/usbmuxd.nix
index 93ced0b9f04d..11a4b0a858f9 100644
--- a/nixos/modules/services/hardware/usbmuxd.nix
+++ b/nixos/modules/services/hardware/usbmuxd.nix
@@ -43,14 +43,16 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = optional (cfg.user == defaultUserGroup) {
-      name = cfg.user;
-      description = "usbmuxd user";
-      group = cfg.group;
+    users.users = optionalAttrs (cfg.user == defaultUserGroup) {
+      ${cfg.user} = {
+        description = "usbmuxd user";
+        group = cfg.group;
+        isSystemUser = true;
+      };
     };
 
-    users.groups = optional (cfg.group == defaultUserGroup) {
-      name = cfg.group;
+    users.groups = optionalAttrs (cfg.group == defaultUserGroup) {
+      ${cfg.group} = { };
     };
 
     # Give usbmuxd permission for Apple devices
diff --git a/nixos/modules/services/hardware/vdr.nix b/nixos/modules/services/hardware/vdr.nix
index 6e246f70f515..8a6cde51b06f 100644
--- a/nixos/modules/services/hardware/vdr.nix
+++ b/nixos/modules/services/hardware/vdr.nix
@@ -66,6 +66,7 @@ in {
     users.users.vdr = {
       group = "vdr";
       home = libDir;
+      isSystemUser = true;
     };
 
     users.groups.vdr = {};
diff --git a/nixos/modules/services/logging/SystemdJournal2Gelf.nix b/nixos/modules/services/logging/SystemdJournal2Gelf.nix
index e90d9e7a12b6..f26aef7262ba 100644
--- a/nixos/modules/services/logging/SystemdJournal2Gelf.nix
+++ b/nixos/modules/services/logging/SystemdJournal2Gelf.nix
@@ -16,7 +16,7 @@ in
       };
 
       graylogServer = mkOption {
-        type = types.string;
+        type = types.str;
         example = "graylog2.example.com:11201";
         description = ''
           Host and port of your graylog2 input. This should be a GELF
@@ -25,7 +25,7 @@ in
       };
 
       extraOptions = mkOption {
-        type = types.string;
+        type = types.separatedString " ";
         default = "";
         description = ''
           Any extra flags to pass to SystemdJournal2Gelf. Note that
@@ -56,4 +56,4 @@ in
       };
     };
   };
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/services/logging/awstats.nix b/nixos/modules/services/logging/awstats.nix
index 612ae06d0a79..5939d7808f7f 100644
--- a/nixos/modules/services/logging/awstats.nix
+++ b/nixos/modules/services/logging/awstats.nix
@@ -5,33 +5,120 @@ with lib;
 let
   cfg = config.services.awstats;
   package = pkgs.awstats;
-in
+  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/spool/nginx/logs/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 = mkOption {
-      type = types.bool;
-      default = cfg.service.enable;
-      description = ''
-        Enable the awstats program (but not service).
-        Currently only simple httpd (Apache) configs are supported,
-        and awstats plugins may not work correctly.
-      '';
-    };
-    vardir = mkOption {
+    enable = mkEnableOption "awstats";
+
+    dataDir = mkOption {
       type = types.path;
       default = "/var/lib/awstats";
-      description = "The directory where variable awstats data will be stored.";
+      description = "The directory where awstats data will be stored.";
     };
 
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      description = "Extra configuration to be appendend to awstats.conf.";
+    configs = mkOption {
+      type = types.attrsOf (types.submodule configOpts);
+      default = {};
+      example = literalExample ''
+        {
+          "mysite" = {
+            domain = "example.com";
+            logFile = "/var/spool/nginx/logs/access.log";
+          };
+        }
+      '';
+      description = "Attribute set of domains to collect stats for.";
     };
 
     updateAt = mkOption {
-      type = types.nullOr types.string;
+      type = types.nullOr types.str;
       default = null;
       example = "hourly";
       description = ''
@@ -41,82 +128,129 @@ in
           <manvolnum>7</manvolnum></citerefentry>)
       '';
     };
-
-    service = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''Enable the awstats web service. This switches on httpd.'';
-      };
-      urlPrefix = mkOption {
-        type = types.string;
-        default = "/awstats";
-        description = "The URL prefix under which the awstats service appears.";
-      };
-    };
   };
 
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ package.bin ];
-    /* TODO:
-      - heed config.services.httpd.logPerVirtualHost, etc.
-      - Can't AllowToUpdateStatsFromBrowser, as CGI scripts don't have permission
-        to read the logs, and our httpd config apparently doesn't an option for that.
-    */
-    environment.etc."awstats/awstats.conf".source = pkgs.runCommand "awstats.conf"
+
+    environment.etc = mapAttrs' (name: opts:
+    nameValuePair "awstats/awstats.${name}.conf" {
+      source = pkgs.runCommand "awstats.${name}.conf"
       { preferLocalBuild = true; }
-      ( let
-          cfg-httpd = config.services.httpd;
-          logFormat =
-            if cfg-httpd.logFormat == "combined" then "1" else
-            if cfg-httpd.logFormat == "common" then "4" else
-            throw "awstats service doesn't support Apache log format `${cfg-httpd.logFormat}`";
-        in
-        ''
-          sed \
-            -e 's|^\(DirData\)=.*$|\1="${cfg.vardir}"|' \
-            -e 's|^\(DirIcons\)=.*$|\1="icons"|' \
-            -e 's|^\(CreateDirDataIfNotExists\)=.*$|\1=1|' \
-            -e 's|^\(SiteDomain\)=.*$|\1="${cfg-httpd.hostName}"|' \
-            -e 's|^\(LogFile\)=.*$|\1="${cfg-httpd.logDir}/access_log"|' \
-            -e 's|^\(LogFormat\)=.*$|\1=${logFormat}|' \
-            < '${package.out}/wwwroot/cgi-bin/awstats.model.conf' > "$out"
-          echo '${cfg.extraConfig}' >> "$out"
-        '');
-
-    # The httpd sub-service showing awstats.
-    services.httpd.enable = mkIf cfg.service.enable true;
-    services.httpd.extraSubservices = mkIf cfg.service.enable [ { function = { serverInfo, ... }: {
-      extraConfig =
+      (''
+        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")
         ''
-          Alias ${cfg.service.urlPrefix}/classes "${package.out}/wwwroot/classes/"
-          Alias ${cfg.service.urlPrefix}/css "${package.out}/wwwroot/css/"
-          Alias ${cfg.service.urlPrefix}/icons "${package.out}/wwwroot/icon/"
-          ScriptAlias ${cfg.service.urlPrefix}/ "${package.out}/wwwroot/cgi-bin/"
-
-          <Directory "${package.out}/wwwroot">
-            Options None
-            AllowOverride None
-            Order allow,deny
-            Allow from all
-          </Directory>
+          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
         '';
-      startupScript =
-        let
-          inherit (serverInfo.serverConfig) user group;
-        in pkgs.writeScript "awstats_startup.sh"
-          ''
-            mkdir -p '${cfg.vardir}'
-            chown '${user}:${group}' '${cfg.vardir}'
-          '';
-    };}];
-
-    systemd.services.awstats-update = mkIf (cfg.updateAt != null) {
-      description = "awstats log collector";
-      script = "exec '${package.bin}/bin/awstats' -update -config=awstats.conf";
-      startAt = cfg.updateAt;
-    };
+        startAt = cfg.updateAt;
+    }) cfg.configs);
   };
 
 }
diff --git a/nixos/modules/services/logging/graylog.nix b/nixos/modules/services/logging/graylog.nix
index 74a7b3c9b470..a889a44d4b2b 100644
--- a/nixos/modules/services/logging/graylog.nix
+++ b/nixos/modules/services/logging/graylog.nix
@@ -108,7 +108,7 @@ in
       };
 
       extraConfig = mkOption {
-        type = types.str;
+        type = types.lines;
         default = "";
         description = "Any other configuration options you might want to add";
       };
@@ -134,6 +134,10 @@ in
       };
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.messageJournalDir}' - ${cfg.user} - - -"
+    ];
+
     systemd.services.graylog = with pkgs; {
       description = "Graylog Server";
       wantedBy = [ "multi-user.target" ];
@@ -143,25 +147,22 @@ in
       };
       path = [ pkgs.jre_headless pkgs.which pkgs.procps ];
       preStart = ''
-        mkdir -p /var/lib/graylog -m 755
-
         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
-        chown -R ${cfg.user} /var/lib/graylog
-
-        mkdir -p ${cfg.messageJournalDir} -m 755
-        chown -R ${cfg.user} ${cfg.messageJournalDir}
       '';
       serviceConfig = {
         User="${cfg.user}";
-        PermissionsStartOnly=true;
+        StateDirectory = "graylog";
         ExecStart = "${cfg.package}/bin/graylogctl run";
       };
     };
diff --git a/nixos/modules/services/logging/heartbeat.nix b/nixos/modules/services/logging/heartbeat.nix
index b595ac07bf5e..56fb4deabda5 100644
--- a/nixos/modules/services/logging/heartbeat.nix
+++ b/nixos/modules/services/logging/heartbeat.nix
@@ -54,16 +54,18 @@ in
 
   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}
-        chown nobody:nogroup "${cfg.stateDir}"/{data,logs}
       '';
       serviceConfig = {
         User = "nobody";
-        PermissionsStartOnly = true;
         AmbientCapabilities = "cap_net_raw";
         ExecStart = "${pkgs.heartbeat}/bin/heartbeat -c \"${heartbeatYml}\" -path.data \"${cfg.stateDir}/data\" -path.logs \"${cfg.stateDir}/logs\"";
       };
diff --git a/nixos/modules/services/logging/journalbeat.nix b/nixos/modules/services/logging/journalbeat.nix
index 8186a3b02c37..89f53b1b2454 100644
--- a/nixos/modules/services/logging/journalbeat.nix
+++ b/nixos/modules/services/logging/journalbeat.nix
@@ -5,11 +5,13 @@ 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}
 
-    journalbeat.cursor_state_file: ${cfg.stateDir}/cursor-state
+    ${optionalString lt6 "journalbeat.cursor_state_file: /var/lib/${cfg.stateDir}/cursor-state"}
 
     ${cfg.extraConfig}
   '';
@@ -22,6 +24,16 @@ in
 
       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";
@@ -36,13 +48,17 @@ in
 
       stateDir = mkOption {
         type = types.str;
-        default = "/var/lib/journalbeat";
-        description = "The state directory. Journalbeat's own logs and other data are stored here.";
+        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 = ''
+        default = optionalString lt6 ''
           journalbeat:
             seek_position: cursor
             cursor_seek_fallback: tail
@@ -61,7 +77,16 @@ in
 
   config = mkIf cfg.enable {
 
-    systemd.services.journalbeat = with pkgs; {
+    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 = ''
@@ -69,7 +94,13 @@ in
         mkdir -p ${cfg.stateDir}/logs
       '';
       serviceConfig = {
-        ExecStart = "${pkgs.journalbeat}/bin/journalbeat -c ${journalbeatYml} -path.data ${cfg.stateDir}/data -path.logs ${cfg.stateDir}/logs";
+        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/nixos/modules/services/logging/journalwatch.nix b/nixos/modules/services/logging/journalwatch.nix
index d0824df38ae3..576c646c0f58 100644
--- a/nixos/modules/services/logging/journalwatch.nix
+++ b/nixos/modules/services/logging/journalwatch.nix
@@ -4,6 +4,8 @@ with lib;
 let
   cfg = config.services.journalwatch;
   user = "journalwatch";
+  # for journal access
+  group = "systemd-journal";
   dataDir = "/var/lib/${user}";
 
   journalwatchConfig = pkgs.writeText "config" (''
@@ -31,6 +33,17 @@ let
 
   '') 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 = {
@@ -199,33 +212,38 @@ in {
 
     users.users.${user} = {
       isSystemUser = true;
-      createHome = true;
       home = dataDir;
-      # for journal access
-      group = "systemd-journal";
+      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 = "${dataDir}/config";
+        XDG_CONFIG_HOME = journalwatchConfigDir;
       };
       serviceConfig = {
         User = user;
+        Group = group;
         Type = "oneshot";
-        PermissionsStartOnly = true;
+        # 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;
       };
-      preStart = ''
-        chown -R ${user}:systemd-journal ${dataDir}
-        chmod -R u+rwX,go-w ${dataDir}
-        mkdir -p ${dataDir}/config/journalwatch
-        ln -sf ${journalwatchConfig} ${dataDir}/config/journalwatch/config
-        ln -sf ${journalwatchPatterns} ${dataDir}/config/journalwatch/patterns
-      '';
     };
 
     systemd.timers.journalwatch = {
diff --git a/nixos/modules/services/logging/logcheck.nix b/nixos/modules/services/logging/logcheck.nix
index 9c64160e92bc..4296b2270c29 100644
--- a/nixos/modules/services/logging/logcheck.nix
+++ b/nixos/modules/services/logging/logcheck.nix
@@ -23,9 +23,9 @@ let
   flags = "-r ${rulesDir} -c ${configFile} -L ${logFiles} -${levelFlag} -m ${cfg.mailTo}";
 
   levelFlag = getAttrFromPath [cfg.level]
-    { "paranoid"    = "p";
-      "server"      = "s";
-      "workstation" = "w";
+    { paranoid    = "p";
+      server      = "s";
+      workstation = "w";
     };
 
   cronJob = ''
@@ -155,7 +155,7 @@ in
 
       config = mkOption {
         default = "FQDN=1";
-        type = types.string;
+        type = types.lines;
         description = ''
           Config options that you would like in logcheck.conf.
         '';
@@ -213,13 +213,14 @@ in
         mapAttrsToList writeIgnoreRule cfg.ignore
         ++ mapAttrsToList writeIgnoreCronRule cfg.ignoreCron;
 
-    users.users = optionalAttrs (cfg.user == "logcheck") (singleton
-      { name = "logcheck";
+    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
@@ -227,7 +228,7 @@ in
     '';
 
     services.cron.systemCronJobs =
-        let withTime = name: {timeArgs, ...}: ! (builtins.isNull timeArgs);
+        let withTime = name: {timeArgs, ...}: timeArgs != null;
             mkCron = name: {user, cmdline, timeArgs, ...}: ''
               ${timeArgs} ${user} ${cmdline}
             '';
diff --git a/nixos/modules/services/logging/logstash.nix b/nixos/modules/services/logging/logstash.nix
index 9b707e9deb58..21a83803fd8c 100644
--- a/nixos/modules/services/logging/logstash.nix
+++ b/nixos/modules/services/logging/logstash.nix
@@ -37,6 +37,11 @@ let
 in
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "logstash" "address" ] [ "services" "logstash" "listenAddress" ])
+    (mkRemovedOptionModule [ "services" "logstash" "enableWeb" ] "The web interface was removed from logstash")
+  ];
+
   ###### interface
 
   options = {
@@ -53,7 +58,7 @@ in
         type = types.package;
         default = pkgs.logstash;
         defaultText = "pkgs.logstash";
-        example = literalExample "pkgs.logstash5";
+        example = literalExample "pkgs.logstash";
         description = "Logstash package to use.";
       };
 
diff --git a/nixos/modules/services/logging/rsyslogd.nix b/nixos/modules/services/logging/rsyslogd.nix
index 1ea96b8f1325..b924d94e0b0d 100644
--- a/nixos/modules/services/logging/rsyslogd.nix
+++ b/nixos/modules/services/logging/rsyslogd.nix
@@ -46,7 +46,7 @@ in
       };
 
       defaultConfig = mkOption {
-        type = types.string;
+        type = types.lines;
         default = defaultConf;
         description = ''
           The default <filename>syslog.conf</filename> file configures a
@@ -56,7 +56,7 @@ in
       };
 
       extraConfig = mkOption {
-        type = types.string;
+        type = types.lines;
         default = "";
         example = "news.* -/var/log/news";
         description = ''
diff --git a/nixos/modules/services/logging/syslog-ng.nix b/nixos/modules/services/logging/syslog-ng.nix
index 65e103ac2ba5..35055311680b 100644
--- a/nixos/modules/services/logging/syslog-ng.nix
+++ b/nixos/modules/services/logging/syslog-ng.nix
@@ -25,6 +25,10 @@ let
   ];
 
 in {
+  imports = [
+    (mkRemovedOptionModule [ "services" "syslog-ng" "serviceName" ] "")
+    (mkRemovedOptionModule [ "services" "syslog-ng" "listenToJournal" ] "")
+  ];
 
   options = {
 
diff --git a/nixos/modules/services/mail/davmail.nix b/nixos/modules/services/mail/davmail.nix
index 5b5cc294e5c7..374a3dd75c1c 100644
--- a/nixos/modules/services/mail/davmail.nix
+++ b/nixos/modules/services/mail/davmail.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.davmail;
 
   configType = with types;
-    either (either (attrsOf configType) str) (either int bool) // {
+    oneOf [ (attrsOf configType) str int bool ] // {
       description = "davmail config type (str, int, bool or attribute set thereof)";
     };
 
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index 30ad7d82fb80..b5ed2c594f77 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -16,13 +16,13 @@ let
       sendmail_path = /run/wrappers/bin/sendmail
     ''
 
-    (if isNull cfg.sslServerCert then ''
+    (if cfg.sslServerCert == null then ''
       ssl = no
       disable_plaintext_auth = no
     '' else ''
       ssl_cert = <${cfg.sslServerCert}
       ssl_key = <${cfg.sslServerKey}
-      ${optionalString (!(isNull cfg.sslCACert)) ("ssl_ca = <" + cfg.sslCACert)}
+      ${optionalString (cfg.sslCACert != null) ("ssl_ca = <" + cfg.sslCACert)}
       ssl_dh = <${config.security.dhparams.params.dovecot2.path}
       disable_plaintext_auth = yes
     '')
@@ -86,7 +86,7 @@ let
       }
 
       plugin {
-        quota_rule = *:storage=${cfg.quotaGlobalPerUser} 
+        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
@@ -133,6 +133,9 @@ let
   };
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "dovecot2" "package" ] "")
+  ];
 
   options.services.dovecot2 = {
     enable = mkEnableOption "Dovecot 2.x POP3/IMAP server";
@@ -181,7 +184,7 @@ in
     };
 
     configFile = mkOption {
-      type = types.nullOr types.str;
+      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;
@@ -298,7 +301,7 @@ in
   config = mkIf cfg.enable {
     security.pam.services.dovecot2 = mkIf cfg.enablePAM {};
 
-    security.dhparams = mkIf (! isNull cfg.sslServerCert) {
+    security.dhparams = mkIf (cfg.sslServerCert != null) {
       enable = true;
       params.dovecot2 = {};
     };
@@ -307,36 +310,32 @@ in
      ++ optional cfg.enablePop3 "pop3"
      ++ optional cfg.enableLmtp "lmtp";
 
-    users.users = [
-      { name = "dovenull";
-        uid = config.ids.uids.dovenull2;
-        description = "Dovecot user for untrusted logins";
-        group = "dovenull";
-      }
-    ] ++ optional (cfg.user == "dovecot2")
-         { name = "dovecot2";
-           uid = config.ids.uids.dovecot2;
+    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;
-         }
-      ++ optional (cfg.createMailUser && cfg.mailUser != null)
-         ({ name = cfg.mailUser;
-            description = "Virtual Mail User";
-         } // optionalAttrs (cfg.mailGroup != null) {
-           group = cfg.mailGroup;
-         });
-
-    users.groups = optional (cfg.group == "dovecot2")
-      { name = "dovecot2";
-        gid = config.ids.gids.dovecot2;
-      }
-    ++ optional (cfg.createMailUser && cfg.mailGroup != null)
-      { name = cfg.mailGroup;
-      }
-    ++ singleton
-      { name = "dovenull";
-        gid = config.ids.gids.dovenull2;
-      };
+         };
+    } // 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;
@@ -344,8 +343,7 @@ in
     systemd.services.dovecot2 = {
       description = "Dovecot IMAP/POP3 server";
 
-      after = [ "keys.target" "network.target" ];
-      wants = [ "keys.target" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ cfg.configFile ];
 
@@ -384,14 +382,14 @@ in
       { assertion = intersectLists cfg.protocols [ "pop3" "imap" ] != [];
         message = "dovecot needs at least one of the IMAP or POP3 listeners enabled";
       }
-      { assertion = isNull cfg.sslServerCert == isNull cfg.sslServerKey
-          && (!(isNull cfg.sslCACert) -> !(isNull cfg.sslServerCert || isNull cfg.sslServerKey));
+      { 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));
+      { 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/nixos/modules/services/mail/dspam.nix b/nixos/modules/services/mail/dspam.nix
index 167b5aeccc84..766ebc8095a0 100644
--- a/nixos/modules/services/mail/dspam.nix
+++ b/nixos/modules/services/mail/dspam.nix
@@ -86,16 +86,16 @@ in {
 
   config = mkIf cfg.enable (mkMerge [
     {
-      users.users = optionalAttrs (cfg.user == "dspam") (singleton
-        { name = "dspam";
+      users.users = optionalAttrs (cfg.user == "dspam") {
+        dspam = {
           group = cfg.group;
           uid = config.ids.uids.dspam;
-        });
+        };
+      };
 
-      users.groups = optionalAttrs (cfg.group == "dspam") (singleton
-        { name = "dspam";
-          gid = config.ids.gids.dspam;
-        });
+      users.groups = optionalAttrs (cfg.group == "dspam") {
+        dspam.gid = config.ids.gids.dspam;
+      };
 
       environment.systemPackages = [ dspam ];
 
@@ -113,19 +113,14 @@ in {
           Group = cfg.group;
           RuntimeDirectory = optional (cfg.domainSocket == defaultSock) "dspam";
           RuntimeDirectoryMode = optional (cfg.domainSocket == defaultSock) "0750";
-          PermissionsStartOnly = true;
+          StateDirectory = "dspam";
+          StateDirectoryMode = "0750";
+          LogsDirectory = "dspam";
+          LogsDirectoryMode = "0750";
           # DSPAM segfaults on just about every error
           Restart = "on-abort";
           RestartSec = "1s";
         };
-
-        preStart = ''
-          mkdir -m750 -p /var/lib/dspam
-          chown -R "${cfg.user}:${cfg.group}" /var/lib/dspam
-
-          mkdir -m750 -p /var/log/dspam
-          chown -R "${cfg.user}:${cfg.group}" /var/log/dspam
-        '';
       };
     }
 
diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix
index c05811291359..892fbd33214a 100644
--- a/nixos/modules/services/mail/exim.nix
+++ b/nixos/modules/services/mail/exim.nix
@@ -21,7 +21,7 @@ in
       };
 
       config = mkOption {
-        type = types.string;
+        type = types.lines;
         default = "";
         description = ''
           Verbatim Exim configuration.  This should not contain exim_user,
@@ -30,7 +30,7 @@ in
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "exim";
         description = ''
           User to use when no root privileges are required.
@@ -42,7 +42,7 @@ in
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "exim";
         description = ''
           Group to use when no root privileges are required.
@@ -50,7 +50,7 @@ in
       };
 
       spoolDir = mkOption {
-        type = types.string;
+        type = types.path;
         default = "/var/spool/exim";
         description = ''
           Location of the spool directory of exim.
@@ -87,15 +87,13 @@ in
       systemPackages = [ cfg.package ];
     };
 
-    users.users = singleton {
-      name = cfg.user;
+    users.users.${cfg.user} = {
       description = "Exim mail transfer agent user";
       uid = config.ids.uids.exim;
       group = cfg.group;
     };
 
-    users.groups = singleton {
-      name = cfg.group;
+    users.groups.${cfg.group} = {
       gid = config.ids.gids.exim;
     };
 
diff --git a/nixos/modules/services/mail/mailcatcher.nix b/nixos/modules/services/mail/mailcatcher.nix
index fa8d41e918d3..84f06ed199dc 100644
--- a/nixos/modules/services/mail/mailcatcher.nix
+++ b/nixos/modules/services/mail/mailcatcher.nix
@@ -3,7 +3,7 @@
 let
   cfg = config.services.mailcatcher;
 
-  inherit (lib) mkEnableOption mkIf mkOption types;
+  inherit (lib) mkEnableOption mkIf mkOption types optionalString;
 in
 {
   # interface
@@ -25,6 +25,13 @@ in
         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";
@@ -53,7 +60,8 @@ in
       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}";
+        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/nixos/modules/services/mail/mailhog.nix b/nixos/modules/services/mail/mailhog.nix
index b78f4c8e0e66..0f998c6d0ea6 100644
--- a/nixos/modules/services/mail/mailhog.nix
+++ b/nixos/modules/services/mail/mailhog.nix
@@ -27,6 +27,7 @@ in {
     users.users.mailhog = {
       name = cfg.user;
       description = "MailHog service user";
+      isSystemUser = true;
     };
 
     systemd.services.mailhog = {
diff --git a/nixos/modules/services/mail/mailman.nix b/nixos/modules/services/mail/mailman.nix
new file mode 100644
index 000000000000..e917209f3d1f
--- /dev/null
+++ b/nixos/modules/services/mail/mailman.nix
@@ -0,0 +1,297 @@
+{ config, pkgs, lib, ... }:          # mailman.nix
+
+with lib;
+
+let
+
+  cfg = config.services.mailman;
+
+  mailmanPyEnv = pkgs.python3.withPackages (ps: with ps; [mailman mailman-hyperkitty]);
+
+  mailmanExe = with pkgs; stdenv.mkDerivation {
+    name = "mailman-" + python3Packages.mailman.version;
+    buildInputs = [makeWrapper];
+    unpackPhase = ":";
+    installPhase = ''
+      mkdir -p $out/bin
+      makeWrapper ${mailmanPyEnv}/bin/mailman $out/bin/mailman \
+        --set MAILMAN_CONFIG_FILE /etc/mailman.cfg
+   '';
+  };
+
+  mailmanWeb = pkgs.python3Packages.mailman-web.override {
+    serverEMail = cfg.siteOwner;
+    archiverKey = cfg.hyperkittyApiKey;
+    allowedHosts = cfg.webHosts;
+  };
+
+  mailmanWebPyEnv = pkgs.python3.withPackages (x: with x; [mailman-web]);
+
+  mailmanWebExe = with pkgs; stdenv.mkDerivation {
+    inherit (mailmanWeb) name;
+    buildInputs = [makeWrapper];
+    unpackPhase = ":";
+    installPhase = ''
+      mkdir -p $out/bin
+      makeWrapper ${mailmanWebPyEnv}/bin/django-admin $out/bin/mailman-web \
+        --set DJANGO_SETTINGS_MODULE 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.hyperkittyApiKey != null) ''
+    [archiver.hyperkitty]
+    class: mailman_hyperkitty.Archiver
+    enable: yes
+    configuration: ${pkgs.writeText "mailman-hyperkitty.cfg" mailmanHyperkittyCfg}
+  '';
+
+  mailmanHyperkittyCfg = ''
+    [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.hyperkittyBaseUrl}
+
+    # Shared API key, must be the identical to the value in HyperKitty's
+    # settings.
+    api_key: ${cfg.hyperkittyApiKey}
+  '';
+
+in {
+
+  ###### interface
+
+  options = {
+
+    services.mailman = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable Mailman on this host. Requires an active Postfix installation.";
+      };
+
+      siteOwner = mkOption {
+        type = types.str;
+        default = "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 = "${mailmanWeb}/${pkgs.python3.sitePackages}";
+        defaultText = "pkgs.python3Packages.mailman-web";
+        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 httpd 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.
+        '';
+      };
+
+      hyperkittyBaseUrl = mkOption {
+        type = types.str;
+        default = "http://localhost/hyperkitty/";
+        description = ''
+          Where can Mailman connect to Hyperkitty's internal API, preferably on
+          localhost?
+        '';
+      };
+
+      hyperkittyApiKey = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The shared secret used to authenticate Mailman's internal
+          communication with Hyperkitty. Must be set to enable support for the
+          Hyperkitty archiver. Note that this secret is going to be visible to
+          all local users in the Nix store.
+        '';
+      };
+
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    assertions = [
+      { assertion = cfg.enable -> config.services.postfix.enable;
+        message = "Mailman requires Postfix";
+      }
+    ];
+
+    users.users.mailman = { description = "GNU Mailman"; isSystemUser = true; };
+
+    environment = {
+      systemPackages = [ mailmanExe mailmanWebExe pkgs.sassc ];
+      etc."mailman.cfg".text = mailmanCfg;
+    };
+
+    services.postfix = {
+      relayDomains = [ "hash:/var/lib/mailman/data/postfix_domains" ];
+      recipientDelimiter = "+";         # bake recipient addresses in mail envelopes via VERP
+      config = {
+        transport_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ];
+        local_recipient_maps = [ "hash:/var/lib/mailman/data/postfix_lmtp" ];
+        owner_request_special = "no";   # Mailman handles -owner addresses on its own
+      };
+    };
+
+    systemd.services.mailman = {
+      description = "GNU Mailman Master Process";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${mailmanExe}/bin/mailman start";
+        ExecStop = "${mailmanExe}/bin/mailman stop";
+        User = "mailman";
+        Type = "forking";
+        StateDirectory = "mailman";
+        StateDirectoryMode = "0700";
+        RuntimeDirectory = "mailman";
+        PIDFile = "/run/mailman/master.pid";
+      };
+    };
+
+    systemd.services.mailman-web = {
+      description = "Init Postorius DB";
+      before = [ "httpd.service" ];
+      requiredBy = [ "httpd.service" ];
+      script = ''
+        ${mailmanWebExe}/bin/mailman-web migrate
+        rm -rf static
+        ${mailmanWebExe}/bin/mailman-web collectstatic
+        ${mailmanWebExe}/bin/mailman-web compress
+      '';
+      serviceConfig = {
+        User = config.services.httpd.user;
+        Type = "oneshot";
+        StateDirectory = "mailman-web";
+        StateDirectoryMode = "0700";
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.mailman-daily = {
+      description = "Trigger daily Mailman events";
+      startAt = "daily";
+      serviceConfig = {
+        ExecStart = "${mailmanExe}/bin/mailman digests --send";
+        User = "mailman";
+      };
+    };
+
+    systemd.services.hyperkitty = {
+      enable = cfg.hyperkittyApiKey != null;
+      description = "GNU Hyperkitty QCluster Process";
+      after = [ "network.target" ];
+      wantedBy = [ "mailman.service" "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = "${mailmanWebExe}/bin/mailman-web qcluster";
+        User = config.services.httpd.user;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-minutely = {
+      enable = cfg.hyperkittyApiKey != null;
+      description = "Trigger minutely Hyperkitty events";
+      startAt = "minutely";
+      serviceConfig = {
+        ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs minutely";
+        User = config.services.httpd.user;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-quarter-hourly = {
+      enable = cfg.hyperkittyApiKey != null;
+      description = "Trigger quarter-hourly Hyperkitty events";
+      startAt = "*:00/15";
+      serviceConfig = {
+        ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs quarter_hourly";
+        User = config.services.httpd.user;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-hourly = {
+      enable = cfg.hyperkittyApiKey != null;
+      description = "Trigger hourly Hyperkitty events";
+      startAt = "hourly";
+      serviceConfig = {
+        ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs hourly";
+        User = config.services.httpd.user;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-daily = {
+      enable = cfg.hyperkittyApiKey != null;
+      description = "Trigger daily Hyperkitty events";
+      startAt = "daily";
+      serviceConfig = {
+        ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs daily";
+        User = config.services.httpd.user;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-weekly = {
+      enable = cfg.hyperkittyApiKey != null;
+      description = "Trigger weekly Hyperkitty events";
+      startAt = "weekly";
+      serviceConfig = {
+        ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs weekly";
+        User = config.services.httpd.user;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+    systemd.services.hyperkitty-yearly = {
+      enable = cfg.hyperkittyApiKey != null;
+      description = "Trigger yearly Hyperkitty events";
+      startAt = "yearly";
+      serviceConfig = {
+        ExecStart = "${mailmanWebExe}/bin/mailman-web runjobs yearly";
+        User = config.services.httpd.user;
+        WorkingDirectory = "/var/lib/mailman-web";
+      };
+    };
+
+  };
+
+}
diff --git a/nixos/modules/services/mail/mlmmj.nix b/nixos/modules/services/mail/mlmmj.nix
index 11565bc02f89..d58d93c4214c 100644
--- a/nixos/modules/services/mail/mlmmj.nix
+++ b/nixos/modules/services/mail/mlmmj.nix
@@ -94,8 +94,7 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = singleton {
-      name = cfg.user;
+    users.users.${cfg.user} = {
       description = "mlmmj user";
       home = stateDir;
       createHome = true;
@@ -104,8 +103,7 @@ in
       useDefaultShell = true;
     };
 
-    users.groups = singleton {
-      name = cfg.group;
+    users.groups.${cfg.group} = {
       gid = config.ids.gids.mlmmj;
     };
 
@@ -137,7 +135,7 @@ in
           ${pkgs.postfix}/bin/postmap ${stateDir}/transports
       '';
 
-    systemd.services."mlmmj-maintd" = {
+    systemd.services.mlmmj-maintd = {
       description = "mlmmj maintenance daemon";
       serviceConfig = {
         User = cfg.user;
@@ -146,7 +144,7 @@ in
       };
     };
 
-    systemd.timers."mlmmj-maintd" = {
+    systemd.timers.mlmmj-maintd = {
       description = "mlmmj maintenance timer";
       timerConfig.OnUnitActiveSec = cfg.maintInterval;
       wantedBy = [ "timers.target" ];
diff --git a/nixos/modules/services/mail/nullmailer.nix b/nixos/modules/services/mail/nullmailer.nix
index 9997d287013e..fe3f8ef9b391 100644
--- a/nixos/modules/services/mail/nullmailer.nix
+++ b/nixos/modules/services/mail/nullmailer.nix
@@ -14,7 +14,7 @@ with lib;
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "nullmailer";
         description = ''
           User to use to run nullmailer-send.
@@ -22,7 +22,7 @@ with lib;
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "nullmailer";
         description = ''
           Group to use to run nullmailer-send.
@@ -201,15 +201,12 @@ with lib;
     };
 
     users = {
-      users = singleton {
-        name = cfg.user;
+      users.${cfg.user} = {
         description = "Nullmailer relay-only mta user";
         group = cfg.group;
       };
 
-      groups = singleton {
-        name = cfg.group;
-      };
+      groups.${cfg.group} = { };
     };
 
     systemd.tmpfiles.rules = [
diff --git a/nixos/modules/services/mail/opendkim.nix b/nixos/modules/services/mail/opendkim.nix
index 7855efb46c73..eb6a426684d4 100644
--- a/nixos/modules/services/mail/opendkim.nix
+++ b/nixos/modules/services/mail/opendkim.nix
@@ -18,6 +18,9 @@ let
          ] ++ optionals (cfg.configFile != null) [ "-x" cfg.configFile ];
 
 in {
+  imports = [
+    (mkRenamedOptionModule [ "services" "opendkim" "keyFile" ] [ "services" "opendkim" "keyPath" ])
+  ];
 
   ###### interface
 
@@ -88,26 +91,29 @@ in {
 
   config = mkIf cfg.enable {
 
-    users.users = optionalAttrs (cfg.user == "opendkim") (singleton
-      { name = "opendkim";
+    users.users = optionalAttrs (cfg.user == "opendkim") {
+      opendkim = {
         group = cfg.group;
         uid = config.ids.uids.opendkim;
-      });
+      };
+    };
 
-    users.groups = optionalAttrs (cfg.group == "opendkim") (singleton
-      { name = "opendkim";
-        gid = config.ids.gids.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 = ''
-        mkdir -p "${cfg.keyPath}"
         cd "${cfg.keyPath}"
         if ! test -f ${cfg.selector}.private; then
           ${pkgs.opendkim}/bin/opendkim-genkey -s ${cfg.selector} -d all-domains-generic-key
@@ -116,7 +122,6 @@ in {
           cat ${cfg.selector}.txt
           echo "-------------------------------------------------------------"
         fi
-        chown ${cfg.user}:${cfg.group} ${cfg.selector}.private
       '';
 
       serviceConfig = {
@@ -124,7 +129,6 @@ in {
         User = cfg.user;
         Group = cfg.group;
         RuntimeDirectory = optional (cfg.socket == defaultSock) "opendkim";
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/mail/opensmtpd.nix b/nixos/modules/services/mail/opensmtpd.nix
index a870550ba50b..1fabe2da45c5 100644
--- a/nixos/modules/services/mail/opensmtpd.nix
+++ b/nixos/modules/services/mail/opensmtpd.nix
@@ -101,6 +101,12 @@ in {
       };
     };
 
+    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";
@@ -110,18 +116,6 @@ in {
     in {
       wantedBy = [ "multi-user.target" ];
       after = [ "network.target" ];
-      preStart = ''
-        mkdir -p /var/spool/smtpd
-        chmod 711 /var/spool/smtpd
-
-        mkdir -p /var/spool/smtpd/offline
-        chown root.smtpq /var/spool/smtpd/offline
-        chmod 770 /var/spool/smtpd/offline
-
-        mkdir -p /var/spool/smtpd/purge
-        chown smtpq.root /var/spool/smtpd/purge
-        chmod 700 /var/spool/smtpd/purge
-      '';
       serviceConfig.ExecStart = "${cfg.package}/sbin/smtpd -d -f ${conf} ${args}";
       environment.OPENSMTPD_PROC_PATH = "${procEnv}/libexec/opensmtpd";
     };
diff --git a/nixos/modules/services/mail/pfix-srsd.nix b/nixos/modules/services/mail/pfix-srsd.nix
index 9599854352c9..38984f896d6a 100644
--- a/nixos/modules/services/mail/pfix-srsd.nix
+++ b/nixos/modules/services/mail/pfix-srsd.nix
@@ -40,7 +40,7 @@ with lib;
       systemPackages = [ pkgs.pfixtools ];
     };
 
-    systemd.services."pfix-srsd" = {
+    systemd.services.pfix-srsd = {
       description = "Postfix sender rewriting scheme daemon";
       before = [ "postfix.service" ];
       #note that we use requires rather than wants because postfix
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index d43733484ffa..19e11b31d9ca 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -13,6 +13,7 @@ let
                       || cfg.extraAliases != "";
   haveTransport = cfg.transport != "";
   haveVirtual = cfg.virtual != "";
+  haveLocalRecipients = cfg.localRecipients != null;
 
   clientAccess =
     optional (cfg.dnsBlacklistOverrides != "")
@@ -244,6 +245,7 @@ let
 
   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;
@@ -445,7 +447,7 @@ in
       };
 
       config = mkOption {
-        type = with types; attrsOf (either bool (either str (listOf str)));
+        type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
         description = ''
           The main.cf configuration file as key value set.
         '';
@@ -506,6 +508,19 @@ in
         '';
       };
 
+      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 = "
@@ -515,7 +530,7 @@ in
 
       dnsBlacklists = mkOption {
         default = [];
-        type = with types; listOf string;
+        type = with types; listOf str;
         description = "dns blacklist servers to use with smtpd_client_restrictions";
       };
 
@@ -597,10 +612,7 @@ in
     {
 
       environment = {
-        etc = singleton
-          { source = "/var/lib/postfix/conf";
-            target = "postfix";
-          };
+        etc.postfix.source = "/var/lib/postfix/conf";
 
         # This makes it comfortable to run 'postqueue/postdrop' for example.
         systemPackages = [ pkgs.postfix ];
@@ -616,6 +628,14 @@ in
         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";
@@ -632,21 +652,20 @@ in
         setgid = true;
       };
 
-      users.users = optional (user == "postfix")
-        { name = "postfix";
-          description = "Postfix mail server user";
-          uid = config.ids.uids.postfix;
-          group = group;
+      users.users = optionalAttrs (user == "postfix")
+        { postfix = {
+            description = "Postfix mail server user";
+            uid = config.ids.uids.postfix;
+            group = group;
+          };
         };
 
       users.groups =
-        optional (group == "postfix")
-        { name = group;
-          gid = config.ids.gids.postfix;
+        optionalAttrs (group == "postfix")
+        { ${group}.gid = config.ids.gids.postfix;
         }
-        ++ optional (setgidGroup == "postdrop")
-        { name = setgidGroup;
-          gid = config.ids.gids.postdrop;
+        // optionalAttrs (setgidGroup == "postdrop")
+        { ${setgidGroup}.gid = config.ids.gids.postdrop;
         };
 
       systemd.services.postfix =
@@ -742,6 +761,7 @@ in
       // 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" ];
@@ -861,19 +881,22 @@ in
     }
 
     (mkIf haveAliases {
-      services.postfix.aliasFiles."aliases" = aliasesFile;
+      services.postfix.aliasFiles.aliases = aliasesFile;
     })
     (mkIf haveTransport {
-      services.postfix.mapFiles."transport" = transportFile;
+      services.postfix.mapFiles.transport = transportFile;
     })
     (mkIf haveVirtual {
-      services.postfix.mapFiles."virtual" = virtualFile;
+      services.postfix.mapFiles.virtual = virtualFile;
+    })
+    (mkIf haveLocalRecipients {
+      services.postfix.mapFiles.local_recipients = localRecipientMapFile;
     })
     (mkIf cfg.enableHeaderChecks {
-      services.postfix.mapFiles."header_checks" = headerChecksFile;
+      services.postfix.mapFiles.header_checks = headerChecksFile;
     })
     (mkIf (cfg.dnsBlacklists != []) {
-      services.postfix.mapFiles."client_access" = checkClientAccessFile;
+      services.postfix.mapFiles.client_access = checkClientAccessFile;
     })
   ]);
 }
diff --git a/nixos/modules/services/mail/postgrey.nix b/nixos/modules/services/mail/postgrey.nix
index 8e2b9c5dbc56..709f6b21aa0a 100644
--- a/nixos/modules/services/mail/postgrey.nix
+++ b/nixos/modules/services/mail/postgrey.nix
@@ -7,12 +7,12 @@ with lib; let
   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");
+  socket = with types; addCheck (either (submodule unixSocket) (submodule inetSocket)) (x: x ? path || x ? port);
 
   inetSocket = with types; {
     options = {
       addr = mkOption {
-        type = nullOr string;
+        type = nullOr str;
         default = null;
         example = "127.0.0.1";
         description = "The address to bind to. Localhost if null";
@@ -34,7 +34,7 @@ with lib; let
       };
 
       mode = mkOption {
-        type = string;
+        type = str;
         default = "0777";
         description = "Mode of the unix socket";
       };
@@ -42,6 +42,17 @@ with lib; let
   };
 
 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; {
@@ -63,17 +74,17 @@ in {
         description = "Socket to bind to";
       };
       greylistText = mkOption {
-        type = string;
+        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 = string;
+        type = str;
         default = "DEFER_IF_PERMIT";
         description = "Response status for greylisted messages (see access(5))";
       };
       greylistHeader = mkOption {
-        type = string;
+        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";
       };
@@ -88,7 +99,7 @@ in {
         description = "Delete entries from whitelist if they haven't been seen for N days";
       };
       retryWindow = mkOption {
-        type = either string natural;
+        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";
@@ -151,7 +162,7 @@ in {
     };
 
     systemd.services.postgrey = let
-      bind-flag = if cfg.socket ? "path" then
+      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}'';
diff --git a/nixos/modules/services/mail/postsrsd.nix b/nixos/modules/services/mail/postsrsd.nix
index 8f12a16906c5..2ebc675ab10a 100644
--- a/nixos/modules/services/mail/postsrsd.nix
+++ b/nixos/modules/services/mail/postsrsd.nix
@@ -90,16 +90,16 @@ in {
 
     services.postsrsd.domain = mkDefault config.networking.hostName;
 
-    users.users = optionalAttrs (cfg.user == "postsrsd") (singleton
-      { name = "postsrsd";
+    users.users = optionalAttrs (cfg.user == "postsrsd") {
+      postsrsd = {
         group = cfg.group;
         uid = config.ids.uids.postsrsd;
-      });
+      };
+    };
 
-    users.groups = optionalAttrs (cfg.group == "postsrsd") (singleton
-      { name = "postsrsd";
-        gid = config.ids.gids.postsrsd;
-      });
+    users.groups = optionalAttrs (cfg.group == "postsrsd") {
+      postsrsd.gid = config.ids.gids.postsrsd;
+    };
 
     systemd.services.postsrsd = {
       description = "PostSRSd SRS rewriting server";
diff --git a/nixos/modules/services/mail/rmilter.nix b/nixos/modules/services/mail/rmilter.nix
deleted file mode 100644
index 466365b6b305..000000000000
--- a/nixos/modules/services/mail/rmilter.nix
+++ /dev/null
@@ -1,252 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  rspamdCfg = config.services.rspamd;
-  postfixCfg = config.services.postfix;
-  cfg = config.services.rmilter;
-
-  inetSocket = addr: port: "inet:${addr}:${toString port}";
-  unixSocket = sock: "unix:${sock}";
-
-  systemdSocket = if cfg.bindSocket.type == "unix" then cfg.bindSocket.path
-    else "${cfg.bindSocket.address}:${toString cfg.bindSocket.port}";
-  rmilterSocket = if cfg.bindSocket.type == "unix" then unixSocket cfg.bindSocket.path
-    else inetSocket cfg.bindSocket.address cfg.bindSocket.port;
-
-  rmilterConf = ''
-    pidfile = /run/rmilter/rmilter.pid;
-    bind_socket = ${if cfg.socketActivation then "fd:3" else rmilterSocket};
-    tempdir = /tmp;
-  '' + (with cfg.rspamd; if enable then ''
-    spamd {
-      servers = ${concatStringsSep ", " servers};
-      connect_timeout = 1s;
-      results_timeout = 20s;
-      error_time = 10;
-      dead_time = 300;
-      maxerrors = 10;
-      reject_message = "${rejectMessage}";
-      ${optionalString (length whitelist != 0)  "whitelist = ${concatStringsSep ", " whitelist};"}
-
-      # rspamd_metric - metric for using with rspamd
-      # Default: "default"
-      rspamd_metric = "default";
-      ${extraConfig}
-    };
-  '' else "") + cfg.extraConfig;
-
-  rmilterConfigFile = pkgs.writeText "rmilter.conf" rmilterConf;
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.rmilter = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to run the rmilter daemon.";
-      };
-
-      debug = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Whether to run the rmilter daemon in debug mode.";
-      };
-
-      user = mkOption {
-        type = types.string;
-        default = "rmilter";
-        description = ''
-          User to use when no root privileges are required.
-        '';
-       };
-
-      group = mkOption {
-        type = types.string;
-        default = "rmilter";
-        description = ''
-          Group to use when no root privileges are required.
-        '';
-       };
-
-      bindSocket.type = mkOption {
-        type = types.enum [ "unix" "inet" ];
-        default = "unix";
-        description = ''
-          What kind of socket rmilter should listen on. Either "unix"
-          for an Unix domain socket or "inet" for a TCP socket.
-        '';
-      };
-
-      bindSocket.path = mkOption {
-       type = types.str;
-       default = "/run/rmilter.sock";
-       description = ''
-          Path to Unix domain socket to listen on.
-        '';
-      };
-
-      bindSocket.address = mkOption {
-        type = types.str;
-        default = "[::1]";
-        example = "0.0.0.0";
-        description = ''
-          Inet address to listen on.
-        '';
-      };
-
-      bindSocket.port = mkOption {
-        type = types.int;
-        default = 11990;
-        description = ''
-          Inet port to listen on.
-        '';
-      };
-
-      socketActivation = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Enable systemd socket activation for rmilter.
-
-          Disabling socket activation is not recommended when a Unix
-          domain socket is used and could lead to incorrect
-          permissions.
-        '';
-      };
-
-      rspamd = {
-        enable = mkOption {
-          type = types.bool;
-          default = rspamdCfg.enable;
-          description = "Whether to use rspamd to filter mails";
-        };
-
-        servers = mkOption {
-          type = types.listOf types.str;
-          default = ["r:/run/rspamd/rspamd.sock"];
-          description = ''
-            Spamd socket definitions.
-            Is server name is prefixed with r: it is rspamd server.
-          '';
-        };
-
-        whitelist = mkOption {
-          type = types.listOf types.str;
-          default = [ ];
-          description = "list of ips or nets that should be not checked with spamd";
-        };
-
-        rejectMessage = mkOption {
-          type = types.str;
-          default = "Spam message rejected; If this is not spam contact abuse";
-          description = "reject message for spam";
-        };
-
-        extraConfig = mkOption {
-          type = types.lines;
-          default = "";
-          description = "Custom snippet to append to end of `spamd' section";
-        };
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = "Custom snippet to append to rmilter config";
-      };
-
-      postfix = {
-        enable = mkOption {
-          type = types.bool;
-          default = false;
-          description = "Add rmilter to postfix main.conf";
-        };
-
-        configFragment = mkOption {
-          type = types.str;
-          description = "Addon to postfix configuration";
-          default = ''
-            smtpd_milters = ${rmilterSocket}
-            milter_protocol = 6
-            milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
-          '';
-        };
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkMerge [
-
-    (mkIf cfg.enable {
-      warnings = [
-        ''`config.services.rmilter' is deprecated, `rmilter' deprecated and unsupported by upstream, and will be removed from next releases. Use built-in rspamd milter instead.''
-      ];
-
-      users.users = singleton {
-        name = cfg.user;
-        description = "rmilter daemon";
-        uid = config.ids.uids.rmilter;
-        group = cfg.group;
-      };
-
-      users.groups = singleton {
-        name = cfg.group;
-        gid = config.ids.gids.rmilter;
-      };
-
-      systemd.services.rmilter = {
-        description = "Rmilter Service";
-
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
-
-        serviceConfig = {
-          ExecStart = "${pkgs.rmilter}/bin/rmilter ${optionalString cfg.debug "-d"} -n -c ${rmilterConfigFile}";
-          ExecReload = "${pkgs.coreutils}/bin/kill -USR1 $MAINPID";
-          User = cfg.user;
-          Group = cfg.group;
-          PermissionsStartOnly = true;
-          Restart = "always";
-          RuntimeDirectory = "rmilter";
-          RuntimeDirectoryMode = "0750";
-        };
-
-      };
-
-      systemd.sockets.rmilter = mkIf cfg.socketActivation {
-        description = "Rmilter service socket";
-        wantedBy = [ "sockets.target" ];
-        socketConfig = {
-          ListenStream = systemdSocket;
-          SocketUser = cfg.user;
-          SocketGroup = cfg.group;
-          SocketMode = "0660";
-        };
-      };
-    })
-
-    (mkIf (cfg.enable && cfg.rspamd.enable && rspamdCfg.enable) {
-      users.users.${cfg.user}.extraGroups = [ rspamdCfg.group ];
-    })
-
-    (mkIf (cfg.enable && cfg.postfix.enable) {
-      services.postfix.extraConfig = cfg.postfix.configFragment;
-      users.users.${postfixCfg.user}.extraGroups = [ cfg.group ];
-    })
-  ];
-}
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
index e8b2e11bf726..0bb0eaedad50 100644
--- a/nixos/modules/services/mail/roundcube.nix
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -4,6 +4,9 @@ with lib;
 
 let
   cfg = config.services.roundcube;
+  fpm = config.services.phpfpm.pools.roundcube;
+  localDB = cfg.database.host == "localhost";
+  user = cfg.database.username;
 in
 {
   options.services.roundcube = {
@@ -43,7 +46,10 @@ in
       username = mkOption {
         type = types.str;
         default = "roundcube";
-        description = "Username for the postgresql connection";
+        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;
@@ -57,7 +63,12 @@ in
       };
       password = mkOption {
         type = types.str;
-        description = "Password for the postgresql connection";
+        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;
@@ -82,14 +93,22 @@ in
   };
 
   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}:${cfg.database.password}@${cfg.database.host}/${cfg.database.dbname}';
+      $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';
       ${cfg.extraConfig}
     '';
 
@@ -105,7 +124,7 @@ in
             extraConfig = ''
               location ~* \.php$ {
                 fastcgi_split_path_info ^(.+\.php)(/.+)$;
-                fastcgi_pass unix:/run/phpfpm/roundcube;
+                fastcgi_pass unix:${fpm.socket};
                 include ${pkgs.nginx}/conf/fastcgi_params;
                 include ${pkgs.nginx}/conf/fastcgi.conf;
               }
@@ -115,33 +134,48 @@ in
       };
     };
 
-    services.postgresql = mkIf (cfg.database.host == "localhost") {
+    services.postgresql = mkIf localDB {
       enable = true;
+      ensureDatabases = [ cfg.database.dbname ];
+      ensureUsers = [ {
+        name = cfg.database.username;
+        ensurePermissions = {
+          "DATABASE ${cfg.database.username}" = "ALL PRIVILEGES";
+        };
+      } ];
     };
 
-    services.phpfpm.poolConfigs.roundcube = ''
-      listen = /run/phpfpm/roundcube
-      listen.owner = nginx
-      listen.group = nginx
-      listen.mode = 0660
-      user = nginx
-      pm = dynamic
-      pm.max_children = 75
-      pm.start_servers = 2
-      pm.min_spare_servers = 1
-      pm.max_spare_servers = 20
-      pm.max_requests = 500
-      php_admin_value[error_log] = 'stderr'
-      php_admin_flag[log_errors] = on
-      php_admin_value[post_max_size] = 25M
-      php_admin_value[upload_max_filesize] = 25M
-      catch_workers_output = yes
-    '';
+    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;
+      };
+    };
     systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ];
 
-    systemd.services.roundcube-setup = let
-      pgSuperUser = config.services.postgresql.superUser;
-    in mkMerge [
+    systemd.services.roundcube-setup = mkMerge [
       (mkIf (cfg.database.host == "localhost") {
         requires = [ "postgresql.service" ];
         after = [ "postgresql.service" ];
@@ -149,22 +183,31 @@ in
       })
       {
         wantedBy = [ "multi-user.target" ];
-        script = ''
-          mkdir -p /var/lib/roundcube
-          if [ ! -f /var/lib/roundcube/db-created ]; then
-            if [ "${cfg.database.host}" = "localhost" ]; then
-              ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create role ${cfg.database.username} with login password '${cfg.database.password}'";
-              ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create database ${cfg.database.dbname} with owner ${cfg.database.username}";
-            fi
-            PGPASSWORD=${cfg.database.password} ${pkgs.postgresql}/bin/psql -U ${cfg.database.username} \
-              -f ${cfg.package}/SQL/postgres.initial.sql \
-              -h ${cfg.database.host} ${cfg.database.dbname}
-            touch /var/lib/roundcube/db-created
+        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
 
           ${pkgs.php}/bin/php ${cfg.package}/bin/update.sh
         '';
-        serviceConfig.Type = "oneshot";
+        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/nixos/modules/services/mail/rspamd.nix b/nixos/modules/services/mail/rspamd.nix
index c9ba86780213..aacdbe2aeed2 100644
--- a/nixos/modules/services/mail/rspamd.nix
+++ b/nixos/modules/services/mail/rspamd.nix
@@ -5,7 +5,6 @@ with lib;
 let
 
   cfg = config.services.rspamd;
-  opts = options.services.rspamd;
   postfixCfg = config.services.postfix;
 
   bindSocketOpts = {options, config, ... }: {
@@ -61,7 +60,7 @@ let
       };
       type = mkOption {
         type = types.nullOr (types.enum [
-          "normal" "controller" "fuzzy_storage" "rspamd_proxy" "lua" "proxy"
+          "normal" "controller" "fuzzy" "rspamd_proxy" "lua" "proxy"
         ]);
         description = ''
           The type of this worker. The type <literal>proxy</literal> is
@@ -69,7 +68,7 @@ let
           replaced with <literal>rspamd_proxy</literal>.
         '';
         apply = let
-            from = "services.rspamd.workers.\”${name}\".type";
+            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;
@@ -221,7 +220,6 @@ let
 in
 
 {
-
   ###### interface
 
   options = {
@@ -309,7 +307,7 @@ in
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "rspamd";
         description = ''
           User to use when no root privileges are required.
@@ -317,7 +315,7 @@ in
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "rspamd";
         description = ''
           Group to use when no root privileges are required.
@@ -332,7 +330,7 @@ in
         };
 
         config = mkOption {
-          type = with types; attrsOf (either bool (either str (listOf str)));
+          type = with types; attrsOf (oneOf [ bool str (listOf str) ]);
           description = ''
             Addon to postfix configuration
           '';
@@ -376,19 +374,17 @@ in
     # Allow users to run 'rspamc' and 'rspamadm'.
     environment.systemPackages = [ pkgs.rspamd ];
 
-    users.users = singleton {
-      name = cfg.user;
+    users.users.${cfg.user} = {
       description = "rspamd daemon";
       uid = config.ids.uids.rspamd;
       group = cfg.group;
     };
 
-    users.groups = singleton {
-      name = cfg.group;
+    users.groups.${cfg.group} = {
       gid = config.ids.gids.rspamd;
     };
 
-    environment.etc."rspamd".source = rspamdDir;
+    environment.etc.rspamd.source = rspamdDir;
 
     systemd.services.rspamd = {
       description = "Rspamd Service";
@@ -415,5 +411,6 @@ in
 	     "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/nixos/modules/services/mail/rss2email.nix b/nixos/modules/services/mail/rss2email.nix
index a123736005ab..c1e5964c4536 100644
--- a/nixos/modules/services/mail/rss2email.nix
+++ b/nixos/modules/services/mail/rss2email.nix
@@ -30,7 +30,7 @@ in {
       };
 
       config = mkOption {
-        type = with types; attrsOf (either str (either int bool));
+        type = with types; attrsOf (oneOf [ str int bool ]);
         default = {};
         description = ''
           The configuration to give rss2email.
@@ -43,9 +43,8 @@ in {
           <literal>[DEFAULT]</literal> block along with the
           <literal>to</literal> parameter.
 
-          See
-          <literal>https://github.com/rss2email/rss2email/blob/master/r2e.1</literal>
-          for more information on which parameters are accepted.
+          See <literal>man r2e</literal> for more information on which
+          parameters are accepted.
         '';
       };
 
diff --git a/nixos/modules/services/mail/spamassassin.nix b/nixos/modules/services/mail/spamassassin.nix
index 1fe77ce5a0c7..75442c7cdb5e 100644
--- a/nixos/modules/services/mail/spamassassin.nix
+++ b/nixos/modules/services/mail/spamassassin.nix
@@ -5,7 +5,6 @@ with lib;
 let
   cfg = config.services.spamassassin;
   spamassassin-local-cf = pkgs.writeText "local.cf" cfg.config;
-  spamassassin-init-pre = pkgs.writeText "init.pre" cfg.initPreConf;
 
   spamdEnv = pkgs.buildEnv {
     name = "spamd-env";
@@ -65,8 +64,9 @@ in
       };
 
       initPreConf = mkOption {
-        type = types.str;
+        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 =
         ''
           #
@@ -124,19 +124,17 @@ in
     # Allow users to run 'spamc'.
 
     environment = {
-      etc = singleton { source = spamdEnv; target = "spamassassin"; };
+      etc.spamassassin.source = spamdEnv;
       systemPackages = [ pkgs.spamassassin ];
     };
 
-    users.users = singleton {
-      name = "spamd";
+    users.users.spamd = {
       description = "Spam Assassin Daemon";
       uid = config.ids.uids.spamd;
       group = "spamd";
     };
 
-    users.groups = singleton {
-      name = "spamd";
+    users.groups.spamd = {
       gid = config.ids.gids.spamd;
     };
 
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index 8b2ec82c7705..c296e048cea4 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -34,7 +34,7 @@ in {
       };
 
       listenAddress = mkOption {
-        type = types.string;
+        type = types.str;
         default = "127.0.0.1";
         description = ''
           The host name or IP address on which to bind Airsonic.
@@ -105,7 +105,7 @@ in {
   config = mkIf cfg.enable {
     systemd.services.airsonic = {
       description = "Airsonic Media Server";
-      after = [ "local-fs.target" "network.target" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
       preStart = ''
@@ -138,8 +138,8 @@ in {
 
     services.nginx = mkIf (cfg.virtualHost != null) {
       enable = true;
-      virtualHosts."${cfg.virtualHost}" = {
-        locations."${cfg.contextPath}".proxyPass = "http://${cfg.listenAddress}:${toString cfg.port}";
+      virtualHosts.${cfg.virtualHost} = {
+        locations.${cfg.contextPath}.proxyPass = "http://${cfg.listenAddress}:${toString cfg.port}";
       };
     };
 
@@ -148,6 +148,7 @@ in {
       name = cfg.user;
       home = cfg.home;
       createHome = true;
+      isSystemUser = true;
     };
   };
 }
diff --git a/nixos/modules/services/misc/apache-kafka.nix b/nixos/modules/services/misc/apache-kafka.nix
index 363ac4411e11..f3a650a260f1 100644
--- a/nixos/modules/services/misc/apache-kafka.nix
+++ b/nixos/modules/services/misc/apache-kafka.nix
@@ -46,7 +46,7 @@ in {
     hostname = mkOption {
       description = "Hostname the broker should bind to.";
       default = "localhost";
-      type = types.string;
+      type = types.str;
     };
 
     logDirs = mkOption {
@@ -54,13 +54,13 @@ in {
       default = [ "/tmp/kafka-logs" ];
       type = types.listOf types.path;
     };
-    
+
     zookeeper = mkOption {
       description = "Zookeeper connection string";
       default = "localhost:2181";
-      type = types.string;
+      type = types.str;
     };
- 
+
     extraProperties = mkOption {
       description = "Extra properties for server.properties.";
       type = types.nullOr types.lines;
@@ -79,8 +79,8 @@ in {
     log4jProperties = mkOption {
       description = "Kafka log4j property configuration.";
       default = ''
-        log4j.rootLogger=INFO, stdout 
-        
+        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
@@ -124,13 +124,14 @@ in {
 
     environment.systemPackages = [cfg.package];
 
-    users.users = singleton {
-      name = "apache-kafka";
+    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" ];
@@ -145,15 +146,8 @@ in {
             ${serverConfig}
         '';
         User = "apache-kafka";
-        PermissionsStartOnly = true;
         SuccessExitStatus = "0 143";
       };
-      preStart = ''
-        mkdir -m 0700 -p ${concatStringsSep " " cfg.logDirs}
-        if [ "$(id -u)" = 0 ]; then
-           chown apache-kafka ${concatStringsSep " " cfg.logDirs};
-        fi
-      '';
     };
 
   };
diff --git a/nixos/modules/services/misc/beanstalkd.nix b/nixos/modules/services/misc/beanstalkd.nix
index 06e881406b52..bcd133c97411 100644
--- a/nixos/modules/services/misc/beanstalkd.nix
+++ b/nixos/modules/services/misc/beanstalkd.nix
@@ -44,7 +44,8 @@ in
       serviceConfig = {
         DynamicUser = true;
         Restart = "always";
-        ExecStart = "${pkg}/bin/beanstalkd -l ${cfg.listen.address} -p ${toString cfg.listen.port}";
+        ExecStart = "${pkg}/bin/beanstalkd -l ${cfg.listen.address} -p ${toString cfg.listen.port} -b $STATE_DIRECTORY";
+        StateDirectory = "beanstalkd";
       };
     };
 
diff --git a/nixos/modules/services/misc/bepasty.nix b/nixos/modules/services/misc/bepasty.nix
index 006feca42b32..f69832e5b2bd 100644
--- a/nixos/modules/services/misc/bepasty.nix
+++ b/nixos/modules/services/misc/bepasty.nix
@@ -143,7 +143,7 @@ in
           serviceConfig = {
             Type = "simple";
             PrivateTmp = true;
-            ExecStartPre = assert !isNull server.secretKeyFile; pkgs.writeScript "bepasty-server.${name}-init" ''
+            ExecStartPre = assert server.secretKeyFile != null; pkgs.writeScript "bepasty-server.${name}-init" ''
               #!/bin/sh
               mkdir -p "${server.workDir}"
               mkdir -p "${server.dataDir}"
@@ -168,16 +168,12 @@ in
         })
     ) cfg.servers;
 
-    users.users = [{
-      uid = config.ids.uids.bepasty;
-      name = user;
-      group = group;
-      home = default_home;
-    }];
-
-    users.groups = [{
-      name = group;
-      gid = config.ids.gids.bepasty;
-    }];
+    users.users.${user} =
+      { uid = config.ids.uids.bepasty;
+        group = group;
+        home = default_home;
+      };
+
+    users.groups.${group}.gid = config.ids.gids.bepasty;
   };
 }
diff --git a/nixos/modules/services/misc/cgminer.nix b/nixos/modules/services/misc/cgminer.nix
index b1cf5a7d1104..9fcae6452696 100644
--- a/nixos/modules/services/misc/cgminer.nix
+++ b/nixos/modules/services/misc/cgminer.nix
@@ -110,11 +110,12 @@ in
 
   config = mkIf config.services.cgminer.enable {
 
-    users.users = optionalAttrs (cfg.user == "cgminer") (singleton
-      { name = "cgminer";
+    users.users = optionalAttrs (cfg.user == "cgminer") {
+      cgminer = {
         uid = config.ids.uids.cgminer;
         description = "Cgminer user";
-      });
+      };
+    };
 
     environment.systemPackages = [ cfg.package ];
 
diff --git a/nixos/modules/services/misc/couchpotato.nix b/nixos/modules/services/misc/couchpotato.nix
index 70aa895f76d8..f5163cf86cf5 100644
--- a/nixos/modules/services/misc/couchpotato.nix
+++ b/nixos/modules/services/misc/couchpotato.nix
@@ -19,32 +19,24 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
 
-      preStart = ''
-        mkdir -p /var/lib/couchpotato
-        chown -R couchpotato:couchpotato /var/lib/couchpotato
-      '';
-
       serviceConfig = {
         Type = "simple";
         User = "couchpotato";
         Group = "couchpotato";
-        PermissionsStartOnly = "true";
+        StateDirectory = "couchpotato";
         ExecStart = "${pkgs.couchpotato}/bin/couchpotato";
         Restart = "on-failure";
       };
     };
 
-    users.users = singleton
-      { name = "couchpotato";
-        group = "couchpotato";
+    users.users.couchpotato =
+      { group = "couchpotato";
         home = "/var/lib/couchpotato/";
         description = "CouchPotato daemon user";
         uid = config.ids.uids.couchpotato;
       };
 
-    users.groups = singleton
-      { name = "couchpotato";
-        gid = config.ids.gids.couchpotato;
-      };
+    users.groups.couchpotato =
+      { gid = config.ids.gids.couchpotato; };
   };
 }
diff --git a/nixos/modules/services/misc/cpuminer-cryptonight.nix b/nixos/modules/services/misc/cpuminer-cryptonight.nix
index f31526f8d107..907b9d90da29 100644
--- a/nixos/modules/services/misc/cpuminer-cryptonight.nix
+++ b/nixos/modules/services/misc/cpuminer-cryptonight.nix
@@ -28,15 +28,15 @@ in
         '';
       };
       url = mkOption {
-        type = types.string;
+        type = types.str;
         description = "URL of mining server";
       };
       user = mkOption {
-        type = types.string;
+        type = types.str;
         description = "Username for mining server";
       };
       pass = mkOption {
-        type = types.string;
+        type = types.str;
         default = "x";
         description = "Password for mining server";
       };
@@ -63,4 +63,4 @@ in
 
   };
 
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/services/misc/dictd.nix b/nixos/modules/services/misc/dictd.nix
index 8d3e294622d1..d175854d2d1e 100644
--- a/nixos/modules/services/misc/dictd.nix
+++ b/nixos/modules/services/misc/dictd.nix
@@ -45,18 +45,14 @@ in
     # get the command line client on system path to make some use of the service
     environment.systemPackages = [ pkgs.dict ];
 
-    users.users = singleton
-      { name = "dictd";
-        group = "dictd";
+    users.users.dictd =
+      { group = "dictd";
         description = "DICT.org dictd server";
         home = "${dictdb}/share/dictd";
         uid = config.ids.uids.dictd;
       };
 
-    users.groups = singleton
-      { name = "dictd";
-        gid = config.ids.gids.dictd;
-      };
+    users.groups.dictd.gid = config.ids.gids.dictd;
 
     systemd.services.dictd = {
       description = "DICT.org Dictionary Server";
diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix
index c87607d2666a..89bac4f47d73 100644
--- a/nixos/modules/services/misc/docker-registry.nix
+++ b/nixos/modules/services/misc/docker-registry.nix
@@ -145,11 +145,13 @@ in {
     };
 
     users.users.docker-registry =
-      if cfg.storagePath != null
+      (if cfg.storagePath != null
       then {
         createHome = true;
         home = cfg.storagePath;
       }
-      else {};
+      else {}) // {
+        isSystemUser = true;
+      };
   };
 }
diff --git a/nixos/modules/services/misc/dwm-status.nix b/nixos/modules/services/misc/dwm-status.nix
new file mode 100644
index 000000000000..b98a42e6a6d2
--- /dev/null
+++ b/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/nixos/modules/services/misc/dysnomia.nix b/nixos/modules/services/misc/dysnomia.nix
index 61ea822890ed..33a6fb152641 100644
--- a/nixos/modules/services/misc/dysnomia.nix
+++ b/nixos/modules/services/misc/dysnomia.nix
@@ -8,9 +8,9 @@ let
   printProperties = properties:
     concatMapStrings (propertyName:
       let
-        property = properties."${propertyName}";
+        property = properties.${propertyName};
       in
-      if isList property then "${propertyName}=(${lib.concatMapStrings (elem: "\"${toString elem}\" ") (properties."${propertyName}")})\n"
+      if isList property then "${propertyName}=(${lib.concatMapStrings (elem: "\"${toString elem}\" ") (properties.${propertyName})})\n"
       else "${propertyName}=\"${toString property}\"\n"
     ) (builtins.attrNames properties);
 
@@ -31,7 +31,7 @@ let
 
       ${concatMapStrings (containerName:
         let
-          containerProperties = cfg.containers."${containerName}";
+          containerProperties = cfg.containers.${containerName};
         in
         ''
           cat > ${containerName} <<EOF
@@ -49,10 +49,10 @@ let
 
       ${concatMapStrings (componentName:
         let
-          component = cfg.components."${containerName}"."${componentName}";
+          component = cfg.components.${containerName}.${componentName};
         in
         "ln -s ${component} ${containerName}/${componentName}\n"
-      ) (builtins.attrNames (cfg.components."${containerName}" or {}))}
+      ) (builtins.attrNames (cfg.components.${containerName} or {}))}
     '';
 
   componentsDir = pkgs.stdenv.mkDerivation {
@@ -151,6 +151,7 @@ in
       enableSubversionRepository = config.services.svnserve.enable;
       enableTomcatWebApplication = config.services.tomcat.enable;
       enableMongoDatabase = config.services.mongodb.enable;
+      enableInfluxDatabase = config.services.influxdb.enable;
     });
 
     dysnomia.properties = {
diff --git a/nixos/modules/services/misc/emby.nix b/nixos/modules/services/misc/emby.nix
deleted file mode 100644
index 0ad4a3f7376f..000000000000
--- a/nixos/modules/services/misc/emby.nix
+++ /dev/null
@@ -1,76 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.services.emby;
-in
-{
-  options = {
-    services.emby = {
-      enable = mkEnableOption "Emby Media Server";
-
-      user = mkOption {
-        type = types.str;
-        default = "emby";
-        description = "User account under which Emby runs.";
-      };
-
-      group = mkOption {
-        type = types.str;
-        default = "emby";
-        description = "Group under which emby runs.";
-      };
-
-      dataDir = mkOption {
-        type = types.path;
-        default = "/var/lib/emby/ProgramData-Server";
-        description = "Location where Emby stores its data.";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.emby = {
-      description = "Emby Media Server";
-      after = [ "network.target" ];
-      wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        if [ -d ${cfg.dataDir} ]
-        then
-            for plugin in ${cfg.dataDir}/plugins/*
-            do
-                echo "Correcting permissions of plugin: $plugin"
-                chmod u+w $plugin
-            done
-        else
-            echo "Creating initial Emby data directory in ${cfg.dataDir}"
-            mkdir -p ${cfg.dataDir}
-            chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
-        fi
-      '';
-
-      serviceConfig = {
-        Type = "simple";
-        User = cfg.user;
-        Group = cfg.group;
-        PermissionsStartOnly = "true";
-        ExecStart = "${pkgs.emby}/bin/emby -programdata ${cfg.dataDir}";
-        Restart = "on-failure";
-      };
-    };
-
-    users.users = mkIf (cfg.user == "emby") {
-      emby = {
-        group = cfg.group;
-        uid = config.ids.uids.emby;
-      };
-    };
-
-    users.groups = mkIf (cfg.group == "emby") {
-      emby = {
-        gid = config.ids.gids.emby;
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/misc/errbot.nix b/nixos/modules/services/misc/errbot.nix
index ac6ba2181de2..b447ba5d438d 100644
--- a/nixos/modules/services/misc/errbot.nix
+++ b/nixos/modules/services/misc/errbot.nix
@@ -76,12 +76,15 @@ in {
   };
 
   config = mkIf (cfg.instances != {}) {
-    users.users.errbot.group = "errbot";
+    users.users.errbot = {
+      group = "errbot";
+      isSystemUser = true;
+    };
     users.groups.errbot = {};
 
     systemd.services = mapAttrs' (name: instanceCfg: nameValuePair "errbot-${name}" (
     let
-      dataDir = if !isNull instanceCfg.dataDir then instanceCfg.dataDir else
+      dataDir = if instanceCfg.dataDir != null then instanceCfg.dataDir else
         "/var/lib/errbot/${name}";
     in {
       after = [ "network-online.target" ];
diff --git a/nixos/modules/services/misc/etcd.nix b/nixos/modules/services/misc/etcd.nix
index e4d5322f9b5f..7322e1c080be 100644
--- a/nixos/modules/services/misc/etcd.nix
+++ b/nixos/modules/services/misc/etcd.nix
@@ -186,8 +186,7 @@ in {
 
     environment.systemPackages = [ pkgs.etcdctl ];
 
-    users.users = singleton {
-      name = "etcd";
+    users.users.etcd = {
       uid = config.ids.uids.etcd;
       description = "Etcd daemon user";
       home = cfg.dataDir;
diff --git a/nixos/modules/services/misc/ethminer.nix b/nixos/modules/services/misc/ethminer.nix
new file mode 100644
index 000000000000..95afb0460fb8
--- /dev/null
+++ b/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/nixos/modules/services/misc/exhibitor.nix b/nixos/modules/services/misc/exhibitor.nix
index 665084a8ae05..f8c79f892da3 100644
--- a/nixos/modules/services/misc/exhibitor.nix
+++ b/nixos/modules/services/misc/exhibitor.nix
@@ -58,7 +58,7 @@ let
     };
   };
   cliOptions = concatStringsSep " " (mapAttrsToList (k: v: "--${k} ${v}") (filterAttrs (k: v: v != null && v != "") (cliOptionsCommon //
-               cliOptionsPerConfig."${cfg.configType}" //
+               cliOptionsPerConfig.${cfg.configType} //
                s3CommonOptions //
                optionalAttrs cfg.s3Backup { s3backup = "true"; } //
                optionalAttrs cfg.fileSystemBackup { filesystembackup = "true"; }
@@ -252,7 +252,7 @@ in
         example = ["host1:2181" "host2:2181"];
       };
       zkConfigExhibitorPath = mkOption {
-        type = types.string;
+        type = types.str;
         description = ''
           If the ZooKeeper shared config is also running Exhibitor, the URI path for the REST call
         '';
@@ -410,8 +410,7 @@ in
         sed -i 's/'"$replace_what"'/'"$replace_with"'/g' ${cfg.baseDir}/zookeeper/bin/zk*.sh
       '';
     };
-    users.users = singleton {
-      name = "zookeeper";
+    users.users.zookeeper = {
       uid = config.ids.uids.zookeeper;
       description = "Zookeeper daemon user";
       home = cfg.baseDir;
diff --git a/nixos/modules/services/misc/felix.nix b/nixos/modules/services/misc/felix.nix
index 1c5ece868258..188e45abc58b 100644
--- a/nixos/modules/services/misc/felix.nix
+++ b/nixos/modules/services/misc/felix.nix
@@ -47,14 +47,10 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-    users.groups = singleton
-      { name = "osgi";
-        gid = config.ids.gids.osgi;
-      };
+    users.groups.osgi.gid = config.ids.gids.osgi;
 
-    users.users = singleton
-      { name = "osgi";
-        uid = config.ids.uids.osgi;
+    users.users.osgi =
+      { uid = config.ids.uids.osgi;
         description = "OSGi user";
         home = "/homeless-shelter";
       };
diff --git a/nixos/modules/services/misc/folding-at-home.nix b/nixos/modules/services/misc/folding-at-home.nix
index 122c89ce0680..fd2ea3948f64 100644
--- a/nixos/modules/services/misc/folding-at-home.nix
+++ b/nixos/modules/services/misc/folding-at-home.nix
@@ -42,9 +42,8 @@ in {
 
   config = mkIf cfg.enable {
 
-    users.users = singleton
-      { name = fahUser;
-        uid = config.ids.uids.foldingathome;
+    users.users.${fahUser} =
+      { uid = config.ids.uids.foldingathome;
         description = "Folding@Home user";
         home = stateDir;
       };
diff --git a/nixos/modules/services/misc/fstrim.nix b/nixos/modules/services/misc/fstrim.nix
index 15f283f093c0..b8841a7fe74c 100644
--- a/nixos/modules/services/misc/fstrim.nix
+++ b/nixos/modules/services/misc/fstrim.nix
@@ -14,7 +14,7 @@ in {
       enable = mkEnableOption "periodic SSD TRIM of mounted partitions in background";
 
       interval = mkOption {
-        type = types.string;
+        type = types.str;
         default = "weekly";
         description = ''
           How often we run fstrim. For most desktop and server systems
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index be4d38719785..38910a5a005d 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -8,6 +8,7 @@ let
   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}
@@ -15,11 +16,15 @@ let
 
     [database]
     DB_TYPE = ${cfg.database.type}
-    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#
-    PATH = ${cfg.database.path}
+    ${optionalString (usePostgresql || useMysql) ''
+      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#
+    ''}
+    ${optionalString useSqlite ''
+      PATH = ${cfg.database.path}
+    ''}
     ${optionalString usePostgresql ''
       SSL_MODE = disable
     ''}
@@ -33,6 +38,7 @@ let
     HTTP_PORT = ${toString cfg.httpPort}
     ROOT_URL = ${cfg.rootUrl}
     STATIC_ROOT_PATH = ${cfg.staticRootPath}
+    LFS_JWT_SECRET = #jwtsecret#
 
     [session]
     COOKIE_NAME = session
@@ -49,6 +55,11 @@ let
     [service]
     DISABLE_REGISTRATION = ${boolToString cfg.disableRegistration}
 
+    ${optionalString (cfg.mailerPasswordFile != null) ''
+      [mailer]
+      PASSWD = #mailerpass#
+    ''}
+
     ${cfg.extraConfig}
   '';
 in
@@ -154,7 +165,8 @@ in
 
         socket = mkOption {
           type = types.nullOr types.path;
-          default = null;
+          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.";
         };
@@ -168,10 +180,7 @@ in
         createDatabase = mkOption {
           type = types.bool;
           default = true;
-          description = ''
-            Whether to create a local postgresql database automatically.
-            This only applies if database type "postgres" is selected.
-          '';
+          description = "Whether to create a local database automatically.";
         };
       };
 
@@ -251,6 +260,13 @@ in
         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.
@@ -272,7 +288,48 @@ in
   };
 
   config = mkIf cfg.enable {
-    services.postgresql.enable = mkIf usePostgresql (mkDefault true);
+    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.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";
@@ -283,28 +340,36 @@ in
       preStart = let
         runConfig = "${cfg.stateDir}/custom/conf/app.ini";
         secretKey = "${cfg.stateDir}/custom/conf/secret_key";
+        jwtSecret = "${cfg.stateDir}/custom/conf/jwt_secret";
       in ''
-        # Make sure that the stateDir exists, as well as the conf dir in there
-        mkdir -p ${cfg.stateDir}/conf
-
         # 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}
+              ${gitea.bin}/bin/gitea generate secret SECRET_KEY > ${secretKey}
+          fi
+
+          if [ ! -e ${jwtSecret} ]; then
+              ${gitea.bin}/bin/gitea generate secret LFS_JWT_SECRET > ${jwtSecret}
           fi
 
-          KEY=$(head -n1 ${secretKey})
-          DBPASS=$(head -n1 ${cfg.database.passwordFile})
+          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}
+          chmod 640 ${runConfig} ${secretKey} ${jwtSecret}
         ''}
 
-        mkdir -p ${cfg.repositoryRoot}
         # update all hooks' binary paths
         HOOKS=$(find ${cfg.repositoryRoot} -mindepth 4 -maxdepth 6 -type f -wholename "*git/hooks/*")
         if [ "$HOOKS" ]
@@ -314,45 +379,41 @@ in
           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
-        # If we have a folder or symlink with gitea locales, remove it
-        if [ -e ${cfg.stateDir}/conf/locale ]
-        then
-          rm -r ${cfg.stateDir}/conf/locale
-        fi
-        # And symlink the current gitea locales in place
-        ln -s ${gitea.out}/locale ${cfg.stateDir}/conf/locale
+
         # 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}/bin/gitea,g' ${cfg.stateDir}/.ssh/authorized_keys
         fi
-      '' + optionalString (usePostgresql && cfg.database.createDatabase) ''
-        if ! test -e "${cfg.stateDir}/db-created"; then
-          echo "CREATE ROLE ${cfg.database.user}
-                  WITH ENCRYPTED PASSWORD '$(head -n1 ${cfg.database.passwordFile})'
-                  NOCREATEDB NOCREATEROLE LOGIN"   |
-            ${pkgs.sudo}/bin/sudo -u ${pg.superUser} ${pg.package}/bin/psql
-          ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \
-            ${pg.package}/bin/createdb             \
-            --owner=${cfg.database.user}           \
-            --encoding=UTF8                        \
-            --lc-collate=C                         \
-            --lc-ctype=C                           \
-            --template=template0                   \
-            ${cfg.database.name}
-          touch "${cfg.stateDir}/db-created"
-        fi
-      '' + ''
-        chown ${cfg.user} -R ${cfg.stateDir}
       '';
 
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
+        Group = "gitea";
         WorkingDirectory = cfg.stateDir;
-        PermissionsStartOnly = true;
         ExecStart = "${gitea.bin}/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 = {
@@ -362,15 +423,18 @@ in
       };
     };
 
-    users = mkIf (cfg.user == "gitea") {
-      users.gitea = {
+    users.users = mkIf (cfg.user == "gitea") {
+      gitea = {
         description = "Gitea Service";
         home = cfg.stateDir;
-        createHome = true;
         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.'';
@@ -409,4 +473,5 @@ in
       timerConfig.OnCalendar = cfg.dump.interval;
     };
   };
+  meta.maintainers = with lib.maintainers; [ srhb ];
 }
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 71277b48ecd9..aa9589853797 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -1,6 +1,4 @@
-{ config, lib, pkgs, ... }:
-
-# TODO: support non-postgresql
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
@@ -9,23 +7,29 @@ let
 
   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;
-  pgSuperUser = config.services.postgresql.superUser;
 
   databaseConfig = {
     production = {
       adapter = "postgresql";
       database = cfg.databaseName;
       host = cfg.databaseHost;
-      password = cfg.databasePassword;
       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"
@@ -52,7 +56,7 @@ let
     gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}";
     http_settings.self_signed_cert = false;
     repos_path = "${cfg.statePath}/repositories";
-    secret_file = "${cfg.statePath}/config/gitlab_shell_secret";
+    secret_file = "${cfg.statePath}/gitlab_shell_secret";
     log_file = "${cfg.statePath}/log/gitlab-shell.log";
     custom_hooks_dir = "${cfg.statePath}/custom_hooks";
     redis = {
@@ -66,13 +70,6 @@ let
 
   redisConfig.production.url = "redis://localhost:6379/";
 
-  secretsConfig.production = {
-    secret_key_base = cfg.secrets.secret;
-    otp_key_base = cfg.secrets.otp;
-    db_key_base = cfg.secrets.db;
-    openid_connect_signing_key = cfg.secrets.jws;
-  };
-
   gitlabConfig = {
     # These are the default settings from config/gitlab.example.yml
     production = flip recursiveUpdate cfg.extraConfig {
@@ -109,7 +106,7 @@ let
       gitlab_shell = {
         path = "${cfg.packages.gitlab-shell}";
         hooks_path = "${cfg.statePath}/shell/hooks";
-        secret_file = "${cfg.statePath}/config/gitlab_shell_secret";
+        secret_file = "${cfg.statePath}/gitlab_shell_secret";
         upload_pack = true;
         receive_pack = true;
       };
@@ -132,44 +129,39 @@ let
     HOME = "${cfg.statePath}/home";
     UNICORN_PATH = "${cfg.statePath}/";
     GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/";
-    GITLAB_STATE_PATH = cfg.statePath;
-    GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
     SCHEMA = "${cfg.statePath}/db/schema.rb";
+    GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
     GITLAB_LOG_PATH = "${cfg.statePath}/log";
-    GITLAB_SHELL_PATH = "${cfg.packages.gitlab-shell}";
-    GITLAB_SHELL_CONFIG_PATH = "${cfg.statePath}/shell/config.yml";
-    GITLAB_SHELL_SECRET_PATH = "${cfg.statePath}/config/gitlab_shell_secret";
-    GITLAB_SHELL_HOOKS_PATH = "${cfg.statePath}/shell/hooks";
     GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig);
     prometheus_multiproc_dir = "/run/gitlab";
     RAILS_ENV = "production";
   };
 
-  gitlab-rake = pkgs.stdenv.mkDerivation rec {
+  gitlab-rake = pkgs.stdenv.mkDerivation {
     name = "gitlab-rake";
     buildInputs = [ pkgs.makeWrapper ];
     dontBuild = true;
-    unpackPhase = ":";
+    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 config.services.postgresql.package pkgs.coreutils pkgs.procps ]}:$PATH' \
+          --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 rec {
+  gitlab-rails = pkgs.stdenv.mkDerivation {
     name = "gitlab-rails";
     buildInputs = [ pkgs.makeWrapper ];
     dontBuild = true;
-    unpackPhase = ":";
+    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 config.services.postgresql.package pkgs.coreutils pkgs.procps ]}:$PATH' \
+          --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'
      '';
   };
@@ -185,10 +177,11 @@ let
         address: "${cfg.smtp.address}",
         port: ${toString cfg.smtp.port},
         ${optionalString (cfg.smtp.username != null) ''user_name: "${cfg.smtp.username}",''}
-        ${optionalString (cfg.smtp.password != null) ''password: "${cfg.smtp.password}",''}
+        ${optionalString (cfg.smtp.passwordFile != null) ''password: "@smtpPassword@",''}
         domain: "${cfg.smtp.domain}",
         ${optionalString (cfg.smtp.authentication != null) "authentication: :${cfg.smtp.authentication},"}
         enable_starttls_auto: ${toString cfg.smtp.enableStartTLSAuto},
+        ca_file: "/etc/ssl/certs/ca-certificates.crt",
         openssl_verify_mode: '${cfg.smtp.opensslVerifyMode}'
       }
     end
@@ -196,6 +189,11 @@ let
 
 in {
 
+  imports = [
+    (mkRenamedOptionModule [ "services" "gitlab" "stateDir" ] [ "services" "gitlab" "statePath" ])
+    (mkRemovedOptionModule [ "services" "gitlab" "satelliteDir" ] "")
+  ];
+
   options = {
     services.gitlab = {
       enable = mkOption {
@@ -238,7 +236,15 @@ in {
       statePath = mkOption {
         type = types.str;
         default = "/var/gitlab/state";
-        description = "Gitlab state directory, logs are stored here.";
+        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 {
@@ -249,13 +255,33 @@ in {
 
       databaseHost = mkOption {
         type = types.str;
-        default = "127.0.0.1";
-        description = "Gitlab database hostname.";
+        default = "";
+        description = ''
+          Gitlab database hostname. An empty string means <quote>use
+          local unix socket connection</quote>.
+        '';
       };
 
-      databasePassword = mkOption {
-        type = types.str;
-        description = "Gitlab database user password.";
+      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 {
@@ -343,10 +369,15 @@ in {
         '';
       };
 
-      initialRootPassword = mkOption {
-        type = types.str;
+      initialRootPasswordFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
         description = ''
-          Initial password of the root account if this is a new install.
+          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.
         '';
       };
 
@@ -370,15 +401,20 @@ in {
         };
 
         username = mkOption {
-          type = types.nullOr types.str;
+          type = with types; nullOr str;
           default = null;
           description = "Username of the SMTP server for Gitlab.";
         };
 
-        password = mkOption {
-          type = types.nullOr types.str;
+        passwordFile = mkOption {
+          type = types.nullOr types.path;
           default = null;
-          description = "Password of the SMTP server for Gitlab.";
+          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 {
@@ -388,7 +424,7 @@ in {
         };
 
         authentication = mkOption {
-          type = types.nullOr types.str;
+          type = with types; nullOr str;
           default = null;
           description = "Authentitcation type to use, see http://api.rubyonrails.org/classes/ActionMailer/Base.html";
         };
@@ -406,68 +442,125 @@ in {
         };
       };
 
-      secrets.secret = mkOption {
-        type = types.str;
+      secrets.secretFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
         description = ''
-          The secret is used to encrypt variables in the DB. If
-          you change or lose this key you will be unable to access variables
-          stored in database.
+          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.db = mkOption {
-        type = types.str;
+      secrets.dbFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
         description = ''
-          The secret is used to encrypt variables in the DB. If
-          you change or lose this key you will be unable to access variables
-          stored in database.
+          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.otp = mkOption {
-        type = types.str;
+      secrets.otpFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
         description = ''
-          The secret is 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.
+          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.jws = mkOption {
-        type = types.str;
+      secrets.jwsFile = mkOption {
+        type = with types; nullOr path;
+        default = null;
         description = ''
-          The secret is used to encrypt session keys. If you change or lose
-          this key, users will be disconnected.
+          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 = {
-          gitlab = {
-            default_projects_features = {
-              builds = false;
+        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 merged into config/gitlab.yml as nix
-          attribute set.
+          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.
         '';
       };
     };
@@ -475,49 +568,114 @@ in {
 
   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.enable = mkDefault true;
+    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 = [
-      { name = cfg.user;
-        group = cfg.group;
+    users.users.${cfg.user} =
+      { group = cfg.group;
         home = "${cfg.statePath}/home";
         shell = "${pkgs.bash}/bin/bash";
         uid = config.ids.uids.gitlab;
-      }
-    ];
+      };
 
-    users.groups = [
-      { name = cfg.group;
-        gid = config.ids.gids.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 = {
@@ -525,12 +683,16 @@ in {
       wantedBy = [ "multi-user.target" ];
       environment = gitlabEnv;
       path = with pkgs; [
-        config.services.postgresql.package
+        postgresqlPackage
         gitAndTools.git
         ruby
         openssh
         nodejs
         gnupg
+
+        # Needed for GitLab project imports
+        gnutar
+        gzip
       ];
       serviceConfig = {
         Type = "simple";
@@ -570,6 +732,7 @@ in {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [
+        exiftool
         gitAndTools.git
         gnutar
         gzip
@@ -577,7 +740,6 @@ in {
         gitlab-workhorse
       ];
       serviceConfig = {
-        PermissionsStartOnly = true; # preStart must be run as root
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
@@ -601,95 +763,109 @@ in {
       wantedBy = [ "multi-user.target" ];
       environment = gitlabEnv;
       path = with pkgs; [
-        config.services.postgresql.package
+        postgresqlPackage
         gitAndTools.git
         openssh
         nodejs
         procps
         gnupg
       ];
-      preStart = ''
-        cp -rf ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
-        rm -rf ${cfg.statePath}/config
-        mkdir ${cfg.statePath}/config
-        if [ -e ${cfg.statePath}/lib ]; then
-          rm ${cfg.statePath}/lib
-        fi
-
-        ln -sf ${cfg.packages.gitlab}/share/gitlab/lib ${cfg.statePath}/lib
-        [ -L /run/gitlab/config ] || ln -sf ${cfg.statePath}/config /run/gitlab/config
-        [ -L /run/gitlab/log ] || ln -sf ${cfg.statePath}/log /run/gitlab/log
-        [ -L /run/gitlab/tmp ] || ln -sf ${cfg.statePath}/tmp /run/gitlab/tmp
-        [ -L /run/gitlab/uploads ] || ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads
-        cp ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
-        cp -rf ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
-        ln -sf ${extraGitlabRb} ${cfg.statePath}/config/initializers/extra-gitlab.rb
-        ${optionalString cfg.smtp.enable ''
-          ln -sf ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
-        ''}
-        ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/config/gitlab_shell_secret
-
-        # JSON is a subset of YAML
-        ln -sf ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)} ${cfg.statePath}/config/gitlab.yml
-        ln -sf ${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)} ${cfg.statePath}/config/database.yml
-        ln -sf ${pkgs.writeText "secrets.yml" (builtins.toJSON secretsConfig)} ${cfg.statePath}/config/secrets.yml
-        ln -sf ${./defaultUnicornConfig.rb} ${cfg.statePath}/config/unicorn.rb
-
-        # Install the shell required to push repositories
-        ln -sf ${pkgs.writeText "config.yml" (builtins.toJSON gitlabShellConfig)} /run/gitlab/shell-config.yml
-        [ -L ${cfg.statePath}/shell/hooks ] ||  ln -sf ${cfg.packages.gitlab-shell}/hooks ${cfg.statePath}/shell/hooks
-        ${cfg.packages.gitlab-shell}/bin/install
-
-        chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/
-        chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/
-        chown -R ${cfg.user}:${cfg.group} /run/gitlab
-
-        if ! test -e "${cfg.statePath}/db-created"; then
-          if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then
-            ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "CREATE ROLE ${cfg.databaseUsername} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.databasePassword}'"
-            ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} ${config.services.postgresql.package}/bin/createdb --owner ${cfg.databaseUsername} ${cfg.databaseName}
-
-            # enable required pg_trgm extension for gitlab
-            ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql ${cfg.databaseName} -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"
-          fi
-
-          ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:schema:load
-
-          touch "${cfg.statePath}/db-created"
-        fi
-
-        # Always do the db migrations just to be sure the database is up-to-date
-        ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:migrate
-
-        if ! test -e "${cfg.statePath}/db-seeded"; then
-          ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${gitlab-rake}/bin/gitlab-rake db:seed_fu \
-            GITLAB_ROOT_PASSWORD='${cfg.initialRootPassword}' GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}'
-          touch "${cfg.statePath}/db-seeded"
-        fi
-
-        # The gitlab:shell:create_hooks task seems broken for fixing links
-        # so we instead delete all the hooks and create them anew
-        rm -f ${cfg.statePath}/repositories/**/*.git/hooks
-        ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake gitlab:shell:create_hooks
-
-        ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${pkgs.git}/bin/git config --global core.autocrlf "input"
-
-        # Change permissions in the last step because some of the
-        # intermediary scripts like to create directories as root.
-        chmod -R u+rwX,go-rwx+X ${gitlabEnv.HOME}
-        chmod -R ug+rwX,o-rwx ${cfg.statePath}/repositories
-        chmod -R ug-s ${cfg.statePath}/repositories
-        find ${cfg.statePath}/repositories -type d -print0 | xargs -0 chmod g+s
-      '';
 
       serviceConfig = {
-        PermissionsStartOnly = true; # preStart must be run as root
         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";
       };
 
diff --git a/nixos/modules/services/misc/gitlab.xml b/nixos/modules/services/misc/gitlab.xml
index ab99d7bd3a60..b6171a9a194c 100644
--- a/nixos/modules/services/misc/gitlab.xml
+++ b/nixos/modules/services/misc/gitlab.xml
@@ -54,8 +54,8 @@
 <programlisting>
 services.gitlab = {
   <link linkend="opt-services.gitlab.enable">enable</link> = true;
-  <link linkend="opt-services.gitlab.databasePassword">databasePassword</link> = "eXaMpl3";
-  <link linkend="opt-services.gitlab.initialRootPassword">initialRootPassword</link> = "UseNixOS!";
+  <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;
@@ -67,38 +67,10 @@ services.gitlab = {
     <link linkend="opt-services.gitlab.smtp.port">port</link> = 25;
   };
   secrets = {
-    <link linkend="opt-services.gitlab.secrets.db">db</link> = "uPgq1gtwwHiatiuE0YHqbGa5lEIXH7fMsvuTNgdzJi8P0Dg12gibTzBQbq5LT7PNzcc3BP9P1snHVnduqtGF43PgrQtU7XL93ts6gqe9CBNhjtaqUwutQUDkygP5NrV6";
-    <link linkend="opt-services.gitlab.secrets.secret">secret</link> = "devzJ0Tz0POiDBlrpWmcsjjrLaltyiAdS8TtgT9YNBOoUcDsfppiY3IXZjMVtKgXrFImIennFGOpPN8IkP8ATXpRgDD5rxVnKuTTwYQaci2NtaV1XxOQGjdIE50VGsR3";
-    <link linkend="opt-services.gitlab.secrets.otp">otp</link> = "e1GATJVuS2sUh7jxiPzZPre4qtzGGaS22FR50Xs1TerRVdgI3CBVUi5XYtQ38W4xFeS4mDqi5cQjExE838iViSzCdcG19XSL6qNsfokQP9JugwiftmhmCadtsnHErBMI";
-    <link linkend="opt-services.gitlab.secrets.jws">jws</link> = ''
-      -----BEGIN RSA PRIVATE KEY-----
-      MIIEpAIBAAKCAQEArrtx4oHKwXoqUbMNqnHgAklnnuDon3XG5LJB35yPsXKv/8GK
-      ke92wkI+s1Xkvsp8tg9BIY/7c6YK4SR07EWL+dB5qwctsWR2Q8z+/BKmTx9D99pm
-      hnsjuNIXTF7BXrx3RX6BxZpH5Vzzh9nCwWKT/JCFqtwH7afNGGL7aMf+hdaiUg/Q
-      SD05yRObioiO4iXDolsJOhrnbZvlzVHl1ZYxFJv0H6/Snc0BBA9Fl/3uj6ANpbjP
-      eXF1SnJCqT87bj46r5NdVauzaRxAsIfqHroHK4UZ98X5LjGQFGvSqTvyjPBS4I1i
-      s7VJU28ObuutHxIxSlH0ibn4HZqWmKWlTS652wIDAQABAoIBAGtPcUTTw2sJlR3x
-      4k2wfAvLexkHNbZhBdKEa5JiO5mWPuLKwUiZEY2CU7Gd6csG3oqNWcm7/IjtC7dz
-      xV8p4yp8T4yq7vQIJ93B80NqTLtBD2QTvG2RCMJEPMzJUObWxkVmyVpLQyZo7KOd
-      KE/OM+aj94OUeEYLjRkSCScz1Gvq/qFG/nAy7KPCmN9JDHuhX26WHo2Rr1OnPNT/
-      7diph0bB9F3b8gjjNTqXDrpdAqVOgR/PsjEBz6DMY+bdyMIn87q2yfmMexxRofN6
-      LulpzSaa6Yup8N8H6PzVO6KAkQuf1aQRj0sMwGk1IZEnj6I0KbuHIZkw21Nc6sf2
-      ESFySDECgYEA1PnCNn5tmLnwe62Ttmrzl20zIS3Me1gUVJ1NTfr6+ai0I9iMYU21
-      5czuAjJPm9JKQF2vY8UAaCj2ZoObtHa/anb3xsCd8NXoM3iJq5JDoXI1ldz3Y+ad
-      U/bZUg1DLRvAniTuXmw9iOTwTwPxlDIGq5k+wG2Xmi1lk7zH8ezr9BMCgYEA0gfk
-      EhgcmPH8Z5cU3YYwOdt6HSJOM0OyN4k/5gnkv+HYVoJTj02gkrJmLr+mi1ugKj46
-      7huYO9TVnrKP21tmbaSv1dp5hS3letVRIxSloEtVGXmmdvJvBRzDWos+G+KcvADi
-      fFCz6w8v9NmO40CB7y/3SxTmSiSxDQeoi9LhDBkCgYEAsPgMWm25sfOnkY2NNUIv
-      wT8bAlHlHQT2d8zx5H9NttBpR3P0ShJhuF8N0sNthSQ7ULrIN5YGHYcUH+DyLAWU
-      TuomP3/kfa+xL7vUYb269tdJEYs4AkoppxBySoz8qenqpz422D0G8M6TpIS5Y5Qi
-      GMrQ6uLl21YnlpiCaFOfSQMCgYEAmZxj1kgEQmhZrnn1LL/D7czz1vMMNrpAUhXz
-      wg9iWmSXkU3oR1sDIceQrIhHCo2M6thwyU0tXjUft93pEQocM/zLDaGoVxtmRxxV
-      J08mg8IVD3jFoyFUyWxsBIDqgAKRl38eJsXvkO+ep3mm49Z+Ma3nM+apN3j2dQ0w
-      3HLzXaECgYBFLMEAboVFwi5+MZjGvqtpg2PVTisfuJy2eYnPwHs+AXUgi/xRNFjI
-      YHEa7UBPb5TEPSzWImQpETi2P5ywcUYL1EbN/nqPWmjFnat8wVmJtV4sUpJhubF4
-      Vqm9LxIWc1uQ1q1HDCejRIxIN3aSH+wgRS3Kcj8kCTIoXd1aERb04g==
-      -----END RSA PRIVATE KEY-----
-    '';
+    <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 = {
@@ -113,12 +85,16 @@ services.gitlab = {
   </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</literal> to generate a new secret. 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.
+   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>
@@ -138,13 +114,13 @@ services.gitlab = {
 
   <para>
    For example, to backup a Gitlab instance:
-<programlisting>
-$ sudo -u git -H gitlab-rake gitlab:backup:create
-</programlisting>
+<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:
-<programlisting>
-$ sudo -u git -H gitlab-rake -T
-</programlisting>
+<screen>
+<prompt>$ </prompt>sudo -u git -H gitlab-rake -T
+</screen>
   </para>
  </section>
 </chapter>
diff --git a/nixos/modules/services/misc/gitolite.nix b/nixos/modules/services/misc/gitolite.nix
index c7f2a168f8ab..cc69f81bbcc4 100644
--- a/nixos/modules/services/misc/gitolite.nix
+++ b/nixos/modules/services/misc/gitolite.nix
@@ -143,21 +143,37 @@ in
     users.users.${cfg.user} = {
       description     = "Gitolite user";
       home            = cfg.dataDir;
-      createHome      = true;
       uid             = config.ids.uids.gitolite;
       group           = cfg.group;
       useDefaultShell = true;
     };
-    users.groups."${cfg.group}".gid = config.ids.gids.gitolite;
+    users.groups.${cfg.group}.gid = config.ids.gids.gitolite;
 
-    systemd.services."gitolite-init" = {
+    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;
 
-      serviceConfig.User = "${cfg.user}";
-      serviceConfig.Type = "oneshot";
-      serviceConfig.RemainAfterExit = true;
+      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 =
@@ -187,11 +203,6 @@ in
           '';
       in
         ''
-          cd ${cfg.dataDir}
-          mkdir -p .gitolite/logs
-
-          GITOLITE_RC=.gitolite.rc
-          GITOLITE_RC_DEFAULT=${rcDir}/gitolite.rc.default
           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/ ]] )
diff --git a/nixos/modules/services/misc/gollum.nix b/nixos/modules/services/misc/gollum.nix
index d1823bc6d4df..f4a9c72b1545 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -71,31 +71,29 @@ in
       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 = let
-          userName = config.users.users.gollum.name;
-          groupName = config.users.groups.gollum.name;
-        in ''
-        # All of this is safe to be run on an existing repo
-        mkdir -p ${cfg.stateDir}
+      preStart = ''
+        # This is safe to be run on an existing repo
         git init ${cfg.stateDir}
-        chmod 755 ${cfg.stateDir}
-        chown -R ${userName}:${groupName} ${cfg.stateDir}
       '';
 
       serviceConfig = {
         User = config.users.users.gollum.name;
         Group = config.users.groups.gollum.name;
-        PermissionsStartOnly = true;
         ExecStart = ''
           ${pkgs.gollum}/bin/gollum \
             --port ${toString cfg.port} \
diff --git a/nixos/modules/services/misc/gpsd.nix b/nixos/modules/services/misc/gpsd.nix
index 3bfcb636a3c6..f954249942a8 100644
--- a/nixos/modules/services/misc/gpsd.nix
+++ b/nixos/modules/services/misc/gpsd.nix
@@ -86,17 +86,13 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = singleton
-      { name = "gpsd";
-        inherit uid;
+    users.users.gpsd =
+      { inherit uid;
         description = "gpsd daemon user";
         home = "/var/empty";
       };
 
-    users.groups = singleton
-      { name = "gpsd";
-        inherit gid;
-      };
+    users.groups.gpsd = { inherit gid; };
 
     systemd.services.gpsd = {
       description = "GPSD daemon";
diff --git a/nixos/modules/services/misc/greenclip.nix b/nixos/modules/services/misc/greenclip.nix
new file mode 100644
index 000000000000..9152a782d7f0
--- /dev/null
+++ b/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/nixos/modules/services/misc/headphones.nix b/nixos/modules/services/misc/headphones.nix
index 4a77045be28e..3ee0a4458bd0 100644
--- a/nixos/modules/services/misc/headphones.nix
+++ b/nixos/modules/services/misc/headphones.nix
@@ -59,19 +59,19 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = optionalAttrs (cfg.user == name) (singleton {
-      name = name;
-      uid = config.ids.uids.headphones;
-      group = cfg.group;
-      description = "headphones user";
-      home = cfg.dataDir;
-      createHome = true;
-    });
+    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) (singleton {
-      name = name;
-      gid = config.ids.gids.headphones;
-    });
+    users.groups = optionalAttrs (cfg.group == name) {
+      ${name}.gid = config.ids.gids.headphones;
+    };
 
     systemd.services.headphones = {
         description = "Headphones Server";
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix
index 7f8d31bcf0b8..d63f38e93b8e 100644
--- a/nixos/modules/services/misc/home-assistant.nix
+++ b/nixos/modules/services/misc/home-assistant.nix
@@ -11,6 +11,9 @@ let
     (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 secrets, that are encoded as custom yaml objects,
+    # https://www.home-assistant.io/docs/configuration/secrets/
+    sed -i -e "s/'\!secret \(.*\)'/\!secret \1/" $out
   '';
 
   lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json"
@@ -21,32 +24,23 @@ let
 
   availableComponents = cfg.package.availableComponents;
 
-  # Given component "parentConfig.platform", returns whether config.parentConfig
-  # is a list containing a set with set.platform == "platform".
+  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 sensor.luftdaten is used as follows:
+  # For example, the component mqtt.sensor is used as follows:
   # config.sensor = [ {
-  #   platform = "luftdaten";
+  #   platform = "mqtt";
   #   ...
   # } ];
-  #
-  # Beginning with 0.87 Home Assistant is migrating their components to the
-  # scheme "platform.subComponent", e.g. "hue.light" instead of "light.hue".
-  # See https://developers.home-assistant.io/blog/2019/02/19/the-great-migration.html.
-  # Hence, we also check whether we find an entry in the config when interpreting
-  # the first part of the path as the component.
-  useComponentPlatform = component:
-    let
-      path = splitString "." component;
-      # old: platform is the last part of path
-      parentConfig = attrByPath (init path) null cfg.config;
-      platform = last path;
-      # new: platform is the first part of the path
-      parentConfig' = attrByPath (tail path) null cfg.config;
-      platform' = head path;
-    in
-      (isList parentConfig && any (item: item.platform or null == platform) parentConfig)
-      || (isList parentConfig' && any (item: item.platform or null == platform') parentConfig');
+  useComponentPlatform = component: elem component (usedPlatforms cfg.config);
 
   # Returns whether component is used in config
   useComponent = component:
@@ -107,6 +101,10 @@ in {
         {
           homeassistant = {
             name = "Home";
+            latitude = "!secret latitude";
+            longitude = "!secret longitude";
+            elevation = "!secret elevation";
+            unit_system = "metric";
             time_zone = "UTC";
           };
           frontend = { };
@@ -117,6 +115,8 @@ in {
       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.
       '';
     };
 
@@ -233,6 +233,7 @@ in {
         KillSignal = "SIGINT";
         PrivateTmp = true;
         RemoveIPC = true;
+        AmbientCapabilities = "cap_net_raw,cap_net_admin+eip";
       };
       path = [
         "/run/wrappers" # needed for ping
@@ -250,6 +251,7 @@ in {
       home = cfg.configDir;
       createHome = true;
       group = "hass";
+      extraGroups = [ "dialout" ];
       uid = config.ids.uids.hass;
     };
 
diff --git a/nixos/modules/services/misc/jackett.nix b/nixos/modules/services/misc/jackett.nix
index a07f20e5c24b..f2dc6635df93 100644
--- a/nixos/modules/services/misc/jackett.nix
+++ b/nixos/modules/services/misc/jackett.nix
@@ -34,6 +34,13 @@ in
         default = "jackett";
         description = "Group under which Jackett runs.";
       };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.jackett;
+        defaultText = "pkgs.jackett";
+        description = "Jackett package to use.";
+      };
     };
   };
 
@@ -51,7 +58,7 @@ in
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${pkgs.jackett}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
+        ExecStart = "${cfg.package}/bin/Jackett --NoUpdates --DataFolder '${cfg.dataDir}'";
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix
new file mode 100644
index 000000000000..6ecdfb57dc35
--- /dev/null
+++ b/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/nixos/modules/services/misc/lidarr.nix b/nixos/modules/services/misc/lidarr.nix
index f466402abfc7..8ff1adadcf23 100644
--- a/nixos/modules/services/misc/lidarr.nix
+++ b/nixos/modules/services/misc/lidarr.nix
@@ -9,10 +9,51 @@ 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" ];
@@ -20,22 +61,29 @@ in
 
       serviceConfig = {
         Type = "simple";
-        User = "lidarr";
-        Group = "lidarr";
-        ExecStart = "${pkgs.lidarr}/bin/Lidarr";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/Lidarr -nobrowser -data='${cfg.dataDir}'";
         Restart = "on-failure";
-
-        StateDirectory = "lidarr";
-        StateDirectoryMode = "0770";
       };
     };
 
-    users.users.lidarr = {
-      uid = config.ids.uids.lidarr;
-      home = "/var/lib/lidarr";
-      group = "lidarr";
+    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.lidarr.gid = config.ids.gids.lidarr;
+    users.groups = mkIf (cfg.group == "lidarr") {
+      lidarr = {
+        gid = config.ids.gids.lidarr;
+      };
+    };
   };
 }
diff --git a/nixos/modules/services/misc/logkeys.nix b/nixos/modules/services/misc/logkeys.nix
index ad13d9eaa674..0082db63a06a 100644
--- a/nixos/modules/services/misc/logkeys.nix
+++ b/nixos/modules/services/misc/logkeys.nix
@@ -11,7 +11,7 @@ in {
     device = mkOption {
       description = "Use the given device as keyboard input event device instead of /dev/input/eventX default.";
       default = null;
-      type = types.nullOr types.string;
+      type = types.nullOr types.str;
       example = "/dev/input/event15";
     };
   };
diff --git a/nixos/modules/services/misc/mame.nix b/nixos/modules/services/misc/mame.nix
new file mode 100644
index 000000000000..c5d5e9e48371
--- /dev/null
+++ b/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/nixos/modules/services/misc/mantisbt.nix b/nixos/modules/services/misc/mantisbt.nix
deleted file mode 100644
index 7e3474feb672..000000000000
--- a/nixos/modules/services/misc/mantisbt.nix
+++ /dev/null
@@ -1,68 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-let
-  cfg = config.services.mantisbt;
-
-  freshInstall = cfg.extraConfig == "";
-
-  # combined code+config directory
-  mantisbt = let
-    config_inc = pkgs.writeText "config_inc.php" ("<?php\n" + cfg.extraConfig);
-    src = pkgs.fetchurl {
-      url = "mirror://sourceforge/mantisbt/${name}.tar.gz";
-      sha256 = "1pl6xn793p3mxc6ibpr2bhg85vkdlcf57yk7pfc399g47l8x4508";
-    };
-    name = "mantisbt-1.2.19";
-    in
-      # We have to copy every time; otherwise config won't be found.
-      pkgs.runCommand name
-        { preferLocalBuild = true; allowSubstitutes = false; }
-        (''
-          mkdir -p "$out"
-          cd "$out"
-          tar -xf '${src}' --strip-components=1
-          ln -s '${config_inc}' config_inc.php
-        ''
-        + lib.optionalString (!freshInstall) "rm -r admin/"
-        );
-in
-{
-  options.services.mantisbt = {
-    enable = mkOption {
-      type = types.bool;
-      default = false;
-      description = ''
-        Enable the mantisbt web service.
-        This switches on httpd with PHP and database.
-      '';
-    };
-    urlPrefix = mkOption {
-      type = types.string;
-      default = "/mantisbt";
-      description = "The URL prefix under which the mantisbt service appears.";
-    };
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      description = ''
-        The contents of config_inc.php, without leading &lt;?php.
-        If left empty, the admin directory will be accessible.
-      '';
-    };
-  };
-
-
-  config = mkIf cfg.enable {
-    services.mysql.enable = true;
-    services.httpd.enable = true;
-    services.httpd.enablePHP = true;
-    # The httpd sub-service showing mantisbt.
-    services.httpd.extraSubservices = [ { function = { ... }: {
-      extraConfig =
-        ''
-          Alias ${cfg.urlPrefix} "${mantisbt}"
-        '';
-    };}];
-  };
-}
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 00c8e7408030..750f4a292fb4 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -79,7 +79,11 @@ 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}
-trusted_third_party_id_servers: ${builtins.toJSON cfg.trusted_third_party_id_servers}
+
+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}"
@@ -102,6 +106,7 @@ perspectives:
     '') cfg.servers)}
     }
   }
+redaction_retention_period: ${toString cfg.redaction_retention_period}
 app_service_config_files: ${builtins.toJSON cfg.app_service_config_files}
 
 ${cfg.extraConfig}
@@ -374,7 +379,7 @@ in {
             user = cfg.database_user;
             database = cfg.database_name;
           };
-        }."${cfg.database_type}";
+        }.${cfg.database_type};
         description = ''
           Arguments to pass to the engine.
         '';
@@ -402,6 +407,9 @@ in {
           "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
@@ -552,14 +560,18 @@ in {
           accessible to anonymous users.
         '';
       };
-      trusted_third_party_id_servers = mkOption {
-        type = types.listOf types.str;
-        default = [
-          "matrix.org"
-          "vector.im"
-        ];
+      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 = ''
-          The list of identity servers trusted to verify third party identifiers by this server.
+          Delegate SMS sending to this local process (https://localhost:8090)
         '';
       };
       room_invite_state_types = mkOption {
@@ -600,6 +612,13 @@ in {
           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 = "";
@@ -638,57 +657,42 @@ in {
   };
 
   config = mkIf cfg.enable {
-    users.users = [
-      { name = "matrix-synapse";
+    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 = [
-      { name = "matrix-synapse";
-        gid = config.ids.gids.matrix-synapse;
-      } ];
+    users.groups.matrix-synapse = {
+      gid = config.ids.gids.matrix-synapse;
+    };
 
-    services.postgresql.enable = mkIf usePostgresql (mkDefault true);
+    services.postgresql = mkIf (usePostgresql && cfg.create_local_database) {
+      enable = mkDefault true;
+      ensureDatabases = [ cfg.database_name ];
+      ensureUsers = [{
+        name = cfg.database_user;
+        ensurePermissions = { "DATABASE \"${cfg.database_name}\"" = "ALL PRIVILEGES"; };
+      }];
+    };
 
     systemd.services.matrix-synapse = {
       description = "Synapse Matrix homeserver";
-      after = [ "network.target" "postgresql.service" ];
+      after = [ "network.target" ] ++ lib.optional config.services.postgresql.enable "postgresql.service" ;
       wantedBy = [ "multi-user.target" ];
       preStart = ''
         ${cfg.package}/bin/homeserver \
           --config-path ${configFile} \
           --keys-directory ${cfg.dataDir} \
           --generate-keys
-      '' + optionalString (usePostgresql && cfg.create_local_database) ''
-        if ! test -e "${cfg.dataDir}/db-created"; then
-          ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \
-            ${pg.package}/bin/createuser \
-            --login \
-            --no-createdb \
-            --no-createrole \
-            --encrypted \
-            ${cfg.database_user}
-          ${pkgs.sudo}/bin/sudo -u ${pg.superUser} \
-            ${pg.package}/bin/createdb \
-            --owner=${cfg.database_user} \
-            --encoding=UTF8 \
-            --lc-collate=C \
-            --lc-ctype=C \
-            --template=template0 \
-            ${cfg.database_name}
-          touch "${cfg.dataDir}/db-created"
-        fi
       '';
       serviceConfig = {
-        Type = "simple";
+        Type = "notify";
         User = "matrix-synapse";
         Group = "matrix-synapse";
         WorkingDirectory = cfg.dataDir;
-        PermissionsStartOnly = true;
         ExecStart = ''
           ${cfg.package}/bin/homeserver \
             ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
@@ -699,4 +703,12 @@ in {
       };
     };
   };
+
+  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.
+    '')
+  ];
+
 }
diff --git a/nixos/modules/services/misc/mediatomb.nix b/nixos/modules/services/misc/mediatomb.nix
index e8e9c0946d7f..529f584a201e 100644
--- a/nixos/modules/services/misc/mediatomb.nix
+++ b/nixos/modules/services/misc/mediatomb.nix
@@ -163,7 +163,7 @@ in {
       };
 
       serverName = mkOption {
-        type = types.string;
+        type = types.str;
         default = "mediatomb";
         description = ''
           How to identify the server on the network.
@@ -259,26 +259,26 @@ in {
   config = mkIf cfg.enable {
     systemd.services.mediatomb = {
       description = "MediaTomb media Server";
-      after = [ "local-fs.target" "network.target" ];
+      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") (singleton {
-      name = "mediatomb";
-      gid = gid;
-    });
+    users.groups = optionalAttrs (cfg.group == "mediatomb") {
+      mediatomb.gid = gid;
+    };
 
-    users.users = optionalAttrs (cfg.user == "mediatomb") (singleton {
-      name = "mediatomb";
-      isSystemUser = true;
-      group = cfg.group;
-      home = "${cfg.dataDir}";
-      createHome = true;
-      description = "Mediatomb DLNA Server User";
-    });
+    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 ];
diff --git a/nixos/modules/services/misc/metabase.nix b/nixos/modules/services/misc/metabase.nix
new file mode 100644
index 000000000000..e78100a046a2
--- /dev/null
+++ b/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/nixos/modules/services/misc/mwlib.nix b/nixos/modules/services/misc/mwlib.nix
index a8edecff2a1e..6b41b552a86d 100644
--- a/nixos/modules/services/misc/mwlib.nix
+++ b/nixos/modules/services/misc/mwlib.nix
@@ -165,7 +165,7 @@ in
 
   }; # options.services
 
-  config = { 
+  config = {
 
     systemd.services.mwlib-nserve = mkIf cfg.nserve.enable
     {
@@ -191,7 +191,6 @@ in
       description = "mwlib job queue server";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "local-fs.target" ];
 
       preStart = ''
         mkdir -pv '${cfg.qserve.datadir}'
@@ -218,7 +217,7 @@ in
       description = "mwlib worker";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "local-fs.target" ];
+      after = [ "network.target" ];
 
       preStart = ''
         mkdir -pv '${cfg.nslave.cachedir}'
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 665215822af8..17c3582db0f6 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -8,10 +8,13 @@ let
 
   nix = cfg.package.out;
 
-  isNix20 = versionAtLeast (getVersion nix) "2.0pre";
+  nixVersion = getVersion nix;
 
-  makeNixBuildUser = nr:
-    { name = "nixbld${toString nr}";
+  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)
@@ -21,45 +24,37 @@ let
       group = "nixbld";
       extraGroups = [ "nixbld" ];
     };
+  };
 
-  nixbldUsers = map makeNixBuildUser (range 1 cfg.nrBuildUsers);
+  nixbldUsers = listToAttrs (map makeNixBuildUser (range 1 cfg.nrBuildUsers));
 
   nixConf =
-    let
-      # In Nix < 2.0, If we're using sandbox for builds, then provide
-      # /bin/sh in the sandbox as a bind-mount to bash. This means we
-      # also need to include the entire closure of bash. Nix >= 2.0
-      # provides a /bin/sh by default.
-      sh = pkgs.runtimeShell;
-      binshDeps = pkgs.writeReferencesToFile sh;
-    in
-      pkgs.runCommand "nix.conf" { preferLocalBuild = true; extraOptions = cfg.extraOptions; } (''
-        ${optionalString (!isNix20) ''
-          extraPaths=$(for i in $(cat ${binshDeps}); do if test -d $i; then echo $i; fi; done)
-        ''}
+    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
-        ${if isNix20 then "max-jobs" else "build-max-jobs"} = ${toString (cfg.maxJobs)}
-        ${if isNix20 then "cores" else "build-cores"} = ${toString (cfg.buildCores)}
-        ${if isNix20 then "sandbox" else "build-use-sandbox"} = ${if (builtins.isBool cfg.useSandbox) then boolToString cfg.useSandbox else cfg.useSandbox}
-        ${if isNix20 then "extra-sandbox-paths" else "build-sandbox-paths"} = ${toString cfg.sandboxPaths} ${optionalString (!isNix20) "/bin/sh=${sh} $(echo $extraPaths)"}
-        ${if isNix20 then "substituters" else "binary-caches"} = ${toString cfg.binaryCaches}
-        ${if isNix20 then "trusted-substituters" else "trusted-binary-caches"} = ${toString cfg.trustedBinaryCaches}
-        ${if isNix20 then "trusted-public-keys" else "binary-cache-public-keys"} = ${toString cfg.binaryCachePublicKeys}
+        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}
-        ${if isNix20 then ''
-          require-sigs = ${if cfg.requireSignedBinaryCaches then "true" else "false"}
-        '' else ''
-          signed-binary-caches = ${if cfg.requireSignedBinaryCaches then "*" else ""}
-        ''}
+        require-sigs = ${if cfg.requireSignedBinaryCaches then "true" else "false"}
         trusted-users = ${toString cfg.trustedUsers}
         allowed-users = ${toString cfg.allowedUsers}
-        ${optionalString (isNix20 && !cfg.distributedBuilds) ''
+        ${optionalString (!cfg.distributedBuilds) ''
           builders =
         ''}
+        system-features = ${toString cfg.systemFeatures}
+        ${optionalString isNix23 ''
+          sandbox-fallback = false
+        ''}
         $extraOptions
         END
       '' + optionalString cfg.checkConfig (
@@ -68,13 +63,17 @@ let
             '' else ''
               echo "Checking that Nix can read nix.conf..."
               ln -s $out ./nix.conf
-              NIX_CONF_DIR=$PWD ${cfg.package}/bin/nix show-config >/dev/null
+              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
 
@@ -159,8 +158,8 @@ in
         type = types.lines;
         default = "";
         example = ''
-          gc-keep-outputs = true
-          gc-keep-derivations = true
+          keep-outputs = true
+          keep-derivations = true
         '';
         description = "Additional text appended to <filename>nix.conf</filename>.";
       };
@@ -271,10 +270,12 @@ in
 
       binaryCaches = mkOption {
         type = types.listOf types.str;
-        default = [ https://cache.nixos.org/ ];
         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>.
         '';
       };
 
@@ -360,6 +361,14 @@ in
         '';
       };
 
+      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;
@@ -377,6 +386,7 @@ in
   config = {
 
     nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
+    nix.binaryCaches = [ "https://cache.nixos.org/" ];
 
     environment.etc."nix/nix.conf".source = nixConf;
 
@@ -404,8 +414,7 @@ in
 
     systemd.services.nix-daemon =
       { path = [ nix pkgs.utillinux config.programs.ssh.package ]
-          ++ optionals cfg.distributedBuilds [ pkgs.gzip ]
-          ++ optionals (!isNix20) [ pkgs.openssl.bin ];
+          ++ optionals cfg.distributedBuilds [ pkgs.gzip ];
 
         environment = cfg.envVars
           // { CURL_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt"; }
@@ -422,62 +431,50 @@ in
         restartTriggers = [ nixConf ];
       };
 
-    nix.envVars =
-      optionalAttrs (!isNix20) {
-        NIX_CONF_DIR = "/etc/nix";
-
-        # Enable the copy-from-other-stores substituter, which allows
-        # builds to be sped up by copying build results from remote
-        # Nix stores.  To do this, mount the remote file system on a
-        # subdirectory of /run/nix/remote-stores.
-        NIX_OTHER_STORES = "/run/nix/remote-stores/*/nix";
-      }
-
-      // optionalAttrs (cfg.distributedBuilds && !isNix20) {
-        NIX_BUILD_HOOK = "${nix}/libexec/nix/build-remote.pl";
-      };
-
     # Set up the environment variables for running Nix.
     environment.sessionVariables = cfg.envVars //
       { NIX_PATH = cfg.nixPath;
       };
 
-    environment.extraInit = optionalString (!isNix20)
+    environment.extraInit =
       ''
-        # Set up secure multi-user builds: non-root users build through the
-        # Nix daemon.
-        if [ "$USER" != root -o ! -w /nix/var/nix/db ]; then
-            export NIX_REMOTE=daemon
-        fi
-      '' + ''
         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 cfg.maxJobs);
+    nix.nrBuildUsers = mkDefault (lib.max 32 (if cfg.maxJobs == "auto" then 0 else cfg.maxJobs));
 
     users.users = nixbldUsers;
 
-    services.xserver.displayManager.hiddenUsers = map ({ name, ... }: name) nixbldUsers;
+    services.xserver.displayManager.hiddenUsers = attrNames nixbldUsers;
 
-    # FIXME: use systemd-tmpfiles to create Nix directories.
     system.activationScripts.nix = stringAfter [ "etc" "users" ]
       ''
-        # Nix initialisation.
-        install -m 0755 -d \
-          /nix/var/nix/gcroots \
-          /nix/var/nix/temproots \
-          /nix/var/nix/userpool \
-          /nix/var/nix/profiles \
-          /nix/var/nix/db \
-          /nix/var/log/nix/drvs
-        install -m 1777 -d \
-          /nix/var/nix/gcroots/per-user \
-          /nix/var/nix/profiles/per-user \
-          /nix/var/nix/gcroots/tmp
+        # Create directories in /nix.
+        ${nix}/bin/nix ping-store --no-net
+
+        # 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/nixos/modules/services/misc/nix-optimise.nix b/nixos/modules/services/misc/nix-optimise.nix
index 416529f690e0..e02026d5f76c 100644
--- a/nixos/modules/services/misc/nix-optimise.nix
+++ b/nixos/modules/services/misc/nix-optimise.nix
@@ -40,8 +40,8 @@ in
 
     systemd.services.nix-optimise =
       { description = "Nix Store Optimiser";
-        # No point running it inside a nixos-container. It should be on the host instead.
-        unitConfig.ConditionVirtualization = "!container";
+        # 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/nixos/modules/services/misc/nixos-manual.nix b/nixos/modules/services/misc/nixos-manual.nix
index df3e71c80dea..ab73f49d4be5 100644
--- a/nixos/modules/services/misc/nixos-manual.nix
+++ b/nixos/modules/services/misc/nixos-manual.nix
@@ -52,9 +52,9 @@ in
       };
     })
     (mkIf (cfg.showManual && cfgd.enable && cfgd.nixos.enable) {
-      boot.extraTTYs = [ "tty${toString cfg.ttyNumber}" ];
+      console.extraTTYs = [ "tty${toString cfg.ttyNumber}" ];
 
-      systemd.services."nixos-manual" = {
+      systemd.services.nixos-manual = {
         description = "NixOS Manual";
         wantedBy = [ "multi-user.target" ];
         serviceConfig = {
diff --git a/nixos/modules/services/misc/nzbget.nix b/nixos/modules/services/misc/nzbget.nix
index 6ab98751c57b..715ec891cd68 100644
--- a/nixos/modules/services/misc/nzbget.nix
+++ b/nixos/modules/services/misc/nzbget.nix
@@ -4,32 +4,40 @@ with lib;
 
 let
   cfg = config.services.nzbget;
-  dataDir = builtins.dirOf cfg.configFile;
-in {
-  options = {
-    services.nzbget = {
-      enable = mkEnableOption "NZBGet";
+  pkg = pkgs.nzbget;
+  stateDir = "/var/lib/nzbget";
+  configFile = "${stateDir}/nzbget.conf";
+  configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${value}") nixosOpts);
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.nzbget;
-        defaultText = "pkgs.nzbget";
-        description = "The NZBGet package to use";
-      };
+  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";
+  };
 
-      dataDir = mkOption {
-        type = types.str;
-        default = "/var/lib/nzbget";
-        description = "The directory where NZBGet stores its configuration files.";
-      };
+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.")
+  ];
 
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Open ports in the firewall for the NZBGet web interface
-        '';
-      };
+  # interface
+
+  options = {
+    services.nzbget = {
+      enable = mkEnableOption "NZBGet";
 
       user = mkOption {
         type = types.str;
@@ -42,15 +50,11 @@ in {
         default = "nzbget";
         description = "Group under which NZBGet runs";
       };
-
-      configFile = mkOption {
-        type = types.str;
-        default = "/var/lib/nzbget/nzbget.conf";
-        description = "Path for NZBGet's config file. (If this doesn't exist, the default config template is copied here.)";
-      };
     };
   };
 
+  # implementation
+
   config = mkIf cfg.enable {
     systemd.services.nzbget = {
       description = "NZBGet Daemon";
@@ -61,50 +65,26 @@ in {
         p7zip
       ];
       preStart = ''
-        cfgtemplate=${cfg.package}/share/nzbget/nzbget.conf
-        if [ ! -f ${cfg.configFile} ]; then
-          echo "${cfg.configFile} not found. Copying default config $cfgtemplate to ${cfg.configFile}"
-          install -m 0700 $cfgtemplate ${cfg.configFile}
-          echo "Setting temporary \$MAINDIR variable in default config required in order to allow nzbget to complete initial start"
-          echo "Remember to change this to a proper value once NZBGet startup has been completed"
-          sed -i -e 's/MainDir=.*/MainDir=\/tmp/g' ${cfg.configFile}
+        if [ ! -f ${configFile} ]; then
+          ${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile}
         fi
       '';
 
-      script = ''
-        args="--daemon --configfile ${cfg.configFile}"
-        # The script in preStart (above) copies nzbget's config template to datadir on first run, containing paths that point to the nzbget derivation installed at the time.
-        # These paths break when nzbget is upgraded & the original derivation is garbage collected. If such broken paths are found in the config file, override them to point to
-        # the currently installed nzbget derivation.
-        cfgfallback () {
-          local hit=`grep -Po "(?<=^$1=).*+" "${cfg.configFile}" | sed 's/[ \t]*$//'` # Strip trailing whitespace
-          ( test $hit && test -e $hit ) || {
-            echo "In ${cfg.configFile}, valid $1 not found; falling back to $1=$2"
-            args+=" -o $1=$2"
-          }
-        }
-        cfgfallback ConfigTemplate ${cfg.package}/share/nzbget/nzbget.conf
-        cfgfallback WebDir ${cfg.package}/share/nzbget/webui
-        ${cfg.package}/bin/nzbget $args
-      '';
-
       serviceConfig = {
-        StateDirectory = dataDir;
-        StateDirectoryMode = "0700";
-        Type = "forking";
+        StateDirectory = "nzbget";
+        StateDirectoryMode = "0750";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = "true";
+        UMask = "0002";
         Restart = "on-failure";
+        ExecStart = "${pkg}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}";
+        ExecStop = "${pkg}/bin/nzbget --quit";
       };
     };
 
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ 8989 ];
-    };
-
     users.users = mkIf (cfg.user == "nzbget") {
       nzbget = {
+        home = stateDir;
         group = cfg.group;
         uid = config.ids.uids.nzbget;
       };
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
index baa7c3ade52e..651ed3743884 100644
--- a/nixos/modules/services/misc/octoprint.nix
+++ b/nixos/modules/services/misc/octoprint.nix
@@ -7,7 +7,7 @@ let
   cfg = config.services.octoprint;
 
   baseConfig = {
-    plugins.cura.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine";
+    plugins.curalegacy.cura_engine = "${pkgs.curaengine_stable}/bin/CuraEngine";
     server.host = cfg.host;
     server.port = cfg.port;
     webcam.ffmpeg = "${pkgs.ffmpeg.bin}/bin/ffmpeg";
@@ -86,16 +86,20 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = optionalAttrs (cfg.user == "octoprint") (singleton
-      { name = "octoprint";
+    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;
+    };
 
-    users.groups = optionalAttrs (cfg.group == "octoprint") (singleton
-      { name = "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";
@@ -105,7 +109,6 @@ in
       environment.PYTHONPATH = makeSearchPathOutput "lib" pkgs.python.sitePackages [ pluginsEnv ];
 
       preStart = ''
-        mkdir -p "${cfg.stateDir}"
         if [ -e "${cfg.stateDir}/config.yaml" ]; then
           ${pkgs.yaml-merge}/bin/yaml-merge "${cfg.stateDir}/config.yaml" "${cfgUpdate}" > "${cfg.stateDir}/config.yaml.tmp"
           mv "${cfg.stateDir}/config.yaml.tmp" "${cfg.stateDir}/config.yaml"
@@ -113,14 +116,12 @@ in
           cp "${cfgUpdate}" "${cfg.stateDir}/config.yaml"
           chmod 600 "${cfg.stateDir}/config.yaml"
         fi
-        chown -R ${cfg.user}:${cfg.group} "${cfg.stateDir}"
       '';
 
       serviceConfig = {
         ExecStart = "${pkgs.octoprint}/bin/octoprint serve -b ${cfg.stateDir}";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/misc/osrm.nix b/nixos/modules/services/misc/osrm.nix
index f89f37ccd9df..79c347ab7e0e 100644
--- a/nixos/modules/services/misc/osrm.nix
+++ b/nixos/modules/services/misc/osrm.nix
@@ -59,6 +59,7 @@ in
       group = config.users.users.osrm.name;
       description = "OSRM user";
       createHome = false;
+      isSystemUser = true;
     };
 
     users.groups.osrm = { };
diff --git a/nixos/modules/services/misc/packagekit.nix b/nixos/modules/services/misc/packagekit.nix
index bce21e8acff3..325c4e84e0d8 100644
--- a/nixos/modules/services/misc/packagekit.nix
+++ b/nixos/modules/services/misc/packagekit.nix
@@ -7,18 +7,19 @@ let
   cfg = config.services.packagekit;
 
   packagekitConf = ''
-[Daemon]
-KeepCache=false
-    '';
+    [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
-      '';
+    [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
 
@@ -33,26 +34,32 @@ in
           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 = [ pkgs.packagekit ];
+    services.dbus.packages = with pkgs; [ packagekit ];
 
-    systemd.services.packagekit = {
-      description = "PackageKit Daemon";
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig.ExecStart = "${pkgs.packagekit}/libexec/packagekitd";
-      serviceConfig.User = "root";
-      serviceConfig.BusName = "org.freedesktop.PackageKit";
-      serviceConfig.Type = "dbus";
-    };
+    systemd.packages = with pkgs; [ packagekit ];
 
     environment.etc."PackageKit/PackageKit.conf".text = packagekitConf;
     environment.etc."PackageKit/Vendor.conf".text = vendorConf;
-
   };
-
 }
diff --git a/nixos/modules/services/misc/paperless.nix b/nixos/modules/services/misc/paperless.nix
new file mode 100644
index 000000000000..bfaf760fb836
--- /dev/null
+++ b/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/nixos/modules/services/misc/parsoid.nix b/nixos/modules/services/misc/parsoid.nix
index c757093e5c1b..61626e78f8b3 100644
--- a/nixos/modules/services/misc/parsoid.nix
+++ b/nixos/modules/services/misc/parsoid.nix
@@ -26,6 +26,10 @@ let
 
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "parsoid" "interwikis" ] "Use services.parsoid.wikis instead")
+  ];
+
   ##### interface
 
   options = {
diff --git a/nixos/modules/services/misc/phd.nix b/nixos/modules/services/misc/phd.nix
deleted file mode 100644
index e605ce5de16e..000000000000
--- a/nixos/modules/services/misc/phd.nix
+++ /dev/null
@@ -1,52 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.phd;
-
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.phd = {
-
-      enable = mkOption {
-        default = false;
-        description = "
-          Enable daemons for phabricator.
-        ";
-      };
-
-    };
-
-  };
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    systemd.services.phd = {
-      path = [ pkgs.phabricator pkgs.php pkgs.mercurial pkgs.git pkgs.subversion ];
-
-      after = [ "httpd.service" ];
-      wantedBy = [ "multi-user.target" ];
-
-      serviceConfig = {
-        ExecStart = "${pkgs.phabricator}/phabricator/bin/phd start";
-        ExecStop = "${pkgs.phabricator}/phabricator/bin/phd stop";
-        User = "wwwrun";
-        RestartSec = "30s";
-        Restart = "always";
-        StartLimitInterval = "1m";
-      };
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/misc/plex.nix b/nixos/modules/services/misc/plex.nix
index fce9b29011f1..7efadf1b9bb1 100644
--- a/nixos/modules/services/misc/plex.nix
+++ b/nixos/modules/services/misc/plex.nix
@@ -10,35 +10,38 @@ in
     services.plex = {
       enable = mkEnableOption "Plex Media Server";
 
-      # FIXME: In order for this config option to work, symlinks in the Plex
-      # package in the Nix store have to be changed to point to this directory.
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/plex";
-        description = "The directory where Plex stores its data files.";
+        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
+          Open ports in the firewall for the media server.
         '';
       };
 
       user = mkOption {
         type = types.str;
         default = "plex";
-        description = "User account under which Plex runs.";
+        description = ''
+          User account under which Plex runs.
+        '';
       };
 
       group = mkOption {
         type = types.str;
         default = "plex";
-        description = "Group under which Plex runs.";
+        description = ''
+          Group under which Plex runs.
+        '';
       };
 
-
       managePlugins = mkOption {
         type = types.bool;
         default = true;
@@ -80,73 +83,48 @@ in
       description = "Plex Media Server";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        test -d "${cfg.dataDir}/Plex Media Server" || {
-          echo "Creating initial Plex data directory in \"${cfg.dataDir}\"."
-          mkdir -p "${cfg.dataDir}/Plex Media Server"
-          chown -R ${cfg.user}:${cfg.group} "${cfg.dataDir}"
-        }
-
-        # Copy the database skeleton files to /var/lib/plex/.skeleton
-        # See the the Nix expression for Plex's package for more information on
-        # why this is done.
-        install --owner ${cfg.user} --group ${cfg.group} -d "${cfg.dataDir}/.skeleton"
-        for db in "com.plexapp.plugins.library.db"; do
-            if [ ! -e  "${cfg.dataDir}/.skeleton/$db" ]; then
-              cp "${cfg.package}/usr/lib/plexmediaserver/Resources/base_$db" "${cfg.dataDir}/.skeleton/$db"
-            fi
-            chmod u+w "${cfg.dataDir}/.skeleton/$db"
-            chown ${cfg.user}:${cfg.group} "${cfg.dataDir}/.skeleton/$db"
-        done
-
-        # If managePlugins is enabled, setup symlinks for plugins.
-        ${optionalString cfg.managePlugins ''
-          echo "Preparing plugin directory."
-          PLUGINDIR="${cfg.dataDir}/Plex Media Server/Plug-ins"
-          test -d "$PLUGINDIR" || {
-            mkdir -p "$PLUGINDIR";
-            chown ${cfg.user}:${cfg.group} "$PLUGINDIR";
-          }
-
-          echo "Removing old symlinks."
-          # First, remove all of the symlinks in the directory.
-          for f in `ls "$PLUGINDIR/"`; do
-            if [[ -L "$PLUGINDIR/$f" ]]; then
-              echo "Removing plugin symlink $PLUGINDIR/$f."
-              rm "$PLUGINDIR/$f"
-            fi
-          done
-
-          echo "Symlinking plugins."
-          for path in ${toString cfg.extraPlugins}; do
-            dest="$PLUGINDIR/$(basename $path)"
-            if [[ ! -d "$path" ]]; then
-              echo "Error symlinking plugin from $path: no such directory."
-            elif [[ -d "$dest" || -L "$dest" ]]; then
-              echo "Error symlinking plugin from $path to $dest: file or directory already exists."
-            else
-              echo "Symlinking plugin at $path..."
-              ln -s "$path" "$dest"
-            fi
-          done
-        ''}
-     '';
+
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = "true";
-        ExecStart = "\"${cfg.package}/usr/lib/plexmediaserver/Plex Media Server\"";
+
+        # 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 = {
-        PLEX_MEDIA_SERVER_APPLICATION_SUPPORT_DIR=cfg.dataDir;
-        PLEX_MEDIA_SERVER_HOME="${cfg.package}/usr/lib/plexmediaserver";
+        # 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";
-        LD_LIBRARY_PATH="/run/opengl-driver/lib:${cfg.package}/usr/lib/plexmediaserver/lib";
         LC_ALL="en_US.UTF-8";
         LANG="en_US.UTF-8";
       };
diff --git a/nixos/modules/services/misc/pykms.nix b/nixos/modules/services/misc/pykms.nix
index ef90d124a284..25aa27ae7673 100644
--- a/nixos/modules/services/misc/pykms.nix
+++ b/nixos/modules/services/misc/pykms.nix
@@ -4,12 +4,17 @@ 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 = rec {
+    services.pykms = {
       enable = mkOption {
         type = types.bool;
         default = false;
@@ -28,12 +33,6 @@ in {
         description = "The port on which to listen.";
       };
 
-      verbose = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Show verbose output.";
-      };
-
       openFirewallPort = mkOption {
         type = types.bool;
         default = false;
@@ -45,30 +44,44 @@ in {
         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 = let
-      home = "/var/lib/pykms";
-    in {
+    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 = home;
+      environment.HOME = libDir;
       serviceConfig = with pkgs; {
         DynamicUser = true;
-        StateDirectory = baseNameOf home;
-        ExecStartPre = "${getBin pykms}/bin/create_pykms_db.sh ${home}/clients.db";
+        StateDirectory = baseNameOf libDir;
+        ExecStartPre = "${getBin pykms}/libexec/create_pykms_db.sh ${libDir}/clients.db";
         ExecStart = lib.concatStringsSep " " ([
-          "${getBin pykms}/bin/server.py"
+          "${getBin pykms}/bin/server"
+          "--logfile STDOUT"
+          "--loglevel ${cfg.logLevel}"
+        ] ++ cfg.extraArgs ++ [
           cfg.listenAddress
           (toString cfg.port)
-        ] ++ lib.optional cfg.verbose "--verbose");
-        WorkingDirectory = home;
+        ]);
+        ProtectHome = "tmpfs";
+        WorkingDirectory = libDir;
         Restart = "on-failure";
         MemoryLimit = cfg.memoryLimit;
       };
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index 91ddf2c3edf3..3b8c14d196f8 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -1,8 +1,10 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 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";
@@ -11,11 +13,11 @@ let
     production:
       adapter: ${cfg.database.type}
       database: ${cfg.database.name}
-      host: ${cfg.database.host}
+      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.socket != null) "socket: ${cfg.database.socket}"}
+      ${optionalString (cfg.database.type == "mysql2" && cfg.database.socket != null) "socket: ${cfg.database.socket}"}
   '';
 
   configurationYml = pkgs.writeText "configuration.yml" ''
@@ -50,31 +52,21 @@ let
       '';
   });
 
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql2";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "postgresql";
+
 in
 
 {
   options = {
     services.redmine = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = "Enable the Redmine service.";
-      };
+      enable = mkEnableOption "Redmine";
 
-      # default to the 4.x series not forcing major version upgrade of those on the 3.x series
       package = mkOption {
         type = types.package;
-        default = if versionAtLeast config.system.stateVersion "19.03"
-          then pkgs.redmine_4
-          else pkgs.redmine
-        ;
-        defaultText = "pkgs.redmine";
-        description = ''
-          Which Redmine package to use. This defaults to version 3.x if
-          <literal>system.stateVersion &lt; 19.03</literal> and version 4.x
-          otherwise.
-        '';
-        example = "pkgs.redmine_4.override { ruby = pkgs.ruby_2_4; }";
+        default = pkgs.redmine;
+        description = "Which Redmine package to use.";
+        example = "pkgs.redmine.override { ruby = pkgs.ruby_2_7; }";
       };
 
       user = mkOption {
@@ -107,7 +99,8 @@ in
         description = ''
           Extra configuration in configuration.yml.
 
-          See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration
+          See <link xlink:href="https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration"/>
+          for details.
         '';
         example = literalExample ''
           email_delivery:
@@ -124,7 +117,8 @@ in
         description = ''
           Extra configuration in additional_environment.rb.
 
-          See https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example
+          See <link xlink:href="https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example"/>
+          for details.
         '';
         example = literalExample ''
           config.logger.level = Logger::DEBUG
@@ -169,13 +163,14 @@ in
 
         host = mkOption {
           type = types.str;
-          default = (if cfg.database.socket != null then "localhost" else "127.0.0.1");
+          default = "localhost";
           description = "Database host address.";
         };
 
         port = mkOption {
           type = types.int;
-          default = 3306;
+          default = if cfg.database.type == "postgresql" then 5432 else 3306;
+          defaultText = "3306";
           description = "Database host port.";
         };
 
@@ -213,10 +208,20 @@ in
 
         socket = mkOption {
           type = types.nullOr types.path;
-          default = null;
+          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.";
+        };
       };
     };
   };
@@ -227,12 +232,37 @@ in
       { 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.socket != null -> (cfg.database.type == "mysql2");
-        message = "Socket authentication is only available for the mysql2 database type";
+      { 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";
       }
     ];
 
-    environment.systemPackages = [ cfg.package ];
+    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 = [
@@ -259,7 +289,7 @@ in
     ];
 
     systemd.services.redmine = {
-      after = [ "network.target" (if cfg.database.type == "mysql2" then "mysql.service" else "postgresql.service") ];
+      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";
@@ -337,17 +367,17 @@ in
 
     };
 
-    users.users = optionalAttrs (cfg.user == "redmine") (singleton
-      { name = "redmine";
+    users.users = optionalAttrs (cfg.user == "redmine") {
+      redmine = {
         group = cfg.group;
         home = cfg.stateDir;
         uid = config.ids.uids.redmine;
-      });
+      };
+    };
 
-    users.groups = optionalAttrs (cfg.group == "redmine") (singleton
-      { name = "redmine";
-        gid = config.ids.gids.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
diff --git a/nixos/modules/services/misc/ripple-data-api.nix b/nixos/modules/services/misc/ripple-data-api.nix
index 042b496d35ee..9fab462f7e3b 100644
--- a/nixos/modules/services/misc/ripple-data-api.nix
+++ b/nixos/modules/services/misc/ripple-data-api.nix
@@ -185,9 +185,8 @@ in {
       ];
     };
 
-    users.users = singleton
-      { name = "ripple-data-api";
-        description = "Ripple data api user";
+    users.users.ripple-data-api =
+      { description = "Ripple data api user";
         uid = config.ids.uids.ripple-data-api;
       };
   };
diff --git a/nixos/modules/services/misc/rippled.nix b/nixos/modules/services/misc/rippled.nix
index cdf61730de33..ef34e3a779f0 100644
--- a/nixos/modules/services/misc/rippled.nix
+++ b/nixos/modules/services/misc/rippled.nix
@@ -406,9 +406,8 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = singleton
-      { name = "rippled";
-        description = "Ripple server user";
+    users.users.rippled =
+      { description = "Ripple server user";
         uid = config.ids.uids.rippled;
         home = cfg.databasePath;
         createHome = true;
diff --git a/nixos/modules/services/misc/rogue.nix b/nixos/modules/services/misc/rogue.nix
index aae02e384c97..d56d103b5f34 100644
--- a/nixos/modules/services/misc/rogue.nix
+++ b/nixos/modules/services/misc/rogue.nix
@@ -40,7 +40,7 @@ in
 
   config = mkIf cfg.enable {
 
-    boot.extraTTYs = [ cfg.tty ];
+    console.extraTTYs = [ cfg.tty ];
 
     systemd.services.rogue =
       { description = "Rogue dungeon crawling game";
diff --git a/nixos/modules/services/misc/serviio.nix b/nixos/modules/services/misc/serviio.nix
index 8808f2d21931..0ead6a816918 100644
--- a/nixos/modules/services/misc/serviio.nix
+++ b/nixos/modules/services/misc/serviio.nix
@@ -10,7 +10,7 @@ let
     #!${pkgs.bash}/bin/sh
 
     SERVIIO_HOME=${pkgs.serviio}
-    
+
     # Setup the classpath
     SERVIIO_CLASS_PATH="$SERVIIO_HOME/lib/*:$SERVIIO_HOME/config"
 
@@ -21,13 +21,13 @@ let
     # 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;
@@ -52,7 +52,7 @@ in {
   config = mkIf cfg.enable {
     systemd.services.serviio = {
       description = "Serviio Media Server";
-      after = [ "local-fs.target" "network.target" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.serviio ];
       serviceConfig = {
@@ -63,28 +63,23 @@ in {
       };
     };
 
-    users.users = [
-      { 
-        name = "serviio";
-        group = "serviio";
+    users.users.serviio =
+      { group = "serviio";
         home = cfg.dataDir;
         description = "Serviio Media Server User";
         createHome = true;
         isSystemUser = true;
-      }
-    ];
+      };
 
-    users.groups = [
-      { name = "serviio";} 
-    ];
+    users.groups.serviio = { };
 
     networking.firewall = {
-      allowedTCPPorts = [ 
+      allowedTCPPorts = [
         8895  # serve UPnP responses
         23423 # console
         23424 # mediabrowser
       ];
-      allowedUDPPorts = [ 
+      allowedUDPPorts = [
         1900 # UPnP service discovey
       ];
     };
diff --git a/nixos/modules/services/misc/sickbeard.nix b/nixos/modules/services/misc/sickbeard.nix
index 5cfbbe516ae1..a32dbfa3108f 100644
--- a/nixos/modules/services/misc/sickbeard.nix
+++ b/nixos/modules/services/misc/sickbeard.nix
@@ -63,19 +63,19 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = optionalAttrs (cfg.user == name) (singleton {
-      name = name;
-      uid = config.ids.uids.sickbeard;
-      group = cfg.group;
-      description = "sickbeard user";
-      home = cfg.dataDir;
-      createHome = true;
-    });
+    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) (singleton {
-      name = name;
-      gid = config.ids.gids.sickbeard;
-    });
+    users.groups = optionalAttrs (cfg.group == name) {
+      ${name}.gid = config.ids.gids.sickbeard;
+    };
 
     systemd.services.sickbeard = {
       description = "Sickbeard Server";
diff --git a/nixos/modules/services/misc/siproxd.nix b/nixos/modules/services/misc/siproxd.nix
index dcaf73aca448..ae7b27de8e70 100644
--- a/nixos/modules/services/misc/siproxd.nix
+++ b/nixos/modules/services/misc/siproxd.nix
@@ -161,8 +161,7 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = singleton {
-      name = "siproxyd";
+    users.users.siproxyd = {
       uid = config.ids.uids.siproxd;
     };
 
diff --git a/nixos/modules/services/misc/snapper.nix b/nixos/modules/services/misc/snapper.nix
index 62b344d11b06..6f3aaa973a04 100644
--- a/nixos/modules/services/misc/snapper.nix
+++ b/nixos/modules/services/misc/snapper.nix
@@ -44,7 +44,7 @@ in
     configs = mkOption {
       default = { };
       example = literalExample {
-        "home" = {
+        home = {
           subvolume = "/home";
           extraConfig = ''
             ALLOW_USERS="alice"
diff --git a/nixos/modules/services/misc/subsonic.nix b/nixos/modules/services/misc/subsonic.nix
index 1612b197f35f..152917d345cc 100644
--- a/nixos/modules/services/misc/subsonic.nix
+++ b/nixos/modules/services/misc/subsonic.nix
@@ -17,7 +17,7 @@ let cfg = config.services.subsonic; in {
       };
 
       listenAddress = mkOption {
-        type = types.string;
+        type = types.str;
         default = "0.0.0.0";
         description = ''
           The host name or IP address on which to bind Subsonic.
@@ -105,7 +105,7 @@ let cfg = config.services.subsonic; in {
   config = mkIf cfg.enable {
     systemd.services.subsonic = {
       description = "Personal media streamer";
-      after = [ "local-fs.target" "network.target" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       script = ''
         ${pkgs.jre}/bin/java -Xmx${toString cfg.maxMemory}m \
diff --git a/nixos/modules/services/misc/synergy.nix b/nixos/modules/services/misc/synergy.nix
index b89cb41ac3ad..bfab8c534d8c 100644
--- a/nixos/modules/services/misc/synergy.nix
+++ b/nixos/modules/services/misc/synergy.nix
@@ -83,7 +83,7 @@ in
 
   config = mkMerge [
     (mkIf cfgC.enable {
-      systemd.user.services."synergy-client" = {
+      systemd.user.services.synergy-client = {
         after = [ "network.target" "graphical-session.target" ];
         description = "Synergy client";
         wantedBy = optional cfgC.autoStart "graphical-session.target";
@@ -93,7 +93,7 @@ in
       };
     })
     (mkIf cfgS.enable {
-      systemd.user.services."synergy-server" = {
+      systemd.user.services.synergy-server = {
         after = [ "network.target" "graphical-session.target" ];
         description = "Synergy server";
         wantedBy = optional cfgS.autoStart "graphical-session.target";
diff --git a/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix
index 483bc99ad946..a894caed1a34 100644
--- a/nixos/modules/services/misc/taskserver/default.nix
+++ b/nixos/modules/services/misc/taskserver/default.nix
@@ -48,7 +48,7 @@ let
     type = types.nullOr types.int;
     default = null;
     example = 365;
-    apply = val: if isNull val then -1 else val;
+    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.
@@ -82,7 +82,7 @@ let
          then attrByPath newPath (notFound newPath) cfg.pki.manual
          else findPkiDefinitions newPath val;
     in flatten (mapAttrsToList mkSublist attrs);
-  in all isNull (findPkiDefinitions [] manualPkiOptions);
+  in all (x: x == null) (findPkiDefinitions [] manualPkiOptions);
 
   orgOptions = { ... }: {
     options.users = mkOption {
@@ -368,16 +368,16 @@ in {
     (mkIf cfg.enable {
       environment.systemPackages = [ nixos-taskserver ];
 
-      users.users = optional (cfg.user == "taskd") {
-        name = "taskd";
-        uid = config.ids.uids.taskd;
-        description = "Taskserver user";
-        group = cfg.group;
+      users.users = optionalAttrs (cfg.user == "taskd") {
+        taskd = {
+          uid = config.ids.uids.taskd;
+          description = "Taskserver user";
+          group = cfg.group;
+        };
       };
 
-      users.groups = optional (cfg.group == "taskd") {
-        name = "taskd";
-        gid = config.ids.gids.taskd;
+      users.groups = optionalAttrs (cfg.group == "taskd") {
+        taskd.gid = config.ids.gids.taskd;
       };
 
       services.taskserver.config = {
@@ -411,7 +411,7 @@ in {
         } else {
           cert = "${cfg.pki.manual.server.cert}";
           key = "${cfg.pki.manual.server.key}";
-          crl = "${cfg.pki.manual.server.crl}";
+          ${mapNullable (_: "crl") cfg.pki.manual.server.crl} = "${cfg.pki.manual.server.crl}";
         });
 
         ca.cert = if needToCreateCA then "${cfg.dataDir}/keys/ca.cert"
diff --git a/nixos/modules/services/misc/taskserver/doc.xml b/nixos/modules/services/misc/taskserver/doc.xml
index 5eac8d9ef784..5656bb85b373 100644
--- a/nixos/modules/services/misc/taskserver/doc.xml
+++ b/nixos/modules/services/misc/taskserver/doc.xml
@@ -105,7 +105,7 @@
    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>
-$ ssh server nixos-taskserver user export my-company alice | sh
+<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.
diff --git a/nixos/modules/services/misc/tautulli.nix b/nixos/modules/services/misc/tautulli.nix
index 50e450366478..aded33629f1c 100644
--- a/nixos/modules/services/misc/tautulli.nix
+++ b/nixos/modules/services/misc/tautulli.nix
@@ -6,6 +6,10 @@ let
   cfg = config.services.tautulli;
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "plexpy" ] [ "services" "tautulli" ])
+  ];
+
   options = {
     services.tautulli = {
       enable = mkEnableOption "Tautulli Plex Monitor";
diff --git a/nixos/modules/services/misc/tiddlywiki.nix b/nixos/modules/services/misc/tiddlywiki.nix
new file mode 100644
index 000000000000..2adc08f6cfed
--- /dev/null
+++ b/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/nixos/modules/services/misc/uhub.nix b/nixos/modules/services/misc/uhub.nix
index 005951b9231e..d1b388310280 100644
--- a/nixos/modules/services/misc/uhub.nix
+++ b/nixos/modules/services/misc/uhub.nix
@@ -41,31 +41,31 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-	description = "Whether to enable the uhub ADC hub.";
+        description = "Whether to enable the uhub ADC hub.";
       };
 
       port = mkOption {
         type = types.int;
         default = 1511;
-	description = "TCP port to bind the hub to.";
+        description = "TCP port to bind the hub to.";
       };
 
       address = mkOption {
-        type = types.string;
+        type = types.str;
         default = "any";
-	description = "Address to bind the hub to.";
+        description = "Address to bind the hub to.";
       };
 
       enableTLS = mkOption {
         type = types.bool;
         default = false;
-	description = "Whether to enable TLS support.";
+        description = "Whether to enable TLS support.";
       };
 
       hubConfig = mkOption {
         type = types.lines;
         default = "";
-	description = "Contents of uhub configuration file.";
+        description = "Contents of uhub configuration file.";
       };
 
       aclConfig = mkOption {
@@ -77,13 +77,13 @@ in
       plugins = {
 
         authSqlite = {
-	  enable = mkOption {
+          enable = mkOption {
             type = types.bool;
             default = false;
             description = "Whether to enable the Sqlite authentication database plugin";
-	  };
+          };
           file = mkOption {
-            type = types.string;
+            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.";
           };
@@ -96,7 +96,7 @@ in
             description = "Whether to enable the logging plugin.";
           };
           file = mkOption {
-            type = types.string;
+            type = types.str;
             default = "";
             description = "Path of log file.";
           };
@@ -117,7 +117,7 @@ in
             default = "";
             type = types.lines;
             description = ''
-              Welcome message displayed to clients after connecting 
+              Welcome message displayed to clients after connecting
               and with the <literal>!motd</literal> command.
             '';
           };
@@ -161,14 +161,8 @@ in
   config = mkIf cfg.enable {
 
     users = {
-      users = singleton {
-        name = "uhub";
-        uid = config.ids.uids.uhub;
-      };
-      groups = singleton {
-        name = "uhub";
-        gid = config.ids.gids.uhub;
-      };
+      users.uhub.uid = config.ids.uids.uhub;
+      groups.uhub.gid = config.ids.gids.uhub;
     };
 
     systemd.services.uhub = {
@@ -183,4 +177,4 @@ in
     };
   };
 
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 2bd2f3c7cc08..d7f7324580c0 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -2,6 +2,7 @@
 
 let
   cfg = config.services.zoneminder;
+  fpm = config.services.phpfpm.pools.zoneminder;
   pkg = pkgs.zoneminder;
 
   dirName = pkg.dirName;
@@ -10,16 +11,14 @@ let
   group = {
     nginx = config.services.nginx.group;
     none  = user;
-  }."${cfg.webserver}";
+  }.${cfg.webserver};
 
   useNginx = cfg.webserver == "nginx";
 
   defaultDir = "/var/lib/${user}";
   home = if useCustomDir then cfg.storageDir else defaultDir;
 
-  useCustomDir = !(builtins.isNull cfg.storageDir);
-
-  socket = "/run/phpfpm/${dirName}.sock";
+  useCustomDir = cfg.storageDir != null;
 
   zms = "/cgi-bin/zms";
 
@@ -50,7 +49,7 @@ let
     ZM_DB_TYPE=mysql
     ZM_DB_HOST=${cfg.database.host}
     ZM_DB_NAME=${cfg.database.name}
-    ZM_DB_USER=${if cfg.database.createLocally then user else cfg.database.username}
+    ZM_DB_USER=${cfg.database.username}
     ZM_DB_PASS=${cfg.database.password}
 
     # Web
@@ -155,6 +154,7 @@ in {
           default = "zmpass";
           description = ''
             Username for accessing the database.
+            Not used if <literal>createLocally</literal> is set.
           '';
         };
       };
@@ -189,12 +189,21 @@ in {
 
   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 ];
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
+      cfg.port
+      6802 # zmtrigger
+    ];
 
     services = {
       fcgiwrap = lib.mkIf useNginx {
@@ -204,10 +213,9 @@ in {
       };
 
       mysql = lib.mkIf cfg.database.createLocally {
+        enable = true;
+        package = lib.mkDefault pkgs.mariadb;
         ensureDatabases = [ cfg.database.name ];
-        initialDatabases = [{
-          inherit (cfg.database) name; schema = "${pkg}/share/zoneminder/db/zm_create.sql";
-        }];
         ensureUsers = [{
           name = cfg.database.username;
           ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
@@ -217,7 +225,7 @@ in {
       nginx = lib.mkIf useNginx {
         enable = true;
         virtualHosts = {
-          "${cfg.hostname}" = {
+          ${cfg.hostname} = {
             default = true;
             root = "${pkg}/share/zoneminder/www";
             listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ];
@@ -229,6 +237,8 @@ in {
               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;
@@ -254,8 +264,8 @@ in {
                   fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress};
                 }
 
-                location /cache {
-                  alias /var/cache/${dirName};
+                location /cache/ {
+                  alias /var/cache/${dirName}/;
                 }
 
                 location ~ \.php$ {
@@ -266,7 +276,7 @@ in {
                   fastcgi_param SCRIPT_FILENAME $request_filename;
                   fastcgi_param HTTP_PROXY "";
 
-                  fastcgi_pass unix:${socket};
+                  fastcgi_pass unix:${fpm.socket};
                 }
               }
             '';
@@ -276,36 +286,33 @@ in {
 
       phpfpm = lib.mkIf useNginx {
         pools.zoneminder = {
-          listen = socket;
+          inherit user group;
           phpOptions = ''
             date.timezone = "${config.time.timeZone}"
 
             ${lib.concatStringsSep "\n" (map (e:
             "extension=${e.pkg}/lib/php/extensions/${e.name}.so") phpExtensions)}
           '';
-          extraConfig = ''
-            user = ${user}
-            group = ${group}
-
-            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
-          '';
+          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; rec {
+      zoneminder = with pkgs; {
         inherit (zoneminder.meta) description;
         documentation = [ "https://zoneminder.readthedocs.org/en/latest/" ];
         path = [
@@ -313,11 +320,16 @@ in {
           procps
           psmisc
         ];
-        after = [ "mysql.service" "nginx.service" ];
+        after = [ "nginx.service" ] ++ lib.optional cfg.database.createLocally "mysql.service";
         wantedBy = [ "multi-user.target" ];
         restartTriggers = [ defaultsFile configFile ];
-        preStart = lib.mkIf useCustomDir ''
+        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
         '';
         serviceConfig = {
           User = user;
@@ -344,11 +356,11 @@ in {
       };
     };
 
-    users.groups."${user}" = {
+    users.groups.${user} = {
       gid = config.ids.gids.zoneminder;
     };
 
-    users.users."${user}" = {
+    users.users.${user} = {
       uid = config.ids.uids.zoneminder;
       group = user;
       inherit home;
diff --git a/nixos/modules/services/misc/zookeeper.nix b/nixos/modules/services/misc/zookeeper.nix
index 50c84e3c6b80..f6af7c75ebae 100644
--- a/nixos/modules/services/misc/zookeeper.nix
+++ b/nixos/modules/services/misc/zookeeper.nix
@@ -121,6 +121,7 @@ in {
 
     systemd.tmpfiles.rules = [
       "d '${cfg.dataDir}' 0700 zookeeper - - -"
+      "Z '${cfg.dataDir}' 0700 zookeeper - - -"
     ];
 
     systemd.services.zookeeper = {
@@ -145,8 +146,7 @@ in {
       '';
     };
 
-    users.users = singleton {
-      name = "zookeeper";
+    users.users.zookeeper = {
       uid = config.ids.uids.zookeeper;
       description = "Zookeeper daemon user";
       home = cfg.dataDir;
diff --git a/nixos/modules/services/monitoring/alerta.nix b/nixos/modules/services/monitoring/alerta.nix
index 8f4258e26ded..34f2d41706a5 100644
--- a/nixos/modules/services/monitoring/alerta.nix
+++ b/nixos/modules/services/monitoring/alerta.nix
@@ -1,4 +1,4 @@
-{ options, config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -83,6 +83,10 @@ in
   };
 
   config = mkIf cfg.enable {
+    systemd.tmpfiles.rules = [
+      "d '${cfg.logDir}' - alerta alerta - -"
+    ];
+
     systemd.services.alerta = {
       description = "Alerta Monitoring System";
       wantedBy = [ "multi-user.target" ];
@@ -94,12 +98,7 @@ in
         ExecStart = "${pkgs.python36Packages.alerta-server}/bin/alertad run --port ${toString cfg.port} --host ${cfg.bind}";
         User = "alerta";
         Group = "alerta";
-        PermissionsStartOnly = true;
       };
-      preStart = ''
-        mkdir -p ${cfg.logDir}
-        chown alerta:alerta ${cfg.logDir}
-      '';
     };
 
     environment.systemPackages = [ pkgs.python36Packages.alerta ];
diff --git a/nixos/modules/services/monitoring/apcupsd.nix b/nixos/modules/services/monitoring/apcupsd.nix
index 49957e652900..75218aa1d46b 100644
--- a/nixos/modules/services/monitoring/apcupsd.nix
+++ b/nixos/modules/services/monitoring/apcupsd.nix
@@ -91,7 +91,7 @@ in
           BATTERYLEVEL 50
           MINUTES 5
         '';
-        type = types.string;
+        type = types.lines;
         description = ''
           Contents of the runtime configuration file, apcupsd.conf. The default
           settings makes apcupsd autodetect USB UPSes, limit network access to
@@ -106,7 +106,7 @@ in
         example = {
           doshutdown = ''# shell commands to notify that the computer is shutting down'';
         };
-        type = types.attrsOf types.string;
+        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
diff --git a/nixos/modules/services/monitoring/bosun.nix b/nixos/modules/services/monitoring/bosun.nix
index 8bf741adb6e3..b1c12cce1f80 100644
--- a/nixos/modules/services/monitoring/bosun.nix
+++ b/nixos/modules/services/monitoring/bosun.nix
@@ -41,7 +41,7 @@ in {
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "bosun";
         description = ''
           User account under which bosun runs.
@@ -49,7 +49,7 @@ in {
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "bosun";
         description = ''
           Group account under which bosun runs.
@@ -57,7 +57,7 @@ in {
       };
 
       opentsdbHost = mkOption {
-        type = types.nullOr types.string;
+        type = types.nullOr types.str;
         default = "localhost:4242";
         description = ''
           Host and port of the OpenTSDB database that stores bosun data.
@@ -66,7 +66,7 @@ in {
       };
 
       influxHost = mkOption {
-        type = types.nullOr types.string;
+        type = types.nullOr types.str;
         default = null;
         example = "localhost:8086";
         description = ''
@@ -75,7 +75,7 @@ in {
       };
 
       listenAddress = mkOption {
-        type = types.string;
+        type = types.str;
         default = ":8070";
         description = ''
           The host address and port that bosun's web interface will listen on.
diff --git a/nixos/modules/services/monitoring/cadvisor.nix b/nixos/modules/services/monitoring/cadvisor.nix
index 6ca420a05b23..695a8c42e85e 100644
--- a/nixos/modules/services/monitoring/cadvisor.nix
+++ b/nixos/modules/services/monitoring/cadvisor.nix
@@ -84,6 +84,16 @@ in {
         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.
+        '';
+      };
     };
   };
 
@@ -112,6 +122,7 @@ in {
             -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}" \
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index 6a4c678eb21f..ef3663c62e04 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -16,13 +16,29 @@ let
       NotifyLevel "OKAY"
     </Plugin>
 
+    ${concatStrings (mapAttrsToList (plugin: pluginConfig: ''
+      LoadPlugin ${plugin}
+      <Plugin "${plugin}">
+      ${pluginConfig}
+      </Plugin>
+    '') cfg.plugins)}
+
     ${concatMapStrings (f: ''
-    Include "${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";
@@ -33,7 +49,15 @@ in {
       description = ''
         Which collectd package to use.
       '';
-      type = package;
+      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 {
@@ -68,6 +92,15 @@ in {
       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 = ''
@@ -89,15 +122,17 @@ in {
       wantedBy = [ "multi-user.target" ];
 
       serviceConfig = {
-        ExecStart = "${cfg.package}/sbin/collectd -C ${conf} -f";
+        ExecStart = "${package}/sbin/collectd -C ${conf} -f";
         User = cfg.user;
         Restart = "on-failure";
         RestartSec = 3;
       };
     };
 
-    users.users = optional (cfg.user == "collectd") {
-      name = "collectd";
+    users.users = optionalAttrs (cfg.user == "collectd") {
+      collectd = {
+        isSystemUser = true;
+      };
     };
   };
 }
diff --git a/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix
index ce3d53fb2c17..2c5fe47242e7 100644
--- a/nixos/modules/services/monitoring/datadog-agent.nix
+++ b/nixos/modules/services/monitoring/datadog-agent.nix
@@ -22,9 +22,9 @@ let
   # 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: mapAttrsToList (name: conf: {
-    source = pkgs.writeText "${name}-check-conf.yaml" (builtins.toJSON conf);
-    target = "datadog-agent/conf.d/${name}.d/conf.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 = {
@@ -34,17 +34,18 @@ let
 
   # Assemble all check configurations and the top-level agent
   # configuration.
-  etcfiles = with pkgs; with builtins; [{
-    source = writeText "datadog.yaml" (toJSON ddConf);
-    target = "datadog-agent/datadog.yaml";
-  }] ++ makeCheckConfigs (cfg.checks // defaultChecks);
+  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.overrideAttrs(_: {
-    python = (pkgs.datadog-integrations-core cfg.extraIntegrations).python;
-  });
+  datadogPkg = cfg.package.override {
+    pythonPackages = pkgs.datadog-integrations-core cfg.extraIntegrations;
+  };
 in {
   options.services.datadog-agent = {
     enable = mkOption {
@@ -60,7 +61,7 @@ in {
       defaultText = "pkgs.datadog-agent";
       description = ''
         Which DataDog v6 agent package to use. Note that the provided
-        package is expected to have an overridable `python`-attribute
+        package is expected to have an overridable `pythonPackages`-attribute
         which configures the Python environment with the Datadog
         checks.
       '';
@@ -87,7 +88,7 @@ in {
       description = "The hostname to show in the Datadog dashboard (optional)";
       default = null;
       example = "mymachine.mydomain";
-      type = types.uniq (types.nullOr types.string);
+      type = types.nullOr types.str;
     };
 
     logLevel = mkOption {
@@ -204,7 +205,7 @@ in {
   config = mkIf cfg.enable {
     environment.systemPackages = [ datadogPkg pkgs.sysstat pkgs.procps pkgs.iproute ];
 
-    users.extraUsers.datadog = {
+    users.users.datadog = {
       description = "Datadog Agent User";
       uid = config.ids.uids.datadog;
       group = "datadog";
@@ -212,7 +213,7 @@ in {
       createHome = true;
     };
 
-    users.extraGroups.datadog.gid = config.ids.gids.datadog;
+    users.groups.datadog.gid = config.ids.gids.datadog;
 
     systemd.services = let
       makeService = attrs: recursiveUpdate {
@@ -224,7 +225,7 @@ in {
           Restart = "always";
           RestartSec = 2;
         };
-        restartTriggers = [ datadogPkg ] ++ map (etc: etc.source) etcfiles;
+        restartTriggers = [ datadogPkg ] ++ attrNames etcfiles;
       } attrs;
     in {
       datadog-agent = makeService {
diff --git a/nixos/modules/services/monitoring/dd-agent/dd-agent.nix b/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
index abc8d65d58f2..e91717fb2054 100644
--- a/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
+++ b/nixos/modules/services/monitoring/dd-agent/dd-agent.nix
@@ -78,37 +78,35 @@ let
   etcfiles =
     let
       defaultConfd = import ./dd-agent-defaults.nix;
-    in (map (f: { source = "${pkgs.dd-agent}/agent/conf.d-system/${f}";
-                  target = "dd-agent/conf.d/${f}";
-                }) defaultConfd) ++ [
-      { source = ddConf;
-        target = "dd-agent/datadog.conf";
-      }
-      { source = diskConfig;
-        target = "dd-agent/conf.d/disk.yaml";
-      }
-      { source = networkConfig;
-        target = "dd-agent/conf.d/network.yaml";
-      } ] ++
-    (optional (cfg.postgresqlConfig != null)
-      { source = postgresqlConfig;
-        target = "dd-agent/conf.d/postgres.yaml";
-      }) ++
-    (optional (cfg.nginxConfig != null)
-      { source = nginxConfig;
-        target = "dd-agent/conf.d/nginx.yaml";
-      }) ++
-    (optional (cfg.mongoConfig != null)
-      { source = mongoConfig;
-        target = "dd-agent/conf.d/mongo.yaml";
-      }) ++
-    (optional (cfg.processConfig != null)
-      { source = processConfig;
-        target = "dd-agent/conf.d/process.yaml";
-      }) ++
-    (optional (cfg.jmxConfig != null)
-      { source = jmxConfig;
-        target = "dd-agent/conf.d/jmx.yaml";
+    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 {
@@ -145,47 +143,46 @@ in {
       description = "The hostname to show in the Datadog dashboard (optional)";
       default = null;
       example = "mymachine.mydomain";
-      type = types.uniq (types.nullOr types.string);
+      type = types.nullOr types.str;
     };
 
     postgresqlConfig = mkOption {
       description = "Datadog PostgreSQL integration configuration";
       default = null;
-      type = types.uniq (types.nullOr types.string);
+      type = types.nullOr types.lines;
     };
 
     nginxConfig = mkOption {
       description = "Datadog nginx integration configuration";
       default = null;
-      type = types.uniq (types.nullOr types.string);
+      type = types.nullOr types.lines;
     };
 
     mongoConfig = mkOption {
       description = "MongoDB integration configuration";
       default = null;
-      type = types.uniq (types.nullOr types.string);
+      type = types.nullOr types.lines;
     };
 
     jmxConfig = mkOption {
       description = "JMX integration configuration";
       default = null;
-      type = types.uniq (types.nullOr types.string);
+      type = types.nullOr types.lines;
     };
 
     processConfig = mkOption {
       description = ''
         Process integration configuration
-
-        See http://docs.datadoghq.com/integrations/process/
+        See <link xlink:href="https://docs.datadoghq.com/integrations/process/"/>
       '';
       default = null;
-      type = types.uniq (types.nullOr types.string);
+      type = types.nullOr types.lines;
     };
 
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = [ pkgs."dd-agent" pkgs.sysstat pkgs.procps ];
+    environment.systemPackages = [ pkgs.dd-agent pkgs.sysstat pkgs.procps ];
 
     users.users.datadog = {
       description = "Datadog Agent User";
diff --git a/nixos/modules/services/monitoring/do-agent.nix b/nixos/modules/services/monitoring/do-agent.nix
new file mode 100644
index 000000000000..2d3fe2f79768
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/fusion-inventory.nix b/nixos/modules/services/monitoring/fusion-inventory.nix
index 9c976c65ea49..9b65c76ce02e 100644
--- a/nixos/modules/services/monitoring/fusion-inventory.nix
+++ b/nixos/modules/services/monitoring/fusion-inventory.nix
@@ -46,12 +46,12 @@ in {
 
   config = mkIf cfg.enable {
 
-    users.users = singleton {
-      name = "fusion-inventory";
+    users.users.fusion-inventory = {
       description = "FusionInventory user";
+      isSystemUser = true;
     };
 
-    systemd.services."fusion-inventory" = {
+    systemd.services.fusion-inventory = {
       description = "Fusion Inventory Agent";
       wantedBy = [ "multi-user.target" ];
 
diff --git a/nixos/modules/services/monitoring/grafana-reporter.nix b/nixos/modules/services/monitoring/grafana-reporter.nix
index 149026d20188..b5a78e4583e1 100644
--- a/nixos/modules/services/monitoring/grafana-reporter.nix
+++ b/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -1,4 +1,4 @@
-{ options, config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -52,7 +52,7 @@ in {
       wantedBy = ["multi-user.target"];
       after = ["network.target"];
       serviceConfig = let
-        args = lib.concatSepString " " [
+        args = lib.concatStringsSep " " [
           "-proto ${cfg.grafana.protocol}://"
           "-ip ${cfg.grafana.addr}:${toString cfg.grafana.port}"
           "-port :${toString cfg.port}"
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 85879cfe0b33..0f8bc2471e33 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -43,7 +43,7 @@ let
 
     ANALYTICS_REPORTING_ENABLED = boolToString cfg.analytics.reporting.enable;
 
-    SMTP_ENABLE = boolToString cfg.smtp.enable;
+    SMTP_ENABLED = boolToString cfg.smtp.enable;
     SMTP_HOST = cfg.smtp.host;
     SMTP_USER = cfg.smtp.user;
     SMTP_PASSWORD = cfg.smtp.password;
@@ -177,7 +177,7 @@ let
       folder = mkOption {
         type = types.str;
         default = "";
-        description = "Add dashboards to the speciied folder";
+        description = "Add dashboards to the specified folder";
       };
       type = mkOption {
         type = types.str;
@@ -503,12 +503,12 @@ in {
         message = "Cannot set both adminPassword and adminPasswordFile";
       }
       {
-        assertion = cfg.security.secretKeyFile != opt.security.secretKeyFile.default -> cfg.security.secretKeyFile == null;
+        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 secretKeyFile";
+        message = "Cannot set both password and passwordFile";
       }
     ];
 
@@ -552,6 +552,8 @@ in {
       description = "Grafana user";
       home = cfg.dataDir;
       createHome = true;
+      group = "grafana";
     };
+    users.groups.grafana = {};
   };
 }
diff --git a/nixos/modules/services/monitoring/graphite.nix b/nixos/modules/services/monitoring/graphite.nix
index 2365142af40e..dd147bb37930 100644
--- a/nixos/modules/services/monitoring/graphite.nix
+++ b/nixos/modules/services/monitoring/graphite.nix
@@ -11,7 +11,7 @@ let
 
   graphiteLocalSettingsDir = pkgs.runCommand "graphite_local_settings" {
       inherit graphiteLocalSettings;
-      preferLocalBuild = true; 
+      preferLocalBuild = true;
     } ''
     mkdir -p $out
     ln -s $graphiteLocalSettings $out/graphite_local_settings.py
@@ -19,13 +19,13 @@ let
 
   graphiteLocalSettings = pkgs.writeText "graphite_local_settings.py" (
     "STATIC_ROOT = '${staticDir}'\n" +
-    optionalString (! isNull config.time.timeZone) "TIME_ZONE = '${config.time.timeZone}'\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 (!isNull config.time.timeZone) ''time_zone: ${config.time.timeZone}''}
+    ${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:''}
@@ -215,7 +215,7 @@ in {
       storageAggregation = mkOption {
         description = "Defines how to aggregate data to lower-precision retentions.";
         default = null;
-        type = types.uniq (types.nullOr types.string);
+        type = types.nullOr types.str;
         example = ''
           [all_min]
           pattern = \.min$
@@ -227,7 +227,7 @@ in {
       storageSchemas = mkOption {
         description = "Defines retention rates for storing metrics.";
         default = "";
-        type = types.uniq (types.nullOr types.string);
+        type = types.nullOr types.str;
         example = ''
           [apache_busyWorkers]
           pattern = ^servers\.www.*\.workers\.busyWorkers$
@@ -238,14 +238,14 @@ in {
       blacklist = mkOption {
         description = "Any metrics received which match one of the experssions will be dropped.";
         default = null;
-        type = types.uniq (types.nullOr types.string);
-        example = "^some\.noisy\.metric\.prefix\..*";
+        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.uniq (types.nullOr types.string);
+        type = types.nullOr types.str;
         example = ".*";
       };
 
@@ -255,7 +255,7 @@ in {
           in a search and replace fashion.
         '';
         default = null;
-        type = types.uniq (types.nullOr types.string);
+        type = types.nullOr types.str;
         example = ''
           [post]
           _sum$ =
@@ -272,7 +272,7 @@ in {
       relayRules = mkOption {
         description = "Relay rules are used to send certain metrics to a certain backend.";
         default = null;
-        type = types.uniq (types.nullOr types.string);
+        type = types.nullOr types.str;
         example = ''
           [example]
           pattern = ^mydata\.foo\..+
@@ -289,7 +289,7 @@ in {
       aggregationRules = mkOption {
         description = "Defines if and how received metrics will be aggregated.";
         default = null;
-        type = types.uniq (types.nullOr types.string);
+        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
@@ -632,8 +632,7 @@ in {
       cfg.web.enable || cfg.api.enable ||
       cfg.seyren.enable || cfg.pager.enable || cfg.beacon.enable
      ) {
-      users.users = singleton {
-        name = "graphite";
+      users.users.graphite = {
         uid = config.ids.uids.graphite;
         description = "Graphite daemon user";
         home = dataDir;
diff --git a/nixos/modules/services/monitoring/heapster.nix b/nixos/modules/services/monitoring/heapster.nix
index fbdff2eb5dbe..585632943fdc 100644
--- a/nixos/modules/services/monitoring/heapster.nix
+++ b/nixos/modules/services/monitoring/heapster.nix
@@ -15,19 +15,19 @@ in {
     source = mkOption {
       description = "Heapster metric source";
       example = "kubernetes:https://kubernetes.default";
-      type = types.string;
+      type = types.str;
     };
 
     sink = mkOption {
       description = "Heapster metic sink";
       example = "influxdb:http://localhost:8086";
-      type = types.string;
+      type = types.str;
     };
 
     extraOpts = mkOption {
       description = "Heapster extra options";
       default = "";
-      type = types.string;
+      type = types.separatedString " ";
     };
 
     package = mkOption {
@@ -49,8 +49,7 @@ in {
       };
     };
 
-    users.users = singleton {
-      name = "heapster";
+    users.users.heapsterrs = {
       uid = config.ids.uids.heapster;
       description = "Heapster user";
     };
diff --git a/nixos/modules/services/monitoring/kapacitor.nix b/nixos/modules/services/monitoring/kapacitor.nix
index a4bdfa8f8053..9b4ff3c56124 100644
--- a/nixos/modules/services/monitoring/kapacitor.nix
+++ b/nixos/modules/services/monitoring/kapacitor.nix
@@ -1,4 +1,4 @@
-{ options, config, lib, pkgs, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -116,17 +116,17 @@ in
       url = mkOption {
         description = "The URL to an InfluxDB server that serves as the default database";
         example = "http://localhost:8086";
-        type = types.string;
+        type = types.str;
       };
 
       username = mkOption {
         description = "The username to connect to the remote InfluxDB server";
-        type = types.string;
+        type = types.str;
       };
 
       password = mkOption {
         description = "The password to connect to the remote InfluxDB server";
-        type = types.string;
+        type = types.str;
       };
     };
 
@@ -137,7 +137,7 @@ in
         description = "The URL to the Alerta REST API";
         default = "http://localhost:5000";
         example = "http://localhost:5000";
-        type = types.string;
+        type = types.str;
       };
 
       token = mkOption {
@@ -163,6 +163,10 @@ in
   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" ];
@@ -171,12 +175,7 @@ in
         ExecStart = "${pkgs.kapacitor}/bin/kapacitord -config ${kapacitorConf}";
         User = "kapacitor";
         Group = "kapacitor";
-        PermissionsStartOnly = true;
       };
-      preStart = ''
-        mkdir -p ${cfg.dataDir}
-        chown ${cfg.user}:${cfg.group} ${cfg.dataDir}
-      '';
     };
 
     users.users.kapacitor = {
diff --git a/nixos/modules/services/monitoring/loki.nix b/nixos/modules/services/monitoring/loki.nix
new file mode 100644
index 000000000000..f4eec7e0d284
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/monit.nix b/nixos/modules/services/monitoring/monit.nix
index 32e14ab21ffc..ca9352272174 100644
--- a/nixos/modules/services/monitoring/monit.nix
+++ b/nixos/modules/services/monitoring/monit.nix
@@ -23,7 +23,7 @@ in
 
     environment.systemPackages = [ pkgs.monit ];
 
-    environment.etc."monitrc" = {
+    environment.etc.monitrc = {
       text = cfg.config;
       mode = "0400";
     };
@@ -39,7 +39,7 @@ in
         KillMode = "process";
         Restart = "always";
       };
-      restartTriggers = [ config.environment.etc."monitrc".source ];
+      restartTriggers = [ config.environment.etc.monitrc.source ];
     };
 
   };
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index ffe223fedbe1..1ebf7ee6a761 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -233,7 +233,7 @@ in
         # In the meantime this at least suppresses a useless graph full of
         # NaNs in the output.
         default = [ "munin_stats" ];
-        type = with types; listOf string;
+        type = with types; listOf str;
         description = ''
           Munin plugins to disable, even if
           <literal>munin-node-configure --suggest</literal> tries to enable
@@ -317,18 +317,16 @@ in
 
     environment.systemPackages = [ pkgs.munin ];
 
-    users.users = [{
-      name = "munin";
+    users.users.munin = {
       description = "Munin monitoring user";
       group = "munin";
       uid = config.ids.uids.munin;
       home = "/var/lib/munin";
-    }];
+    };
 
-    users.groups = [{
-      name = "munin";
+    users.groups.munin = {
       gid = config.ids.gids.munin;
-    }];
+    };
 
   }) (mkIf nodeCfg.enable {
 
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 7f65236ed3d3..3ca79dddaf57 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -8,6 +8,7 @@ let
 
   nagiosState = "/var/lib/nagios";
   nagiosLogDir = "/var/log/nagios";
+  urlPath = "/nagios";
 
   nagiosObjectDefs = cfg.objectDefs;
 
@@ -16,32 +17,39 @@ let
       preferLocalBuild = true;
     } "mkdir -p $out; ln -s $nagiosObjectDefs $out/";
 
-  nagiosCfgFile = pkgs.writeText "nagios.cfg"
-    ''
-      # Paths for state and logs.
-      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 # Not used I think.
-      state_retention_file=${nagiosState}/retention.dat
-      query_socket=${nagiosState}/nagios.qh
-      check_result_path=${nagiosState}
-      command_file=${nagiosState}/nagios.cmd
-
-      # Configuration files.
-      #resource_file=resource.cfg
-      cfg_dir=${nagiosObjectDefsDir}
-
-      # Uid/gid that the daemon runs under.
-      nagios_user=nagios
-      nagios_group=nogroup
-
-      # Misc. options.
-      illegal_macro_output_chars=`~$&|'"<>
-      retain_state_information=1
-    ''; # "
+  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.
@@ -49,43 +57,38 @@ let
     ''
       main_config_file=${cfg.mainConfigFile}
       use_authentication=0
-      url_html_path=${cfg.urlPath}
+      url_html_path=${urlPath}
     '';
 
   extraHttpdConfig =
     ''
-      ScriptAlias ${cfg.urlPath}/cgi-bin ${pkgs.nagios}/sbin
+      ScriptAlias ${urlPath}/cgi-bin ${pkgs.nagios}/sbin
 
       <Directory "${pkgs.nagios}/sbin">
         Options ExecCGI
-        AllowOverride None
-        Order allow,deny
-        Allow from all
+        Require all granted
         SetEnv NAGIOS_CGI_CONFIG ${cfg.cgiConfigFile}
       </Directory>
 
-      Alias ${cfg.urlPath} ${pkgs.nagios}/share
+      Alias ${urlPath} ${pkgs.nagios}/share
 
       <Directory "${pkgs.nagios}/share">
         Options None
-        AllowOverride None
-        Order allow,deny
-        Allow from all
+        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 = mkOption {
-        default = false;
-        description = "
-          Whether to use <link
-          xlink:href='http://www.nagios.org/'>Nagios</link> to monitor
-          your system or network.
-        ";
-      };
+      enable = mkEnableOption "<link xlink:href='http://www.nagios.org/'>Nagios</link> to monitor your system or network.";
 
       objectDefs = mkOption {
         description = "
@@ -93,12 +96,14 @@ in
           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 = [pkgs.nagiosPluginsOfficial pkgs.ssmtp];
-        defaultText = "[pkgs.nagiosPluginsOfficial pkgs.ssmtp]";
+        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.
@@ -106,14 +111,29 @@ in
       };
 
       mainConfigFile = mkOption {
-        type = types.package;
-        default = nagiosCfgFile;
-        defaultText = "nagiosCfgFile";
+        type = types.nullOr types.package;
+        default = null;
         description = "
-          Derivation for the main configuration file of Nagios.
+          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;
@@ -125,6 +145,7 @@ in
       };
 
       enableWebInterface = mkOption {
+        type = types.bool;
         default = false;
         description = "
           Whether to enable the Nagios web interface.  You should also
@@ -132,13 +153,20 @@ in
         ";
       };
 
-      urlPath = mkOption {
-        default = "/nagios";
-        description = "
-          The URL path under which the Nagios web interface appears.
-          That is, you can access the Nagios web interface through
-          <literal>http://<replaceable>server</replaceable>/<replaceable>urlPath</replaceable></literal>.
-        ";
+      virtualHost = mkOption {
+        type = types.submodule (import ../web-servers/apache-httpd/per-server-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.
+        '';
       };
     };
   };
@@ -149,44 +177,36 @@ in
       description = "Nagios user ";
       uid         = config.ids.uids.nagios;
       home        = nagiosState;
-      createHome  = true;
+      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 = [
-      { source = cfg.mainConfigFile;
-        target = "nagios.cfg";
-      }
-    ];
+    environment.etc."nagios.cfg".source = nagiosCfgFile;
 
     environment.systemPackages = [ pkgs.nagios ];
     systemd.services.nagios = {
       description = "Nagios monitoring daemon";
-      path     = [ pkgs.nagios ];
+      path     = [ pkgs.nagios ] ++ cfg.plugins;
       wantedBy = [ "multi-user.target" ];
       after    = [ "network.target" ];
 
       serviceConfig = {
         User = "nagios";
+        Group = "nagios";
         Restart = "always";
         RestartSec = 2;
-        PermissionsStartOnly = true;
+        LogsDirectory = "nagios";
+        StateDirectory = "nagios";
+        ExecStart = "${pkgs.nagios}/bin/nagios /etc/nagios.cfg";
+        X-ReloadIfChanged = nagiosCfgFile;
       };
-
-      preStart = ''
-        mkdir -m 0755 -p ${nagiosState} ${nagiosLogDir}
-        chown nagios ${nagiosState} ${nagiosLogDir}
-      '';
-
-      script = ''
-        for i in ${toString cfg.plugins}; do
-          export PATH=$i/bin:$i/sbin:$i/libexec:$PATH
-        done
-        exec ${pkgs.nagios}/bin/nagios ${cfg.mainConfigFile}
-      '';
     };
 
-    services.httpd.extraConfig = optionalString cfg.enableWebInterface extraHttpdConfig;
+    services.httpd.virtualHosts = optionalAttrs cfg.enableWebInterface {
+      ${cfg.virtualHost.hostName} = mkMerge [ cfg.virtualHost { extraConfig = extraHttpdConfig; } ];
+    };
   };
 }
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index a49555cf677f..f8225af20428 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -8,6 +8,7 @@ let
   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
   '';
 
   plugins = [
@@ -137,18 +138,26 @@ in {
       description = "Real time performance monitoring";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = (with pkgs; [ gawk curl ]) ++ lib.optional cfg.python.enable
+      path = (with pkgs; [ curl gawk which ]) ++ lib.optional cfg.python.enable
         (pkgs.python3.withPackages cfg.python.extraPackages);
       serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
         Environment="PYTHONPATH=${pkgs.netdata}/libexec/netdata/python.d/python_modules";
-        PermissionsStartOnly = true;
-        ExecStart = "${pkgs.netdata}/bin/netdata -D -c ${configFile}";
+        ExecStart = "${pkgs.netdata}/bin/netdata -P /run/netdata/netdata.pid -D -c ${configFile}";
+        ExecReload = "${pkgs.utillinux}/bin/kill -s HUP -s USR1 -s USR2 $MAINPID";
         TimeoutStopSec = 60;
+        # 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 = "${pkgs.netdata}/libexec/netdata/plugins.d/apps.plugin.org";
       capabilities = "cap_dac_read_search,cap_sys_ptrace+ep";
@@ -157,13 +166,27 @@ in {
       permissions = "u+rx,g+rx,o-rwx";
     };
 
+    security.wrappers."freeipmi.plugin" = {
+      source = "${pkgs.netdata}/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.pam.loginLimits = [
+      { domain = "netdata"; type = "soft"; item = "nofile"; value = "10000"; }
+      { domain = "netdata"; type = "hard"; item = "nofile"; value = "30000"; }
+    ];
 
-    users.users = optional (cfg.user == defaultUser) {
-      name = defaultUser;
+    users.users = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} = {
+        isSystemUser = true;
+      };
     };
 
-    users.groups = optional (cfg.group == defaultUser) {
-      name = defaultUser;
+    users.groups = optionalAttrs (cfg.group == defaultUser) {
+      ${defaultUser} = { };
     };
 
   };
diff --git a/nixos/modules/services/monitoring/osquery.nix b/nixos/modules/services/monitoring/osquery.nix
deleted file mode 100644
index c8c625577d39..000000000000
--- a/nixos/modules/services/monitoring/osquery.nix
+++ /dev/null
@@ -1,91 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with builtins;
-with lib;
-
-let
-  cfg = config.services.osquery;
-
-in
-
-{
-
-  options = {
-
-    services.osquery = {
-
-      enable = mkEnableOption "osquery";
-
-      loggerPath = mkOption {
-        type = types.path;
-        description = "Base directory used for logging.";
-        default = "/var/log/osquery";
-      };
-
-      pidfile = mkOption {
-        type = types.path;
-        description = "Path used for pid file.";
-        default = "/var/osquery/osqueryd.pidfile";
-      };
-
-      utc = mkOption {
-        type = types.bool;
-        description = "Attempt to convert all UNIX calendar times to UTC.";
-        default = true;
-      };
-
-      databasePath = mkOption {
-        type = types.path;
-        description = "Path used for database file.";
-        default = "/var/osquery/osquery.db";
-      };
-
-      extraConfig = mkOption {
-        type = types.attrs // {
-          merge = loc: foldl' (res: def: recursiveUpdate res def.value) {};
-        };
-        description = "Extra config to be recursively merged into the JSON config file.";
-        default = { };
-      };
-    };
-
-  };
-
-  config = mkIf cfg.enable {
-
-    environment.systemPackages = [ pkgs.osquery ];
-
-    environment.etc."osquery/osquery.conf".text = toJSON (
-      recursiveUpdate {
-        options = {
-          config_plugin = "filesystem";
-          logger_plugin = "filesystem";
-          logger_path = cfg.loggerPath;
-          database_path = cfg.databasePath;
-          utc = cfg.utc;
-        };
-      } cfg.extraConfig
-    );
-
-    systemd.services.osqueryd = {
-      description = "The osquery Daemon";
-      after = [ "network.target" "syslog.service" ];
-      wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.osquery ];
-      preStart = ''
-        mkdir -p ${escapeShellArg cfg.loggerPath}
-        mkdir -p "$(dirname ${escapeShellArg cfg.pidfile})"
-        mkdir -p "$(dirname ${escapeShellArg cfg.databasePath})"
-      '';
-      serviceConfig = {
-        TimeoutStartSec = "infinity";
-        ExecStart = "${pkgs.osquery}/bin/osqueryd --logger_path ${escapeShellArg cfg.loggerPath} --pidfile ${escapeShellArg cfg.pidfile} --database_path ${escapeShellArg cfg.databasePath}";
-        KillMode = "process";
-        KillSignal = "SIGTERM";
-        Restart = "on-failure";
-      };
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index 11d85e9c4fc3..4534d150885e 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -18,7 +18,7 @@ let
     in checkedConfig yml;
 
   cmdlineArgs = cfg.extraFlags ++ [
-    "--config.file ${alertmanagerYml}"
+    "--config.file /tmp/alert-manager-substituted.yaml"
     "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
     "--log.level ${cfg.logLevel}"
     ] ++ (optional (cfg.webExternalUrl != null)
@@ -27,6 +27,15 @@ let
       "--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";
@@ -118,6 +127,18 @@ in {
           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>
+        '';
+      };
     };
   };
 
@@ -135,9 +156,14 @@ in {
       systemd.services.alertmanager = {
         wantedBy = [ "multi-user.target" ];
         after    = [ "network.target" ];
+        preStart = ''
+           ${lib.getBin pkgs.envsubst}/bin/envsubst -o "/tmp/alert-manager-substituted.yaml" \
+                                                    -i "${alertmanagerYml}"
+        '';
         serviceConfig = {
           Restart  = "always";
-          DynamicUser = true;
+          DynamicUser = true; # implies PrivateTmp
+          EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
           WorkingDirectory = "/tmp";
           ExecStart = "${cfg.package}/bin/alertmanager" +
             optionalString (length cmdlineArgs != 0) (" \\\n  " +
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index d8384e0d35b3..b67f697ca0de 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -4,37 +4,14 @@ with lib;
 
 let
   cfg = config.services.prometheus;
-  cfg2 = config.services.prometheus2;
-  promUser = "prometheus";
-  promGroup = "prometheus";
-
-  stateDir =
-    if cfg.stateDir != null
-    then cfg.stateDir
-    else
-      if cfg.dataDir != null
-      then
-        # This assumes /var/lib/ is a prefix of cfg.dataDir.
-        # This is checked as an assertion below.
-        removePrefix stateDirBase cfg.dataDir
-      else "prometheus";
-  stateDirBase = "/var/lib/";
-  workingDir  = stateDirBase + stateDir;
-  workingDir2 = stateDirBase + cfg2.stateDir;
 
-  # a wrapper that verifies that the configuration is valid
-  promtoolCheck = what: name: file: pkgs.runCommand "${name}-${what}-checked"
-    { buildInputs = [ cfg.package ]; } ''
-    ln -s ${file} $out
-    promtool ${what} $out
-  '';
+  workingDir = "/var/lib/" + cfg.stateDir;
 
-  # a wrapper that verifies that the configuration is valid for
-  # prometheus 2
-  prom2toolCheck = what: name: file:
+  # a wrapper that verifies that the configuration is valid
+  promtoolCheck = what: name: file:
     pkgs.runCommand
       "${name}-${replaceStrings [" "] [""] what}-checked"
-      { buildInputs = [ cfg2.package ]; } ''
+      { buildInputs = [ cfg.package ]; } ''
     ln -s ${file} $out
     promtool ${what} $out
   '';
@@ -45,65 +22,34 @@ let
       echo '${builtins.toJSON x}' | ${pkgs.jq}/bin/jq . > $out
     '';
 
-  # This becomes the main config file for Prometheus 1
+  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 ++ [
+    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;
+    };
   };
 
-  generatedPrometheusYml = writePrettyJSON "prometheus.yml" promConfig;
-
   prometheusYml = let
     yml = if cfg.configText != null then
       pkgs.writeText "prometheus.yml" cfg.configText
       else generatedPrometheusYml;
-    in promtoolCheck "check-config" "prometheus.yml" yml;
+    in promtoolCheck "check config" "prometheus.yml" yml;
 
   cmdlineArgs = cfg.extraFlags ++ [
-    "-storage.local.path=${workingDir}/metrics"
-    "-config.file=${prometheusYml}"
-    "-web.listen-address=${cfg.listenAddress}"
-    "-alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
-    "-alertmanager.timeout=${toString cfg.alertmanagerTimeout}s"
-  ] ++
-  optional (cfg.alertmanagerURL != []) "-alertmanager.url=${concatStringsSep "," cfg.alertmanagerURL}" ++
-  optional (cfg.webExternalUrl != null) "-web.external-url=${cfg.webExternalUrl}";
-
-  # This becomes the main config file for Prometheus 2
-  promConfig2 = {
-    global = filterValidPrometheus cfg2.globalConfig;
-    rule_files = map (prom2toolCheck "check rules" "rules") (cfg2.ruleFiles ++ [
-      (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg2.rules))
-    ]);
-    scrape_configs = filterValidPrometheus cfg2.scrapeConfigs;
-    alerting = optionalAttrs (cfg2.alertmanagerURL != []) {
-      alertmanagers = [{
-        static_configs = [{
-          targets = cfg2.alertmanagerURL;
-        }];
-      }];
-    };
-  };
-
-  generatedPrometheus2Yml = writePrettyJSON "prometheus.yml" promConfig2;
-
-  prometheus2Yml = let
-    yml = if cfg2.configText != null then
-      pkgs.writeText "prometheus.yml" cfg2.configText
-      else generatedPrometheus2Yml;
-    in prom2toolCheck "check config" "prometheus.yml" yml;
-
-  cmdlineArgs2 = cfg2.extraFlags ++ [
-    "--storage.tsdb.path=${workingDir2}/data/"
-    "--config.file=${prometheus2Yml}"
-    "--web.listen-address=${cfg2.listenAddress}"
-    "--alertmanager.notification-queue-capacity=${toString cfg2.alertmanagerNotificationQueueCapacity}"
-    "--alertmanager.timeout=${toString cfg2.alertmanagerTimeout}s"
+    "--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 (cfg2.webExternalUrl != null) "--web.external-url=${cfg2.webExternalUrl}";
+  optional (cfg.webExternalUrl != null) "--web.external-url=${cfg.webExternalUrl}";
 
   filterValidPrometheus = filterAttrsListRecursive (n: v: !(n == "_module" || v == null));
   filterAttrsListRecursive = pred: x:
@@ -518,331 +464,164 @@ let
   };
 
 in {
-  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.
-        '';
-      };
-
-      dataDir = mkOption {
-        type = types.nullOr types.path;
-        default = null;
-        description = ''
-          Directory to store Prometheus metrics data.
-          This option is deprecated, please use <option>services.prometheus.stateDir</option>.
-        '';
-      };
-
-      stateDir = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        description = ''
-          Directory below <literal>${stateDirBase}</literal> to store Prometheus metrics data.
-          This directory will be created automatically using systemd's StateDirectory mechanism.
-          Defaults to <literal>prometheus</literal>.
-        '';
-      };
-
-      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.
-        '';
-      };
-
-      alertmanagerURL = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          List of Alertmanager URLs to send notifications to.
-        '';
-      };
-
-      alertmanagerNotificationQueueCapacity = mkOption {
-        type = types.int;
-        default = 10000;
-        description = ''
-          The capacity of the queue for pending alert manager notifications.
-        '';
-      };
+  imports = [
+    (mkRenamedOptionModule [ "services" "prometheus2" ] [ "services" "prometheus" ])
+  ];
 
-      alertmanagerTimeout = mkOption {
-        type = types.int;
-        default = 10;
-        description = ''
-          Alert manager HTTP API timeout (in seconds).
-        '';
-      };
+  options.services.prometheus = {
 
-      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).
-        '';
-      };
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable the Prometheus monitoring daemon.
+      '';
     };
-    services.prometheus2 = {
 
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enable the Prometheus 2 monitoring daemon.
-        '';
-      };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.prometheus;
+      defaultText = "pkgs.prometheus";
+      description = ''
+        The prometheus package that should be used.
+      '';
+    };
 
-      package = mkOption {
-        type = types.package;
-        default = pkgs.prometheus_2;
-        defaultText = "pkgs.prometheus_2";
-        description = ''
-          The prometheus2 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.
+      '';
+    };
 
-      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.
+      '';
+    };
 
-      stateDir = mkOption {
-        type = types.str;
-        default = "prometheus2";
-        description = ''
-          Directory below <literal>${stateDirBase}</literal> to store Prometheus metrics data.
-          This directory will be created automatically using systemd's StateDirectory mechanism.
-          Defaults to <literal>prometheus2</literal>.
-        '';
-      };
+    extraFlags = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra commandline options when launching Prometheus.
+      '';
+    };
 
-      extraFlags = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          Extra commandline options when launching Prometheus 2.
-        '';
-      };
+    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.
+      '';
+    };
 
-      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
+      '';
+    };
 
-      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.
+      '';
+    };
 
-      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.
+      '';
+    };
 
-      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.
+      '';
+    };
 
-      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.
+      '';
+    };
 
-      alertmanagerURL = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          List of Alertmanager URLs to send notifications to.
-        '';
-      };
+    alertmanagerNotificationQueueCapacity = mkOption {
+      type = types.int;
+      default = 10000;
+      description = ''
+        The capacity of the queue for pending alert manager notifications.
+      '';
+    };
 
-      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).
+      '';
+    };
 
-      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).
+      '';
+    };
+  };
 
-      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).
-        '';
-      };
+  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";
     };
-   };
-
-  config = mkMerge [
-    (mkIf (cfg.enable || cfg2.enable) {
-      users.groups.${promGroup}.gid = config.ids.gids.prometheus;
-      users.users.${promUser} = {
-        description = "Prometheus daemon user";
-        uid = config.ids.uids.prometheus;
-        group = promGroup;
-      };
-    })
-    (mkIf cfg.enable {
-      warnings =
-        optional (cfg.dataDir != null) ''
-          The option services.prometheus.dataDir is deprecated, please use
-          services.prometheus.stateDir.
-        '';
-      assertions = [
-        {
-          assertion = !(cfg.dataDir != null && cfg.stateDir != null);
-          message =
-            "The options services.prometheus.dataDir and services.prometheus.stateDir" +
-            " can't both be set at the same time! It's recommended to only set the latter" +
-            " since the former is deprecated.";
-        }
-        {
-          assertion = cfg.dataDir != null -> hasPrefix stateDirBase cfg.dataDir;
-          message =
-            "The option services.prometheus.dataDir should have ${stateDirBase} as a prefix!";
-        }
-        {
-          assertion = cfg.stateDir != null -> !hasPrefix "/" cfg.stateDir;
-          message =
-            "The option services.prometheus.stateDir shouldn't be an absolute directory." +
-            " It should be a directory relative to ${stateDirBase}.";
-        }
-        {
-          assertion = cfg2.stateDir != null -> !hasPrefix "/" cfg2.stateDir;
-          message =
-            "The option services.prometheus2.stateDir shouldn't be an absolute directory." +
-            " It should be a directory relative to ${stateDirBase}.";
-        }
-      ];
-      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 = promUser;
-          Restart  = "always";
-          WorkingDirectory = workingDir;
-          StateDirectory = stateDir;
-        };
+    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;
       };
-    })
-    (mkIf cfg2.enable {
-      systemd.services.prometheus2 = {
-        wantedBy = [ "multi-user.target" ];
-        after    = [ "network.target" ];
-        serviceConfig = {
-          ExecStart = "${cfg2.package}/bin/prometheus" +
-            optionalString (length cmdlineArgs2 != 0) (" \\\n  " +
-              concatStringsSep " \\\n  " cmdlineArgs2);
-          User = promUser;
-          Restart  = "always";
-          WorkingDirectory = workingDir2;
-          StateDirectory = cfg2.stateDir;
-        };
-      };
-    })
-  ];
+    };
+  };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index fa53107ef24b..36ebffa44636 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -1,8 +1,10 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
+{ 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:
@@ -17,24 +19,32 @@ let
   #  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 = {
-    blackbox  = import ./exporters/blackbox.nix  { inherit config lib pkgs; };
-    collectd  = import ./exporters/collectd.nix  { inherit config lib pkgs; };
-    dnsmasq   = import ./exporters/dnsmasq.nix   { inherit config lib pkgs; };
-    dovecot   = import ./exporters/dovecot.nix   { inherit config lib pkgs; };
-    fritzbox  = import ./exporters/fritzbox.nix  { inherit config lib pkgs; };
-    json      = import ./exporters/json.nix      { inherit config lib pkgs; };
-    minio     = import ./exporters/minio.nix     { inherit config lib pkgs; };
-    nginx     = import ./exporters/nginx.nix     { inherit config lib pkgs; };
-    node      = import ./exporters/node.nix      { inherit config lib pkgs; };
-    postfix   = import ./exporters/postfix.nix   { inherit config lib pkgs; };
-    snmp      = import ./exporters/snmp.nix      { inherit config lib pkgs; };
-    surfboard = import ./exporters/surfboard.nix { inherit config lib pkgs; };
-    tor       = import ./exporters/tor.nix       { inherit config lib pkgs; };
-    unifi     = import ./exporters/unifi.nix     { inherit config lib pkgs; };
-    varnish   = import ./exporters/varnish.nix   { inherit config lib pkgs; };
-    bind      = import ./exporters/bind.nix      { inherit config lib pkgs; };
-  };
+
+  exporterOpts = genAttrs [
+    "bind"
+    "blackbox"
+    "collectd"
+    "dnsmasq"
+    "dovecot"
+    "fritzbox"
+    "json"
+    "mail"
+    "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";
@@ -80,7 +90,7 @@ let
     };
     user = mkOption {
       type = types.str;
-      default = "nobody";
+      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.
@@ -88,7 +98,7 @@ let
     };
     group = mkOption {
       type = types.str;
-      default = "nobody";
+      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.
@@ -96,9 +106,10 @@ let
     };
   });
 
-  mkSubModule = { name, port, extraOpts, ... }: {
+  mkSubModule = { name, port, extraOpts, imports }: {
     ${name} = mkOption {
       type = types.submodule {
+        inherit imports;
         options = (mkExporterOpts {
           inherit name port;
         } // extraOpts);
@@ -111,13 +122,26 @@ let
   mkSubModules = (foldl' (a: b: a//b) {}
     (mapAttrsToList (name: opts: mkSubModule {
       inherit name;
-      inherit (opts) port serviceOpts;
+      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"
@@ -128,13 +152,32 @@ let
         serviceConfig.Restart = mkDefault "always";
         serviceConfig.PrivateTmp = mkDefault true;
         serviceConfig.WorkingDirectory = mkDefault /tmp;
-      } serviceOpts ] ++ optional (!(serviceOpts.serviceConfig.DynamicUser or false)) {
+        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);
@@ -153,17 +196,28 @@ in
   };
 
   config = mkMerge ([{
-    assertions = [{
+    assertions = [ {
       assertion = (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.mail.configFile == null) != (cfg.mail.configuration == {});
+      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;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixos/modules/services/monitoring/prometheus/exporters.xml
index 7a0a1bdf2c14..c2d4b05996a4 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.xml
+++ b/nixos/modules/services/monitoring/prometheus/exporters.xml
@@ -17,7 +17,7 @@
    exporter</link>, it provides hardware and OS metrics from the host it's
    running on. The exporter could be configured as follows:
 <programlisting>
-  services.promtheus.exporters.node = {
+  services.prometheus.exporters.node = {
     enable = true;
     enabledCollectors = [
       "logind"
@@ -112,65 +112,67 @@
      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 }:
+# nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
+{ config, lib, pkgs, options }:
 
-        with lib;
+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
+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.
-              '';
-            };
-          };
+  # `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.
-          serviceOpts = {
-            serviceConfig = {
-              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>
+  # `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>
@@ -184,4 +186,42 @@
    </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/nixos/modules/services/monitoring/prometheus/exporters/bind.nix b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
index a9746c4d65d5..972632b5a24a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -39,7 +39,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-bind-exporter}/bin/bind_exporter \
           -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
index d09d1c4f3663..8a90afa99842 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
@@ -1,11 +1,36 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
 let
+  logPrefix = "services.prometheus.exporter.blackbox";
   cfg = config.services.prometheus.exporters.blackbox;
-in
-{
+
+  # 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 {
@@ -14,15 +39,29 @@ in
         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 = {
+
+  serviceOpts = let
+    adjustedConfigFile = if cfg.enableConfigCheck then
+      checkConfig cfg.configFile
+    else
+      checkConfigLocation cfg.configFile;
+  in {
     serviceConfig = {
       AmbientCapabilities = [ "CAP_NET_RAW" ]; # for ping probes
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-blackbox-exporter}/bin/blackbox_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-          --config.file ${cfg.configFile} \
+          --config.file ${adjustedConfigFile} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
       ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
index 0eba3527162d..1cc346418091 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/collectd.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -64,7 +64,6 @@ in
     '' else "";
   in {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-collectd-exporter}/bin/collectd_exporter \
           -log.format ${cfg.logFormat} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
index b1fab85109af..e9fa26cb1f5a 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/dnsmasq.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -26,7 +26,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-dnsmasq-exporter}/bin/dnsmasq_exporter \
           --listen ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
index c47e87a3dc35..a01074758ff8 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -39,8 +39,8 @@ in
             mail_plugins = $mail_plugins old_stats
             service old-stats {
               unix_listener old-stats {
-                user = nobody
-                group = nobody
+                user = dovecot-exporter
+                group = dovecot-exporter
               }
             }
           ''';
@@ -59,6 +59,7 @@ in
   };
   serviceOpts = {
     serviceConfig = {
+      DynamicUser = false;
       ExecStart = ''
         ${pkgs.prometheus-dovecot-exporter}/bin/dovecot_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
index a3f1d9d31323..9526597b8c96 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/fritzbox.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -26,9 +26,8 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
-        ${pkgs.prometheus-fritzbox-exporter}/bin/fritzbox_exporter \
+        ${pkgs.prometheus-fritzbox-exporter}/bin/exporter \
           -listen-address ${cfg.listenAddress}:${toString cfg.port} \
           -gateway-address ${cfg.gatewayAddress} \
           -gateway-port ${toString cfg.gatewayPort} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/json.nix b/nixos/modules/services/monitoring/prometheus/exporters/json.nix
index a5494e85e016..82a55bafc982 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/json.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/json.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -24,7 +24,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-json-exporter}/bin/prometheus-json-exporter \
           --port ${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/mail.nix b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
new file mode 100644
index 000000000000..7d8c6fb61404
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/mail.nix
@@ -0,0 +1,157 @@
+{ 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".
+      '';
+    };
+    disableFileDelition = 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.submodule exporterOptions;
+      default = {};
+      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} \
+          --config.file ${
+            if cfg.configuration != {} then configurationFile else cfg.configFile
+          } \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
index 3cc4ffdbc8fd..ab3e3d7d5d50 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/minio.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -50,7 +50,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-minio-exporter}/bin/minio-exporter \
           -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix b/nixos/modules/services/monitoring/prometheus/exporters/nextcloud.nix
new file mode 100644
index 000000000000..5f9a52053f79
--- /dev/null
+++ b/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 @${cfg.passwordFile} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
index 431dd8b4ead7..ba852fea4336 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -16,32 +16,39 @@ in
         Can be enabled with services.nginx.statusPage = true.
       '';
     };
-    telemetryEndpoint = mkOption {
+    telemetryPath = mkOption {
       type = types.str;
       default = "/metrics";
       description = ''
         Path under which to expose metrics.
       '';
     };
-    insecure = mkOption {
+    sslVerify = mkOption {
       type = types.bool;
       default = true;
       description = ''
-        Ignore server certificate if using https.
+        Whether to perform certificate verification for https.
       '';
     };
+
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
-        ${pkgs.prometheus-nginx-exporter}/bin/nginx_exporter \
-          --nginx.scrape_uri '${cfg.scrapeUri}' \
-          --telemetry.address ${cfg.listenAddress}:${toString cfg.port} \
-          --telemetry.endpoint ${cfg.telemetryEndpoint} \
-          --insecure ${toString cfg.insecure} \
+        ${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} \
           ${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/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixos/modules/services/monitoring/prometheus/exporters/node.nix
index ee7bf39f199a..adc2abe0b91c 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/node.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/node.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -9,7 +9,7 @@ in
   port = 9100;
   extraOpts = {
     enabledCollectors = mkOption {
-      type = types.listOf types.string;
+      type = types.listOf types.str;
       default = [];
       example = ''[ "systemd" ]'';
       description = ''
@@ -27,13 +27,13 @@ in
   };
   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 " \\\n  " cfg.extraFlags}
+          --web.listen-address ${cfg.listenAddress}:${toString cfg.port} ${concatStringsSep " " cfg.extraFlags}
       '';
     };
   };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
index efe78ebcba86..d50564717eaf 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/postfix.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -62,6 +62,7 @@ in
   };
   serviceOpts = {
     serviceConfig = {
+      DynamicUser = false;
       ExecStart = ''
         ${pkgs.prometheus-postfix-exporter}/bin/postfix_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
@@ -73,7 +74,7 @@ in
                                           then "--systemd.slice ${cfg.systemd.slice}"
                                           else "--systemd.unit ${cfg.systemd.unit}")
           ++ optional (cfg.systemd.enable && (cfg.systemd.journalPath != null))
-                       "--systemd.jounal_path ${cfg.systemd.journalPath}"
+                       "--systemd.journal_path ${cfg.systemd.journalPath}"
           ++ optional (!cfg.systemd.enable) "--postfix.logfile_path ${cfg.logfilePath}")}
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix b/nixos/modules/services/monitoring/prometheus/exporters/postgres.nix
new file mode 100644
index 000000000000..1ece73a1159a
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix b/nixos/modules/services/monitoring/prometheus/exporters/rspamd.nix
new file mode 100644
index 000000000000..1f02ae207249
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index 0d9194124325..fe7ae8a8ac90 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -57,7 +57,6 @@ in
                  else "${pkgs.writeText "snmp-eporter-conf.yml" (builtins.toJSON cfg.configuration)}";
     in {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-snmp-exporter.bin}/bin/snmp_exporter \
           --config.file=${configFile} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix b/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
index 715dba06a3dc..81c5c70ed93f 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/surfboard.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -20,7 +20,6 @@ in
     description = "Prometheus exporter for surfboard cable modem";
     unitConfig.Documentation = "https://github.com/ipstatic/surfboard_exporter";
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-surfboard-exporter}/bin/surfboard_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
index e0ae83802425..36c473677efa 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -26,7 +26,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-tor-exporter}/bin/prometheus-tor-exporter \
           -b ${cfg.listenAddress} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
index 011dcbe208e4..9aa0f1b85aac 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/unifi.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -51,7 +51,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-unifi-exporter}/bin/unifi_exporter \
           -telemetry.addr ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
index aaed76175b84..12153fa021ec 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -68,8 +68,8 @@ in
   serviceOpts = {
     path = [ pkgs.varnish ];
     serviceConfig = {
-      DynamicUser = true;
       RestartSec = mkDefault 1;
+      DynamicUser = false;
       ExecStart = ''
         ${pkgs.prometheus-varnish-exporter}/bin/prometheus_varnish_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
new file mode 100644
index 000000000000..374f83a2939d
--- /dev/null
+++ b/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 ${cfg.wireguardConfig}"}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/riemann-dash.nix b/nixos/modules/services/monitoring/riemann-dash.nix
index 7eb4d888b0cc..16eb83008509 100644
--- a/nixos/modules/services/monitoring/riemann-dash.nix
+++ b/nixos/modules/services/monitoring/riemann-dash.nix
@@ -59,18 +59,20 @@ in {
       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
-        chown -R riemanndash:riemanndash ${cfg.dataDir}
+        mkdir -p '${cfg.dataDir}/config'
       '';
       serviceConfig = {
         User = "riemanndash";
         ExecStart = "${launcher}/bin/riemann-dash";
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/monitoring/riemann-tools.nix b/nixos/modules/services/monitoring/riemann-tools.nix
index 4e8832dadc5e..86a11694e7b4 100644
--- a/nixos/modules/services/monitoring/riemann-tools.nix
+++ b/nixos/modules/services/monitoring/riemann-tools.nix
@@ -11,7 +11,7 @@ let
 
   healthLauncher = writeScriptBin "riemann-health" ''
     #!/bin/sh
-    exec ${pkgs.riemann-tools}/bin/riemann-health --host ${riemannHost}
+    exec ${pkgs.riemann-tools}/bin/riemann-health ${builtins.concatStringsSep " " cfg.extraArgs} --host ${riemannHost}
   '';
 
 
@@ -34,8 +34,16 @@ in {
           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 {
@@ -54,7 +62,6 @@ in {
       serviceConfig = {
         User = "riemanntools";
         ExecStart = "${healthLauncher}/bin/riemann-health";
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/monitoring/scollector.nix b/nixos/modules/services/monitoring/scollector.nix
index fbded746a5f7..38cd2213de76 100644
--- a/nixos/modules/services/monitoring/scollector.nix
+++ b/nixos/modules/services/monitoring/scollector.nix
@@ -51,7 +51,7 @@ in {
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "scollector";
         description = ''
           User account under which scollector runs.
@@ -59,7 +59,7 @@ in {
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "scollector";
         description = ''
           Group account under which scollector runs.
@@ -67,7 +67,7 @@ in {
       };
 
       bosunHost = mkOption {
-        type = types.string;
+        type = types.str;
         default = "localhost:8070";
         description = ''
           Host and port of the bosun server that will store the collected
@@ -116,7 +116,6 @@ in {
       path = [ pkgs.coreutils pkgs.iproute ];
 
       serviceConfig = {
-        PermissionsStartOnly = true;
         User = cfg.user;
         Group = cfg.group;
         ExecStart = "${cfg.package.bin}/bin/scollector -conf=${conf} ${lib.concatStringsSep " " cfg.extraOpts}";
diff --git a/nixos/modules/services/monitoring/statsd.nix b/nixos/modules/services/monitoring/statsd.nix
index ea155821ecc9..17836e95a6f8 100644
--- a/nixos/modules/services/monitoring/statsd.nix
+++ b/nixos/modules/services/monitoring/statsd.nix
@@ -125,8 +125,7 @@ in
       message = "Only builtin backends (graphite, console, repeater) or backends enumerated in `pkgs.nodePackages` are allowed!";
     }) cfg.backends;
 
-    users.users = singleton {
-      name = "statsd";
+    users.use.statsdrs = {
       uid = config.ids.uids.statsd;
       description = "Statsd daemon user";
     };
diff --git a/nixos/modules/services/monitoring/sysstat.nix b/nixos/modules/services/monitoring/sysstat.nix
index d668faa53cc3..ca2cff827232 100644
--- a/nixos/modules/services/monitoring/sysstat.nix
+++ b/nixos/modules/services/monitoring/sysstat.nix
@@ -5,15 +5,10 @@ let
 in {
   options = {
     services.sysstat = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable sar system activity collection.
-        '';
-      };
+      enable = mkEnableOption "sar system activity collection";
 
       collect-frequency = mkOption {
+        type = types.str;
         default = "*:00/10";
         description = ''
           OnCalendar specification for sysstat-collect
@@ -21,6 +16,7 @@ in {
       };
 
       collect-args = mkOption {
+        type = types.str;
         default = "1 1";
         description = ''
           Arguments to pass sa1 when collecting statistics
@@ -33,13 +29,13 @@ in {
     systemd.services.sysstat = {
       description = "Resets System Activity Logs";
       wantedBy = [ "multi-user.target" ];
-      preStart = "test -d /var/log/sa || mkdir -p /var/log/sa";
 
       serviceConfig = {
         User = "root";
         RemainAfterExit = true;
         Type = "oneshot";
         ExecStart = "${pkgs.sysstat}/lib/sa/sa1 --boot";
+        LogsDirectory = "sa";
       };
     };
 
diff --git a/nixos/modules/services/monitoring/systemhealth.nix b/nixos/modules/services/monitoring/systemhealth.nix
deleted file mode 100644
index 32d4314d5f77..000000000000
--- a/nixos/modules/services/monitoring/systemhealth.nix
+++ /dev/null
@@ -1,133 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.systemhealth;
-
-  systemhealth = with pkgs; stdenv.mkDerivation {
-    name = "systemhealth-1.0";
-    src = fetchurl {
-      url = "https://www.brianlane.com/downloads/systemhealth/systemhealth-1.0.tar.bz2";
-      sha256 = "1q69lz7hmpbdpbz36zb06nzfkj651413n9icx0njmyr3xzq1j9qy";
-    };
-    buildInputs = [ python ];
-    installPhase = ''
-      mkdir -p $out/bin
-      # Make it work for kernels 3.x, not so different than 2.6
-      sed -i 's/2\.6/4.0/' system_health.py
-      cp system_health.py $out/bin
-    '';
-  };
-
-  rrdDir = "/var/lib/health/rrd";
-  htmlDir = "/var/lib/health/html";
-
-  configFile = rrdDir + "/.syshealthrc";
-  # The program will try to read $HOME/.syshealthrc, so we set the proper home.
-  command = "HOME=${rrdDir} ${systemhealth}/bin/system_health.py";
-
-  cronJob = ''
-    */5 * * * * wwwrun ${command} --log
-    5 * * * * wwwrun ${command} --graph
-  '';
-
-  nameEqualName = s: "${s} = ${s}";
-  interfacesSection = concatStringsSep "\n" (map nameEqualName cfg.interfaces);
-
-  driveLine = d: "${d.path} = ${d.name}";
-  drivesSection = concatStringsSep "\n" (map driveLine cfg.drives);
-
-in
-{
-  options = {
-    services.systemhealth = {
-      enable = mkOption {
-        default = false;
-        description = ''
-          Enable the system health monitor and its generation of graphs.
-        '';
-      };
-
-      urlPrefix = mkOption {
-        default = "/health";
-        description = ''
-          The URL prefix under which the System Health web pages appear in httpd.
-        '';
-      };
-
-      interfaces = mkOption {
-        default = [ "lo" ];
-        example = [ "lo" "eth0" "eth1" ];
-        description = ''
-          Interfaces to monitor (minimum one).
-        '';
-      };
-
-      drives = mkOption {
-        default = [ ];
-        example = [ { name = "root"; path = "/"; } ];
-        description = ''
-          Drives to monitor.
-        '';
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    services.cron.systemCronJobs = [ cronJob ];
-
-    system.activationScripts.systemhealth = stringAfter [ "var" ]
-      ''
-        mkdir -p ${rrdDir} ${htmlDir}
-        chown wwwrun:wwwrun ${rrdDir} ${htmlDir}
-
-        cat >${configFile} << EOF
-        [paths]
-        rrdtool = ${pkgs.rrdtool}/bin/rrdtool
-        loadavg_rrd = loadavg
-        ps = /run/current-system/sw/bin/ps
-        df = /run/current-system/sw/bin/df
-        meminfo_rrd = meminfo
-        uptime_rrd = uptime
-        rrd_path = ${rrdDir}
-        png_path = ${htmlDir}
-
-        [processes]
-
-        [interfaces]
-        ${interfacesSection}
-
-        [drives]
-        ${drivesSection}
-
-        [graphs]
-        width = 400
-        time = ['-3hours', '-32hours', '-8days', '-5weeks', '-13months']
-        height = 100
-
-        [external]
-
-        EOF
-
-        chown wwwrun:wwwrun ${configFile}
-
-        ${pkgs.su}/bin/su -s "/bin/sh" -c "${command} --check" wwwrun
-        ${pkgs.su}/bin/su -s "/bin/sh" -c "${command} --html" wwwrun
-      '';
-
-    services.httpd.extraSubservices = [
-      { function = f: {
-          extraConfig = ''
-            Alias ${cfg.urlPrefix} ${htmlDir}
-
-            <Directory ${htmlDir}>
-                Order allow,deny
-                Allow from all
-            </Directory>
-          '';
-        };
-      }
-    ];
-  };
-}
diff --git a/nixos/modules/services/monitoring/telegraf.nix b/nixos/modules/services/monitoring/telegraf.nix
index d87867326682..5d131557e8be 100644
--- a/nixos/modules/services/monitoring/telegraf.nix
+++ b/nixos/modules/services/monitoring/telegraf.nix
@@ -63,10 +63,9 @@ in {
       };
     };
 
-    users.users = [{
-      name = "telegraf";
+    users.users.telegraf = {
       uid = config.ids.uids.telegraf;
       description = "telegraf daemon user";
-    }];
+    };
   };
 }
diff --git a/nixos/modules/services/monitoring/thanos.nix b/nixos/modules/services/monitoring/thanos.nix
new file mode 100644
index 000000000000..52dab28cf72f
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/ups.nix b/nixos/modules/services/monitoring/ups.nix
index bc755612fd9b..a45e806d4ad8 100644
--- a/nixos/modules/services/monitoring/ups.nix
+++ b/nixos/modules/services/monitoring/ups.nix
@@ -55,7 +55,7 @@ let
 
       description = mkOption {
         default = "";
-        type = types.string;
+        type = types.str;
         description = ''
           Description of the UPS.
         '';
@@ -71,7 +71,7 @@ let
 
       summary = mkOption {
         default = "";
-        type = types.string;
+        type = types.lines;
         description = ''
           Lines which would be added inside ups.conf for handling this UPS.
         '';
@@ -214,40 +214,28 @@ in
       environment.NUT_STATEPATH = "/var/lib/nut/";
     };
 
-    environment.etc = [
-      { source = pkgs.writeText "nut.conf"
+    environment.etc = {
+      "nut/nut.conf".source = pkgs.writeText "nut.conf"
         ''
           MODE = ${cfg.mode}
         '';
-        target = "nut/nut.conf";
-      }
-      { source = pkgs.writeText "ups.conf"
+      "nut/ups.conf".source = pkgs.writeText "ups.conf"
         ''
           maxstartdelay = ${toString cfg.maxStartDelay}
 
-          ${flip concatStringsSep (flip map (attrValues cfg.ups) (ups: ups.summary)) "
+          ${flip concatStringsSep (forEach (attrValues cfg.ups) (ups: ups.summary)) "
 
           "}
         '';
-        target = "nut/ups.conf";
-      }
-      { source = cfg.schedulerRules;
-        target = "nut/upssched.conf";
-      }
+      "nut/upssched.conf".source = cfg.schedulerRules;
       # These file are containing private informations and thus should not
       # be stored inside the Nix store.
       /*
-      { source = ;
-        target = "nut/upsd.conf";
-      }
-      { source = ;
-        target = "nut/upsd.users";
-      }
-      { source = ;
-        target = "nut/upsmon.conf;
-      }
+      "nut/upsd.conf".source = "";
+      "nut/upsd.users".source = "";
+      "nut/upsmon.conf".source = "";
       */
-    ];
+    };
 
     power.ups.schedulerRules = mkDefault "${pkgs.nut}/etc/upssched.conf.sample";
 
@@ -259,21 +247,16 @@ in
 
 
 /*
-    users.users = [
-      { name = "nut";
-        uid = 84;
+    users.users.nut =
+      { uid = 84;
         home = "/var/lib/nut";
         createHome = true;
         group = "nut";
         description = "UPnP A/V Media Server user";
-      }
-    ];
-
-    users.groups = [
-      { name = "nut";
-        gid = 84;
-      }
-    ];
+      };
+
+    users.groups."nut" =
+      { gid = 84; };
 */
 
   };
diff --git a/nixos/modules/services/monitoring/uptime.nix b/nixos/modules/services/monitoring/uptime.nix
index c0993f3bc2e7..245badc3e44f 100644
--- a/nixos/modules/services/monitoring/uptime.nix
+++ b/nixos/modules/services/monitoring/uptime.nix
@@ -57,7 +57,7 @@ in {
     nodeEnv = mkOption {
       description = "The node environment to run in (development, production, etc.)";
 
-      type = types.string;
+      type = types.str;
 
       default = "production";
     };
diff --git a/nixos/modules/services/monitoring/vnstat.nix b/nixos/modules/services/monitoring/vnstat.nix
index cb2f8c07edb9..e9bedb704a43 100644
--- a/nixos/modules/services/monitoring/vnstat.nix
+++ b/nixos/modules/services/monitoring/vnstat.nix
@@ -28,14 +28,29 @@ in {
       path = [ pkgs.coreutils ];
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      unitConfig.documentation = "man:vnstatd(1) man:vnstat(1) man:vnstat.conf(5)";
+      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";
-        ProtectHome = true;
+
+        # 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/nixos/modules/services/monitoring/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix
index 0519e7c2ad6a..b3383ed628b2 100644
--- a/nixos/modules/services/monitoring/zabbix-agent.nix
+++ b/nixos/modules/services/monitoring/zabbix-agent.nix
@@ -1,73 +1,118 @@
-# Zabbix agent daemon.
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   cfg = config.services.zabbixAgent;
 
-  zabbix = cfg.package;
-
-  stateDir = "/run/zabbix";
-
-  logDir = "/var/log/zabbix";
-
-  pidFile = "${stateDir}/zabbix_agentd.pid";
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+  inherit (lib) attrValues concatMapStringsSep literalExample optionalString types;
 
-  configFile = pkgs.writeText "zabbix_agentd.conf"
-    ''
-      Server = ${cfg.server}
+  user = "zabbix-agent";
+  group = "zabbix-agent";
 
-      LogFile = ${logDir}/zabbix_agentd
-
-      PidFile = ${pidFile}
-
-      StartAgents = 1
+  moduleEnv = pkgs.symlinkJoin {
+    name = "zabbix-agent-module-env";
+    paths = attrValues cfg.modules;
+  };
 
-      ${config.services.zabbixAgent.extraConfig}
-    '';
+  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
+  # interface
 
   options = {
 
     services.zabbixAgent = {
+      enable = mkEnableOption "the Zabbix Agent";
 
-      enable = mkOption {
-        default = false;
+      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 = ''
-          Whether to run the Zabbix monitoring agent on this machine.
-          It will send monitoring data to a Zabbix server.
+          Packages to be added to the Zabbix <envar>PATH</envar>.
+          Typically used to add executables for scripts, but can be anything.
         '';
       };
 
-      package = mkOption {
-        type = types.attrs; # Note: pkgs.zabbixXY isn't a derivation, but an attrset of { server = ...; agent = ...; }.
-        default = pkgs.zabbix;
-        defaultText = "pkgs.zabbix";
-        example = literalExample "pkgs.zabbix34";
-        description = ''
-          The Zabbix package to use.
+      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 {
-        default = "127.0.0.1";
+        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.
+          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.
         '';
       };
 
@@ -75,38 +120,39 @@ in
 
   };
 
-
-  ###### implementation
+  # implementation
 
   config = mkIf cfg.enable {
 
-    users.users = mkIf (!config.services.zabbixServer.enable) (singleton
-      { name = "zabbix";
-        uid = config.ids.uids.zabbix;
-        description = "Zabbix daemon user";
-      });
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ cfg.listen.port ];
+    };
 
-    systemd.services."zabbix-agent" =
-      { description = "Zabbix Agent";
+    users.users.${user} = {
+      description = "Zabbix Agent daemon user";
+      inherit group;
+      isSystemUser = true;
+    };
 
-        wantedBy = [ "multi-user.target" ];
+    users.groups.${group} = { };
 
-        path = [ pkgs.nettools ];
+    systemd.services.zabbix-agent = {
+      description = "Zabbix Agent";
 
-        preStart =
-          ''
-            mkdir -m 0755 -p ${stateDir} ${logDir}
-            chown zabbix ${stateDir} ${logDir}
-          '';
+      wantedBy = [ "multi-user.target" ];
 
-        serviceConfig.ExecStart = "@${zabbix.agent}/sbin/zabbix_agentd zabbix_agentd --config ${configFile}";
-        serviceConfig.Type = "forking";
-        serviceConfig.RemainAfterExit = true;
-        serviceConfig.Restart = "always";
-        serviceConfig.RestartSec = 2;
-      };
+      path = [ "/run/wrappers" ] ++ cfg.extraPackages;
+
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/zabbix_agentd zabbix_agentd -f --config ${configFile}";
+        Restart = "always";
+        RestartSec = 2;
 
-    environment.systemPackages = [ zabbix.agent ];
+        User = user;
+        Group = group;
+        PrivateTmp = true;
+      };
+    };
 
   };
 
diff --git a/nixos/modules/services/monitoring/zabbix-proxy.nix b/nixos/modules/services/monitoring/zabbix-proxy.nix
new file mode 100644
index 000000000000..9d214469c3b3
--- /dev/null
+++ b/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/nixos/modules/services/monitoring/zabbix-server.nix b/nixos/modules/services/monitoring/zabbix-server.nix
index fdeab6af4417..b4e4378ce1e7 100644
--- a/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixos/modules/services/monitoring/zabbix-server.nix
@@ -1,125 +1,298 @@
-# Zabbix server daemon.
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
-
   cfg = config.services.zabbixServer;
+  pgsql = config.services.postgresql;
+  mysql = config.services.mysql;
 
-  stateDir = "/run/zabbix";
+  inherit (lib) mkDefault mkEnableOption mkIf mkOption;
+  inherit (lib) attrValues concatMapStringsSep literalExample optional optionalAttrs optionalString types;
 
-  logDir = "/var/log/zabbix";
+  user = "zabbix";
+  group = "zabbix";
+  runtimeDir = "/run/zabbix";
+  stateDir = "/var/lib/zabbix";
+  passwordFile = "${runtimeDir}/zabbix-dbpassword.conf";
 
-  libDir = "/var/lib/zabbix";
+  moduleEnv = pkgs.symlinkJoin {
+    name = "zabbix-server-module-env";
+    paths = attrValues cfg.modules;
+  };
 
-  pidFile = "${stateDir}/zabbix_server.pid";
+  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";
 
-  configFile = pkgs.writeText "zabbix_server.conf"
-    ''
-      LogFile = ${logDir}/zabbix_server
+in
 
-      PidFile = ${pidFile}
+{
+  imports = [
+    (lib.mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ])
+    (lib.mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.")
+  ];
 
-      ${optionalString (cfg.dbServer != "localhost") ''
-        DBHost = ${cfg.dbServer}
-      ''}
+  # interface
 
-      DBName = zabbix
+  options = {
 
-      DBUser = zabbix
+    services.zabbixServer = {
+      enable = mkEnableOption "the Zabbix Server";
 
-      ${optionalString (cfg.dbPassword != "") ''
-        DBPassword = ${cfg.dbPassword}
-      ''}
+      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.";
+      };
 
-      ${config.services.zabbixServer.extraConfig}
-    '';
+      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.
+        '';
+      };
 
-  useLocalPostgres = cfg.dbServer == "localhost" || cfg.dbServer == "";
+      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/
+              ''';
+            };
+          }
+        '';
+      };
 
-in
+      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.
+          '';
+        };
 
-  ###### interface
+        port = mkOption {
+          type = types.port;
+          default = 10051;
+          description = ''
+            Listen port for trapper.
+          '';
+        };
+      };
 
-  options = {
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Zabbix Server.
+        '';
+      };
 
-    services.zabbixServer.enable = mkOption {
-      default = false;
-      type = types.bool;
-      description = ''
-        Whether to run the Zabbix server on this machine.
-      '';
-    };
+      # 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.
+        '';
+      };
 
-    services.zabbixServer.dbServer = mkOption {
-      default = "localhost";
-      type = types.str;
-      description = ''
-        Hostname or IP address of the database server.
-        Use an empty string ("") to use peer authentication.
-      '';
     };
 
-    services.zabbixServer.dbPassword = mkOption {
-      default = "";
-      type = types.str;
-      description = "Password used to connect to the database server.";
-    };
+  };
 
-    services.zabbixServer.extraConfig = mkOption {
-      default = "";
-      type = types.lines;
-      description = ''
-        Configuration that is injected verbatim into the configuration file.
-      '';
+  # 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"; };
+        }
+      ];
+    };
 
-  ###### implementation
+    services.postgresql = optionalAttrs pgsqlLocal {
+      enable = true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.database.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
+    };
 
-  config = mkIf cfg.enable {
+    users.users.${user} = {
+      description = "Zabbix daemon user";
+      uid = config.ids.uids.zabbix;
+      inherit group;
+    };
 
-    services.postgresql.enable = useLocalPostgres;
+    users.groups.${group} = {
+      gid = config.ids.gids.zabbix;
+    };
 
-    users.users = singleton
-      { name = "zabbix";
-        uid = config.ids.uids.zabbix;
-        description = "Zabbix daemon user";
-      };
+    security.wrappers = {
+      fping.source = "${pkgs.fping}/bin/fping";
+    };
 
-    systemd.services."zabbix-server" =
-      { description = "Zabbix Server";
-
-        wantedBy = [ "multi-user.target" ];
-        after = optional useLocalPostgres "postgresql.service";
-
-        preStart =
-          ''
-            mkdir -m 0755 -p ${stateDir} ${logDir} ${libDir}
-            chown zabbix ${stateDir} ${logDir} ${libDir}
-
-            if ! test -e "${libDir}/db-created"; then
-                ${pkgs.su}/bin/su -s "$SHELL" ${config.services.postgresql.superUser} -c '${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole zabbix' || true
-                ${pkgs.su}/bin/su -s "$SHELL" ${config.services.postgresql.superUser} -c '${pkgs.postgresql}/bin/createdb --owner zabbix zabbix' || true
-                cat ${pkgs.zabbix.server}/share/zabbix/db/schema/postgresql.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix'
-                cat ${pkgs.zabbix.server}/share/zabbix/db/data/images_pgsql.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix'
-                cat ${pkgs.zabbix.server}/share/zabbix/db/data/data.sql | ${pkgs.su}/bin/su -s "$SHELL" zabbix -c '${pkgs.postgresql}/bin/psql zabbix'
-                touch "${libDir}/db-created"
-            fi
-          '';
+    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}
+      '';
 
-        path = [ pkgs.nettools ];
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/zabbix_server zabbix_server -f --config ${configFile}";
+        Restart = "always";
+        RestartSec = 2;
 
-        serviceConfig.ExecStart = "@${pkgs.zabbix.server}/sbin/zabbix_server zabbix_server --config ${configFile}";
-        serviceConfig.Type = "forking";
-        serviceConfig.Restart = "always";
-        serviceConfig.RestartSec = 2;
-        serviceConfig.PIDFile = pidFile;
+        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/nixos/modules/services/network-filesystems/beegfs.nix b/nixos/modules/services/network-filesystems/beegfs.nix
deleted file mode 100644
index 86b1bb9160f1..000000000000
--- a/nixos/modules/services/network-filesystems/beegfs.nix
+++ /dev/null
@@ -1,357 +0,0 @@
-{ config, lib, pkgs, ...} :
-
-with lib;
-
-let
-  cfg = config.services.beegfs;
-
-  # functions for the generations of config files
-
-  configMgmtd = name: cfg: pkgs.writeText "mgmt-${name}.conf" ''
-    storeMgmtdDirectory = ${cfg.mgmtd.storeDir}
-    storeAllowFirstRunInit = false
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-
-    ${cfg.mgmtd.extraConfig}
-  '';
-
-  configAdmon = name: cfg: pkgs.writeText "admon-${name}.conf" ''
-    sysMgmtdHost = ${cfg.mgmtdHost}
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-
-    ${cfg.admon.extraConfig}
-  '';
-
-  configMeta = name: cfg: pkgs.writeText "meta-${name}.conf" ''
-    storeMetaDirectory = ${cfg.meta.storeDir}
-    sysMgmtdHost = ${cfg.mgmtdHost}
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-    storeAllowFirstRunInit = false
-
-    ${cfg.meta.extraConfig}
-  '';
-
-  configStorage = name: cfg: pkgs.writeText "storage-${name}.conf" ''
-    storeStorageDirectory = ${cfg.storage.storeDir}
-    sysMgmtdHost = ${cfg.mgmtdHost}
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-    storeAllowFirstRunInit = false
-
-    ${cfg.storage.extraConfig}
-  '';
-
-  configHelperd = name: cfg: pkgs.writeText "helperd-${name}.conf" ''
-    connAuthFile = ${cfg.connAuthFile}
-    ${cfg.helperd.extraConfig}
-  '';
-
-  configClientFilename = name : "/etc/beegfs/client-${name}.conf";
-
-  configClient = name: cfg: ''
-    sysMgmtdHost = ${cfg.mgmtdHost}
-    connAuthFile = ${cfg.connAuthFile}
-    connPortShift = ${toString cfg.connPortShift}
-
-    ${cfg.client.extraConfig}
-  '';
-
-  serviceList = [
-    { service = "admon"; cfgFile = configAdmon; }
-    { service = "meta"; cfgFile = configMeta; }
-    { service = "mgmtd"; cfgFile = configMgmtd; }
-    { service = "storage"; cfgFile = configStorage; }
-  ];
-
-  # functions to generate systemd.service entries
-
-  systemdEntry = service: cfgFile: (mapAttrs' ( name: cfg:
-    (nameValuePair "beegfs-${service}-${name}" (mkIf cfg."${service}".enable {
-    wantedBy = [ "multi-user.target" ];
-    requires = [ "network-online.target" ];
-    after = [ "network-online.target" ];
-    serviceConfig = rec {
-      ExecStart = ''
-        ${pkgs.beegfs}/bin/beegfs-${service} \
-          cfgFile=${cfgFile name cfg} \
-          pidFile=${PIDFile}
-      '';
-      PIDFile = "/run/beegfs-${service}-${name}.pid";
-      TimeoutStopSec = "300";
-    };
-  }))) cfg);
-
-  systemdHelperd =  mapAttrs' ( name: cfg:
-    (nameValuePair "beegfs-helperd-${name}" (mkIf cfg.client.enable {
-    wantedBy = [ "multi-user.target" ];
-    requires = [ "network-online.target" ];
-    after = [ "network-online.target" ];
-    serviceConfig = rec {
-      ExecStart = ''
-        ${pkgs.beegfs}/bin/beegfs-helperd \
-          cfgFile=${configHelperd name cfg} \
-          pidFile=${PIDFile}
-      '';
-      PIDFile = "/run/beegfs-helperd-${name}.pid";
-      TimeoutStopSec = "300";
-    };
-   }))) cfg;
-
-  # wrappers to beegfs tools. Avoid typing path of config files
-  utilWrappers = mapAttrsToList ( name: cfg:
-    ( pkgs.runCommand "beegfs-utils-${name}" {
-        nativeBuildInputs = [ pkgs.makeWrapper ];
-        preferLocalBuild = true;
-        } ''
-        mkdir -p $out/bin
-
-        makeWrapper ${pkgs.beegfs}/bin/beegfs-check-servers \
-                    $out/bin/beegfs-check-servers-${name} \
-                    --add-flags "-c ${configClientFilename name}" \
-                    --prefix PATH : ${lib.makeBinPath [ pkgs.beegfs ]}
-
-        makeWrapper ${pkgs.beegfs}/bin/beegfs-ctl \
-                    $out/bin/beegfs-ctl-${name} \
-                    --add-flags "--cfgFile=${configClientFilename name}"
-
-        makeWrapper ${pkgs.beegfs}/bin/beegfs-ctl \
-                    $out/bin/beegfs-df-${name} \
-                    --add-flags "--cfgFile=${configClientFilename name}" \
-                    --add-flags --listtargets  \
-                    --add-flags --hidenodeid \
-                    --add-flags --pools \
-                    --add-flags --spaceinfo
-
-        makeWrapper ${pkgs.beegfs}/bin/beegfs-fsck \
-                    $out/bin/beegfs-fsck-${name} \
-                    --add-flags "--cfgFile=${configClientFilename name}"
-      ''
-     )) cfg;
-in
-{
-  ###### interface
-
-  options = {
-    services.beegfsEnable = mkEnableOption "BeeGFS";
-
-    services.beegfs = mkOption {
-      default = {};
-      description = ''
-        BeeGFS configurations. Every mount point requires a separate configuration.
-      '';
-      type = with types; attrsOf (submodule ({ ... } : {
-        options = {
-          mgmtdHost = mkOption {
-            type = types.str;
-            default = null;
-            example = "master";
-            description = ''Hostname of managament host.'';
-          };
-
-          connAuthFile = mkOption {
-            type = types.str;
-            default = "";
-            example = "/etc/my.key";
-            description = "File containing shared secret authentication.";
-          };
-
-          connPortShift = mkOption {
-            type = types.int;
-            default = 0;
-            example = 5;
-            description = ''
-              For each additional beegfs configuration shift all
-              service TCP/UDP ports by at least 5.
-            '';
-          };
-
-          client = {
-            enable = mkEnableOption "BeeGFS client";
-
-            mount = mkOption {
-              type = types.bool;
-              default = true;
-              description = "Create fstab entry automatically";
-            };
-
-            mountPoint = mkOption {
-              type = types.str;
-              default = "/run/beegfs";
-              description = ''
-                Mount point under which the beegfs filesytem should be mounted.
-                If mounted manually the mount option specifing the config file is needed:
-                cfgFile=/etc/beegfs/beegfs-client-&lt;name&gt;.conf
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.lines;
-              default = "";
-              description = ''
-                Additional lines for beegfs-client.conf.
-                See documentation for further details.
-             '';
-            };
-          };
-
-          helperd = {
-            enable = mkOption {
-              type = types.bool;
-              default = true;
-              description = ''
-                Enable the BeeGFS helperd.
-                The helpered is need for logging purposes on the client.
-                Disabling <literal>helperd</literal> allows for runing the client
-                with <literal>allowUnfree = false</literal>.
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.lines;
-              default = "";
-              description = ''
-                Additional lines for beegfs-helperd.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-
-          mgmtd = {
-            enable = mkEnableOption "BeeGFS mgmtd daemon";
-
-            storeDir = mkOption {
-              type = types.path;
-              default = null;
-              example = "/data/beegfs-mgmtd";
-              description = ''
-                Data directory for mgmtd.
-                Must not be shared with other beegfs daemons.
-                This directory must exist and it must be initialized
-                with beegfs-setup-mgmtd, e.g. "beegfs-setup-mgmtd -C -p &lt;storeDir&gt;"
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.lines;
-              default = "";
-              description = ''
-                Additional lines for beegfs-mgmtd.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-
-          admon = {
-            enable = mkEnableOption "BeeGFS admon daemon";
-
-            extraConfig = mkOption {
-              type = types.lines;
-              default = "";
-              description = ''
-                Additional lines for beegfs-admon.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-
-          meta = {
-            enable = mkEnableOption "BeeGFS meta data daemon";
-
-            storeDir = mkOption {
-              type = types.path;
-              default = null;
-              example = "/data/beegfs-meta";
-              description = ''
-                Data directory for meta data service.
-                Must not be shared with other beegfs daemons.
-                The underlying filesystem must be mounted with xattr turned on.
-                This directory must exist and it must be initialized
-                with beegfs-setup-meta, e.g.
-                "beegfs-setup-meta -C -s &lt;serviceID&gt; -p &lt;storeDir&gt;"
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.str;
-              default = "";
-              description = ''
-                Additional lines for beegfs-meta.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-
-          storage = {
-            enable = mkEnableOption "BeeGFS storage daemon";
-
-            storeDir = mkOption {
-              type = types.path;
-              default = null;
-              example = "/data/beegfs-storage";
-              description = ''
-                Data directories for storage service.
-                Must not be shared with other beegfs daemons.
-                The underlying filesystem must be mounted with xattr turned on.
-                This directory must exist and it must be initialized
-                with beegfs-setup-storage, e.g.
-                "beegfs-setup-storage -C -s &lt;serviceID&gt; -i &lt;storageTargetID&gt; -p &lt;storeDir&gt;"
-              '';
-            };
-
-            extraConfig = mkOption {
-              type = types.str;
-              default = "";
-              description = ''
-                Addional lines for beegfs-storage.conf. See documentation
-                for further details.
-              '';
-            };
-          };
-        };
-      }));
-    };
-  };
-
-  ###### implementation
-
-  config =
-    mkIf config.services.beegfsEnable {
-
-    environment.systemPackages = utilWrappers;
-
-    # Put the client.conf files in /etc since they are needed
-    # by the commandline tools
-    environment.etc = mapAttrs' ( name: cfg:
-      (nameValuePair "beegfs/client-${name}.conf" (mkIf (cfg.client.enable)
-    {
-      enable = true;
-      text = configClient name cfg;
-    }))) cfg;
-
-    # Kernel module, we need it only once per host.
-    boot = mkIf (
-      foldr (a: b: a || b) false
-        (map (x: x.client.enable) (collect (x: x ? client) cfg)))
-    {
-      kernelModules = [ "beegfs" ];
-      extraModulePackages = [ pkgs.linuxPackages.beegfs-module ];
-    };
-
-    # generate fstab entries
-    fileSystems = mapAttrs' (name: cfg:
-      (nameValuePair cfg.client.mountPoint (optionalAttrs cfg.client.mount (mkIf cfg.client.enable {
-      device = "beegfs_nodev";
-      fsType = "beegfs";
-      mountPoint = cfg.client.mountPoint;
-      options = [ "cfgFile=${configClientFilename name}" "_netdev" ];
-    })))) cfg;
-
-    # generate systemd services
-    systemd.services = systemdHelperd //
-      foldr (a: b: a // b) {}
-        (map (x: systemdEntry x.service x.cfgFile) serviceList);
-  };
-}
diff --git a/nixos/modules/services/network-filesystems/ceph.nix b/nixos/modules/services/network-filesystems/ceph.nix
index 4e3bc839d400..d17959a6a305 100644
--- a/nixos/modules/services/network-filesystems/ceph.nix
+++ b/nixos/modules/services/network-filesystems/ceph.nix
@@ -3,25 +3,32 @@
 with lib;
 
 let
-  ceph = pkgs.ceph;
   cfg  = config.services.ceph;
+
   # function that translates "camelCaseOptions" to "camel case options", credits to tilpner in #nixos@freenode
-  translateOption = replaceStrings upperChars (map (s: " ${s}") lowerChars);
-  generateDaemonList = (daemonType: daemons: extraServiceConfig:
-    mkMerge (
-      map (daemon: 
-        { "ceph-${daemonType}-${daemon}" = generateServiceFile daemonType daemon cfg.global.clusterName ceph extraServiceConfig; }
-      ) daemons
-    )
-  );
-  generateServiceFile = (daemonType: daemonId: clusterName: ceph: extraServiceConfig: {
+  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" "local-fs.target" "time-sync.target" ] ++ optional (daemonType == "osd") "ceph-mon.target";
-    wants = [ "network-online.target" "local-fs.target" "time-sync.target" ];
+    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;
@@ -34,28 +41,35 @@ let
       Restart = "on-failure";
       StartLimitBurst = "5";
       StartLimitInterval = "30min";
-      ExecStart = "${ceph.out}/bin/${if daemonType == "rgw" then "radosgw" else "ceph-${daemonType}"} -f --cluster ${clusterName} --id ${if daemonType == "rgw" then "client.${daemonId}" else daemonId} --setuser ceph --setgroup ceph";
-    } // extraServiceConfig
-      // optionalAttrs (daemonType == "osd") { ExecStartPre = "${ceph.out}/libexec/ceph/ceph-osd-prestart.sh --id ${daemonId} --cluster ${clusterName}"; };
-    } // optionalAttrs (builtins.elem daemonType [ "mds" "mon" "rgw" "mgr" ]) { preStart = ''
-        daemonPath="/var/lib/ceph/${if daemonType == "rgw" then "radosgw" else daemonType}/${clusterName}-${daemonId}"
-        if [ ! -d ''$daemonPath ]; then
-          mkdir -m 755 -p ''$daemonPath
-          chown -R ceph:ceph ''$daemonPath 
-        fi
-      '';
-    } // optionalAttrs (daemonType == "osd") { path = [ pkgs.getopt ]; }
-  );
-  generateTargetFile = (daemonType:
+      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 
+in
 {
   options.services.ceph = {
     # Ceph has a monolithic configuration file but different sections for
@@ -82,11 +96,19 @@ in
         '';
       };
 
+      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 
+          node0, node1, node2
         '';
         description = ''
           List of hosts that will be used as monitors at startup.
@@ -157,6 +179,27 @@ in
           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 = {
@@ -216,6 +259,7 @@ in
           to the id part in ceph i.e. [ "name1" ] would result in osd.name1
         '';
       };
+
       extraConfig = mkOption {
         type = with types; attrsOf str;
         default = {
@@ -296,9 +340,6 @@ in
       { assertion = cfg.global.fsid != "";
         message = "fsid has to be set to a valid uuid for the cluster to function";
       }
-      { assertion = cfg.mgr.enable == true;
-        message = "ceph 12.x requires atleast 1 MGR daemon enabled 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";
       }
@@ -313,59 +354,65 @@ in
       }
     ];
 
-    warnings = optional (cfg.global.monInitialMembers == null) 
+    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
-      # Translate camelCaseOptions to the expected camel case option for ceph.conf
-      translatedGlobalConfig = mapAttrs' (name: value: nameValuePair (translateOption name) value) cfg.global;
       # Merge the extraConfig set for mgr daemons, as mgr don't have their own section
-      globalAndMgrConfig = translatedGlobalConfig // optionalAttrs cfg.mgr.enable cfg.mgr.extraConfig;
+      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
-      globalConfig = mapAttrs' (name: value: nameValuePair (translateOption name) value) (filterAttrs (name: value: value != null) globalAndMgrConfig);
+      globalSection' = filterAttrs (name: value: value != null) globalSection;
       totalConfig = {
-          "global" = globalConfig;
-        } // 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; }
+          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 = singleton {
-      name = "ceph";
+    users.users.ceph = {
       uid = config.ids.uids.ceph;
       description = "Ceph daemon user";
+      group = "ceph";
+      extraGroups = [ "disk" ];
     };
 
-    users.groups = singleton {
-      name = "ceph";
+    users.groups.ceph = {
       gid = config.ids.gids.ceph;
     };
 
     systemd.services = let
-      services = [] 
-        ++ optional cfg.mon.enable (generateDaemonList "mon" cfg.mon.daemons { RestartSec = "10"; }) 
-        ++ optional cfg.mds.enable (generateDaemonList "mds" cfg.mds.daemons { StartLimitBurst = "3"; })
-        ++ optional cfg.osd.enable (generateDaemonList "osd" cfg.osd.daemons { StartLimitBurst = "30"; RestartSec = "20s"; })
-        ++ optional cfg.rgw.enable (generateDaemonList "rgw" cfg.rgw.daemons { })
-        ++ optional cfg.mgr.enable (generateDaemonList "mgr" cfg.mgr.daemons { StartLimitBurst = "3"; });
-      in 
+      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"; }; }
-      ] ++ optional cfg.mon.enable (generateTargetFile "mon")
-        ++ optional cfg.mds.enable (generateTargetFile "mds")
-        ++ optional cfg.osd.enable (generateTargetFile "osd")
-        ++ optional cfg.rgw.enable (generateTargetFile "rgw")
-        ++ optional cfg.mgr.enable (generateTargetFile "mgr");
+        { 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/nixos/modules/services/network-filesystems/davfs2.nix b/nixos/modules/services/network-filesystems/davfs2.nix
index c16e12378d75..4b6f85e4a2c9 100644
--- a/nixos/modules/services/network-filesystems/davfs2.nix
+++ b/nixos/modules/services/network-filesystems/davfs2.nix
@@ -21,7 +21,7 @@ in
     };
 
     davUser = mkOption {
-      type = types.string;
+      type = types.str;
       default = "davfs2";
       description = ''
         When invoked by root the mount.davfs daemon will run as this user.
@@ -30,7 +30,7 @@ in
     };
 
     davGroup = mkOption {
-      type = types.string;
+      type = types.str;
       default = "davfs2";
       description = ''
         The group of the running mount.davfs daemon. Ordinary users must be
@@ -57,18 +57,19 @@ in
     environment.systemPackages = [ pkgs.davfs2 ];
     environment.etc."davfs2/davfs2.conf".source = cfgFile;
 
-    users.groups = optionalAttrs (cfg.davGroup == "davfs2") (singleton {
-      name = "davfs2";
-      gid = config.ids.gids.davfs2;
-    });
+    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";
+      };
+    };
 
-    users.users = optionalAttrs (cfg.davUser == "davfs2") (singleton {
-      name = "davfs2";
-      createHome = false;
-      group = cfg.davGroup;
-      uid = config.ids.uids.davfs2;
-      description = "davfs2 user";
-    });
   };
 
 }
diff --git a/nixos/modules/services/network-filesystems/drbd.nix b/nixos/modules/services/network-filesystems/drbd.nix
index 57b1fbb597c7..916e7eaaaa94 100644
--- a/nixos/modules/services/network-filesystems/drbd.nix
+++ b/nixos/modules/services/network-filesystems/drbd.nix
@@ -23,7 +23,7 @@ let cfg = config.services.drbd; in
 
     services.drbd.config = mkOption {
       default = "";
-      type = types.string;
+      type = types.lines;
       description = ''
         Contents of the <filename>drbd.conf</filename> configuration file.
       '';
@@ -47,10 +47,8 @@ let cfg = config.services.drbd; in
         options drbd usermode_helper=/run/current-system/sw/bin/drbdadm
       '';
 
-    environment.etc = singleton
-      { source = pkgs.writeText "drbd.conf" cfg.config;
-        target = "drbd.conf";
-      };
+    environment.etc.drbd.conf =
+      { source = pkgs.writeText "drbd.conf" cfg.config; };
 
     systemd.services.drbd = {
       after = [ "systemd-udev.settle.service" "network.target" ];
diff --git a/nixos/modules/services/network-filesystems/glusterfs.nix b/nixos/modules/services/network-filesystems/glusterfs.nix
index 00875c6c4a18..d70092999f67 100644
--- a/nixos/modules/services/network-filesystems/glusterfs.nix
+++ b/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -156,7 +156,7 @@ in
       wantedBy = [ "multi-user.target" ];
 
       requires = lib.optional cfg.useRpcbind "rpcbind.service";
-      after = [ "network.target" "local-fs.target" ] ++ lib.optional cfg.useRpcbind "rpcbind.service";
+      after = [ "network.target" ] ++ lib.optional cfg.useRpcbind "rpcbind.service";
 
       preStart = ''
         install -m 0755 -d /var/log/glusterfs
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index f19bf9d81396..b6d881afd7bd 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -208,9 +208,9 @@ in {
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ wrapped ];
-    environment.etc."fuse.conf" = mkIf cfg.autoMount { text = ''
-      user_allow_other
-    ''; };
+    programs.fuse = mkIf cfg.autoMount {
+      userAllowOther = true;
+    };
 
     users.users = mkIf (cfg.user == "ipfs") {
       ipfs = {
@@ -236,7 +236,6 @@ in {
     systemd.services.ipfs-init = recursiveUpdate commonEnv {
       description = "IPFS Initializer";
 
-      after = [ "local-fs.target" ];
       before = [ "ipfs.service" "ipfs-offline.service" "ipfs-norouting.service" ];
 
       script = ''
@@ -263,21 +262,21 @@ in {
     systemd.services.ipfs = recursiveUpdate baseService {
       description = "IPFS Daemon";
       wantedBy = mkIf (cfg.defaultMode == "online") [ "multi-user.target" ];
-      after = [ "network.target" "local-fs.target" "ipfs-init.service" ];
+      after = [ "network.target" "ipfs-init.service" ];
       conflicts = [ "ipfs-offline.service" "ipfs-norouting.service"];
     };
 
     systemd.services.ipfs-offline = recursiveUpdate baseService {
       description = "IPFS Daemon (offline mode)";
       wantedBy = mkIf (cfg.defaultMode == "offline") [ "multi-user.target" ];
-      after = [ "local-fs.target" "ipfs-init.service" ];
+      after = [ "ipfs-init.service" ];
       conflicts = [ "ipfs.service" "ipfs-norouting.service"];
     };
 
     systemd.services.ipfs-norouting = recursiveUpdate baseService {
       description = "IPFS Daemon (no routing mode)";
       wantedBy = mkIf (cfg.defaultMode == "norouting") [ "multi-user.target" ];
-      after = [ "local-fs.target" "ipfs-init.service" ];
+      after = [ "ipfs-init.service" ];
       conflicts = [ "ipfs.service" "ipfs-offline.service"];
     };
 
diff --git a/nixos/modules/services/network-filesystems/kbfs.nix b/nixos/modules/services/network-filesystems/kbfs.nix
index 7b2eea3b5850..a43ac656f667 100644
--- a/nixos/modules/services/network-filesystems/kbfs.nix
+++ b/nixos/modules/services/network-filesystems/kbfs.nix
@@ -1,6 +1,7 @@
 { config, lib, pkgs, ... }:
 with lib;
 let
+  inherit (config.security) wrapperDir;
   cfg = config.services.kbfs;
 
 in {
@@ -17,6 +18,16 @@ in {
         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";
@@ -41,25 +52,67 @@ in {
 
   ###### implementation
 
-  config = mkIf cfg.enable {
-
-    systemd.user.services.kbfs = {
-      description = "Keybase File System";
-      requires = [ "keybase.service" ];
-      after = [ "keybase.service" ];
-      path = [ "/run/wrappers" ];
-      serviceConfig = {
-        ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${cfg.mountPoint}";
-        ExecStart = "${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} ${cfg.mountPoint}";
-        ExecStopPost = "/run/wrappers/bin/fusermount -u ${cfg.mountPoint}";
-        Restart = "on-failure";
-        PrivateTmp = true;
+  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" ];
       };
-      wantedBy = [ "default.target" ];
-    };
 
-    services.keybase.enable = true;
+      services.keybase.enable = true;
 
-    environment.systemPackages = [ pkgs.kbfs ];
-  };
+      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/nixos/modules/services/network-filesystems/nfsd.nix b/nixos/modules/services/network-filesystems/nfsd.nix
index 1a78f9a76a33..1b62bfa82035 100644
--- a/nixos/modules/services/network-filesystems/nfsd.nix
+++ b/nixos/modules/services/network-filesystems/nfsd.nix
@@ -11,6 +11,10 @@ let
 in
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "nfs" "lockdPort" ] [ "services" "nfs" "server" "lockdPort" ])
+    (mkRenamedOptionModule [ "services" "nfs" "statdPort" ] [ "services" "nfs" "server" "statdPort" ])
+  ];
 
   ###### interface
 
diff --git a/nixos/modules/services/network-filesystems/openafs/lib.nix b/nixos/modules/services/network-filesystems/openafs/lib.nix
index 1cc9bed847ab..e068ee761c2a 100644
--- a/nixos/modules/services/network-filesystems/openafs/lib.nix
+++ b/nixos/modules/services/network-filesystems/openafs/lib.nix
@@ -3,7 +3,7 @@
 let
   inherit (lib) concatStringsSep mkOption types;
 
-in rec {
+in {
 
   mkCellServDB = cellName: db: ''
     >${cellName}
diff --git a/nixos/modules/services/network-filesystems/orangefs/client.nix b/nixos/modules/services/network-filesystems/orangefs/client.nix
new file mode 100644
index 000000000000..b69d9e713c3d
--- /dev/null
+++ b/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/nixos/modules/services/network-filesystems/orangefs/server.nix b/nixos/modules/services/network-filesystems/orangefs/server.nix
new file mode 100644
index 000000000000..74ebdc134024
--- /dev/null
+++ b/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/nixos/modules/services/network-filesystems/rsyncd.nix b/nixos/modules/services/network-filesystems/rsyncd.nix
index 054057d52ab1..b17ec3aa9300 100644
--- a/nixos/modules/services/network-filesystems/rsyncd.nix
+++ b/nixos/modules/services/network-filesystems/rsyncd.nix
@@ -35,7 +35,7 @@ in
       };
 
       motd = mkOption {
-        type = types.string;
+        type = types.str;
         default = "";
         description = ''
           Message of the day to display to clients on each connect.
diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix
index 10dc58311212..a3c22ce69484 100644
--- a/nixos/modules/services/network-filesystems/samba.nix
+++ b/nixos/modules/services/network-filesystems/samba.nix
@@ -12,11 +12,6 @@ let
 
   samba = cfg.package;
 
-  setupScript =
-    ''
-      mkdir -p /var/lock/samba /var/log/samba /var/cache/samba /var/lib/samba/private
-    '';
-
   shareConfig = name:
     let share = getAttr name cfg.shares; in
     "[${name}]\n " + (smbToString (
@@ -45,6 +40,7 @@ let
   daemonService = appName: args:
     { description = "Samba Service Daemon ${appName}";
 
+      after = [ (mkIf (cfg.enableNmbd && "${appName}" == "smbd") "samba-nmbd.service") ];
       requiredBy = [ "samba.target" ];
       partOf = [ "samba.target" ];
 
@@ -61,6 +57,7 @@ let
         Type = "notify";
         NotifyAccess = "all"; #may not do anything...
       };
+      unitConfig.RequiresMountsFor = "/var/lib/samba";
 
       restartTriggers = [ configFile ];
     };
@@ -68,6 +65,9 @@ let
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "samba" "defaultShare" ] "")
+  ];
 
   ###### interface
 
@@ -86,10 +86,10 @@ in
 
           <note>
             <para>If you use the firewall consider adding the following:</para>
-            <programlisting>
-              networking.firewall.allowedTCPPorts = [ 139 445 ];
-              networking.firewall.allowedUDPPorts = [ 137 138 ];
-            </programlisting>
+          <programlisting>
+            networking.firewall.allowedTCPPorts = [ 139 445 ];
+            networking.firewall.allowedUDPPorts = [ 137 138 ];
+          </programlisting>
           </note>
         '';
       };
@@ -118,7 +118,7 @@ in
         type = types.package;
         default = pkgs.samba;
         defaultText = "pkgs.samba";
-        example = literalExample "pkgs.samba3";
+        example = literalExample "pkgs.samba4Full";
         description = ''
           Defines which package should be used for the samba server.
         '';
@@ -227,22 +227,22 @@ in
         systemd = {
           targets.samba = {
             description = "Samba Server";
-            requires = [ "samba-setup.service" ];
-            after = [ "samba-setup.service" "network.target" ];
+            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" "");
-            "samba-setup" = {
-              description = "Samba Setup Task";
-              script = setupScript;
-              unitConfig.RequiresMountsFor = "/var/lib/samba";
-            };
+            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/nixos/modules/services/network-filesystems/tahoe.nix b/nixos/modules/services/network-filesystems/tahoe.nix
index 534862a3c9e4..7d75eb286106 100644
--- a/nixos/modules/services/network-filesystems/tahoe.nix
+++ b/nixos/modules/services/network-filesystems/tahoe.nix
@@ -234,16 +234,19 @@ in
               Type = "simple";
               PIDFile = pidfile;
               # Believe it or not, Tahoe is very brittle about the order of
-              # arguments to $(tahoe start). The node directory must come first,
+              # arguments to $(tahoe run). The node directory must come first,
               # and arguments which alter Twisted's behavior come afterwards.
               ExecStart = ''
-                ${settings.package}/bin/tahoe start ${lib.escapeShellArg nodedir} -n -l- --pidfile=${lib.escapeShellArg pidfile}
+                ${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-introducer ${lib.escapeShellArg nodedir}
+                # 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
@@ -334,10 +337,10 @@ in
               Type = "simple";
               PIDFile = pidfile;
               # Believe it or not, Tahoe is very brittle about the order of
-              # arguments to $(tahoe start). The node directory must come first,
+              # arguments to $(tahoe run). The node directory must come first,
               # and arguments which alter Twisted's behavior come afterwards.
               ExecStart = ''
-                ${settings.package}/bin/tahoe start ${lib.escapeShellArg nodedir} -n -l- --pidfile=${lib.escapeShellArg pidfile}
+                ${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
               '';
             };
             preStart = ''
diff --git a/nixos/modules/services/network-filesystems/u9fs.nix b/nixos/modules/services/network-filesystems/u9fs.nix
index 4f37fc2a9e5c..77961b78cadb 100644
--- a/nixos/modules/services/network-filesystems/u9fs.nix
+++ b/nixos/modules/services/network-filesystems/u9fs.nix
@@ -55,6 +55,7 @@ in
       sockets.u9fs = {
         description = "U9fs Listening Socket";
         wantedBy = [ "sockets.target" ];
+        after = [ "network.target" ];
         inherit (cfg) listenStreams;
         socketConfig.Accept = "yes";
       };
diff --git a/nixos/modules/services/network-filesystems/yandex-disk.nix b/nixos/modules/services/network-filesystems/yandex-disk.nix
index e93f45b49867..0aa01ef9e6d9 100644
--- a/nixos/modules/services/network-filesystems/yandex-disk.nix
+++ b/nixos/modules/services/network-filesystems/yandex-disk.nix
@@ -29,7 +29,7 @@ in
 
       username = mkOption {
         default = "";
-        type = types.string;
+        type = types.str;
         description = ''
           Your yandex.com login name.
         '';
@@ -37,7 +37,7 @@ in
 
       password = mkOption {
         default = "";
-        type = types.string;
+        type = types.str;
         description = ''
           Your yandex.com password. Warning: it will be world-readable in /nix/store.
         '';
@@ -57,7 +57,7 @@ in
 
       excludes = mkOption {
         default = "";
-        type = types.string;
+        type = types.commas;
         example = "data,backup";
         description = ''
           Comma-separated list of directories which are excluded from synchronization.
diff --git a/nixos/modules/services/networking/3proxy.nix b/nixos/modules/services/networking/3proxy.nix
new file mode 100644
index 000000000000..26aa16679467
--- /dev/null
+++ b/nixos/modules/services/networking/3proxy.nix
@@ -0,0 +1,424 @@
+{ 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 = {
+              "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/nixos/modules/services/networking/aria2.nix b/nixos/modules/services/networking/aria2.nix
index 98eb00861016..156fef144791 100644
--- a/nixos/modules/services/networking/aria2.nix
+++ b/nixos/modules/services/networking/aria2.nix
@@ -47,8 +47,8 @@ in
         '';
       };
       downloadDir = mkOption {
-        type = types.string;
-        default = "${downloadDir}";
+        type = types.path;
+        default = downloadDir;
         description = ''
           Directory to store downloaded files.
         '';
@@ -66,7 +66,7 @@ in
         description = "Specify a port number for JSON-RPC/XML-RPC server to listen to. Possible Values: 1024-65535";
       };
       rpcSecret = mkOption {
-        type = types.string;
+        type = types.str;
         default = "aria2rpc";
         description = ''
           Set RPC secret authorization token.
@@ -74,7 +74,7 @@ in
         '';
       };
       extraArguments = mkOption {
-        type = types.string;
+        type = types.separatedString " ";
         example = "--rpc-listen-all --remote-time=true";
         default = "";
         description = ''
@@ -102,22 +102,19 @@ in
 
     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 = [ "local-fs.target" "network.target" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       preStart = ''
-        mkdir -m 0770 -p "${homeDir}"
-        chown aria2:aria2 "${homeDir}"
-        if [[ ! -d "${config.services.aria2.downloadDir}" ]]
-        then
-          mkdir -m 0770 -p "${config.services.aria2.downloadDir}"
-          chown aria2:aria2 "${config.services.aria2.downloadDir}"
-        fi
         if [[ ! -e "${sessionFile}" ]]
         then
           touch "${sessionFile}"
-          chown aria2:aria2 "${sessionFile}"
         fi
         cp -f "${settingsFile}" "${settingsDir}/aria2.conf"
       '';
@@ -128,7 +125,6 @@ in
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
         User = "aria2";
         Group = "aria2";
-        PermissionsStartOnly = true;
       };
     };
   };
diff --git a/nixos/modules/services/networking/autossh.nix b/nixos/modules/services/networking/autossh.nix
index 9ea17469870d..a8d9a027e9fa 100644
--- a/nixos/modules/services/networking/autossh.nix
+++ b/nixos/modules/services/networking/autossh.nix
@@ -20,12 +20,12 @@ in
         type = types.listOf (types.submodule {
           options = {
             name = mkOption {
-              type = types.string;
+              type = types.str;
               example = "socks-peer";
               description = "Name of the local AutoSSH session";
             };
             user = mkOption {
-              type = types.string;
+              type = types.str;
               example = "bill";
               description = "Name of the user the AutoSSH session should run as";
             };
@@ -40,7 +40,7 @@ in
               '';
             };
             extraArguments = mkOption {
-              type = types.string;
+              type = types.separatedString " ";
               example = "-N -D4343 bill@socks.example.net";
               description = ''
                 Arguments to be passed to AutoSSH and retransmitted to SSH
@@ -99,7 +99,6 @@ in
 
               serviceConfig = {
                   User = "${s.user}";
-                  PermissionsStartOnly = true;
                   # AutoSSH may exit with 0 code if the SSH session was
                   # gracefully terminated by either local or remote side.
                   Restart = "on-success";
diff --git a/nixos/modules/services/networking/avahi-daemon.nix b/nixos/modules/services/networking/avahi-daemon.nix
index 4c91a0c415b6..ddcfe3d77e2f 100644
--- a/nixos/modules/services/networking/avahi-daemon.nix
+++ b/nixos/modules/services/networking/avahi-daemon.nix
@@ -1,10 +1,8 @@
-# Avahi daemon.
 { config, lib, pkgs, ... }:
 
 with lib;
 
 let
-
   cfg = config.services.avahi;
 
   yesNo = yes : if yes then "yes" else "no";
@@ -39,215 +37,245 @@ let
     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').
+      '';
+    };
 
-  ###### interface
+    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>.
+      '';
+    };
 
-  options = {
+    domainName = mkOption {
+      type = types.str;
+      default = "local";
+      description = ''
+        Domain name for all advertisements.
+      '';
+    };
 
-    services.avahi = {
+    browseDomains = mkOption {
+      type = types.listOf types.str;
+      default = [ ];
+      example = [ "0pointer.de" "zeroconf.org" ];
+      description = ''
+        List of non-local DNS domains to be browsed.
+      '';
+    };
 
-      enable = mkOption {
-        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').
-        '';
-      };
+    ipv4 = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to use IPv4.";
+    };
 
-      hostName = mkOption {
-        type = types.str;
-        description = ''
-          Host name advertised on the LAN. If not set, avahi will use the value
-          of config.networking.hostName.
-        '';
-      };
+    ipv6 = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Whether to use IPv6.";
+    };
 
-      domainName = mkOption {
-        type = types.str;
-        default = "local";
-        description = ''
-          Domain name for all advertisements.
-        '';
-      };
+    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.
+      '';
+    };
 
-      browseDomains = mkOption {
-        default = [ ];
-        example = [ "0pointer.de" "zeroconf.org" ];
-        description = ''
-          List of non-local DNS domains to be browsed.
-        '';
-      };
+    openFirewall = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to open the firewall for UDP port 5353.
+      '';
+    };
 
-      ipv4 = mkOption {
-        default = true;
-        description = ''Whether to use IPv4'';
-      };
+    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.
+      '';
+    };
 
-      ipv6 = mkOption {
-        default = false;
-        description = ''Whether to use IPv6'';
-      };
+    wideArea = mkOption {
+      type = types.bool;
+      default = true;
+      description = "Whether to enable wide-area service discovery.";
+    };
 
-      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.
-        '';
-      };
+    reflector = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Reflect incoming mDNS requests to all allowed network interfaces.";
+    };
 
-      allowPointToPoint = mkOption {
-        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. Use with care and YMMV!
-        '';
-      };
+    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.
+      '';
+    };
 
-      wideArea = mkOption {
-        default = true;
-        description = ''Whether to enable wide-area service discovery.'';
+    publish = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to allow publishing in general.";
       };
 
-      reflector = mkOption {
+      userServices = mkOption {
+        type = types.bool;
         default = false;
-        description = ''Reflect incoming mDNS requests to all allowed network interfaces.'';
+        description = "Whether to publish user services. Will set <literal>addresses=true</literal>.";
       };
 
-      publish = {
-        enable = mkOption {
-          default = false;
-          description = ''Whether to allow publishing in general.'';
-        };
-
-        userServices = mkOption {
-          default = false;
-          description = ''Whether to publish user services. Will set <literal>addresses=true</literal>.'';
-        };
-
-        addresses = mkOption {
-          default = false;
-          description = ''Whether to register mDNS address records for all local IP addresses.'';
-        };
-
-        hinfo = mkOption {
-          default = false;
-          description = ''
-            Whether to register an mDNS HINFO record which contains information about the
-            local operating system and CPU.
-          '';
-        };
-
-        workstation = mkOption {
-          default = false;
-          description = ''Whether to register a service of type "_workstation._tcp" on the local LAN.'';
-        };
-
-        domain = mkOption {
-          default = false;
-          description = ''Whether to announce the locally used domain name for browsing by other hosts.'';
-        };
-
+      addresses = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to register mDNS address records for all local IP addresses.";
       };
 
-      nssmdns = mkOption {
+      hinfo = 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.
+          Whether to register a mDNS HINFO record which contains information about the
+          local operating system and CPU.
         '';
       };
 
-      cacheEntriesMax = mkOption {
-        default = null;
-        type = types.nullOr types.int;
+      workstation = mkOption {
+        type = types.bool;
+        default = false;
         description = ''
-          Number of resource records to be cached per interface. Use 0 to
-          disable caching. Avahi daemon defaults to 4096 if not set.
+          Whether to register a service of type "_workstation._tcp" on the local LAN.
         '';
       };
 
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
-        description = ''
-          Extra config to append to avahi-daemon.conf.
-        '';
+      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.
+      '';
+    };
 
-  ###### implementation
+    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;
+    };
 
-    services.avahi.hostName = mkDefault config.networking.hostName;
-
-    users.users = singleton
-      { name = "avahi";
-        uid = config.ids.uids.avahi;
-        description = "`avahi-daemon' privilege separation user";
-        home = "/var/empty";
-      };
-
-    users.groups = singleton
-      { name = "avahi";
-        gid = config.ids.gids.avahi;
-      };
+    users.groups.avahi = {};
 
     system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
 
     environment.systemPackages = [ pkgs.avahi ];
 
-    systemd.sockets.avahi-daemon =
-      { description = "Avahi mDNS/DNS-SD Stack Activation Socket";
-        listenStreams = [ "/run/avahi-daemon/socket" ];
-        wantedBy = [ "sockets.target" ];
-      };
+    environment.etc = (mapAttrs' (n: v: nameValuePair
+      "avahi/services/${n}.service"
+      { ${if types.path.check v then "source" else "text"} = v; }
+    ) cfg.extraServiceFiles);
 
-    systemd.services.avahi-daemon =
-      { description = "Avahi mDNS/DNS-SD Stack";
-        wantedBy = [ "multi-user.target" ];
-        requires = [ "avahi-daemon.socket" ];
+    systemd.sockets.avahi-daemon = {
+      description = "Avahi mDNS/DNS-SD Stack Activation Socket";
+      listenStreams = [ "/run/avahi-daemon/socket" ];
+      wantedBy = [ "sockets.target" ];
+    };
 
-        serviceConfig."NotifyAccess" = "main";
-        serviceConfig."BusName" = "org.freedesktop.Avahi";
-        serviceConfig."Type" = "dbus";
+    systemd.tmpfiles.rules = [ "d /run/avahi-daemon - avahi avahi -" ];
 
-        path = [ pkgs.coreutils pkgs.avahi ];
+    systemd.services.avahi-daemon = {
+      description = "Avahi mDNS/DNS-SD Stack";
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "avahi-daemon.socket" ];
 
-        preStart = "mkdir -p /run/avahi-daemon";
+      # Make NSS modules visible so that `avahi_nss_support ()' can
+      # return a sensible value.
+      environment.LD_LIBRARY_PATH = config.system.nssModules.path;
 
-        script =
-          ''
-            # Make NSS modules visible so that `avahi_nss_support ()' can
-            # return a sensible value.
-            export LD_LIBRARY_PATH="${config.system.nssModules.path}"
+      path = [ pkgs.coreutils pkgs.avahi ];
 
-            exec ${pkgs.avahi}/sbin/avahi-daemon --syslog -f "${avahiDaemonConf}"
-          '';
+      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 ];
 
-    # Enabling Avahi without exposing it in the firewall doesn't make
-    # sense.
-    networking.firewall.allowedUDPPorts = [ 5353 ];
-
+    networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ 5353 ];
   };
-
 }
diff --git a/nixos/modules/services/networking/babeld.nix b/nixos/modules/services/networking/babeld.nix
index 3dfd80f6ff52..de863461eab2 100644
--- a/nixos/modules/services/networking/babeld.nix
+++ b/nixos/modules/services/networking/babeld.nix
@@ -52,7 +52,7 @@ in
         example =
           {
             type = "tunnel";
-            "split-horizon" = true;
+            split-horizon = true;
           };
       };
 
@@ -66,8 +66,8 @@ in
         example =
           { enp0s2 =
             { type = "wired";
-              "hello-interval" = 5;
-              "split-horizon" = "auto";
+              hello-interval = 5;
+              split-horizon = "auto";
             };
           };
       };
diff --git a/nixos/modules/services/networking/bind.nix b/nixos/modules/services/networking/bind.nix
index 98486cefd528..e3b95afb3d86 100644
--- a/nixos/modules/services/networking/bind.nix
+++ b/nixos/modules/services/networking/bind.nix
@@ -33,7 +33,7 @@ let
       ${cfg.extraConfig}
 
       ${ concatMapStrings
-          ({ name, file, master ? true, slaves ? [], masters ? [] }:
+          ({ name, file, master ? true, slaves ? [], masters ? [], extraConfig ? "" }:
             ''
               zone "${name}" {
                 type ${if master then "master" else "slave"};
@@ -52,6 +52,7 @@ let
                    ''
                 }
                 allow-query { any; };
+                ${extraConfig}
               };
             '')
           cfg.zones }
@@ -77,7 +78,11 @@ in
       cacheNetworks = mkOption {
         default = ["127.0.0.0/24"];
         description = "
-          What networks are allowed to use us as a resolver.
+          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.
         ";
       };
 
@@ -131,6 +136,7 @@ in
           file = "/var/dns/example.com";
           masters = ["192.168.0.1"];
           slaves = [];
+          extraConfig = "";
         }];
       };
 
@@ -168,11 +174,12 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.bind.enable {
+  config = mkIf cfg.enable {
 
-    users.users = singleton
-      { name = bindUser;
-        uid = config.ids.uids.bind;
+    networking.resolvconf.useLocalResolver = mkDefault true;
+
+    users.users.${bindUser} =
+      { uid = config.ids.uids.bind;
         description = "BIND daemon user";
       };
 
@@ -184,7 +191,7 @@ in
       preStart = ''
         mkdir -m 0755 -p /etc/bind
         if ! [ -f "/etc/bind/rndc.key" ]; then
-          ${pkgs.bind.out}/sbin/rndc-confgen -r /dev/urandom -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null
+          ${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
diff --git a/nixos/modules/services/networking/bird.nix b/nixos/modules/services/networking/bird.nix
index 555733aab7c0..4ae35875c0f0 100644
--- a/nixos/modules/services/networking/bird.nix
+++ b/nixos/modules/services/networking/bird.nix
@@ -14,15 +14,6 @@ let
           bird6 = "1.9.x with IPv6 suport";
           bird2 = "2.x";
         }.${variant};
-      configFile = pkgs.stdenv.mkDerivation {
-        name = "${variant}.conf";
-        text = cfg.config;
-        preferLocalBuild = true;
-        buildCommand = ''
-          echo -n "$text" > $out
-          ${pkg}/bin/${birdBin} -d -p -c $out
-        '';
-      };
     in {
       ###### interface
       options = {
@@ -41,14 +32,24 @@ let
       ###### 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 ${configFile} -u ${variant} -g ${variant}";
+            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"
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
new file mode 100644
index 000000000000..4e00a8865474
--- /dev/null
+++ b/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/nixos/modules/services/networking/bitlbee.nix b/nixos/modules/services/networking/bitlbee.nix
index 274b36171608..01a16698384a 100644
--- a/nixos/modules/services/networking/bitlbee.nix
+++ b/nixos/modules/services/networking/bitlbee.nix
@@ -161,16 +161,14 @@ in
 
   config =  mkMerge [
     (mkIf config.services.bitlbee.enable {
-      users.users = singleton {
-        name = "bitlbee";
+      users.users.bitlbee = {
         uid = bitlbeeUid;
         description = "BitlBee user";
         home = "/var/lib/bitlbee";
         createHome = true;
       };
 
-      users.groups = singleton {
-        name = "bitlbee";
+      users.groups.bitlbee = {
         gid = config.ids.gids.bitlbee;
       };
 
diff --git a/nixos/modules/services/networking/btsync.nix b/nixos/modules/services/networking/btsync.nix
deleted file mode 100644
index 33e85ef58e6e..000000000000
--- a/nixos/modules/services/networking/btsync.nix
+++ /dev/null
@@ -1,324 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.btsync;
-
-  bittorrentSync = cfg.package;
-
-  listenAddr = cfg.httpListenAddr + ":" + (toString cfg.httpListenPort);
-
-  optionalEmptyStr = b: v: optionalString (b != "") v;
-
-  webUIConfig = optionalString cfg.enableWebUI
-    ''
-      "webui":
-      {
-        ${optionalEmptyStr cfg.httpLogin     "\"login\":          \"${cfg.httpLogin}\","}
-        ${optionalEmptyStr cfg.httpPass      "\"password\":       \"${cfg.httpPass}\","}
-        ${optionalEmptyStr cfg.apiKey        "\"api_key\":        \"${cfg.apiKey}\","}
-        ${optionalEmptyStr cfg.directoryRoot "\"directory_root\": \"${cfg.directoryRoot}\","}
-        "listen": "${listenAddr}"
-      }
-    '';
-
-  knownHosts = e:
-    optionalString (e ? "knownHosts")
-      (concatStringsSep "," (map (v: "\"${v}\"") e."knownHosts"));
-
-  sharedFoldersRecord =
-    concatStringsSep "," (map (entry:
-      let helper = attr: v:
-        if (entry ? attr) then boolToString entry.attr else boolToString v;
-      in
-      ''
-        {
-          "secret": "${entry.secret}",
-          "dir":    "${entry.directory}",
-
-          "use_relay_server": ${helper "useRelayServer" true},
-          "use_tracker":      ${helper "useTracker"     true},
-          "use_dht":          ${helper "useDHT"        false},
-
-          "search_lan":       ${helper "searchLAN"      true},
-          "use_sync_trash":   ${helper "useSyncTrash"   true},
-
-          "known_hosts": [${knownHosts entry}]
-        }
-      '') cfg.sharedFolders);
-
-  sharedFoldersConfig = optionalString (cfg.sharedFolders != [])
-    ''
-      "shared_folders":
-        [
-        ${sharedFoldersRecord}
-        ]
-    '';
-
-  configFile = pkgs.writeText "btsync.config"
-    ''
-      {
-        "device_name":     "${cfg.deviceName}",
-        "storage_path":    "${cfg.storagePath}",
-        "listening_port":  ${toString cfg.listeningPort},
-        "use_gui":         false,
-
-        "check_for_updates": ${boolToString cfg.checkForUpdates},
-        "use_upnp":          ${boolToString cfg.useUpnp},
-        "download_limit":    ${toString cfg.downloadLimit},
-        "upload_limit":      ${toString cfg.uploadLimit},
-        "lan_encrypt_data":  ${boolToString cfg.encryptLAN},
-
-        ${webUIConfig}
-        ${sharedFoldersConfig}
-      }
-    '';
-in
-{
-  options = {
-    services.btsync = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          If enabled, start the Bittorrent Sync daemon. Once enabled, you can
-          interact with the service through the Web UI, or configure it in your
-          NixOS configuration. Enabling the <literal>btsync</literal> service
-          also installs a systemd user unit which can be used to start
-          user-specific copies of the daemon. Once installed, you can use
-          <literal>systemctl --user start btsync</literal> as your user to start
-          the daemon using the configuration file located at
-          <literal>$HOME/.config/btsync.conf</literal>.
-        '';
-      };
-
-      deviceName = mkOption {
-        type = types.str;
-        example = "Voltron";
-        description = ''
-          Name of the Bittorrent 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>.
-          '';
-      };
-
-      package = mkOption {
-        type = types.package;
-        example = literalExample "pkgs.bittorrentSync20";
-        description = ''
-          Branch of bittorrent sync to use.
-        '';
-      };
-
-      storagePath = mkOption {
-        type = types.path;
-        default = "/var/lib/btsync/";
-        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>btsync
-          --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>btsync</literal> group.
-
-          Directories in this list should be in the
-          <literal>btsync</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>btsync</literal> group. Also,
-          <literal>setfacl -d -m group:btsync:rwx</literal> and
-          <literal>setfacl -m group:btsync: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.";
-        }
-      ];
-
-    services.btsync.package = mkOptionDefault pkgs.bittorrentSync14;
-
-    users.users.btsync = {
-      description     = "Bittorrent Sync Service user";
-      home            = cfg.storagePath;
-      createHome      = true;
-      uid             = config.ids.uids.btsync;
-      group           = "btsync";
-    };
-
-    users.groups = [
-      { name = "btsync";
-      }];
-
-    systemd.services.btsync = with pkgs; {
-      description = "Bittorrent Sync Service";
-      wantedBy    = [ "multi-user.target" ];
-      after       = [ "network.target" "local-fs.target" ];
-      serviceConfig = {
-        Restart   = "on-abort";
-        UMask     = "0002";
-        User      = "btsync";
-        ExecStart =
-          "${bittorrentSync}/bin/btsync --nodaemon --config ${configFile}";
-      };
-    };
-
-    systemd.user.services.btsync = with pkgs; {
-      description = "Bittorrent Sync user service";
-      after       = [ "network.target" "local-fs.target" ];
-      serviceConfig = {
-        Restart   = "on-abort";
-        ExecStart =
-          "${bittorrentSync}/bin/btsync --nodaemon --config %h/.config/btsync.conf";
-      };
-    };
-
-    environment.systemPackages = [ cfg.package ];
-  };
-}
diff --git a/nixos/modules/services/networking/charybdis.nix b/nixos/modules/services/networking/charybdis.nix
index 3d02dc8d1375..43829d36e417 100644
--- a/nixos/modules/services/networking/charybdis.nix
+++ b/nixos/modules/services/networking/charybdis.nix
@@ -21,14 +21,14 @@ in
       enable = mkEnableOption "Charybdis IRC daemon";
 
       config = mkOption {
-        type = types.string;
+        type = types.str;
         description = ''
           Charybdis IRC daemon configuration file.
         '';
       };
 
       statedir = mkOption {
-        type = types.string;
+        type = types.path;
         default = "/var/lib/charybdis";
         description = ''
           Location of the state directory of charybdis.
@@ -36,7 +36,7 @@ in
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "ircd";
         description = ''
           Charybdis IRC daemon user.
@@ -44,7 +44,7 @@ in
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "ircd";
         description = ''
           Charybdis IRC daemon group.
@@ -71,18 +71,20 @@ in
 
   config = mkIf cfg.enable (lib.mkMerge [
     {
-      users.users = singleton {
-        name = cfg.user;
+      users.users.${cfg.user} = {
         description = "Charybdis IRC daemon user";
         uid = config.ids.uids.ircd;
         group = cfg.group;
       };
 
-      users.groups = singleton {
-        name = 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" ];
@@ -93,16 +95,11 @@ in
           ExecStart   = "${charybdis}/bin/charybdis -foreground -logfile /dev/stdout -configfile ${configFile}";
           Group = cfg.group;
           User = cfg.user;
-          PermissionsStartOnly = true; # preStart needs to run with root permissions
         };
-        preStart = ''
-          ${coreutils}/bin/mkdir -p ${cfg.statedir}
-          ${coreutils}/bin/chown ${cfg.user}:${cfg.group} ${cfg.statedir}
-        '';
       };
 
     }
-    
+
     (mkIf (cfg.motd != null) {
       environment.etc."charybdis/ircd.motd".text = cfg.motd;
     })
diff --git a/nixos/modules/services/networking/cjdns.nix b/nixos/modules/services/networking/cjdns.nix
index c40962f4aa82..3fb85b16cbe2 100644
--- a/nixos/modules/services/networking/cjdns.nix
+++ b/nixos/modules/services/networking/cjdns.nix
@@ -44,9 +44,7 @@ let
   parseModules = x:
     x // { connectTo = mapAttrs (name: value: { inherit (value) password publicKey; }) x.connectTo; };
 
-  # would be nice to  merge 'cfg' with a //,
-  # but the json nesting is wacky.
-  cjdrouteConf = builtins.toJSON ( {
+  cjdrouteConf = builtins.toJSON ( recursiveUpdate {
     admin = {
       bind = cfg.admin.bind;
       password = "@CJDNS_ADMIN_PASSWORD@";
@@ -71,7 +69,7 @@ let
 
     security = [ { exemptAngel = 1; setuser = "nobody"; } ];
 
-  });
+  } cfg.extraConfig);
 
 in
 
@@ -91,6 +89,16 @@ in
         '';
       };
 
+      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;
@@ -246,7 +254,10 @@ in
         if cfg.confFile != null then "${pkg}/bin/cjdroute < ${cfg.confFile}" else
           ''
             source /etc/cjdns.keys
-            echo '${cjdrouteConf}' | sed \
+            (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
diff --git a/nixos/modules/services/networking/connman.nix b/nixos/modules/services/networking/connman.nix
index c3ca6fbe725e..e8eadc4e187d 100644
--- a/nixos/modules/services/networking/connman.nix
+++ b/nixos/modules/services/networking/connman.nix
@@ -4,20 +4,25 @@ with pkgs;
 with lib;
 
 let
-  cfg = config.networking.connman;
+  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 = {
 
-    networking.connman = {
+    services.connman = {
 
       enable = mkOption {
         type = types.bool;
@@ -45,15 +50,26 @@ in {
       };
 
       networkInterfaceBlacklist = mkOption {
-        type = with types; listOf string;
+        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 string;
+        type = with types; listOf str;
         default = [ ];
         example = [ "--nodnsproxy" ];
         description = ''
@@ -71,31 +87,34 @@ in {
 
     assertions = [{
       assertion = !config.networking.useDHCP;
-      message = "You can not use services.networking.connman with services.networking.useDHCP";
-    }{
-      assertion = config.networking.wireless.enable;
-      message = "You must use services.networking.connman with services.networking.wireless";
+      message = "You can not use services.connman with networking.useDHCP";
     }{
       assertion = !config.networking.networkmanager.enable;
-      message = "You can not use services.networking.connman with services.networking.networkmanager";
+      message = "You can not use services.connman with networking.networkmanager";
     }];
 
     environment.systemPackages = [ connman ];
 
-    systemd.services."connman" = {
+    systemd.services.connman = {
       description = "Connection service";
       wantedBy = [ "multi-user.target" ];
-      after = [ "syslog.target" ];
+      after = [ "syslog.target" ] ++ optional enableIwd "iwd.service";
+      requires = optional enableIwd "iwd.service";
       serviceConfig = {
         Type = "dbus";
         BusName = "net.connman";
         Restart = "on-failure";
-        ExecStart = "${pkgs.connman}/sbin/connmand --config=${configFile} --nodaemon ${toString cfg.extraFlags}";
+        ExecStart = toString ([
+          "${pkgs.connman}/sbin/connmand"
+          "--config=${configFile}"
+          "--nodaemon"
+        ] ++ optional enableIwd "--wifi=iwd_agent"
+          ++ cfg.extraFlags);
         StandardOutput = "null";
       };
     };
 
-    systemd.services."connman-vpn" = mkIf cfg.enableVPN {
+    systemd.services.connman-vpn = mkIf cfg.enableVPN {
       description = "ConnMan VPN service";
       wantedBy = [ "multi-user.target" ];
       after = [ "syslog.target" ];
@@ -108,7 +127,7 @@ in {
       };
     };
 
-    systemd.services."net-connman-vpn" = mkIf cfg.enableVPN {
+    systemd.services.net-connman-vpn = mkIf cfg.enableVPN {
       description = "D-BUS Service";
       serviceConfig = {
         Name = "net.connman.vpn";
@@ -121,7 +140,12 @@ in {
 
     networking = {
       useDHCP = false;
-      wireless.enable = true;
+      wireless = {
+        enable = mkIf (!enableIwd) true;
+        iwd = mkIf enableIwd {
+          enable = true;
+        };
+      };
       networkmanager.enable = false;
     };
   };
diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix
index 3a92a883fbf8..689cbc8a986d 100644
--- a/nixos/modules/services/networking/consul.nix
+++ b/nixos/modules/services/networking/consul.nix
@@ -15,7 +15,7 @@ let
     ++ cfg.extraConfigFiles;
 
   devices = attrValues (filterAttrs (_: i: i != null) cfg.interface);
-  systemdDevices = flip map devices
+  systemdDevices = forEach devices
     (i: "sys-subsystem-net-devices-${utils.escapeSystemdPath i}.device");
 in
 {
@@ -156,7 +156,7 @@ in
   config = mkIf cfg.enable (
     mkMerge [{
 
-      users.users."consul" = {
+      users.users.consul = {
         description = "Consul agent daemon user";
         uid = config.ids.uids.consul;
         # The shell is needed for health checks
diff --git a/nixos/modules/services/networking/corerad.nix b/nixos/modules/services/networking/corerad.nix
new file mode 100644
index 000000000000..1a2c4aec6651
--- /dev/null
+++ b/nixos/modules/services/networking/corerad.nix
@@ -0,0 +1,46 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.corerad;
+in {
+  meta = {
+    maintainers = with maintainers; [ mdlayher ];
+  };
+
+  options.services.corerad = {
+    enable = mkEnableOption "CoreRAD IPv6 NDP RA daemon";
+
+    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 {
+    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/nixos/modules/services/networking/coturn.nix b/nixos/modules/services/networking/coturn.nix
index c430ce5af92a..1bfbc307c59d 100644
--- a/nixos/modules/services/networking/coturn.nix
+++ b/nixos/modules/services/networking/coturn.nix
@@ -294,16 +294,14 @@ in {
   };
 
   config = mkIf cfg.enable {
-    users.users = [
-      { name = "turnserver";
-        uid = config.ids.uids.turnserver;
+    users.users.turnserver =
+      { uid = config.ids.uids.turnserver;
         description = "coturn TURN server user";
-      } ];
-    users.groups = [
-      { name = "turnserver";
-        gid = config.ids.gids.turnserver;
+      };
+    users.groups.turnserver =
+      { gid = config.ids.gids.turnserver;
         members = [ "turnserver" ];
-      } ];
+      };
 
     systemd.services.coturn = {
       description = "coturn TURN server";
diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix
index 9a2e13e9553c..053efe712709 100644
--- a/nixos/modules/services/networking/ddclient.nix
+++ b/nixos/modules/services/networking/ddclient.nix
@@ -20,8 +20,8 @@ let
     wildcard=YES
     quiet=${boolToStr cfg.quiet}
     verbose=${boolToStr cfg.verbose}
-    ${lib.concatStringsSep "," cfg.domains}
     ${cfg.extraConfig}
+    ${lib.concatStringsSep "," cfg.domains}
   '';
 
 in
@@ -30,6 +30,14 @@ 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 = {
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index b32aef8a50d0..6972c833cc5b 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -187,7 +187,7 @@ in
 
         wantedBy = [ "multi-user.target" ] ++ optional (!hasDefaultGatewaySet) "network-online.target";
         wants = [ "network.target" "systemd-udev-settle.service" ];
-        before = [ "network.target" ];
+        before = [ "network-online.target" ];
         after = [ "systemd-udev-settle.service" ];
 
         # Stopping dhcpcd during a reconfiguration is undesirable
@@ -210,11 +210,7 @@ in
 
     environment.systemPackages = [ dhcpcd ];
 
-    environment.etc =
-      [ { source = exitHook;
-          target = "dhcpcd.exit-hook";
-        }
-      ];
+    environment.etc."dhcpcd.exit-hook".source = exitHook;
 
     powerManagement.resumeCommands = mkIf config.systemd.services.dhcpcd.enable
       ''
diff --git a/nixos/modules/services/networking/dhcpd.nix b/nixos/modules/services/networking/dhcpd.nix
index 0b2063bc4246..67f7d8118870 100644
--- a/nixos/modules/services/networking/dhcpd.nix
+++ b/nixos/modules/services/networking/dhcpd.nix
@@ -182,6 +182,10 @@ in
 
 {
 
+  imports = [
+    (mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ])
+  ];
+
   ###### interface
 
   options = {
diff --git a/nixos/modules/services/networking/dnscache.nix b/nixos/modules/services/networking/dnscache.nix
index 5051fc916d96..d123bca93219 100644
--- a/nixos/modules/services/networking/dnscache.nix
+++ b/nixos/modules/services/networking/dnscache.nix
@@ -84,7 +84,7 @@ in {
 
   config = mkIf config.services.dnscache.enable {
     environment.systemPackages = [ pkgs.djbdns ];
-    users.users.dnscache = {};
+    users.users.dnscache.isSystemUser = true;
 
     systemd.services.dnscache = {
       description = "djbdns dnscache server";
diff --git a/nixos/modules/services/networking/dnschain.nix b/nixos/modules/services/networking/dnschain.nix
index 0c2add424bac..003609ea7054 100644
--- a/nixos/modules/services/networking/dnschain.nix
+++ b/nixos/modules/services/networking/dnschain.nix
@@ -136,13 +136,18 @@ in
         "/.dns/127.0.0.1#${toString cfg.dns.port}"
       ];
 
-    services.pdns-recursor.forwardZones = mkIf cfgs.pdns-recursor.resolveDNSChainQueries
-      { bit = "127.0.0.1:${toString cfg.dns.port}";
-        dns = "127.0.0.1:${toString cfg.dns.port}";
-      };
+    services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveDNSChainQueries {
+      forwardZonesRecurse =
+        { bit = "127.0.0.1:${toString cfg.dns.port}";
+          dns = "127.0.0.1:${toString cfg.dns.port}";
+        };
+      luaConfig =''
+        addNTA("bit", "namecoin doesn't support DNSSEC")
+        addNTA("dns", "namecoin doesn't support DNSSEC")
+      '';
+    };
 
-    users.users = singleton {
-      name = username;
+    users.users.${username} = {
       description = "DNSChain daemon user";
       home = dataDir;
       createHome = true;
@@ -174,4 +179,6 @@ in
 
   };
 
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
 }
diff --git a/nixos/modules/services/networking/dnscrypt-proxy.xml b/nixos/modules/services/networking/dnscrypt-proxy.xml
index f90eef69848c..afc7880392a1 100644
--- a/nixos/modules/services/networking/dnscrypt-proxy.xml
+++ b/nixos/modules/services/networking/dnscrypt-proxy.xml
@@ -18,7 +18,7 @@
    To enable the client proxy, set
 <programlisting>
 <xref linkend="opt-services.dnscrypt-proxy.enable"/> = true;
-    </programlisting>
+</programlisting>
   </para>
 
   <para>
@@ -36,7 +36,7 @@
    the other client to it:
 <programlisting>
 <xref linkend="opt-services.dnscrypt-proxy.localPort"/> = 43;
-    </programlisting>
+</programlisting>
   </para>
 
   <sect2 xml:id="sec-dnscrypt-proxy-forwarder-dsnmasq">
@@ -47,7 +47,7 @@
   <xref linkend="opt-services.dnsmasq.enable"/> = true;
   <xref linkend="opt-services.dnsmasq.servers"/> = [ "127.0.0.1#43" ];
 }
-    </programlisting>
+</programlisting>
    </para>
   </sect2>
 
@@ -59,7 +59,7 @@
   <xref linkend="opt-services.unbound.enable"/> = true;
   <xref linkend="opt-services.unbound.forwardAddresses"/> = [ "127.0.0.1@43" ];
 }
-    </programlisting>
+</programlisting>
    </para>
   </sect2>
  </sect1>
diff --git a/nixos/modules/services/networking/dnscrypt-wrapper.nix b/nixos/modules/services/networking/dnscrypt-wrapper.nix
index bf13d5c6f5fe..e53fb7a15782 100644
--- a/nixos/modules/services/networking/dnscrypt-wrapper.nix
+++ b/nixos/modules/services/networking/dnscrypt-wrapper.nix
@@ -142,6 +142,7 @@ in {
       description = "dnscrypt-wrapper daemon user";
       home = "${dataDir}";
       createHome = true;
+      isSystemUser = true;
     };
     users.groups.dnscrypt-wrapper = { };
 
@@ -196,4 +197,7 @@ in {
     };
 
   };
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
 }
diff --git a/nixos/modules/services/networking/dnsdist.nix b/nixos/modules/services/networking/dnsdist.nix
index 12eee136e639..8249da69bc1a 100644
--- a/nixos/modules/services/networking/dnsdist.nix
+++ b/nixos/modules/services/networking/dnsdist.nix
@@ -46,11 +46,10 @@ in {
         RestartSec="1";
         DynamicUser = true;
         StartLimitInterval="0";
-        PrivateTmp=true;
         PrivateDevices=true;
-        CapabilityBoundingSet="CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID";
+        AmbientCapabilities="CAP_NET_BIND_SERVICE";
+        CapabilityBoundingSet="CAP_NET_BIND_SERVICE";
         ExecStart = "${pkgs.dnsdist}/bin/dnsdist --supervised --disable-syslog --config ${configFile}";
-        ProtectSystem="full";
         ProtectHome=true;
         RestrictAddressFamilies="AF_UNIX AF_INET AF_INET6";
         LimitNOFILE="16384";
diff --git a/nixos/modules/services/networking/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index 24d16046c63e..377d7bc57058 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -79,19 +79,27 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.dnsmasq.enable {
+  config = mkIf cfg.enable {
 
     networking.nameservers =
       optional cfg.resolveLocalQueries "127.0.0.1";
 
     services.dbus.packages = [ dnsmasq ];
 
-    users.users = singleton {
-      name = "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" ];
diff --git a/nixos/modules/services/networking/ejabberd.nix b/nixos/modules/services/networking/ejabberd.nix
index ef5e2cee6f20..a5af25b983b9 100644
--- a/nixos/modules/services/networking/ejabberd.nix
+++ b/nixos/modules/services/networking/ejabberd.nix
@@ -11,7 +11,7 @@ let
     ${cfg.ctlConfig}
   '';
 
-  ectl = ''${cfg.package}/bin/ejabberdctl ${if cfg.configFile == null then "" else "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"'';
+  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;
 
@@ -94,45 +94,34 @@ in {
   config = mkIf cfg.enable {
     environment.systemPackages = [ cfg.package ];
 
-    users.users = optionalAttrs (cfg.user == "ejabberd") (singleton
-      { name = "ejabberd";
+    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") (singleton
-      { name = "ejabberd";
-        gid = config.ids.gids.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 pkgs.runit ] ++ lib.optional cfg.imagemagick pkgs.imagemagick;
+      path = [ pkgs.findutils pkgs.coreutils ] ++ lib.optional cfg.imagemagick pkgs.imagemagick;
 
       serviceConfig = {
-        ExecStart = ''${ectl} foreground'';
-        # FIXME: runit is used for `chpst` -- can we get rid of this?
-        ExecStop = ''${pkgs.runit}/bin/chpst -u "${cfg.user}:${cfg.group}" ${ectl} stop'';
-        ExecReload = ''${pkgs.runit}/bin/chpst -u "${cfg.user}:${cfg.group}" ${ectl} reload_config'';
         User = cfg.user;
         Group = cfg.group;
-        PermissionsStartOnly = true;
+        ExecStart = "${ectl} foreground";
+        ExecStop = "${ectl} stop";
+        ExecReload = "${ectl} reload_config";
       };
 
       preStart = ''
-        mkdir -p -m750 "${cfg.logsDir}"
-        chown "${cfg.user}:${cfg.group}" "${cfg.logsDir}"
-
-        mkdir -p -m750 "/var/lock/ejabberdctl"
-        chown "${cfg.user}:${cfg.group}" "/var/lock/ejabberdctl"
-
-        mkdir -p -m750 "${cfg.spoolDir}"
-        chown -R "${cfg.user}:${cfg.group}" "${cfg.spoolDir}"
-
         if [ -z "$(ls -A '${cfg.spoolDir}')" ]; then
           touch "${cfg.spoolDir}/.firstRun"
         fi
@@ -149,13 +138,18 @@ in {
           for src in ${dumps}; do
             find "$src" -type f | while read dump; do
               echo "Loading configuration dump at $dump"
-              chpst -u "${cfg.user}:${cfg.group}" ${ectl} load "$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/nixos/modules/services/networking/eternal-terminal.nix b/nixos/modules/services/networking/eternal-terminal.nix
index be7337ece7e4..a2e5b30dc0f0 100644
--- a/nixos/modules/services/networking/eternal-terminal.nix
+++ b/nixos/modules/services/networking/eternal-terminal.nix
@@ -23,6 +23,8 @@ in
         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.
         '';
       };
 
@@ -86,4 +88,8 @@ in
       };
     };
   };
+
+  meta = {
+    maintainers = with lib.maintainers; [ pingiun ];
+  };
 }
diff --git a/nixos/modules/services/networking/fakeroute.nix b/nixos/modules/services/networking/fakeroute.nix
index 82a9fb729d84..7916ad4098a7 100644
--- a/nixos/modules/services/networking/fakeroute.nix
+++ b/nixos/modules/services/networking/fakeroute.nix
@@ -60,4 +60,6 @@ in
 
   };
 
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
 }
diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix
index 4ea891262e56..15aaf7410674 100644
--- a/nixos/modules/services/networking/firewall.nix
+++ b/nixos/modules/services/networking/firewall.nix
@@ -42,23 +42,14 @@ let
 
   kernelHasRPFilter = ((kernel.config.isEnabled or (x: false)) "IP_NF_MATCH_RPFILTER") || (kernel.features.netfilterRPFilter or false);
 
-  helpers =
-    ''
-      # Helper command to manipulate both the IPv4 and IPv6 tables.
-      ip46tables() {
-        iptables -w "$@"
-        ${optionalString config.networking.enableIPv6 ''
-          ip6tables -w "$@"
-        ''}
-      }
-    '';
+  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; };
+  defaultInterface = { default = mapAttrs (name: value: cfg.${name}) commonOptions; };
   allInterfaces = defaultInterface // cfg.interfaces;
 
   startScript = writeShScript "firewall-start" ''
@@ -271,7 +262,7 @@ let
       apply = canonicalizePortList;
       example = [ 22 80 ];
       description =
-        '' 
+        ''
           List of TCP ports on which incoming connections are
           accepted.
         '';
@@ -282,7 +273,7 @@ let
       default = [ ];
       example = [ { from = 8999; to = 9003; } ];
       description =
-        '' 
+        ''
           A range of TCP ports on which incoming connections are
           accepted.
         '';
@@ -331,6 +322,17 @@ in
           '';
       };
 
+      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;
@@ -536,7 +538,7 @@ in
 
     networking.firewall.trustedInterfaces = [ "lo" ];
 
-    environment.systemPackages = [ pkgs.iptables ] ++ cfg.extraPackages;
+    environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
 
     boot.kernelModules = (optional cfg.autoLoadConntrackHelpers "nf_conntrack")
       ++ map (x: "nf_conntrack_${x}") cfg.connectionTrackingModules;
@@ -555,7 +557,7 @@ in
       before = [ "network-pre.target" ];
       after = [ "systemd-modules-load.service" ];
 
-      path = [ pkgs.iptables ] ++ cfg.extraPackages;
+      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
diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix
index c1f778ac139a..dd2f6454e954 100644
--- a/nixos/modules/services/networking/flannel.nix
+++ b/nixos/modules/services/networking/flannel.nix
@@ -92,7 +92,7 @@ in {
         Needed when running with Kubernetes as backend as this cannot be auto-detected";
       '';
       type = types.nullOr types.str;
-      default = with config.networking; (hostName + optionalString (!isNull domain) ".${domain}");
+      default = with config.networking; (hostName + optionalString (domain != null) ".${domain}");
       example = "node1.example.com";
     };
 
diff --git a/nixos/modules/services/networking/gale.nix b/nixos/modules/services/networking/gale.nix
index 7083d87c4073..cb954fd836bc 100644
--- a/nixos/modules/services/networking/gale.nix
+++ b/nixos/modules/services/networking/gale.nix
@@ -104,14 +104,13 @@ in
          systemPackages = [ pkgs.gale ];
        };
 
-       users.users = [{
-         name = cfg.user;
+       users.users.${cfg.user} = {
          description = "Gale daemon";
          uid = config.ids.uids.gale;
          group = cfg.group;
          home = home;
          createHome = true;
-       }];
+       };
 
        users.groups = [{
          name = cfg.group;
diff --git a/nixos/modules/services/networking/git-daemon.nix b/nixos/modules/services/networking/git-daemon.nix
index c0020349ec74..6f2e149433f3 100644
--- a/nixos/modules/services/networking/git-daemon.nix
+++ b/nixos/modules/services/networking/git-daemon.nix
@@ -104,18 +104,18 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = if cfg.user != "git" then {} else singleton
-      { name = "git";
+    users.users = optionalAttrs (cfg.user != "git") {
+      git = {
         uid = config.ids.uids.git;
         description = "Git daemon user";
       };
+    };
 
-    users.groups = if cfg.group != "git" then {} else singleton
-      { name = "git";
-        gid = config.ids.gids.git;
-      };
+    users.groups = optionalAttrs (cfg.group != "git") {
+      git.gid = config.ids.gids.git;
+    };
 
-    systemd.services."git-daemon" = {
+    systemd.services.git-daemon = {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       script = "${pkgs.git}/bin/git daemon --reuseaddr "
diff --git a/nixos/modules/services/networking/gnunet.nix b/nixos/modules/services/networking/gnunet.nix
index 178a832c166e..69d4ed047756 100644
--- a/nixos/modules/services/networking/gnunet.nix
+++ b/nixos/modules/services/networking/gnunet.nix
@@ -42,6 +42,7 @@ in
     services.gnunet = {
 
       enable = mkOption {
+        type = types.bool;
         default = false;
         description = ''
           Whether to run the GNUnet daemon.  GNUnet is GNU's anonymous
@@ -51,6 +52,7 @@ in
 
       fileSharing = {
         quota = mkOption {
+          type = types.int;
           default = 1024;
           description = ''
             Maximum file system usage (in MiB) for file sharing.
@@ -60,6 +62,7 @@ in
 
       udp = {
         port = mkOption {
+          type = types.port;
           default = 2086;  # assigned by IANA
           description = ''
             The UDP port for use by GNUnet.
@@ -69,6 +72,7 @@ in
 
       tcp = {
         port = mkOption {
+          type = types.port;
           default = 2086;  # assigned by IANA
           description = ''
             The TCP port for use by GNUnet.
@@ -78,6 +82,7 @@ in
 
       load = {
         maxNetDownBandwidth = mkOption {
+          type = types.int;
           default = 50000;
           description = ''
             Maximum bandwidth usage (in bits per second) for GNUnet
@@ -86,6 +91,7 @@ in
         };
 
         maxNetUpBandwidth = mkOption {
+          type = types.int;
           default = 50000;
           description = ''
             Maximum bandwidth usage (in bits per second) for GNUnet
@@ -94,6 +100,7 @@ in
         };
 
         hardNetUpBandwidth = mkOption {
+          type = types.int;
           default = 0;
           description = ''
             Hard bandwidth limit (in bits per second) when uploading
@@ -111,6 +118,7 @@ in
       };
 
       extraOptions = mkOption {
+        type = types.lines;
         default = "";
         description = ''
           Additional options that will be copied verbatim in `gnunet.conf'.
diff --git a/nixos/modules/services/networking/go-shadowsocks2.nix b/nixos/modules/services/networking/go-shadowsocks2.nix
new file mode 100644
index 000000000000..afbd7ea27c65
--- /dev/null
+++ b/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/nixos/modules/services/networking/gogoclient.nix b/nixos/modules/services/networking/gogoclient.nix
index 9d16f0efb435..c9b03bca7112 100644
--- a/nixos/modules/services/networking/gogoclient.nix
+++ b/nixos/modules/services/networking/gogoclient.nix
@@ -34,7 +34,7 @@ in
 
       password = mkOption {
         default = "";
-        type = types.string;
+        type = types.str;
         description = ''
           Path to a file (as a string), containing your gogoNET password, if any.
         '';
diff --git a/nixos/modules/services/networking/hans.nix b/nixos/modules/services/networking/hans.nix
index 20e57e4626ef..8334dc68d623 100644
--- a/nixos/modules/services/networking/hans.nix
+++ b/nixos/modules/services/networking/hans.nix
@@ -135,9 +135,9 @@ in
       };
     };
 
-    users.users = singleton {
-      name = hansUser;
+    users.users.${hansUser} = {
       description = "Hans daemon user";
+      isSystemUser = true;
     };
   };
 
diff --git a/nixos/modules/services/networking/haproxy.nix b/nixos/modules/services/networking/haproxy.nix
index 0438d0bf8d86..aff71e5e97da 100644
--- a/nixos/modules/services/networking/haproxy.nix
+++ b/nixos/modules/services/networking/haproxy.nix
@@ -1,7 +1,16 @@
 { config, lib, pkgs, ... }:
+
 let
   cfg = config.services.haproxy;
-  haproxyCfg = pkgs.writeText "haproxy.conf" cfg.config;
+
+  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;
 {
@@ -25,9 +34,7 @@ with lib;
           <filename>haproxy.conf</filename>.
         '';
       };
-
     };
-
   };
 
   config = mkIf cfg.enable {
@@ -42,21 +49,16 @@ with lib;
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig = {
-        Type = "forking";
-        PIDFile = "/run/haproxy.pid";
-        ExecStartPre = "${pkgs.haproxy}/sbin/haproxy -c -q -f ${haproxyCfg}";
-        ExecStart = "${pkgs.haproxy}/sbin/haproxy -D -f ${haproxyCfg} -p /run/haproxy.pid";
-        ExecReload = "-${pkgs.bash}/bin/bash -c \"exec ${pkgs.haproxy}/sbin/haproxy -D -f ${haproxyCfg} -p /run/haproxy.pid -sf $MAINPID\"";
+        DynamicUser = true;
+        Type = "notify";
+        # when running the config test, don't be quiet so we can see what goes wrong
+        ExecStartPre = "${pkgs.haproxy}/sbin/haproxy -c -f ${haproxyCfg}";
+        ExecStart = "${pkgs.haproxy}/sbin/haproxy -Ws -f ${haproxyCfg}";
+        Restart = "on-failure";
+        RuntimeDirectory = "haproxy";
+        # needed in case we bind to port < 1024
+        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
       };
     };
-
-    environment.systemPackages = [ pkgs.haproxy ];
-
-    users.users.haproxy = {
-      group = "haproxy";
-      uid = config.ids.uids.haproxy;
-    };
-
-    users.groups.haproxy.gid = config.ids.uids.haproxy;
   };
 }
diff --git a/nixos/modules/services/networking/helpers.nix b/nixos/modules/services/networking/helpers.nix
new file mode 100644
index 000000000000..d7d42de0e3a8
--- /dev/null
+++ b/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/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 3fbc08e90607..2915b54f05b4 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 # TODO:
 #
@@ -12,6 +12,8 @@ let
 
   cfg = config.services.hostapd;
 
+  escapedInterface = utils.escapeSystemdPath cfg.interface;
+
   configFile = pkgs.writeText "hostapd.conf" ''
     interface=${cfg.interface}
     driver=${cfg.driver}
@@ -28,10 +30,11 @@ let
     ctrl_interface=/run/hostapd
     ctrl_interface_group=${cfg.group}
 
-    ${if cfg.wpa then ''
+    ${optionalString cfg.wpa ''
       wpa=2
       wpa_passphrase=${cfg.wpaPassphrase}
-      '' else ""}
+    ''}
+    ${optionalString cfg.noScan "noscan=1"}
 
     ${cfg.extraConfig}
   '' ;
@@ -67,10 +70,18 @@ in
         '';
       };
 
+      noScan = mkOption {
+        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.string;
+        type = types.str;
         description = ''
           Which driver <command>hostapd</command> will use.
           Most applications will probably use the default.
@@ -80,7 +91,7 @@ in
       ssid = mkOption {
         default = "nixos";
         example = "mySpecialSSID";
-        type = types.string;
+        type = types.str;
         description = "SSID to be used in IEEE 802.11 management frames.";
       };
 
@@ -108,7 +119,7 @@ in
       group = mkOption {
         default = "wheel";
         example = "network";
-        type = types.string;
+        type = types.str;
         description = ''
           Members of this group can control <command>hostapd</command>.
         '';
@@ -124,7 +135,7 @@ in
       wpaPassphrase = mkOption {
         default = "my_sekret";
         example = "any_64_char_string";
-        type = types.string;
+        type = types.str;
         description = ''
           WPA-PSK (pre-shared-key) passphrase. Clients will need this
           passphrase to associate with this access point.
@@ -157,9 +168,10 @@ in
       { description = "hostapd wireless AP";
 
         path = [ pkgs.hostapd ];
-        after = [ "sys-subsystem-net-devices-${cfg.interface}.device" ];
-        bindsTo = [ "sys-subsystem-net-devices-${cfg.interface}.device" ];
+        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}";
diff --git a/nixos/modules/services/networking/hylafax/systemd.nix b/nixos/modules/services/networking/hylafax/systemd.nix
index ef177e4be345..b9b9b9dca4f0 100644
--- a/nixos/modules/services/networking/hylafax/systemd.nix
+++ b/nixos/modules/services/networking/hylafax/systemd.nix
@@ -7,7 +7,7 @@ let
   inherit (lib) concatStringsSep optionalString;
 
   cfg = config.services.hylafax;
-  mapModems = lib.flip map (lib.attrValues cfg.modems);
+  mapModems = lib.forEach (lib.attrValues cfg.modems);
 
   mkConfigFile = name: conf:
     # creates hylafax config file,
@@ -68,7 +68,7 @@ let
     inherit (cfg) spoolAreaPath;
   };
 
-  sockets."hylafax-hfaxd" = {
+  sockets.hylafax-hfaxd = {
     description = "HylaFAX server socket";
     documentation = [ "man:hfaxd(8)" ];
     wantedBy = [ "multi-user.target" ];
@@ -77,7 +77,7 @@ let
     socketConfig.Accept = true;
   };
 
-  paths."hylafax-faxq" = {
+  paths.hylafax-faxq = {
     description = "HylaFAX queue manager sendq watch";
     documentation = [ "man:faxq(8)" "man:sendq(5)" ];
     wantedBy = [ "multi-user.target" ];
@@ -87,11 +87,11 @@ let
   timers = mkMerge [
     (
       mkIf (cfg.faxcron.enable.frequency!=null)
-      { "hylafax-faxcron".timerConfig.Persistent = true; }
+      { hylafax-faxcron.timerConfig.Persistent = true; }
     )
     (
       mkIf (cfg.faxqclean.enable.frequency!=null)
-      { "hylafax-faxqclean".timerConfig.Persistent = true; }
+      { hylafax-faxqclean.timerConfig.Persistent = true; }
     )
   ];
 
@@ -121,7 +121,7 @@ let
     in
       service: service // { serviceConfig = apply service; };
 
-  services."hylafax-spool" = {
+  services.hylafax-spool = {
     description = "HylaFAX spool area preparation";
     documentation = [ "man:hylafax-server(4)" ];
     script = ''
@@ -140,7 +140,7 @@ let
     unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ];
   };
 
-  services."hylafax-faxq" = {
+  services.hylafax-faxq = {
     description = "HylaFAX queue manager";
     documentation = [ "man:faxq(8)" ];
     requires = [ "hylafax-spool.service" ];
@@ -178,7 +178,7 @@ let
     serviceConfig.PrivateNetwork = null;
   };
 
-  services."hylafax-faxcron" = rec {
+  services.hylafax-faxcron = rec {
     description = "HylaFAX spool area maintenance";
     documentation = [ "man:faxcron(8)" ];
     after = [ "hylafax-spool.service" ];
@@ -194,7 +194,7 @@ let
     ];
   };
 
-  services."hylafax-faxqclean" = rec {
+  services.hylafax-faxqclean = rec {
     description = "HylaFAX spool area queue cleaner";
     documentation = [ "man:faxqclean(8)" ];
     after = [ "hylafax-spool.service" ];
diff --git a/nixos/modules/services/networking/i2pd.nix b/nixos/modules/services/networking/i2pd.nix
index 40478b85b75e..326d34f6ca92 100644
--- a/nixos/modules/services/networking/i2pd.nix
+++ b/nixos/modules/services/networking/i2pd.nix
@@ -12,9 +12,9 @@ let
   boolOpt = k: v: k + " = " + boolToString v;
   intOpt = k: v: k + " = " + toString v;
   lstOpt = k: xs: k + " = " + concatStringsSep "," xs;
-  optionalNullString = o: s: optional (! isNull s) (strOpt o s);
-  optionalNullBool = o: b: optional (! isNull b) (boolOpt o b);
-  optionalNullInt = o: i: optional (! isNull i) (intOpt o i);
+  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; };
@@ -158,10 +158,10 @@ let
       (sec "addressbook")
       (strOpt "defaulturl" cfg.addressbook.defaulturl)
     ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
-      ++ (flip map
-      (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto)
+      ++ (flip mapAttrs
+      (collect (name: proto: proto ? port && proto ? address && proto ? name) cfg.proto)
       (proto: let protoOpts = [
-        (sec proto.name)
+        (sec name)
         (boolOpt "enabled" proto.enable)
         (strOpt "address" proto.address)
         (intOpt "port" proto.port)
@@ -181,10 +181,10 @@ let
 
   tunnelConf = let opts = [
     notice
-    (flip map
-      (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
+    (flip mapAttrs
+      (collect (name: tun: tun ? port && tun ? destination) cfg.outTunnels)
       (tun: let outTunOpts = [
-        (sec tun.name)
+        (sec name)
         "type = client"
         (intOpt "port" tun.port)
         (strOpt "destination" tun.destination)
@@ -204,10 +204,10 @@ let
         ++ (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)
+    (flip mapAttrs
+      (collect (name: tun: tun ? port && tun ? address) cfg.inTunnels)
       (tun: let inTunOpts = [
-        (sec tun.name)
+        (sec name)
         "type = server"
         (intOpt "port" tun.port)
         (strOpt "host" tun.address)
@@ -225,7 +225,7 @@ let
   i2pdSh = pkgs.writeScriptBin "i2pd" ''
     #!/bin/sh
     exec ${pkgs.i2pd}/bin/i2pd \
-      ${if isNull cfg.address then "" else "--host="+cfg.address} \
+      ${if cfg.address == null then "" else "--host="+cfg.address} \
       --service \
       --conf=${i2pdConf} \
       --tunconf=${tunnelConf}
@@ -235,6 +235,10 @@ in
 
 {
 
+  imports = [
+    (mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ])
+  ];
+
   ###### interface
 
   options = {
diff --git a/nixos/modules/services/networking/iodine.nix b/nixos/modules/services/networking/iodine.nix
index 344f84374bbd..f9ca26c27960 100644
--- a/nixos/modules/services/networking/iodine.nix
+++ b/nixos/modules/services/networking/iodine.nix
@@ -11,6 +11,13 @@ let
 
 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
 
@@ -140,8 +147,7 @@ in
       };
     };
 
-    users.users = singleton {
-      name = iodinedUser;
+    users.users.${iodinedUser} = {
       uid = config.ids.uids.iodined;
       description = "Iodine daemon user";
     };
diff --git a/nixos/modules/services/networking/iperf3.nix b/nixos/modules/services/networking/iperf3.nix
index 742404a5692f..0fe378b225d7 100644
--- a/nixos/modules/services/networking/iperf3.nix
+++ b/nixos/modules/services/networking/iperf3.nix
@@ -19,6 +19,11 @@ let
       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;
@@ -52,6 +57,11 @@ let
   };
 
   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";
diff --git a/nixos/modules/services/networking/ircd-hybrid/default.nix b/nixos/modules/services/networking/ircd-hybrid/default.nix
index 2bd898edf897..b236552eb653 100644
--- a/nixos/modules/services/networking/ircd-hybrid/default.nix
+++ b/nixos/modules/services/networking/ircd-hybrid/default.nix
@@ -112,16 +112,15 @@ in
 
   config = mkIf config.services.ircdHybrid.enable {
 
-    users.users = singleton
-      { name = "ircd";
-        description = "IRCD owner";
+    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" = {
+    systemd.services.ircd-hybrid = {
       description = "IRCD Hybrid server";
       after = [ "started networking" ];
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/networking/iwd.nix b/nixos/modules/services/networking/iwd.nix
index 18ed20e28886..839fa48d9a42 100644
--- a/nixos/modules/services/networking/iwd.nix
+++ b/nixos/modules/services/networking/iwd.nix
@@ -26,6 +26,7 @@ in {
 
     systemd.tmpfiles.rules = [
       "d /var/lib/iwd 0700 root root -"
+      "d /var/lib/ead 0700 root root -"
     ];
   };
 
diff --git a/nixos/modules/services/networking/keybase.nix b/nixos/modules/services/networking/keybase.nix
index a149f16a84cb..495102cb7eee 100644
--- a/nixos/modules/services/networking/keybase.nix
+++ b/nixos/modules/services/networking/keybase.nix
@@ -24,12 +24,18 @@ in {
 
   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 = {
-        ExecStart = ''
-          ${pkgs.keybase}/bin/keybase service --auto-forked
-        '';
+        Type = "notify";
+        EnvironmentFile = [
+          "-%E/keybase/keybase.autogen.env"
+          "-%E/keybase/keybase.env"
+        ];
+        ExecStart = "${pkgs.keybase}/bin/keybase service";
         Restart = "on-failure";
         PrivateTmp = true;
       };
diff --git a/nixos/modules/services/networking/kippo.nix b/nixos/modules/services/networking/kippo.nix
index 40c38254a57c..553415a2f329 100644
--- a/nixos/modules/services/networking/kippo.nix
+++ b/nixos/modules/services/networking/kippo.nix
@@ -11,7 +11,7 @@ with lib;
 let
   cfg = config.services.kippo;
 in
-rec {
+{
   options = {
     services.kippo = {
       enable = mkOption {
@@ -26,22 +26,22 @@ rec {
       };
       hostname = mkOption {
         default = "nas3";
-        type = types.string;
+        type = types.str;
         description = ''Hostname for kippo to present to SSH login'';
       };
       varPath = mkOption {
         default = "/var/lib/kippo";
-        type = types.string;
+        type = types.path;
         description = ''Path of read/write files needed for operation and configuration.'';
       };
       logPath = mkOption {
         default = "/var/log/kippo";
-        type = types.string;
+        type = types.path;
         description = ''Path of log files needed for operation and configuration.'';
       };
       pidPath = mkOption {
         default = "/run/kippo";
-        type = types.string;
+        type = types.path;
         description = ''Path of pid files needed for operation.'';
       };
       extraConfig = mkOption {
@@ -73,12 +73,11 @@ rec {
         ${cfg.extraConfig}
     '';
 
-    users.users = singleton {
-      name = "kippo";
+    users.users.kippo = {
       description = "kippo web server privilege separation user";
       uid = 108; # why does config.ids.uids.kippo give an error?
     };
-    users.groups = singleton { name = "kippo";gid=108; };
+    users.groups.kippo.gid = 108;
 
     systemd.services.kippo = with pkgs; {
       description = "Kippo Web Server";
@@ -109,8 +108,8 @@ rec {
 
       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"; 
+      serviceConfig.User = "kippo";
+      serviceConfig.Group = "kippo";
     };
 };
 }
diff --git a/nixos/modules/services/networking/knot.nix b/nixos/modules/services/networking/knot.nix
index 1cc1dd3f2f62..47364ecb8464 100644
--- a/nixos/modules/services/networking/knot.nix
+++ b/nixos/modules/services/networking/knot.nix
@@ -56,6 +56,7 @@ in {
       package = mkOption {
         type = types.package;
         default = pkgs.knot-dns;
+        defaultText = "pkgs.knot-dns";
         description = ''
           Which Knot DNS package to use
         '';
@@ -92,4 +93,3 @@ in {
     environment.systemPackages = [ knot-cli-wrappers ];
   };
 }
-
diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix
index ca34ff9df4ef..bb941e93e150 100644
--- a/nixos/modules/services/networking/kresd.nix
+++ b/nixos/modules/services/networking/kresd.nix
@@ -5,14 +5,28 @@ with lib;
 let
 
   cfg = config.services.kresd;
-  package = pkgs.knot-resolver;
+  configFile = pkgs.writeText "kresd.conf" ''
+    ${optionalString (cfg.listenDoH != []) "modules.load('http')"}
+    ${cfg.extraConfig};
+  '';
 
-  configFile = pkgs.writeText "kresd.conf" cfg.extraConfig;
-in
-
-{
+  package = pkgs.knot-resolver.override {
+    extraFeatures = cfg.listenDoH != [];
+  };
+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
+      )
+    )
+  ];
+
   ###### interface
   options.services.kresd = {
     enable = mkOption {
@@ -39,11 +53,12 @@ in
         Directory for caches.  They are intended to survive reboots.
       '';
     };
-    interfaces = mkOption {
+    listenPlain = mkOption {
       type = with types; listOf str;
-      default = [ "::1" "127.0.0.1" ];
+      default = [ "[::1]:53" "127.0.0.1:53" ];
       description = ''
-        What addresses the server should listen on. (UDP+TCP 53)
+        What addresses and ports the server should listen on.
+        For detailed syntax see ListenStream in man systemd.socket.
       '';
     };
     listenTLS = mkOption {
@@ -51,7 +66,16 @@ in
       default = [];
       example = [ "198.51.100.1:853" "[2001:db8::1]:853" "853" ];
       description = ''
-        Addresses on which kresd should provide DNS over TLS (see RFC 7858).
+        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 7858).
         For detailed syntax see ListenStream in man systemd.socket.
       '';
     };
@@ -62,26 +86,22 @@ in
   config = mkIf cfg.enable {
     environment.etc."kresd.conf".source = configFile; # not required
 
-    users.users = singleton
-      { name = "kresd";
-        uid = config.ids.uids.kresd;
+    users.users.kresd =
+      { uid = config.ids.uids.kresd;
         group = "kresd";
         description = "Knot-resolver daemon user";
       };
-    users.groups = singleton
-      { name = "kresd";
-        gid = config.ids.gids.kresd;
-      };
+    users.groups.kresd.gid = config.ids.gids.kresd;
 
     systemd.sockets.kresd = rec {
       wantedBy = [ "sockets.target" ];
       before = wantedBy;
-      listenStreams = map
-        # Syntax depends on being IPv6 or IPv4.
-        (iface: if elem ":" (stringToCharacters iface) then "[${iface}]:53" else "${iface}:53")
-        cfg.interfaces;
-      socketConfig.ListenDatagram = listenStreams;
-      socketConfig.FreeBind = true;
+      listenStreams = cfg.listenPlain;
+      socketConfig = {
+        ListenDatagram = listenStreams;
+        FreeBind = true;
+        FileDescriptorName = "dns";
+      };
     };
 
     systemd.sockets.kresd-tls = mkIf (cfg.listenTLS != []) rec {
@@ -96,6 +116,18 @@ in
       };
     };
 
+    systemd.sockets.kresd-doh = mkIf (cfg.listenDoH != []) rec {
+      wantedBy = [ "sockets.target" ];
+      before = wantedBy;
+      partOf = [ "kresd.socket" ];
+      listenStreams = cfg.listenDoH;
+      socketConfig = {
+        FileDescriptorName = "doh";
+        FreeBind = true;
+        Service = "kresd.service";
+      };
+    };
+
     systemd.sockets.kresd-control = rec {
       wantedBy = [ "sockets.target" ];
       before = wantedBy;
diff --git a/nixos/modules/services/networking/logmein-hamachi.nix b/nixos/modules/services/networking/logmein-hamachi.nix
index 406626a8a343..11cbdda2f845 100644
--- a/nixos/modules/services/networking/logmein-hamachi.nix
+++ b/nixos/modules/services/networking/logmein-hamachi.nix
@@ -35,7 +35,7 @@ in
       description = "LogMeIn Hamachi Daemon";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" "local-fs.target" ];
+      after = [ "network.target" ];
 
       serviceConfig = {
         Type = "forking";
diff --git a/nixos/modules/services/networking/matterbridge.nix b/nixos/modules/services/networking/matterbridge.nix
index 1fd63348c16c..b8b4f37c84a8 100644
--- a/nixos/modules/services/networking/matterbridge.nix
+++ b/nixos/modules/services/networking/matterbridge.nix
@@ -92,13 +92,15 @@ in
     warnings = optional options.services.matterbridge.configFile.isDefined
       "The option services.matterbridge.configFile is insecure and should be replaced with services.matterbridge.configPath";
 
-    users.users = optional (cfg.user == "matterbridge")
-      { name = "matterbridge";
-        group = "matterbridge";
+    users.users = optionalAttrs (cfg.user == "matterbridge")
+      { matterbridge = {
+          group = "matterbridge";
+          isSystemUser = true;
+        };
       };
 
-    users.groups = optional (cfg.group == "matterbridge")
-      { name = "matterbridge";
+    users.groups = optionalAttrs (cfg.group == "matterbridge")
+      { matterbridge = { };
       };
 
     systemd.services.matterbridge = {
@@ -109,7 +111,7 @@ in
       serviceConfig = {
         User = cfg.user;
         Group = cfg.group;
-        ExecStart = "${pkgs.matterbridge.bin}/bin/matterbridge -conf ${matterbridgeConfToml}";
+        ExecStart = "${pkgs.matterbridge}/bin/matterbridge -conf ${matterbridgeConfToml}";
         Restart = "always";
         RestartSec = "10";
       };
diff --git a/nixos/modules/services/networking/minidlna.nix b/nixos/modules/services/networking/minidlna.nix
index 1858f03cac1f..3ddea3c9757b 100644
--- a/nixos/modules/services/networking/minidlna.nix
+++ b/nixos/modules/services/networking/minidlna.nix
@@ -36,6 +36,37 @@ in
         '';
     };
 
+    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";
@@ -66,7 +97,37 @@ in
 
     services.minidlna.config = mkOption {
       type = types.lines;
-      description = "The contents of MiniDLNA's configuration file.";
+      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.
+      '';
     };
   };
 
@@ -75,13 +136,15 @@ in
     services.minidlna.config =
       ''
         port=${toString port}
-        friendly_name=${config.networking.hostName} MiniDLNA
+        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}
+        ${cfg.extraConfig}
       '';
 
     users.users.minidlna = {
@@ -96,18 +159,12 @@ in
       { description = "MiniDLNA Server";
 
         wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" "local-fs.target" ];
-
-        preStart =
-          ''
-            mkdir -p /var/cache/minidlna
-            chown -R minidlna:minidlna /var/cache/minidlna
-          '';
+        after = [ "network.target" ];
 
         serviceConfig =
           { User = "minidlna";
             Group = "minidlna";
-            PermissionsStartOnly = true;
+            CacheDirectory = "minidlna";
             RuntimeDirectory = "minidlna";
             PIDFile = "/run/minidlna/pid";
             ExecStart =
diff --git a/nixos/modules/services/networking/mjpg-streamer.nix b/nixos/modules/services/networking/mjpg-streamer.nix
index e0a6c112e3cb..dbc35e2e71c0 100644
--- a/nixos/modules/services/networking/mjpg-streamer.nix
+++ b/nixos/modules/services/networking/mjpg-streamer.nix
@@ -49,10 +49,11 @@ in {
 
   config = mkIf cfg.enable {
 
-    users.users = optional (cfg.user == "mjpg-streamer") {
-      name = "mjpg-streamer";
-      uid = config.ids.uids.mjpg-streamer;
-      group = cfg.group;
+    users.users = optionalAttrs (cfg.user == "mjpg-streamer") {
+      mjpg-streamer = {
+        uid = config.ids.uids.mjpg-streamer;
+        group = cfg.group;
+      };
     };
 
     systemd.services.mjpg-streamer = {
diff --git a/nixos/modules/services/networking/monero.nix b/nixos/modules/services/networking/monero.nix
index 831e4d60d8da..b95364308682 100644
--- a/nixos/modules/services/networking/monero.nix
+++ b/nixos/modules/services/networking/monero.nix
@@ -197,17 +197,15 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = singleton {
-      name = "monero";
+    users.users.monero = {
       uid  = config.ids.uids.monero;
       description = "Monero daemon user";
       home = dataDir;
       createHome = true;
     };
 
-    users.groups = singleton {
-      name = "monero";
-      gid  = config.ids.gids.monero;
+    users.groups.monero = {
+      gid = config.ids.gids.monero;
     };
 
     systemd.services.monero = {
@@ -224,15 +222,17 @@ in
       };
     };
 
-   assertions = singleton {
-     assertion = cfg.mining.enable -> cfg.mining.address != "";
-     message   = ''
+    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/nixos/modules/services/networking/morty.nix b/nixos/modules/services/networking/morty.nix
index cc81e27e9399..e3a6444c1163 100644
--- a/nixos/modules/services/networking/morty.nix
+++ b/nixos/modules/services/networking/morty.nix
@@ -27,7 +27,7 @@ in
       };
 
       key = mkOption {
-        type = types.string;
+        type = types.str;
         default = "";
         description = "HMAC url validation key (hexadecimal encoded).
 	Leave blank to disable. Without validation key, anyone can
@@ -56,7 +56,7 @@ in
       };
 
       listenAddress = mkOption {
-        type = types.string;
+        type = types.str;
         default = "127.0.0.1";
         description = "The address on which the service listens";
         defaultText = "127.0.0.1 (localhost)";
@@ -74,6 +74,7 @@ in
       { description = "Morty user";
         createHome = true;
         home = "/var/lib/morty";
+        isSystemUser = true;
       };
 
     systemd.services.morty =
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index 1d49c137723c..d2feb93e2b72 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -49,7 +49,7 @@ in
       host = mkOption {
         default = "127.0.0.1";
         example = "0.0.0.0";
-        type = types.string;
+        type = types.str;
         description = ''
           Host to listen on without SSL.
         '';
@@ -88,7 +88,7 @@ in
         host = mkOption {
           default = "0.0.0.0";
           example = "localhost";
-          type = types.string;
+          type = types.str;
           description = ''
             Host to listen on with SSL.
           '';
@@ -135,7 +135,7 @@ in
             };
 
             acl = mkOption {
-              type = types.listOf types.string;
+              type = types.listOf types.str;
               example = [ "topic read A/B" "topic A/#" ];
               description = ''
                 Control client access to topics on the broker.
diff --git a/nixos/modules/services/networking/mtprotoproxy.nix b/nixos/modules/services/networking/mtprotoproxy.nix
new file mode 100644
index 000000000000..d896f227b82c
--- /dev/null
+++ b/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/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index a6e90feff7ea..3054ae1b201f 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.murmur;
+  forking = cfg.logFile != null;
   configFile = pkgs.writeText "murmurd.ini" ''
     database=/var/lib/murmur/murmur.sqlite
     dbDriver=QSQLITE
@@ -12,8 +13,8 @@ let
     autobanTimeframe=${toString cfg.autobanTimeframe}
     autobanTime=${toString cfg.autobanTime}
 
-    logfile=/var/log/murmur/murmurd.log
-    pidfile=${cfg.pidfile}
+    logfile=${optionalString (cfg.logFile != null) cfg.logFile}
+    ${optionalString forking "pidfile=/run/murmur/murmurd.pid"}
 
     welcometext="${cfg.welcometext}"
     port=${toString cfg.port}
@@ -45,6 +46,11 @@ let
   '';
 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 {
@@ -78,10 +84,11 @@ in
         description = "The amount of time an IP ban lasts (in seconds).";
       };
 
-      pidfile = mkOption {
-        type = types.path;
-        default = "/run/murmur/murmurd.pid";
-        description = "Path to PID file for Murmur daemon.";
+      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 {
@@ -232,7 +239,7 @@ in
       extraConfig = mkOption {
         type = types.lines;
         default = "";
-        description = "Extra configuration to put into mumur.ini.";
+        description = "Extra configuration to put into murmur.ini.";
       };
     };
   };
@@ -251,19 +258,13 @@ in
       after       = [ "network-online.target "];
 
       serviceConfig = {
-        Type      = "forking";
-        RuntimeDirectory = "murmur";
-        PIDFile   = cfg.pidfile;
-        Restart   = "always";
+        # 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}";
-        PermissionsStartOnly = true;
       };
-
-      preStart = ''
-        mkdir -p /var/log/murmur
-        chown -R murmur /var/log/murmur
-      '';
     };
   };
 }
diff --git a/nixos/modules/services/networking/mxisd.nix b/nixos/modules/services/networking/mxisd.nix
index 0b9824f29fd7..482d6ff456b1 100644
--- a/nixos/modules/services/networking/mxisd.nix
+++ b/nixos/modules/services/networking/mxisd.nix
@@ -3,6 +3,15 @@
 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; }
@@ -12,37 +21,41 @@ let
     matrix.domain = cfg.matrix.domain;
     key.path = "${cfg.dataDir}/signing.key";
     storage = {
-      provider.sqlite.database = "${cfg.dataDir}/mxisd.db";
+      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 = pkgs.writeText "mxisd-config.yaml" (builtins.toJSON fullConfig);
+  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 "mxisd matrix federated identity server";
+      enable = mkEnableOption "matrix federated identity server";
 
       package = mkOption {
         type = types.package;
         default = pkgs.mxisd;
         defaultText = "pkgs.mxisd";
-        description = "The mxisd package to use";
+        description = "The mxisd/ma1sd package to use";
       };
 
       dataDir = mkOption {
         type = types.str;
         default = "/var/lib/mxisd";
-        description = "Where data mxisd uses resides";
+        description = "Where data mxisd/ma1sd uses resides";
       };
 
       extraConfig = mkOption {
         type = types.attrs;
         default = {};
-        description = "Extra options merged into the mxisd configuration";
+        description = "Extra options merged into the mxisd/ma1sd configuration";
       };
 
       matrix = {
@@ -62,7 +75,7 @@ in {
           type = types.nullOr types.str;
           default = null;
           description = ''
-            Public hostname of mxisd, if different from the Matrix domain.
+            Public hostname of mxisd/ma1sd, if different from the Matrix domain.
           '';
         };
 
@@ -80,43 +93,33 @@ in {
   };
 
   config = mkIf cfg.enable {
-    users.users = [
+    users.users.mxisd =
       {
-        name = "mxisd";
         group = "mxisd";
         home = cfg.dataDir;
         createHome = true;
         shell = "${pkgs.bash}/bin/bash";
         uid = config.ids.uids.mxisd;
-      }
-    ];
+      };
 
-    users.groups = [
+    users.groups.mxisd =
       {
-        name = "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" ];
 
-      # mxisd / spring.boot needs the configuration to be named "application.yaml"
-      preStart = ''
-        config=${cfg.dataDir}/application.yaml
-        cp ${configFile} $config
-        chmod 444 $config
-      '';
-
-      serviceConfig = {
+      serviceConfig = let
+        executable = if isMa1sd cfg.package then "ma1sd" else "mxisd";
+      in {
         Type = "simple";
         User = "mxisd";
         Group = "mxisd";
-        ExecStart = "${cfg.package}/bin/mxisd --spring.config.location=${cfg.dataDir}/ --spring.profiles.active=systemd --java.security.egd=file:/dev/./urandom";
+        ExecStart = "${cfg.package}/bin/${executable} -c ${configFile}";
         WorkingDirectory = cfg.dataDir;
-        SuccessExitStatus = 143;
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/networking/namecoind.nix b/nixos/modules/services/networking/namecoind.nix
index a569ca87e262..ead7f0859434 100644
--- a/nixos/modules/services/networking/namecoind.nix
+++ b/nixos/modules/services/networking/namecoind.nix
@@ -154,16 +154,14 @@ in
       config = ${configFile}
     '';
 
-    users.users = singleton {
-      name = "namecoin";
+    users.users.namecoin = {
       uid  = config.ids.uids.namecoin;
       description = "Namecoin daemon user";
       home = dataDir;
       createHome = true;
     };
 
-    users.groups = singleton {
-      name = "namecoin";
+    users.groups.namecoin = {
       gid  = config.ids.gids.namecoin;
     };
 
@@ -175,7 +173,7 @@ in
       serviceConfig = {
         User  = "namecoin";
         Group = "namecoin";
-        ExecStart  = "${pkgs.altcoins.namecoind}/bin/namecoind -conf=${configFile} -datadir=${dataDir} -printtoconsole";
+        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";
@@ -201,4 +199,6 @@ in
 
   };
 
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
 }
diff --git a/nixos/modules/services/networking/nat.nix b/nixos/modules/services/networking/nat.nix
index 89d8590093dd..9c658af30f75 100644
--- a/nixos/modules/services/networking/nat.nix
+++ b/nixos/modules/services/networking/nat.nix
@@ -7,29 +7,36 @@
 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 = ''
-    iptables -w -t nat -D PREROUTING -j nixos-nat-pre 2>/dev/null|| true
-    iptables -w -t nat -F nixos-nat-pre 2>/dev/null || true
-    iptables -w -t nat -X nixos-nat-pre 2>/dev/null || true
-    iptables -w -t nat -D POSTROUTING -j nixos-nat-post 2>/dev/null || true
-    iptables -w -t nat -F nixos-nat-post 2>/dev/null || true
-    iptables -w -t nat -X nixos-nat-post 2>/dev/null || true
+    ${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
-    iptables -w -t nat -N nixos-nat-pre
-    iptables -w -t nat -N nixos-nat-post
+    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 external interfaces.
+    # mark packets coming from the internal interfaces.
     ${concatMapStrings (iface: ''
       iptables -w -t nat -A nixos-nat-pre \
         -i '${iface}' -j MARK --set-mark 1
@@ -61,7 +68,7 @@ let
           destinationPorts = if (m == null) then throw "bad ip:ports `${fwd.destination}'" else elemAt m 1;
         in ''
           # Allow connections to ${loopbackip}:${toString fwd.sourcePort} from the host itself
-          iptables -w -t nat -A OUTPUT \
+          iptables -w -t nat -A nixos-nat-out \
             -d ${loopbackip} -p ${fwd.proto} \
             --dport ${builtins.toString fwd.sourcePort} \
             -j DNAT --to-destination ${fwd.destination}
@@ -88,8 +95,9 @@ let
     ${cfg.extraCommands}
 
     # Append our chains to the nat tables
-    iptables -w -t nat -A PREROUTING -j nixos-nat-pre
-    iptables -w -t nat -A POSTROUTING -j nixos-nat-post
+    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
diff --git a/nixos/modules/services/networking/ndppd.nix b/nixos/modules/services/networking/ndppd.nix
index ba17f1ba825a..e015f76f622b 100644
--- a/nixos/modules/services/networking/ndppd.nix
+++ b/nixos/modules/services/networking/ndppd.nix
@@ -142,7 +142,7 @@ in {
         messages, and respond to them according to a set of rules.
       '';
       default = {};
-      example = { "eth0".rules."1111::/64" = {}; };
+      example = { eth0.rules."1111::/64" = {}; };
     };
   };
 
@@ -153,7 +153,7 @@ in {
     '' ];
 
     services.ndppd.proxies = mkIf (cfg.interface != null && cfg.network != null) {
-      "${cfg.interface}".rules."${cfg.network}" = {};
+      ${cfg.interface}.rules.${cfg.network} = {};
     };
 
     systemd.services.ndppd = {
@@ -161,7 +161,25 @@ in {
       documentation = [ "man:ndppd(1)" "man:ndppd.conf(5)" ];
       after = [ "network-pre.target" ];
       wantedBy = [ "multi-user.target" ];
-      serviceConfig.ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}";
+      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/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 5e5544471818..e817f295a445 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -1,22 +1,33 @@
 { config, lib, pkgs, ... }:
 
-with pkgs;
 with lib;
 
 let
   cfg = config.networking.networkmanager;
 
-  dynamicHostsEnabled =
-    cfg.dynamicHosts.enable && cfg.dynamicHosts.hostsDirs != {};
+  basePackages = with pkgs; [
+    crda
+    modemmanager
+    networkmanager
+    networkmanager-fortisslvpn
+    networkmanager-iodine
+    networkmanager-l2tp
+    networkmanager-openconnect
+    networkmanager-openvpn
+    networkmanager-vpnc
+   ] ++ optional (!delegateWireless && !enableIwd) wpa_supplicant;
 
-  # /var/lib/misc is for dnsmasq.leases.
-  stateDirs = "/var/lib/NetworkManager /var/lib/dhclient /var/lib/misc";
+  delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [];
 
-  configFile = writeText "NetworkManager.conf" ''
+  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 != [])
@@ -24,6 +35,7 @@ let
 
     [logging]
     level=${cfg.logLevel}
+    audit=${lib.boolToString config.security.audit.enable}
 
     [connection]
     ipv6.ip6-privacy=2
@@ -34,6 +46,7 @@ let
 
     [device]
     wifi.scan-rand-mac-address=${if cfg.wifi.scanRandMacAddress then "yes" else "no"}
+    wifi.backend=${cfg.wifi.backend}
 
     ${cfg.extraConfig}
   '';
@@ -64,25 +77,25 @@ let
     });
   '';
 
-  ns = xs: writeText "nameservers" (
+  ns = xs: pkgs.writeText "nameservers" (
     concatStrings (map (s: "nameserver ${s}\n") xs)
   );
 
-  overrideNameserversScript = writeScript "02overridedns" ''
+  overrideNameserversScript = pkgs.writeScript "02overridedns" ''
     #!/bin/sh
-    tmp=`${coreutils}/bin/mktemp`
-    ${gnused}/bin/sed '/nameserver /d' /etc/resolv.conf > $tmp
-    ${gnugrep}/bin/grep 'nameserver ' /etc/resolv.conf | \
-      ${gnugrep}/bin/grep -vf ${ns (cfg.appendNameservers ++ cfg.insertNameservers)} > $tmp.ns
-    ${optionalString (cfg.appendNameservers != []) "${coreutils}/bin/cat $tmp $tmp.ns ${ns cfg.appendNameservers} > /etc/resolv.conf"}
-    ${optionalString (cfg.insertNameservers != []) "${coreutils}/bin/cat $tmp ${ns cfg.insertNameservers} $tmp.ns > /etc/resolv.conf"}
-    ${coreutils}/bin/rm -f $tmp $tmp.ns
+    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/";
+    basic = "";
+    pre-up = "pre-up.d/";
+    pre-down = "pre-down.d/";
   };
 
   macAddressOpt = mkOption {
@@ -90,11 +103,29 @@ let
     default = "preserve";
     example = "00:11:22:33:44:55";
     description = ''
-      "XX:XX:XX:XX:XX:XX": MAC address of the interface.
-      <literal>permanent</literal>: use the permanent MAC address of the device.
-      <literal>preserve</literal>: don’t change the MAC address of the device upon activation.
-      <literal>random</literal>: generate a randomized value upon each connect.
-      <literal>stable</literal>: generate a stable, hashed MAC address.
+      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>
     '';
   };
 
@@ -123,41 +154,49 @@ in {
         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.string;
+        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
-          see "Device List Format" in NetworkManager.conf man page.
+          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>.
         '';
       };
 
-      # Ugly hack for using the correct gnome3 packageSet
-      basePackages = mkOption {
-        type = types.attrsOf types.package;
-        default = { inherit networkmanager modemmanager wpa_supplicant
-                            networkmanager-openvpn networkmanager-vpnc
-                            networkmanager-openconnect networkmanager-fortisslvpn
-                            networkmanager-l2tp networkmanager-iodine; };
-        internal = true;
-      };
-
       packages = mkOption {
-        type = types.listOf types.path;
+        type = types.listOf types.package;
         default = [ ];
         description = ''
           Extra packages that provide NetworkManager plugins.
         '';
-        apply = list: (attrValues cfg.basePackages) ++ list;
+        apply = list: basePackages ++ list;
       };
 
       dhcp = mkOption {
         type = types.enum [ "dhclient" "dhcpcd" "internal" ];
-        default = "dhclient";
+        default = "internal";
         description = ''
           Which program (or internal library) should be used for DHCP.
         '';
@@ -194,6 +233,15 @@ in {
       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;
@@ -219,67 +267,15 @@ in {
           Set the DNS (<literal>resolv.conf</literal>) processing mode.
           </para>
           <para>
-          Options:
-          <variablelist>
-          <varlistentry>
-            <term><literal>"default"</literal></term>
-            <listitem><para>
-              NetworkManager will update <literal>/etc/resolv.conf</literal> to
-              reflect the nameservers provided by currently active connections.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"dnsmasq"</literal></term>
-            <listitem>
-              <para>
-                Enable NetworkManager's dnsmasq integration. NetworkManager will
-                run dnsmasq as a local caching nameserver, using a "split DNS"
-                configuration if you are connected to a VPN, and then update
-                <literal>resolv.conf</literal> to point to the local nameserver.
-              </para>
-              <para>
-                It is possible to pass custom options to the dnsmasq instance by
-                adding them to files in the
-                <literal>/etc/NetworkManager/dnsmasq.d/</literal> directory.
-              </para>
-              <para>
-                When multiple upstream servers are available, dnsmasq will
-                initially contact them in parallel and then use the fastest to
-                respond, probing again other servers after some time.  This
-                behavior can be modified passing the
-                <literal>all-servers</literal> or <literal>strict-order</literal>
-                options to dnsmasq (see the manual page for more details).
-              </para>
-              <para>
-                Note that this option causes NetworkManager to launch and manage
-                its own instance of the dnsmasq daemon, which is
-                <emphasis>not</emphasis> the same as setting
-                <literal>services.dnsmasq.enable = true;</literal>.
-              </para>
-            </listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"unbound"</literal></term>
-            <listitem><para>
-              NetworkManager will talk to unbound and dnssec-triggerd,
-              providing a "split DNS" configuration with DNSSEC support.
-              <literal>/etc/resolv.conf</literal> will be managed by
-              dnssec-trigger daemon.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"systemd-resolved"</literal></term>
-            <listitem><para>
-              NetworkManager will push the DNS configuration to systemd-resolved.
-            </para></listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><literal>"none"</literal></term>
-            <listitem><para>
-              NetworkManager will not modify resolv.conf.
-            </para></listitem>
-          </varlistentry>
-          </variablelist>
+          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>.
         '';
       };
 
@@ -312,6 +308,7 @@ in {
 
                 if [ "$2" != "up" ]; then
                     logger "exit: event $2 != up"
+                    exit
                 fi
 
                 # coreutils and iproute are in PATH too
@@ -336,184 +333,147 @@ in {
           so you don't need to to that yourself.
         '';
       };
-
-      dynamicHosts = {
-        enable = mkOption {
-          type = types.bool;
-          default = false;
-          description = ''
-            Enabling this option requires the
-            <option>networking.networkmanager.dns</option> option to be
-            set to <literal>dnsmasq</literal>. If enabled, the directories
-            defined by the
-            <option>networking.networkmanager.dynamicHosts.hostsDirs</option>
-            option will be set up when the service starts. The dnsmasq instance
-            managed by NetworkManager will then watch those directories for
-            hosts files (see the <literal>--hostsdir</literal> option of
-            dnsmasq). This way a non-privileged user can add or override DNS
-            entries on the local system (depending on what hosts directories
-            that are configured)..
-          '';
-        };
-        hostsDirs = mkOption {
-          type = with types; attrsOf (submodule {
-            options = {
-              user = mkOption {
-                type = types.str;
-                default = "root";
-                description = ''
-                  The user that will own the hosts directory.
-                '';
-              };
-              group = mkOption {
-                type = types.str;
-                default = "root";
-                description = ''
-                  The group that will own the hosts directory.
-                '';
-              };
-            };
-          });
-          default = {};
-          description = ''
-            Defines a set of directories (relative to
-            <literal>/run/NetworkManager/hostdirs</literal>) that dnsmasq will
-            watch for hosts files.
-          '';
-        };
-      };
     };
   };
 
+  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 == false;
-        message = "You can not use networking.networkmanager with networking.wireless";
-      }
-      { assertion = !dynamicHostsEnabled || (dynamicHostsEnabled && cfg.dns == "dnsmasq");
+      { assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [];
         message = ''
-          To use networking.networkmanager.dynamicHosts you also need to set
-          networking.networkmanager.dns = "dnsmasq"
+          You can not use networking.networkmanager with networking.wireless.
+          Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager.
         '';
       }
     ];
 
-    environment.etc = with cfg.basePackages; [
-      { source = configFile;
-        target = "NetworkManager/NetworkManager.conf";
-      }
-      { source = "${networkmanager-openvpn}/lib/NetworkManager/VPN/nm-openvpn-service.name";
-        target = "NetworkManager/VPN/nm-openvpn-service.name";
-      }
-      { source = "${networkmanager-vpnc}/lib/NetworkManager/VPN/nm-vpnc-service.name";
-        target = "NetworkManager/VPN/nm-vpnc-service.name";
-      }
-      { source = "${networkmanager-openconnect}/lib/NetworkManager/VPN/nm-openconnect-service.name";
-        target = "NetworkManager/VPN/nm-openconnect-service.name";
-      }
-      { source = "${networkmanager-fortisslvpn}/lib/NetworkManager/VPN/nm-fortisslvpn-service.name";
-        target = "NetworkManager/VPN/nm-fortisslvpn-service.name";
-      }
-      { source = "${networkmanager-l2tp}/lib/NetworkManager/VPN/nm-l2tp-service.name";
-        target = "NetworkManager/VPN/nm-l2tp-service.name";
-      }
-      { source = "${networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
-        target = "NetworkManager/VPN/nm-strongswan-service.name";
-      }
-      { source = "${networkmanager-iodine}/lib/NetworkManager/VPN/nm-iodine-service.name";
-        target = "NetworkManager/VPN/nm-iodine-service.name";
+    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";
       }
-    ] ++ optional (cfg.appendNameservers == [] || cfg.insertNameservers == [])
-           { source = overrideNameserversScript;
-             target = "NetworkManager/dispatcher.d/02overridedns";
-           }
-      ++ lib.imap1 (i: s: {
-        inherit (s) source;
-        target = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
-        mode = "0544";
-      }) cfg.dispatcherScripts
-      ++ optional (dynamicHostsEnabled)
-           { target = "NetworkManager/dnsmasq.d/dyndns.conf";
-             text = concatMapStrings (n: ''
-               hostsdir=/run/NetworkManager/hostsdirs/${n}
-             '') (attrNames cfg.dynamicHosts.hostsDirs);
-           };
+      // 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 = [{
-      name = "networkmanager";
-      gid = config.ids.gids.networkmanager;
-    }
-    {
-      name = "nm-openvpn";
-      gid = config.ids.gids.nm-openvpn;
-    }];
-    users.users = [{
-      name = "nm-openvpn";
-      uid = config.ids.uids.nm-openvpn;
-      extraGroups = [ "networkmanager" ];
-    }
-    {
-      name = "nm-iodine";
-      isSystemUser = true;
-      group = "networkmanager";
-    }];
+    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.services."NetworkManager" = {
+    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 ];
 
-      preStart = ''
-        mkdir -m 700 -p /etc/NetworkManager/system-connections
-        mkdir -m 700 -p /etc/ipsec.d
-        mkdir -m 755 -p ${stateDirs}
-      '';
-    };
+      aliases = [ "dbus-org.freedesktop.NetworkManager.service" ];
 
-    systemd.services.nm-setup-hostsdirs = mkIf dynamicHostsEnabled {
-      wantedBy = [ "NetworkManager.service" ];
-      before = [ "NetworkManager.service" ];
-      partOf = [ "NetworkManager.service" ];
-      script = concatStrings (mapAttrsToList (n: d: ''
-        mkdir -p "/run/NetworkManager/hostsdirs/${n}"
-        chown "${d.user}:${d.group}" "/run/NetworkManager/hostsdirs/${n}"
-        chmod 0775 "/run/NetworkManager/hostsdirs/${n}"
-      '') cfg.dynamicHosts.hostsDirs);
       serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = true;
+        StateDirectory = "NetworkManager";
+        StateDirectoryMode = 755; # not sure if this really needs to be 755
       };
     };
 
-    systemd.services."NetworkManager-dispatcher" = {
+    systemd.services.NetworkManager-wait-online = {
+      wantedBy = [ "network-online.target" ];
+    };
+
+    systemd.services.ModemManager.aliases = [ "dbus-org.freedesktop.ModemManager1.service" ];
+
+    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
-    networking = {
-      useDHCP = false;
-      # use mkDefault to trigger the assertion about the conflict above
-      wireless.enable = lib.mkDefault false;
-    };
+    # Turn off NixOS' network management when networking is managed entirely by NetworkManager
+    networking = mkMerge [
+      (mkIf (!delegateWireless) {
+        useDHCP = false;
+      })
 
-    security.polkit.extraConfig = polkitConf;
+      (mkIf cfg.enableStrongSwan {
+        networkmanager.packages = [ pkgs.networkmanager_strongswan ];
+      })
 
-    networking.networkmanager.packages =
-      mkIf cfg.enableStrongSwan [ pkgs.networkmanager_strongswan ];
+      (mkIf enableIwd {
+        wireless.iwd.enable = true;
+      })
+    ];
+
+    security.polkit.extraConfig = polkitConf;
 
-    services.dbus.packages =
-      optional cfg.enableStrongSwan pkgs.strongswanNM ++ cfg.packages;
+    services.dbus.packages = cfg.packages
+      ++ optional cfg.enableStrongSwan pkgs.strongswanNM
+      ++ optional (cfg.dns == "dnsmasq") pkgs.dnsmasq;
 
     services.udev.packages = cfg.packages;
   };
diff --git a/nixos/modules/services/networking/nghttpx/default.nix b/nixos/modules/services/networking/nghttpx/default.nix
index d6e1906e3881..881a2670f5db 100644
--- a/nixos/modules/services/networking/nghttpx/default.nix
+++ b/nixos/modules/services/networking/nghttpx/default.nix
@@ -96,6 +96,7 @@ in
     users.groups.nghttpx = { };
     users.users.nghttpx = {
       group = config.users.groups.nghttpx.name;
+      isSystemUser = true;
     };
       
 
diff --git a/nixos/modules/services/networking/nix-serve.nix b/nixos/modules/services/networking/nix-serve.nix
index ca458d089dcc..347d87b3f385 100644
--- a/nixos/modules/services/networking/nix-serve.nix
+++ b/nixos/modules/services/networking/nix-serve.nix
@@ -19,7 +19,7 @@ in
       };
 
       bindAddress = mkOption {
-        type = types.string;
+        type = types.str;
         default = "0.0.0.0";
         description = ''
           IP address where nix-serve will bind its listening socket.
@@ -44,7 +44,7 @@ in
       };
 
       extraParams = mkOption {
-        type = types.string;
+        type = types.separatedString " ";
         default = "";
         description = ''
           Extra command line parameters for nix-serve.
diff --git a/nixos/modules/services/networking/nntp-proxy.nix b/nixos/modules/services/networking/nntp-proxy.nix
index d24d6f77a491..cc061bf6e3b9 100644
--- a/nixos/modules/services/networking/nntp-proxy.nix
+++ b/nixos/modules/services/networking/nntp-proxy.nix
@@ -210,9 +210,8 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = singleton
-      { name = proxyUser;
-        uid = config.ids.uids.nntp-proxy;
+    users.users.${proxyUser} =
+      { uid = config.ids.uids.nntp-proxy;
         description = "NNTP-Proxy daemon user";
       };
 
diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix
index 8b918dab86dd..344396638a6c 100644
--- a/nixos/modules/services/networking/nsd.nix
+++ b/nixos/modules/services/networking/nsd.nix
@@ -899,13 +899,9 @@ in
 
     environment.systemPackages = [ nsdPkg ];
 
-    users.groups = singleton {
-      name = username;
-      gid = config.ids.gids.nsd;
-    };
+    users.groups.${username}.gid = config.ids.gids.nsd;
 
-    users.users = singleton {
-      name = username;
+    users.users.${username} = {
       description = "NSD service user";
       home = stateDir;
       createHome  = true;
@@ -916,9 +912,8 @@ in
     systemd.services.nsd = {
       description = "NSD authoritative only domain name service";
 
-      after = [ "keys.target" "network.target" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      wants = [ "keys.target" ];
 
       serviceConfig = {
         ExecStart = "${nsdPkg}/sbin/nsd -d -c ${nsdEnv}/nsd.conf";
@@ -955,7 +950,7 @@ in
       '';
     };
 
-    systemd.timers."nsd-dnssec" = mkIf dnssec {
+    systemd.timers.nsd-dnssec = mkIf dnssec {
       description = "Automatic DNSSEC key rollover";
 
       wantedBy = [ "nsd.service" ];
@@ -966,7 +961,7 @@ in
       };
     };
 
-    systemd.services."nsd-dnssec" = mkIf dnssec {
+    systemd.services.nsd-dnssec = mkIf dnssec {
       description = "DNSSEC key rollover";
 
       wantedBy = [ "nsd.service" ];
diff --git a/nixos/modules/services/networking/chrony.nix b/nixos/modules/services/networking/ntp/chrony.nix
index 77f702577000..da9d960cc142 100644
--- a/nixos/modules/services/networking/chrony.nix
+++ b/nixos/modules/services/networking/ntp/chrony.nix
@@ -9,11 +9,11 @@ let
   keyFile = "${stateDir}/chrony.keys";
 
   configFile = pkgs.writeText "chrony.conf" ''
-    ${concatMapStringsSep "\n" (server: "server " + server) cfg.servers}
+    ${concatMapStringsSep "\n" (server: "server " + server + " iburst") cfg.servers}
 
     ${optionalString
       (cfg.initstepslew.enabled && (cfg.servers != []))
-      "initstepslew ${toString cfg.initstepslew.threshold} ${concatStringsSep " " cfg.initstepslew.servers}"
+      "initstepslew ${toString cfg.initstepslew.threshold} ${concatStringsSep " " cfg.servers}"
     }
 
     driftfile ${stateDir}/chrony.drift
@@ -24,7 +24,7 @@ let
     ${cfg.extraConfig}
   '';
 
-  chronyFlags = "-m -u chrony -f ${configFile} ${toString cfg.extraFlags}";
+  chronyFlags = "-n -m -u chrony -f ${configFile} ${toString cfg.extraFlags}";
 in
 {
   options = {
@@ -48,7 +48,6 @@ in
         default = {
           enabled = true;
           threshold = 1000; # by default, same threshold as 'ntpd -g' (1000s)
-          servers = cfg.servers;
         };
         description = ''
           Allow chronyd to make a rapid measurement of the system clock error at
@@ -76,16 +75,14 @@ in
   };
 
   config = mkIf cfg.enable {
+    meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+
     environment.systemPackages = [ pkgs.chrony ];
 
-    users.groups = singleton
-      { name = "chrony";
-        gid = config.ids.gids.chrony;
-      };
+    users.groups.chrony.gid = config.ids.gids.chrony;
 
-    users.users = singleton
-      { name = "chrony";
-        uid = config.ids.uids.chrony;
+    users.users.chrony =
+      { uid = config.ids.uids.chrony;
         group = "chrony";
         description = "chrony daemon user";
         home = stateDir;
@@ -115,7 +112,7 @@ in
 
         unitConfig.ConditionCapability = "CAP_SYS_TIME";
         serviceConfig =
-          { Type = "forking";
+          { Type = "simple";
             ExecStart = "${pkgs.chrony}/bin/chronyd ${chronyFlags}";
 
             ProtectHome = "yes";
diff --git a/nixos/modules/services/networking/ntpd.nix b/nixos/modules/services/networking/ntp/ntpd.nix
index 588d1c6edb07..b5403cb747d0 100644
--- a/nixos/modules/services/networking/ntpd.nix
+++ b/nixos/modules/services/networking/ntp/ntpd.nix
@@ -96,6 +96,7 @@ in
   ###### 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 ];
@@ -103,9 +104,8 @@ in
 
     systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd.service"; };
 
-    users.users = singleton
-      { name = ntpUser;
-        uid = config.ids.uids.ntp;
+    users.users.${ntpUser} =
+      { uid = config.ids.uids.ntp;
         description = "NTP daemon user";
         home = stateDir;
       };
diff --git a/nixos/modules/services/networking/openntpd.nix b/nixos/modules/services/networking/ntp/openntpd.nix
index 57638ebc9c01..67a04d48d308 100644
--- a/nixos/modules/services/networking/openntpd.nix
+++ b/nixos/modules/services/networking/ntp/openntpd.nix
@@ -40,7 +40,7 @@ in
     };
 
     extraOptions = mkOption {
-      type = with types; string;
+      type = with types; separatedString " ";
       default = "";
       example = "-s";
       description = ''
@@ -52,6 +52,7 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
+    meta.maintainers = with lib.maintainers; [ thoughtpolice ];
     services.timesyncd.enable = mkForce false;
 
     # Add ntpctl to the environment for status checking
@@ -59,8 +60,7 @@ in
 
     environment.etc."ntpd.conf".text = configFile;
 
-    users.users = singleton {
-      name = "ntp";
+    users.users.ntp = {
       uid = config.ids.uids.ntp;
       description = "OpenNTP daemon user";
       home = "/var/empty";
diff --git a/nixos/modules/services/networking/nylon.nix b/nixos/modules/services/networking/nylon.nix
index b061ce34ed2c..7c171281a926 100644
--- a/nixos/modules/services/networking/nylon.nix
+++ b/nixos/modules/services/networking/nylon.nix
@@ -65,7 +65,7 @@ let
       };
 
       acceptInterface = mkOption {
-        type = types.string;
+        type = types.str;
         default = "lo";
         description = ''
           Tell nylon which interface to listen for client requests on, default is "lo".
@@ -73,7 +73,7 @@ let
       };
 
       bindInterface = mkOption {
-        type = types.string;
+        type = types.str;
         default = "enp3s0f0";
         description = ''
           Tell nylon which interface to use as an uplink, default is "enp3s0f0".
@@ -89,7 +89,7 @@ let
       };
 
       allowedIPRanges = mkOption {
-        type = with types; listOf string;
+        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:
@@ -98,7 +98,7 @@ let
       };
 
       deniedIPRanges = mkOption {
-        type = with types; listOf string;
+        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:
diff --git a/nixos/modules/services/networking/ofono.nix b/nixos/modules/services/networking/ofono.nix
new file mode 100644
index 000000000000..40ef9433de0f
--- /dev/null
+++ b/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/nixos/modules/services/networking/openvpn.nix b/nixos/modules/services/networking/openvpn.nix
index b94b4026fd91..dcd7e9e5fa4c 100644
--- a/nixos/modules/services/networking/openvpn.nix
+++ b/nixos/modules/services/networking/openvpn.nix
@@ -73,6 +73,9 @@ let
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "services" "openvpn" "enable" ] "")
+  ];
 
   ###### interface
 
@@ -85,7 +88,7 @@ in
         {
           server = {
             config = '''
-              # Simplest server configuration: http://openvpn.net/index.php/documentation/miscellaneous/static-key-mini-howto.html.
+              # Simplest server configuration: https://community.openvpn.net/openvpn/wiki/StaticKeyMiniHowto
               # server :
               dev tun
               ifconfig 10.8.0.1 10.8.0.2
@@ -182,12 +185,12 @@ in
               options = {
                 username = mkOption {
                   description = "The username to store inside the credentials file.";
-                  type = types.string;
+                  type = types.str;
                 };
 
                 password = mkOption {
                   description = "The password to store inside the credentials file.";
-                  type = types.string;
+                  type = types.str;
                 };
               };
             });
diff --git a/nixos/modules/services/networking/ostinato.nix b/nixos/modules/services/networking/ostinato.nix
index 13f784dc53c1..5e8cce5b89aa 100644
--- a/nixos/modules/services/networking/ostinato.nix
+++ b/nixos/modules/services/networking/ostinato.nix
@@ -50,7 +50,7 @@ in
 
       rpcServer = {
         address = mkOption {
-          type = types.string;
+          type = types.str;
           default = "0.0.0.0";
           description = ''
             By default, the Drone RPC server will listen on all interfaces and
@@ -63,7 +63,7 @@ in
 
       portList = {
         include = mkOption {
-          type = types.listOf types.string;
+          type = types.listOf types.str;
           default = [];
           example = ''[ "eth*" "lo*" ]'';
           description = ''
diff --git a/nixos/modules/services/networking/owamp.nix b/nixos/modules/services/networking/owamp.nix
index 821a0258f4be..637ed618b893 100644
--- a/nixos/modules/services/networking/owamp.nix
+++ b/nixos/modules/services/networking/owamp.nix
@@ -17,15 +17,13 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-    users.users = singleton {
-      name = "owamp";
+    users.users.owamp = {
       group = "owamp";
       description = "Owamp daemon";
+      isSystemUser = true;
     };
 
-    users.groups = singleton {
-      name = "owamp";
-    };
+    users.groups.owamp = { };
 
     systemd.services.owamp = {
       description = "Owamp server";
diff --git a/nixos/modules/services/networking/pdns-recursor.nix b/nixos/modules/services/networking/pdns-recursor.nix
index d07deb9dcc67..6ff181377fcc 100644
--- a/nixos/modules/services/networking/pdns-recursor.nix
+++ b/nixos/modules/services/networking/pdns-recursor.nix
@@ -6,25 +6,27 @@ let
   dataDir  = "/var/lib/pdns-recursor";
   username = "pdns-recursor";
 
-  cfg   = config.services.pdns-recursor;
-  zones = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones;
+  cfg = config.services.pdns-recursor;
 
-  configFile = pkgs.writeText "recursor.conf" ''
-    local-address=${cfg.dns.address}
-    local-port=${toString cfg.dns.port}
-    allow-from=${concatStringsSep "," cfg.dns.allowFrom}
+  oneOrMore  = type: with types; either type (listOf type);
+  valueType  = with types; oneOf [ int str bool path ];
+  configType = with types; attrsOf (nullOr (oneOrMore valueType));
 
-    webserver-address=${cfg.api.address}
-    webserver-port=${toString cfg.api.port}
-    webserver-allow-from=${concatStringsSep "," cfg.api.allowFrom}
+  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 "";
 
-    forward-zones=${concatStringsSep "," zones}
-    export-etc-hosts=${if cfg.exportHosts then "yes" else "no"}
-    dnssec=${cfg.dnssecValidation}
-    serve-rfc1918=${if cfg.serveRFC1918 then "yes" else "no"}
+  configFile = pkgs.writeText "recursor.conf"
+    (concatStringsSep "\n"
+      (flip mapAttrsToList cfg.settings
+        (name: val: "${name}=${serialize val}")));
 
-    ${cfg.extraConfig}
-  '';
+  mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
 
 in {
   options.services.pdns-recursor = {
@@ -89,10 +91,18 @@ in {
 
     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 servers.
+        DNS zones to be forwarded to other recursive servers.
       '';
     };
 
@@ -117,18 +127,57 @@ in {
       '';
     };
 
-    extraConfig = mkOption {
+    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 = ''
-        Extra options to be appended to the configuration file.
+        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 {
 
-    users.users."${username}" = {
+    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;
@@ -150,8 +199,7 @@ in {
         AmbientCapabilities = "cap_net_bind_service";
         ExecStart = ''${pkgs.pdns-recursor}/bin/pdns_recursor \
           --config-dir=${dataDir} \
-          --socket-dir=${dataDir} \
-          --disable-syslog
+          --socket-dir=${dataDir}
         '';
       };
 
@@ -165,4 +213,12 @@ in {
       '';
     };
   };
+
+  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/nixos/modules/services/networking/pdnsd.nix b/nixos/modules/services/networking/pdnsd.nix
index f5b174dd7b7b..24b5bbc5104e 100644
--- a/nixos/modules/services/networking/pdnsd.nix
+++ b/nixos/modules/services/networking/pdnsd.nix
@@ -62,15 +62,13 @@ in
     };
 
   config = mkIf cfg.enable {
-    users.users = singleton {
-      name = pdnsdUser;
+    users.users.${pdnsdUser} = {
       uid = config.ids.uids.pdnsd;
       group = pdnsdGroup;
       description = "pdnsd user";
     };
 
-    users.groups = singleton {
-      name = pdnsdGroup;
+    users.groups.${pdnsdGroup} = {
       gid = config.ids.gids.pdnsd;
     };
 
diff --git a/nixos/modules/services/networking/polipo.nix b/nixos/modules/services/networking/polipo.nix
index 529115a1c6e1..1ff9388346b6 100644
--- a/nixos/modules/services/networking/polipo.nix
+++ b/nixos/modules/services/networking/polipo.nix
@@ -30,7 +30,7 @@ in
       };
 
       proxyAddress = mkOption {
-        type = types.string;
+        type = types.str;
         default = "127.0.0.1";
         description = "IP address on which Polipo will listen.";
       };
@@ -51,7 +51,7 @@ in
       };
 
       parentProxy = mkOption {
-        type = types.string;
+        type = types.str;
         default = "";
         example = "localhost:8124";
         description = ''
@@ -61,7 +61,7 @@ in
       };
 
       socksParentProxy = mkOption {
-        type = types.string;
+        type = types.str;
         default = "";
         example = "localhost:9050";
         description = ''
@@ -74,7 +74,7 @@ in
         type = types.lines;
         default = "";
         description = ''
-          Polio configuration. Contents will be added 
+          Polio configuration. Contents will be added
           verbatim to the configuration file.
         '';
       };
@@ -85,17 +85,15 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = singleton
-      { name = "polipo";
-        uid = config.ids.uids.polipo;
+    users.users.polipo =
+      { uid = config.ids.uids.polipo;
         description = "Polipo caching proxy user";
         home = "/var/cache/polipo";
         createHome = true;
       };
 
-    users.groups = singleton
-      { name = "polipo";
-        gid = config.ids.gids.polipo;
+    users.groups.polipo =
+      { gid = config.ids.gids.polipo;
         members = [ "polipo" ];
       };
 
@@ -111,4 +109,4 @@ in
 
   };
 
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/services/networking/pppd.nix b/nixos/modules/services/networking/pppd.nix
new file mode 100644
index 000000000000..b31bfa642358
--- /dev/null
+++ b/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 = mkMerge etcFiles;
+    systemd.services = mkMerge systemdConfigs;
+  };
+}
diff --git a/nixos/modules/services/networking/pptpd.nix b/nixos/modules/services/networking/pptpd.nix
index d8b9e8f8341a..3e7753b9dd35 100644
--- a/nixos/modules/services/networking/pptpd.nix
+++ b/nixos/modules/services/networking/pptpd.nix
@@ -8,13 +8,13 @@ with lib;
       enable = mkEnableOption "pptpd, the Point-to-Point Tunneling Protocol daemon";
 
       serverIp = mkOption {
-        type        = types.string;
+        type        = types.str;
         description = "The server-side IP address.";
         default     = "10.124.124.1";
       };
 
       clientIpRange = mkOption {
-        type        = types.string;
+        type        = types.str;
         description = "The range from which client IPs are drawn.";
         default     = "10.124.124.2-11";
       };
diff --git a/nixos/modules/services/networking/prayer.nix b/nixos/modules/services/networking/prayer.nix
index c936417e68cb..9c9eeba23da2 100644
--- a/nixos/modules/services/networking/prayer.nix
+++ b/nixos/modules/services/networking/prayer.nix
@@ -72,17 +72,14 @@ in
   config = mkIf config.services.prayer.enable {
     environment.systemPackages = [ prayer ];
 
-    users.users = singleton
-      { name = prayerUser;
-        uid = config.ids.uids.prayer;
+    users.users.${prayerUser} =
+      { uid = config.ids.uids.prayer;
         description = "Prayer daemon user";
         home = stateDir;
       };
 
-    users.groups = singleton
-      { name = prayerGroup;
-        gid = config.ids.gids.prayer;
-      };
+    users.groups.${prayerGroup} =
+      { gid = config.ids.gids.prayer; };
 
     systemd.services.prayer = {
       wantedBy = [ "multi-user.target" ];
diff --git a/nixos/modules/services/networking/privoxy.nix b/nixos/modules/services/networking/privoxy.nix
index 49ca839a2c37..1f41c720adf5 100644
--- a/nixos/modules/services/networking/privoxy.nix
+++ b/nixos/modules/services/networking/privoxy.nix
@@ -109,4 +109,6 @@ in
 
   };
 
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
 }
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index de316e5f4669..7a503e711665 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -297,7 +297,7 @@ in
       };
 
       dataDir = mkOption {
-        type = types.string;
+        type = types.path;
         description = "Directory where Prosody stores its data";
         default = "/var/lib/prosody";
       };
@@ -422,6 +422,13 @@ in
         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 = "";
@@ -458,7 +465,7 @@ in
 
       modules_enabled = {
 
-        ${ lib.concatStringsSep "\n\ \ " (lib.mapAttrsToList
+        ${ lib.concatStringsSep "\n  " (lib.mapAttrsToList
           (name: val: optionalString val "${toLua name};")
         cfg.modules) }
         ${ lib.concatStringsSep "\n" (map (x: "${toLua x};") cfg.package.communityModules)}
@@ -477,6 +484,7 @@ in
 
       s2s_secure_domains = ${toLua cfg.s2sSecureDomains}
 
+      authentication = ${toLua cfg.authentication}
 
       ${ cfg.extraConfig }
 
diff --git a/nixos/modules/services/networking/quassel.nix b/nixos/modules/services/networking/quassel.nix
index b223a48e0550..52ecd90b7c69 100644
--- a/nixos/modules/services/networking/quassel.nix
+++ b/nixos/modules/services/networking/quassel.nix
@@ -92,17 +92,25 @@ in
         message = "Quassel needs a certificate file in order to require SSL";
       }];
 
-    users.users = mkIf (cfg.user == null) [
-      { name = "quassel";
+    users.users = optionalAttrs (cfg.user == null) {
+      quassel = {
+        name = "quassel";
         description = "Quassel IRC client daemon";
         group = "quassel";
         uid = config.ids.uids.quassel;
-      }];
+      };
+    };
 
-    users.groups = mkIf (cfg.user == null) [
-      { name = "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";
@@ -111,11 +119,6 @@ in
         after = [ "network.target" ] ++ optional config.services.postgresql.enable "postgresql.service"
                                      ++ optional config.services.mysql.enable "mysql.service";
 
-        preStart = ''
-          mkdir -p ${cfg.dataDir}
-          chown ${user} ${cfg.dataDir}
-        '';
-
         serviceConfig =
         {
           ExecStart = concatStringsSep " " ([
@@ -126,7 +129,6 @@ in
           ] ++ optional cfg.requireSSL "--require-ssl"
             ++ optional (cfg.certificateFile != null) "--ssl-cert=${cfg.certificateFile}");
           User = user;
-          PermissionsStartOnly = true;
         };
       };
 
diff --git a/nixos/modules/services/networking/quicktun.nix b/nixos/modules/services/networking/quicktun.nix
index 5bcf923f909c..fb783c836464 100644
--- a/nixos/modules/services/networking/quicktun.nix
+++ b/nixos/modules/services/networking/quicktun.nix
@@ -93,18 +93,18 @@ with lib;
           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";
+            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";
diff --git a/nixos/modules/services/networking/radicale.nix b/nixos/modules/services/networking/radicale.nix
index d6fabbcd4700..30bf22586f86 100644
--- a/nixos/modules/services/networking/radicale.nix
+++ b/nixos/modules/services/networking/radicale.nix
@@ -41,7 +41,7 @@ in
     };
 
     services.radicale.config = mkOption {
-      type = types.string;
+      type = types.str;
       default = "";
       description = ''
         Radicale configuration, this will set the service
@@ -50,7 +50,7 @@ in
     };
 
     services.radicale.extraArgs = mkOption {
-      type = types.listOf types.string;
+      type = types.listOf types.str;
       default = [];
       description = "Extra arguments passed to the Radicale daemon.";
     };
@@ -59,18 +59,15 @@ in
   config = mkIf cfg.enable {
     environment.systemPackages = [ cfg.package ];
 
-    users.users = singleton
-      { name = "radicale";
-        uid = config.ids.uids.radicale;
+    users.users.radicale =
+      { uid = config.ids.uids.radicale;
         description = "radicale user";
         home = "/var/lib/radicale";
         createHome = true;
       };
 
-    users.groups = singleton
-      { name = "radicale";
-        gid = config.ids.gids.radicale;
-      };
+    users.groups.radicale =
+      { gid = config.ids.gids.radicale; };
 
     systemd.services.radicale = {
       description = "A Simple Calendar and Contact Server";
diff --git a/nixos/modules/services/networking/rdnssd.nix b/nixos/modules/services/networking/rdnssd.nix
index 887772f6e5f0..bccab805beeb 100644
--- a/nixos/modules/services/networking/rdnssd.nix
+++ b/nixos/modules/services/networking/rdnssd.nix
@@ -35,6 +35,11 @@ in
 
   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" ];
diff --git a/nixos/modules/services/networking/resilio.nix b/nixos/modules/services/networking/resilio.nix
index ee7f82ac7bee..9b25aa575837 100644
--- a/nixos/modules/services/networking/resilio.nix
+++ b/nixos/modules/services/networking/resilio.nix
@@ -249,7 +249,7 @@ in
     systemd.services.resilio = with pkgs; {
       description = "Resilio Sync Service";
       wantedBy    = [ "multi-user.target" ];
-      after       = [ "network.target" "local-fs.target" ];
+      after       = [ "network.target" ];
       serviceConfig = {
         Restart   = "on-abort";
         UMask     = "0002";
diff --git a/nixos/modules/services/networking/searx.nix b/nixos/modules/services/networking/searx.nix
index 9412d0ef8a62..60fb3d5d6d44 100644
--- a/nixos/modules/services/networking/searx.nix
+++ b/nixos/modules/services/networking/searx.nix
@@ -75,4 +75,6 @@ in
 
   };
 
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
 }
diff --git a/nixos/modules/services/networking/shadowsocks.nix b/nixos/modules/services/networking/shadowsocks.nix
index fe6d65a5f963..af12db590f00 100644
--- a/nixos/modules/services/networking/shadowsocks.nix
+++ b/nixos/modules/services/networking/shadowsocks.nix
@@ -35,10 +35,10 @@ in
       };
 
       localAddress = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
+        type = types.coercedTo types.str singleton (types.listOf types.str);
+        default = [ "[::0]" "0.0.0.0" ];
         description = ''
-          Local address to which the server binds.
+          Local addresses to which the server binds.
         '';
       };
 
diff --git a/nixos/modules/services/networking/shairport-sync.nix b/nixos/modules/services/networking/shairport-sync.nix
index 68e005ab81da..2e988e0ca2e0 100644
--- a/nixos/modules/services/networking/shairport-sync.nix
+++ b/nixos/modules/services/networking/shairport-sync.nix
@@ -55,9 +55,8 @@ in
     services.avahi.publish.enable = true;
     services.avahi.publish.userServices = true;
 
-    users.users = singleton
-      { name = cfg.user;
-        description = "Shairport user";
+    users.users.${cfg.user} =
+      { description = "Shairport user";
         isSystemUser = true;
         createHome = true;
         home = "/var/lib/shairport-sync";
diff --git a/nixos/modules/services/networking/shorewall.nix b/nixos/modules/services/networking/shorewall.nix
new file mode 100644
index 000000000000..0f94d414fcf7
--- /dev/null
+++ b/nixos/modules/services/networking/shorewall.nix
@@ -0,0 +1,75 @@
+{ 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.str;
+        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.mapAttrsToList
+              (name: file:
+                { source = file;
+                  target = "shorewall/${name}";
+                })
+              cfg.configs;
+      systemPackages = [ cfg.package ];
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/shorewall6.nix b/nixos/modules/services/networking/shorewall6.nix
new file mode 100644
index 000000000000..9c22a037c0b4
--- /dev/null
+++ b/nixos/modules/services/networking/shorewall6.nix
@@ -0,0 +1,75 @@
+{ 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.str;
+        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.mapAttrsToList
+              (name: file:
+                { source = file;
+                  target = "shorewall6/${name}";
+                })
+              cfg.configs;
+      systemPackages = [ cfg.package ];
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/shout.nix b/nixos/modules/services/networking/shout.nix
index f511a9af2562..a808a7f39d05 100644
--- a/nixos/modules/services/networking/shout.nix
+++ b/nixos/modules/services/networking/shout.nix
@@ -35,7 +35,7 @@ in {
     };
 
     listenAddress = mkOption {
-      type = types.string;
+      type = types.str;
       default = "0.0.0.0";
       description = "IP interface to listen on for http connections.";
     };
@@ -82,8 +82,7 @@ in {
   };
 
   config = mkIf cfg.enable {
-    users.users = singleton {
-      name = "shout";
+    users.users.shout = {
       uid = config.ids.uids.shout;
       description = "Shout daemon user";
       home = shoutHome;
diff --git a/nixos/modules/services/networking/smokeping.nix b/nixos/modules/services/networking/smokeping.nix
index fab3ed5bb39d..37ee2a803890 100644
--- a/nixos/modules/services/networking/smokeping.nix
+++ b/nixos/modules/services/networking/smokeping.nix
@@ -55,7 +55,7 @@ in
         description = "Enable the smokeping service";
       };
       alertConfig = mkOption {
-        type = types.string;
+        type = types.lines;
         default = ''
           to = root@localhost
           from = smokeping@localhost
@@ -73,19 +73,20 @@ in
         description = "Configuration for alerts.";
       };
       cgiUrl = mkOption {
-        type = types.string;
-        default = "http://${cfg.hostName}:${builtins.toString cfg.port}/smokeping.cgi";
+        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.string;
+        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.string;
+        type = types.lines;
         default = ''
           step     = 300
           pings    = 20
@@ -101,17 +102,17 @@ in
         '';
         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
-				'';
+          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.'';
@@ -122,14 +123,15 @@ in
         description = "Any additional customization not already included.";
       };
       hostName = mkOption {
-        type = types.string;
+        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.string;
-        default = "http://${cfg.hostName}:${builtins.toString cfg.port}/cache";
+        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.";
       };
@@ -140,19 +142,19 @@ in
         description = "DNS name for the urls generated in the cgi.";
       };
       mailHost = mkOption {
-        type = types.string;
+        type = types.str;
         default = "";
         example = "localhost";
         description = "Use this SMTP server to send alerts";
       };
       owner = mkOption {
-        type = types.string;
+        type = types.str;
         default = "nobody";
         example = "Joe Admin";
         description = "Real name of the owner of the instance";
       };
       ownerEmail = mkOption {
-        type = types.string;
+        type = types.str;
         default = "no-reply@${cfg.hostName}";
         example = "no-reply@yourdomain.com";
         description = "Email contact for owner";
@@ -170,7 +172,7 @@ in
         description = "TCP port to use for the web server.";
       };
       presentationConfig = mkOption {
-        type = types.string;
+        type = types.lines;
         default = ''
           + charts
           menu = Charts
@@ -211,12 +213,12 @@ in
         description = "presentation graph style";
       };
       presentationTemplate = mkOption {
-        type = types.string;
+        type = types.str;
         default = "${pkgs.smokeping}/etc/basepage.html.dist";
         description = "Default page layout for the web UI.";
       };
       probeConfig = mkOption {
-        type = types.string;
+        type = types.lines;
         default = ''
           + FPing
           binary = ${config.security.wrapperDir}/fping
@@ -230,12 +232,12 @@ in
         description = "Use this sendmail compatible script to deliver alerts";
       };
       smokeMailTemplate = mkOption {
-        type = types.string;
+        type = types.str;
         default = "${cfg.package}/etc/smokemail.dist";
         description = "Specify the smokemail template for alerts.";
       };
       targetConfig = mkOption {
-        type = types.string;
+        type = types.lines;
         default = ''
 					probe = FPing
 					menu = Top
@@ -253,7 +255,7 @@ in
         description = "Target configuration";
       };
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = "smokeping";
         description = "User that runs smokeping and (optionally) thttpd";
       };
@@ -275,11 +277,10 @@ in
     ];
     security.wrappers = {
       fping.source = "${pkgs.fping}/bin/fping";
-      "fping6".source = "${pkgs.fping}/bin/fping6";
+      fping6.source = "${pkgs.fping}/bin/fping6";
     };
     environment.systemPackages = [ pkgs.fping ];
-    users.users = singleton {
-      name = cfg.user;
+    users.users.${cfg.user} = {
       isNormalUser = false;
       isSystemUser = true;
       uid = config.ids.uids.smokeping;
@@ -297,7 +298,8 @@ in
         mkdir -m 0755 -p ${smokepingHome}/cache ${smokepingHome}/data
         rm -f ${smokepingHome}/cropper
         ln -s ${cfg.package}/htdocs/cropper ${smokepingHome}/cropper
-        cp ${cgiHome} ${smokepingHome}/smokeping.fcgi
+        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}
       '';
@@ -312,5 +314,7 @@ in
       serviceConfig.Restart = "always";
     };
   };
+
+  meta.maintainers = with lib.maintainers; [ erictapen ];
 }
 
diff --git a/nixos/modules/services/networking/softether.nix b/nixos/modules/services/networking/softether.nix
index 65df93a00da9..2dc73d81b258 100644
--- a/nixos/modules/services/networking/softether.nix
+++ b/nixos/modules/services/networking/softether.nix
@@ -50,7 +50,7 @@ in
       };
 
       dataDir = mkOption {
-        type = types.string;
+        type = types.path;
         default = "/var/lib/softether";
         description = ''
           Data directory for SoftEther VPN.
@@ -68,7 +68,7 @@ in
     mkMerge [{
       environment.systemPackages = [ package ];
 
-      systemd.services."softether-init" = {
+      systemd.services.softether-init = {
         description = "SoftEther VPN services initial task";
         wantedBy = [ "network.target" ];
         serviceConfig = {
diff --git a/nixos/modules/services/networking/spacecookie.nix b/nixos/modules/services/networking/spacecookie.nix
new file mode 100644
index 000000000000..c4d06df6ad4a
--- /dev/null
+++ b/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/nixos/modules/services/networking/squid.nix b/nixos/modules/services/networking/squid.nix
index b220c21b604f..9d063b92aa1e 100644
--- a/nixos/modules/services/networking/squid.nix
+++ b/nixos/modules/services/networking/squid.nix
@@ -159,11 +159,10 @@ in
       serviceConfig = {
         Type="forking";
         PIDFile="/run/squid.pid";
-        PermissionsStartOnly = true;
         ExecStart  = "${pkgs.squid}/bin/squid -YCs -f ${squidConfig}";
       };
     };
 
   };
 
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index cbb305cd3825..b0e2e303cbc0 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -4,6 +4,23 @@ 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 -f mock-hostkey -N ""
+    sshd -t -f $out -h mock-hostkey
+  '';
+
   cfg  = config.services.openssh;
   cfgc = config.programs.ssh;
 
@@ -57,6 +74,10 @@ let
 in
 
 {
+  imports = [
+    (mkAliasOptionModule [ "services" "sshd" "enable" ] [ "services" "openssh" "enable" ])
+    (mkAliasOptionModule [ "services" "openssh" "knownHosts" ] [ "programs" "ssh" "knownHosts" ])
+  ];
 
   ###### interface
 
@@ -339,7 +360,7 @@ in
 
     environment.etc = authKeysFiles //
       { "ssh/moduli".source = cfg.moduliFile;
-        "ssh/sshd_config".text = cfg.extraConfig;
+        "ssh/sshd_config".source = sshconf;
       };
 
     systemd =
@@ -485,7 +506,7 @@ in
 
     assertions = [{ assertion = if cfg.forwardX11 then cfgc.setXAuthLocation else true;
                     message = "cannot enable X11 forwarding without setting xauth location";}]
-      ++ flip map cfg.listenAddresses ({ addr, ... }: {
+      ++ forEach cfg.listenAddresses ({ addr, ... }: {
         assertion = addr != null;
         message = "addr must be specified in each listenAddresses entry";
       });
diff --git a/nixos/modules/services/networking/strongswan-swanctl/module.nix b/nixos/modules/services/networking/strongswan-swanctl/module.nix
index 817b5ec55f78..0fec3ef00ad9 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/module.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/module.nix
@@ -62,9 +62,8 @@ in  {
     systemd.services.strongswan-swanctl = {
       description = "strongSwan IPsec IKEv1/IKEv2 daemon using swanctl";
       wantedBy = [ "multi-user.target" ];
-      after    = [ "network-online.target" "keys.target" ];
-      wants    = [ "keys.target" ];
-      path = with pkgs; [ kmod iproute iptables utillinux ];
+      after    = [ "network-online.target" ];
+      path     = with pkgs; [ kmod iproute iptables utillinux ];
       environment = {
         STRONGSWAN_CONF = pkgs.writeTextFile {
           name = "strongswan.conf";
diff --git a/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix b/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
index 5e74a96664f0..dfdfc50d8ae2 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/param-constructors.nix
@@ -56,14 +56,14 @@ rec {
   };
 
   documentDefault = description : strongswanDefault :
-    if isNull 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; };
+  single = f: name: value: { ${name} = f value; };
 
   mkStrParam         = mkParamOfType types.str;
   mkOptionalStrParam = mkStrParam null;
diff --git a/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix b/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
index fb87e81f3215..2bbb39a76049 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/param-lib.nix
@@ -21,7 +21,7 @@ rec {
   mkConf = indent : ps :
     concatMapStringsSep "\n"
       (name:
-        let value = ps."${name}";
+        let value = ps.${name};
             indentation = replicate indent " ";
         in
         indentation + (
@@ -45,10 +45,10 @@ rec {
     filterEmptySets (
       (mapParamsRecursive (path: name: param:
         let value = attrByPath path null cfg;
-        in optionalAttrs (!isNull value) (param.render name value)
+        in optionalAttrs (value != null) (param.render name value)
       ) ps));
 
-  filterEmptySets = set : filterAttrs (n: v: !(isNull v)) (mapAttrs (name: value:
+  filterEmptySets = set : filterAttrs (n: v: (v != null)) (mapAttrs (name: value:
     if isAttrs value
     then let value' = filterEmptySets value;
          in if value' == {}
@@ -58,7 +58,7 @@ rec {
   ) set);
 
   # Recursively map over every parameter in the given attribute set.
-  mapParamsRecursive = mapAttrsRecursiveCond' (as: (!(as ? "_type" && as._type == "param")));
+  mapParamsRecursive = mapAttrsRecursiveCond' (as: (!(as ? _type && as._type == "param")));
 
   mapAttrsRecursiveCond' = cond: f: set:
     let
@@ -67,7 +67,7 @@ rec {
           g =
             name: value:
             if isAttrs value && cond value
-              then { "${name}" = recurse (path ++ [name]) value; }
+              then { ${name} = recurse (path ++ [name]) value; }
               else f (path ++ [name]) name value;
         in mapAttrs'' g set;
     in recurse [] set;
@@ -77,6 +77,6 @@ rec {
 
   # Extract the options from the given set of parameters.
   paramsToOptions = ps :
-    mapParamsRecursive (_path: name: param: { "${name}" = param.option; }) ps;
+    mapParamsRecursive (_path: name: param: { ${name} = param.option; }) ps;
 
 }
diff --git a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
index d4f7e95f859f..808cb863a9cf 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -6,7 +6,7 @@
 #
 #   git clone https://github.com/strongswan/strongswan.git
 #   cd strongswan
-#   git diff 5.5.3..5.6.0 src/swanctl/swanctl.opt
+#   git diff 5.7.2..5.8.0 src/swanctl/swanctl.opt
 
 lib: with (import ./param-constructors.nix lib);
 
@@ -227,6 +227,22 @@ in {
       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
@@ -350,6 +366,16 @@ in {
       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
@@ -799,7 +825,7 @@ in {
         Updown script to invoke on CHILD_SA up and down events.
       '';
 
-      hostaccess = mkYesNoParam yes ''
+      hostaccess = mkYesNoParam no ''
         Hostaccess variable to pass to <literal>updown</literal> script.
       '';
 
@@ -960,6 +986,26 @@ in {
         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
diff --git a/nixos/modules/services/networking/strongswan.nix b/nixos/modules/services/networking/strongswan.nix
index 41b69039ba7a..4ff9c486059c 100644
--- a/nixos/modules/services/networking/strongswan.nix
+++ b/nixos/modules/services/networking/strongswan.nix
@@ -151,8 +151,7 @@ in
       description = "strongSwan IPSec Service";
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [ kmod iproute iptables utillinux ]; # XXX Linux
-      wants = [ "keys.target" ];
-      after = [ "network-online.target" "keys.target" ];
+      after = [ "network-online.target" ];
       environment = {
         STRONGSWAN_CONF = strongswanConf { inherit setup connections ca secretsFile managePlugins enabledPlugins; };
       };
diff --git a/nixos/modules/services/networking/stubby.nix b/nixos/modules/services/networking/stubby.nix
index 3fbf6eb60e9d..b38bcd4cec05 100644
--- a/nixos/modules/services/networking/stubby.nix
+++ b/nixos/modules/services/networking/stubby.nix
@@ -168,7 +168,7 @@ in
         default = defaultUpstream;
         type = types.lines;
         description = ''
-          Add additional upstreams. See <citerefentry><refentrytitle>stubby
+          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:
diff --git a/nixos/modules/services/networking/stunnel.nix b/nixos/modules/services/networking/stunnel.nix
index 89a14966eca7..ab51bba2f6ac 100644
--- a/nixos/modules/services/networking/stunnel.nix
+++ b/nixos/modules/services/networking/stunnel.nix
@@ -35,12 +35,12 @@ let
   clientConfig = {
     options = {
       accept = mkOption {
-        type = types.string;
+        type = types.str;
         description = "IP:Port on which connections should be accepted.";
       };
 
       connect = mkOption {
-        type = types.string;
+        type = types.str;
         description = "IP:Port destination to connect to.";
       };
 
@@ -57,13 +57,19 @@ let
       };
 
       CAPath = mkOption {
-        type = types.path;
+        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 string;
+        type = with types; nullOr str;
         default = null;
         description = "If set, stunnel checks if the provided certificate is valid for the given hostname.";
       };
@@ -88,13 +94,13 @@ in
       };
 
       user = mkOption {
-        type = with types; nullOr string;
+        type = with types; nullOr str;
         default = "nobody";
         description = "The user under which stunnel runs.";
       };
 
       group = mkOption {
-        type = with types; nullOr string;
+        type = with types; nullOr str;
         default = "nogroup";
         description = "The group under which stunnel runs.";
       };
@@ -196,6 +202,7 @@ in
                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
 
@@ -216,6 +223,12 @@ in
       };
     };
 
+    meta.maintainers = with maintainers; [
+      # Server side
+      lschuermann
+      # Client side
+      das_j
+    ];
   };
 
 }
diff --git a/nixos/modules/services/networking/supybot.nix b/nixos/modules/services/networking/supybot.nix
index 64eb11068329..92c84bd0e1e2 100644
--- a/nixos/modules/services/networking/supybot.nix
+++ b/nixos/modules/services/networking/supybot.nix
@@ -45,8 +45,7 @@ in
 
     environment.systemPackages = [ pkgs.pythonPackages.limnoria ];
 
-    users.users = singleton {
-      name = "supybot";
+    users.users.supybotrs = {
       uid = config.ids.uids.supybot;
       group = "supybot";
       description = "Supybot IRC bot user";
@@ -55,7 +54,6 @@ in
     };
 
     users.groups.supybot = {
-      name = "supybot";
       gid = config.ids.gids.supybot;
     };
 
diff --git a/nixos/modules/services/networking/syncplay.nix b/nixos/modules/services/networking/syncplay.nix
new file mode 100644
index 000000000000..e3147c10502c
--- /dev/null
+++ b/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/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index 114a64dfb175..5b3eb6f04b42 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -5,6 +5,61 @@ 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 = {
@@ -16,6 +71,265 @@ in {
         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 = {
+            "/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";
@@ -41,7 +355,7 @@ in {
 
       group = mkOption {
         type = types.str;
-        default = "nogroup";
+        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).
@@ -122,16 +436,18 @@ in {
 
     systemd.packages = [ pkgs.syncthing ];
 
-    users = mkIf (cfg.systemService && cfg.user == defaultUser) {
-      users."${defaultUser}" =
+    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";
         };
+    };
 
-      groups."${defaultUser}".gid =
+    users.groups = mkIf (cfg.systemService && cfg.group == defaultUser) {
+      ${defaultUser}.gid =
         config.ids.gids.syncthing;
     };
 
@@ -151,12 +467,54 @@ in {
           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;
         };
       };
 
diff --git a/nixos/modules/services/networking/tcpcrypt.nix b/nixos/modules/services/networking/tcpcrypt.nix
index a0ccb9950094..18f2e135124b 100644
--- a/nixos/modules/services/networking/tcpcrypt.nix
+++ b/nixos/modules/services/networking/tcpcrypt.nix
@@ -29,8 +29,7 @@ in
 
   config = mkIf cfg.enable {
 
-    users.users = singleton {
-      name = "tcpcryptd";
+    users.users.tcpcryptd = {
       uid = config.ids.uids.tcpcryptd;
       description = "tcpcrypt daemon user";
     };
diff --git a/nixos/modules/services/networking/teamspeak3.nix b/nixos/modules/services/networking/teamspeak3.nix
index 9ea9c83e37cd..fadb32dcd777 100644
--- a/nixos/modules/services/networking/teamspeak3.nix
+++ b/nixos/modules/services/networking/teamspeak3.nix
@@ -111,16 +111,15 @@ in
       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" ];
 
-      preStart = ''
-        mkdir -p ${cfg.logPath}
-        chown ${user}:${group} ${cfg.logPath}
-      '';
-
       serviceConfig = {
         ExecStart = ''
           ${ts3}/bin/ts3server \
@@ -135,7 +134,6 @@ in
         WorkingDirectory = cfg.dataDir;
         User = user;
         Group = group;
-        PermissionsStartOnly = true;
       };
     };
   };
diff --git a/nixos/modules/services/networking/tedicross.nix b/nixos/modules/services/networking/tedicross.nix
new file mode 100644
index 000000000000..0716975f594a
--- /dev/null
+++ b/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/nixos/modules/services/networking/thelounge.nix b/nixos/modules/services/networking/thelounge.nix
new file mode 100644
index 000000000000..875d8f661697
--- /dev/null
+++ b/nixos/modules/services/networking/thelounge.nix
@@ -0,0 +1,76 @@
+{ 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" ];
+      environment = { THELOUNGE_HOME = dataDir; };
+      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/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index 3379efd1afce..e98aafc20937 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -153,7 +153,6 @@ in
       ({
         description = "Tinc Daemon - ${network}";
         wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
         path = [ data.package ];
         restartTriggers = [ config.environment.etc."tinc/${network}/tinc.conf".source ];
         serviceConfig = {
diff --git a/nixos/modules/services/networking/tinydns.nix b/nixos/modules/services/networking/tinydns.nix
index 7d5db71601ef..79507b2ebcdd 100644
--- a/nixos/modules/services/networking/tinydns.nix
+++ b/nixos/modules/services/networking/tinydns.nix
@@ -32,11 +32,12 @@ with lib;
   config = mkIf config.services.tinydns.enable {
     environment.systemPackages = [ pkgs.djbdns ];
 
-    users.users.tinydns = {};
+    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
diff --git a/nixos/modules/services/networking/tox-bootstrapd.nix b/nixos/modules/services/networking/tox-bootstrapd.nix
index 1d3492151690..f88e34827d00 100644
--- a/nixos/modules/services/networking/tox-bootstrapd.nix
+++ b/nixos/modules/services/networking/tox-bootstrapd.nix
@@ -56,9 +56,8 @@ in
 
   config = mkIf config.services.toxBootstrapd.enable {
 
-    users.users = singleton
-      { name = "tox-bootstrapd";
-        uid = config.ids.uids.tox-bootstrapd;
+    users.users.tox-bootstrapd =
+      { uid = config.ids.uids.tox-bootstrapd;
         description = "Tox bootstrap daemon user";
         inherit home;
         createHome = true;
diff --git a/nixos/modules/services/networking/toxvpn.nix b/nixos/modules/services/networking/toxvpn.nix
index 7830dfb1834c..9e97faeebc1e 100644
--- a/nixos/modules/services/networking/toxvpn.nix
+++ b/nixos/modules/services/networking/toxvpn.nix
@@ -8,7 +8,7 @@ with lib;
       enable = mkEnableOption "toxvpn running on startup";
 
       localip = mkOption {
-        type        = types.string;
+        type        = types.str;
         default     = "10.123.123.1";
         description = "your ip on the vpn";
       };
@@ -20,10 +20,10 @@ with lib;
       };
 
       auto_add_peers = mkOption {
-        type        = types.listOf types.string;
+        type        = types.listOf types.str;
         default     = [];
         example     = ''[ "toxid1" "toxid2" ]'';
-        description = "peers to automacally connect to on startup";
+        description = "peers to automatically connect to on startup";
       };
     };
   };
diff --git a/nixos/modules/services/networking/trickster.nix b/nixos/modules/services/networking/trickster.nix
new file mode 100644
index 000000000000..8760dd5a9382
--- /dev/null
+++ b/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/nixos/modules/services/networking/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 1a35979ad44c..baed83591e1e 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -53,6 +53,13 @@ in
 
       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;
@@ -94,13 +101,15 @@ in
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ pkgs.unbound ];
+    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" ];
@@ -112,7 +121,7 @@ in
         mkdir -m 0755 -p ${stateDir}/dev/
         cp ${confFile} ${stateDir}/unbound.conf
         ${optionalString cfg.enableRootTrustAnchor ''
-          ${pkgs.unbound}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!"
+          ${cfg.package}/bin/unbound-anchor -a ${rootTrustAnchorFile} || echo "Root anchor updated!"
           chown unbound ${stateDir} ${rootTrustAnchorFile}
         ''}
         touch ${stateDir}/dev/random
@@ -120,7 +129,7 @@ in
       '';
 
       serviceConfig = {
-        ExecStart = "${pkgs.unbound}/bin/unbound -d -c ${stateDir}/unbound.conf";
+        ExecStart = "${cfg.package}/bin/unbound -d -c ${stateDir}/unbound.conf";
         ExecStopPost="${pkgs.utillinux}/bin/umount ${stateDir}/dev/random";
 
         ProtectSystem = true;
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index c82e0af2803d..c922ba15960f 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -146,6 +146,11 @@ in
         where = where;
       }) mountPoints;
 
+    systemd.tmpfiles.rules = [
+      "e '${stateDir}' 0700 unifi - - -"
+      "d '${stateDir}/data' 0700 unifi - - -"
+    ];
+
     systemd.services.unifi = {
       description = "UniFi controller daemon";
       wantedBy = [ "multi-user.target" ];
@@ -157,14 +162,9 @@ in
       environment.LD_LIBRARY_PATH = with pkgs.stdenv; "${cc.cc.lib}/lib";
 
       preStart = ''
-        # Ensure privacy of state and data.
-        chown unifi "${stateDir}" "${stateDir}/data"
-        chmod 0700 "${stateDir}" "${stateDir}/data"
-
         # Create the volatile webapps
         rm -rf "${stateDir}/webapps"
         mkdir -p "${stateDir}/webapps"
-        chown unifi "${stateDir}/webapps"
         ln -s "${cfg.unifiPackage}/webapps/ROOT" "${stateDir}/webapps/ROOT"
       '';
 
@@ -176,8 +176,8 @@ in
         Type = "simple";
         ExecStart = "${(removeSuffix "\n" cmd)} start";
         ExecStop = "${(removeSuffix "\n" cmd)} stop";
+        Restart = "on-failure";
         User = "unifi";
-        PermissionsStartOnly = true;
         UMask = "0077";
         WorkingDirectory = "${stateDir}";
       };
diff --git a/nixos/modules/services/networking/v2ray.nix b/nixos/modules/services/networking/v2ray.nix
new file mode 100644
index 000000000000..a1774cdffbb9
--- /dev/null
+++ b/nixos/modules/services/networking/v2ray.nix
@@ -0,0 +1,81 @@
+{ 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.writeText "v2ray.json" (builtins.toJSON cfg.config));
+
+  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/nixos/modules/services/networking/vsftpd.nix b/nixos/modules/services/networking/vsftpd.nix
index 31e1e65fa9ca..47990dbb3772 100644
--- a/nixos/modules/services/networking/vsftpd.nix
+++ b/nixos/modules/services/networking/vsftpd.nix
@@ -34,6 +34,15 @@ let
   };
 
   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.
     '')
@@ -76,9 +85,21 @@ let
       outgoing data connections can only connect to the client. Only enable if you
       know what you are doing!
     '')
-    (yesNoOption "ssl_tlsv1" "ssl_tlsv1" true  '' '')
-    (yesNoOption "ssl_sslv2" "ssl_sslv2" false '' '')
-    (yesNoOption "ssl_sslv3" "ssl_sslv3" false '' '')
+    (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"
@@ -98,6 +119,9 @@ let
       listen=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
@@ -106,6 +130,11 @@ let
       ${optionalString cfg.anonymousUser ''
         anon_root=${cfg.anonymousUserHome}
       ''}
+      ${optionalString cfg.enableVirtualUsers ''
+        guest_enable=YES
+        guest_username=vsftpd
+        pam_service_name=vsftpd
+      ''}
       ${cfg.extraConfig}
     '';
 
@@ -119,10 +148,7 @@ in
 
     services.vsftpd = {
 
-      enable = mkOption {
-        default = false;
-        description = "Whether to enable the vsftpd FTP server.";
-      };
+      enable = mkEnableOption "vsftpd";
 
       userlist = mkOption {
         default = [];
@@ -143,6 +169,61 @@ in
         '';
       };
 
+      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/";
@@ -164,7 +245,7 @@ in
       };
 
       anonymousUmask = mkOption {
-        type = types.string;
+        type = types.str;
         default = "077";
         example = "002";
         description = "Anonymous write umask.";
@@ -186,26 +267,34 @@ in
 
   config = mkIf cfg.enable {
 
-    assertions = singleton
+    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";
       };
-
-    users.users =
-      [ { name = "vsftpd";
-          uid = config.ids.uids.vsftpd;
-          description = "VSFTPD user";
-          home = "/homeless-shelter";
-        }
-      ] ++ optional cfg.anonymousUser
-        { name = "ftp";
+    } // 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;
 
@@ -213,23 +302,24 @@ in
     # = false and whitelist root
     services.vsftpd.userlist = if cfg.userlistDeny then ["root"] else [];
 
-    systemd.services.vsftpd =
-      { description = "Vsftpd Server";
+    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" ];
 
-        preStart =
-          optionalString cfg.anonymousUser
-            ''
-              mkdir -p -m 555 ${cfg.anonymousUserHome}
-              chown -R ftp:ftp ${cfg.anonymousUserHome}
-            '';
-
         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/nixos/modules/services/networking/websockify.nix b/nixos/modules/services/networking/websockify.nix
index 4b76350ecf8a..d9177df65bd6 100644
--- a/nixos/modules/services/networking/websockify.nix
+++ b/nixos/modules/services/networking/websockify.nix
@@ -44,9 +44,9 @@ let cfg = config.services.networking.websockify; in {
       scriptArgs = "%i";
     };
 
-    systemd.targets."default-websockify" = {
+    systemd.targets.default-websockify = {
       description = "Target to start all default websockify@ services";
-      unitConfig."X-StopOnReconfiguration" = true;
+      unitConfig.X-StopOnReconfiguration = true;
       wants = mapAttrsToList (name: value: "websockify@${name}:${toString value}.service") cfg.portMap;
       wantedBy = [ "multi-user.target" ];
     };
diff --git a/nixos/modules/services/networking/wg-quick.nix b/nixos/modules/services/networking/wg-quick.nix
new file mode 100644
index 000000000000..b770d47d269e
--- /dev/null
+++ b/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 = [ 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/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 41aff1480a05..980961225c9e 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -26,19 +26,28 @@ let
         type = with types; nullOr str;
         default = null;
         description = ''
-          Base64 private key generated by wg genkey.
+          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 wg genkey.
+          Private key file as generated by <command>wg genkey</command>.
         '';
       };
 
@@ -103,6 +112,32 @@ let
           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>.
+        '';
+      };
     };
 
   };
@@ -124,8 +159,8 @@ let
         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
+          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.
 
@@ -139,8 +174,8 @@ let
         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
+          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.
         '';
@@ -182,10 +217,113 @@ let
 
   };
 
-  generateUnit = name: values:
+
+  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);
+    #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}"
       {
@@ -202,47 +340,37 @@ let
         };
 
         script = ''
-          ${optionalString (!config.boot.isContainer) "modprobe wireguard"}
+          ${optionalString (!config.boot.isContainer) "modprobe wireguard || true"}
 
           ${values.preSetup}
 
-          ip link add dev ${name} type wireguard
+          ${ipPreMove} link add dev ${name} type wireguard
+          ${optionalString (values.interfaceNamespace != null && values.interfaceNamespace != values.socketNamespace) "${ipPreMove} link set ${name} netns ${ns}"}
 
           ${concatMapStringsSep "\n" (ip:
-            "ip address add ${ip} dev ${name}"
+            "${ipPostMove} address add ${ip} dev ${name}"
           ) values.ips}
 
-          wg set ${name} private-key ${privKey} ${
+          ${wg} set ${name} private-key ${privKey} ${
             optionalString (values.listenPort != null) " listen-port ${toString values.listenPort}"}
 
-          ${concatMapStringsSep "\n" (peer:
-            assert (peer.presharedKeyFile == null) || (peer.presharedKey == null); # at most one of the two must be set
-            let psk = if peer.presharedKey != null then pkgs.writeText "wg-psk" peer.presharedKey else peer.presharedKeyFile;
-            in
-              "wg set ${name} 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}"
-            ) values.peers}
-
-          ip link set up dev ${name}
-
-          ${optionalString (values.allowedIPsAsRoutes != false) (concatStringsSep "\n" (concatMap (peer:
-              (map (allowedIP:
-                "ip route replace ${allowedIP} dev ${name} table ${values.table}"
-              ) peer.allowedIPs)
-            ) values.peers))}
+          ${ipPostMove} link set up dev ${name}
 
           ${values.postSetup}
         '';
 
         postStop = ''
-          ip link del dev ${name}
+          ${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
 
 {
@@ -253,8 +381,16 @@ in
 
     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.";
+        description = "WireGuard interfaces.";
         default = {};
         example = {
           wg0 = {
@@ -277,13 +413,40 @@ in
 
   ###### implementation
 
-  config = mkIf (cfg.interfaces != {}) {
+  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}.generatePrivateKey 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 = [ kernel.wireguard ];
     environment.systemPackages = [ pkgs.wireguard-tools ];
 
-    systemd.services = mapAttrs' generateUnit cfg.interfaces;
+    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/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index 0bd9edf4a41c..de0f11595a94 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -103,6 +103,13 @@ in {
               description = ''
                 Set this to <literal>true</literal> if the SSID of the network is hidden.
               '';
+              example = literalExample ''
+                { echelon = {
+                    hidden = true;
+                    psk = "abcdefgh";
+                  };
+                }
+              '';
             };
 
             priority = mkOption {
@@ -146,10 +153,13 @@ in {
         '';
         default = {};
         example = literalExample ''
-          { echelon = {
+          { echelon = {                   # SSID with no spaces or special characters
               psk = "abcdefgh";
             };
-            "free.wifi" = {};
+            "echelon's AP" = {            # SSID with spaces and/or special characters
+               psk = "ijklmnop";
+            };
+            "free.wifi" = {};             # Public wireless network
           }
         '';
       };
@@ -204,6 +214,7 @@ in {
     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
@@ -222,18 +233,22 @@ in {
       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=
-            source /sys/class/net/$i/uevent
-            if [ "$DEVTYPE" = "wlan" -o -e /sys/class/net/$i/wireless ]; then
-              ifaces="$ifaces''${ifaces:+ -N} -i$i"
+            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 ''
-          ifaces="${concatStringsSep " -N " (map (i: "-i${i}") ifaces)}"
+          args="${concatMapStringsSep " -N " (i: "-i${i} $iface_args") ifaces}"
         ''}
-        exec wpa_supplicant -s -u -D${cfg.driver} -c ${configFile} $ifaces
+        exec wpa_supplicant $args
       '';
     };
 
diff --git a/nixos/modules/services/networking/xandikos.nix b/nixos/modules/services/networking/xandikos.nix
new file mode 100644
index 000000000000..87c029156b9e
--- /dev/null
+++ b/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/nixos/modules/services/networking/xinetd.nix b/nixos/modules/services/networking/xinetd.nix
index 002245027804..8dc6f845ed85 100644
--- a/nixos/modules/services/networking/xinetd.nix
+++ b/nixos/modules/services/networking/xinetd.nix
@@ -53,7 +53,7 @@ in
 
     services.xinetd.extraDefaults = mkOption {
       default = "";
-      type = types.string;
+      type = types.lines;
       description = ''
         Additional configuration lines added to the default section of xinetd's configuration.
       '';
@@ -70,13 +70,13 @@ in
         options = {
 
           name = mkOption {
-            type = types.string;
+            type = types.str;
             example = "login";
             description = "Name of the service.";
           };
 
           protocol = mkOption {
-            type = types.string;
+            type = types.str;
             default = "tcp";
             description =
               "Protocol of the service.  Usually <literal>tcp</literal> or <literal>udp</literal>.";
@@ -90,25 +90,25 @@ in
           };
 
           user = mkOption {
-            type = types.string;
+            type = types.str;
             default = "nobody";
             description = "User account for the service";
           };
 
           server = mkOption {
-            type = types.string;
+            type = types.str;
             example = "/foo/bin/ftpd";
             description = "Path of the program that implements the service.";
           };
 
           serverArgs = mkOption {
-            type = types.string;
+            type = types.separatedString " ";
             default = "";
             description = "Command-line arguments for the server program.";
           };
 
           flags = mkOption {
-            type = types.string;
+            type = types.str;
             default = "";
             description = "";
           };
@@ -146,7 +146,7 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.xinetd ];
-      script = "xinetd -syslog daemon -dontfork -stayalive -f ${configFile}";
+      script = "exec xinetd -syslog daemon -dontfork -stayalive -f ${configFile}";
     };
   };
 }
diff --git a/nixos/modules/services/networking/xl2tpd.nix b/nixos/modules/services/networking/xl2tpd.nix
index d0a3ed7bb5e0..7dbe51422d96 100644
--- a/nixos/modules/services/networking/xl2tpd.nix
+++ b/nixos/modules/services/networking/xl2tpd.nix
@@ -8,13 +8,13 @@ with lib;
       enable = mkEnableOption "xl2tpd, the Layer 2 Tunnelling Protocol Daemon";
 
       serverIp = mkOption {
-        type        = types.string;
+        type        = types.str;
         description = "The server-side IP address.";
         default     = "10.125.125.1";
       };
 
       clientIpRange = mkOption {
-        type        = types.string;
+        type        = types.str;
         description = "The range from which client IPs are drawn.";
         default     = "10.125.125.2-11";
       };
diff --git a/nixos/modules/services/networking/yggdrasil.nix b/nixos/modules/services/networking/yggdrasil.nix
new file mode 100644
index 000000000000..9e675ecd6f4b
--- /dev/null
+++ b/nixos/modules/services/networking/yggdrasil.nix
@@ -0,0 +1,187 @@
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  cfg = config.services.yggdrasil;
+  configProvided = (cfg.config != {});
+  configAsFile = (if configProvided then
+                   toString (pkgs.writeTextFile {
+                     name = "yggdrasil-conf";
+                     text = builtins.toJSON cfg.config;
+                   })
+                   else null);
+  configFileProvided = (cfg.configFile != null);
+  generateConfig = (
+    if configProvided && configFileProvided then
+      "${pkgs.jq}/bin/jq -s add ${configAsFile} ${cfg.configFile}"
+    else if configProvided then
+      "cat ${configAsFile}"
+    else if configFileProvided then
+      "cat ${cfg.configFile}"
+    else
+      "${cfg.package}/bin/yggdrasil -genconf"
+  );
+
+in {
+  options = with types; {
+    services.yggdrasil = {
+      enable = mkEnableOption "the yggdrasil system service";
+
+      configFile = mkOption {
+        type =  nullOr str;
+        default = null;
+        example = "/run/keys/yggdrasil.conf";
+        description = ''
+          A file which contains JSON configuration for yggdrasil.
+
+          You do not have to supply a complete configuration, as
+          yggdrasil will use default values for anything which is
+          omitted.  If the encryption and signing keys are omitted,
+          yggdrasil will generate new ones each time the service is
+          started, resulting in a random IPv6 address on the yggdrasil
+          network each time.
+
+          If both this option and <option>config</option> are
+          supplied, they will be combined, with values from
+          <option>config</option> taking precedence.
+
+          You can use the command <code>nix-shell -p yggdrasil --run
+          "yggdrasil -genconf -json"</code> to generate a default
+          JSON configuration.
+        '';
+      };
+
+      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
+          do not specify the keys, yggdrasil will generate a new set
+          each time the service is started, creating a random IPv6
+          address on the yggdrasil network each time.
+
+          If you wish to specify the keys, use
+          <option>configFile</option>.  If both
+          <option>configFile</option> and <option>config</option> are
+          supplied, they will be combined, with values from
+          <option>config</option> taking precedence.
+
+          You can use the command <code>nix-shell -p yggdrasil --run
+          "yggdrasil -genconf"</code> to generate default
+          configuration values with documentation.
+        '';
+      };
+
+      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.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = config.networking.enableIPv6;
+        message = "networking.enableIPv6 must be true for yggdrasil to work";
+      }
+    ];
+
+    systemd.services.yggdrasil = {
+      description = "Yggdrasil Network Service";
+      path = [ cfg.package ] ++ optional (configProvided && configFileProvided) pkgs.jq;
+      bindsTo = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      preStart = ''
+        ${generateConfig} | yggdrasil -normaliseconf -useconf > /run/yggdrasil/yggdrasil.conf
+      '';
+
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/yggdrasil -useconffile /run/yggdrasil/yggdrasil.conf";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        Restart = "always";
+
+        RuntimeDirectory = "yggdrasil";
+        RuntimeDirectoryMode = "0700";
+        BindReadOnlyPaths = mkIf configFileProvided
+          [ "${cfg.configFile}" ];
+
+        # 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 ];
+}
diff --git a/nixos/modules/services/networking/zerobin.nix b/nixos/modules/services/networking/zerobin.nix
index 06ccd7032e6c..78de246a816f 100644
--- a/nixos/modules/services/networking/zerobin.nix
+++ b/nixos/modules/services/networking/zerobin.nix
@@ -74,7 +74,7 @@ in
     };
 
     config = mkIf (cfg.enable) {
-      users.users."${cfg.user}" =
+      users.users.${cfg.user} =
       if cfg.user == "zerobin" then {
         isSystemUser = true;
         group = cfg.group;
@@ -82,7 +82,7 @@ in
         createHome = true;
       }
       else {};
-      users.groups."${cfg.group}" = {};
+      users.groups.${cfg.group} = {};
 
       systemd.services.zerobin = {
         enable = true;
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index 8b60799891ca..f354a9d42c79 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -1,46 +1,52 @@
 { config, lib, pkgs, ... }:
 
 let
+  inherit (lib) generators literalExample mkEnableOption mkIf mkOption recursiveUpdate types;
   cfg = config.services.zeronet;
-
-  zConfFile = pkgs.writeTextFile {
-    name = "zeronet.conf";
-    
-    text = ''
-      [global]
-      data_dir = ${cfg.dataDir}
-      log_dir = ${cfg.logDir}
-    '' + lib.optionalString (cfg.port != null) ''
-      ui_port = ${toString cfg.port}
-    '' + lib.optionalString (cfg.torAlways) ''
-      tor = always
-    '' + cfg.extraConfig;
+  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";
 
-    dataDir = mkOption {
-      type = types.path;
-      default = "/var/lib/zeronet";
-      example = "/home/okina/zeronet";
-      description = "Path to the zeronet data directory.";
-    };
+    settings = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
+      default = {};
+      example = literalExample "global.tor = enable;";
 
-    logDir = mkOption {
-      type = types.path;
-      default = "/var/log/zeronet";
-      example = "/home/okina/zeronet/log";
-      description = "Path to the zeronet log directory.";
+      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.nullOr types.int;
-      default = null;
+      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;
@@ -52,22 +58,13 @@ in with lib; {
       default = false;
       description = "Use TOR for all zeronet traffic.";
     };
-
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-
-      description = ''
-        Extra configuration. Contents will be added verbatim to the
-        configuration file at the end.
-      '';
-    };
   };
 
   config = mkIf cfg.enable {
     services.tor = mkIf cfg.tor {
       enable = true;
       controlPort = 9051;
+
       extraConfig = ''
         CacheDirectoryGroupReadable 1
         CookieAuthentication 1
@@ -80,35 +77,20 @@ in with lib; {
       after = [ "network.target" (optionalString cfg.tor "tor.service") ];
       wantedBy = [ "multi-user.target" ];
 
-      preStart = ''
-        # Ensure folder exists or create it and permissions are correct
-        mkdir -p ${escapeShellArg cfg.dataDir} ${escapeShellArg cfg.logDir}
-        chmod 750 ${escapeShellArg cfg.dataDir} ${escapeShellArg cfg.logDir}
-        chown zeronet:zeronet ${escapeShellArg cfg.dataDir} ${escapeShellArg cfg.logDir}
-      '';
-
       serviceConfig = {
-        PermissionsStartOnly = true;
-        PrivateTmp = "yes";
         User = "zeronet";
-        Group = "zeronet";
-        ExecStart = "${pkgs.zeronet}/bin/zeronet --config_file ${zConfFile}";
-      };
-    };
-
-    users = {
-      groups.zeronet.gid = config.ids.gids.zeronet;
-
-      users.zeronet = {
-        description = "zeronet service user";
-        home = cfg.dataDir;
-        createHome = true;
-        group = "zeronet";
-        extraGroups = mkIf cfg.tor [ "tor" ];
-        uid = config.ids.uids.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/nixos/modules/services/networking/zerotierone.nix b/nixos/modules/services/networking/zerotierone.nix
index 764af3846fe5..069e15a909b7 100644
--- a/nixos/modules/services/networking/zerotierone.nix
+++ b/nixos/modules/services/networking/zerotierone.nix
@@ -38,10 +38,13 @@ in
   config = mkIf cfg.enable {
     systemd.services.zerotierone = {
       description = "ZeroTierOne";
-      path = [ cfg.package ];
-      bindsTo = [ "network-online.target" ];
-      after = [ "network-online.target" ];
+
       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
@@ -53,6 +56,7 @@ in
         ExecStart = "${cfg.package}/bin/zerotier-one -p${toString cfg.port}";
         Restart = "always";
         KillMode = "process";
+        TimeoutStopSec = 5;
       };
     };
 
diff --git a/nixos/modules/services/networking/znc/default.nix b/nixos/modules/services/networking/znc/default.nix
index 1ad8855b86db..a7315896c506 100644
--- a/nixos/modules/services/networking/znc/default.nix
+++ b/nixos/modules/services/networking/znc/default.nix
@@ -47,11 +47,11 @@ let
           #     Baz=baz
           #     Qux=qux
           #   </Foo>
-          set = concatMap (subname: [
+          set = concatMap (subname: optionals (value.${subname} != null) ([
               "<${name} ${subname}>"
             ] ++ map (line: "\t${line}") (toLines value.${subname}) ++ [
               "</${name}>"
-            ]) (filter (v: v != null) (attrNames value));
+            ])) (filter (v: v != null) (attrNames value));
 
         }.${builtins.typeOf value};
 
@@ -62,9 +62,9 @@ let
       concatStringsSep "\n" (toLines cfg.config);
 
   semanticTypes = with types; rec {
-    zncAtom = nullOr (either (either int bool) str);
+    zncAtom = nullOr (oneOf [ int bool str ]);
     zncAttr = attrsOf (nullOr zncConf);
-    zncAll = either (either zncAtom (listOf zncAtom)) zncAttr;
+    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
@@ -239,7 +239,7 @@ in
     services.znc = {
       configFile = mkDefault (pkgs.writeText "znc-generated.conf" semanticString);
       config = {
-        Version = (builtins.parseDrvName pkgs.znc.name).version;
+        Version = lib.getVersion pkgs.znc;
         Listener.l.Port = mkDefault 5000;
         Listener.l.SSL = mkDefault true;
       };
@@ -287,20 +287,22 @@ in
       '';
     };
 
-    users.users = optional (cfg.user == defaultUser)
-      { name = defaultUser;
-        description = "ZNC server daemon owner";
-        group = defaultUser;
-        uid = config.ids.uids.znc;
-        home = cfg.dataDir;
-        createHome = true;
+    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 = optional (cfg.user == defaultUser)
-      { name = defaultUser;
-        gid = config.ids.gids.znc;
-        members = [ defaultUser ];
-      };
+    users.groups = optionalAttrs (cfg.user == defaultUser) {
+      ${defaultUser} =
+        { gid = config.ids.gids.znc;
+          members = [ defaultUser ];
+        };
+    };
 
   };
 }
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index 9e9bdedff126..59306d625e6b 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -31,7 +31,7 @@ let
   # 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
-  # cupsd.conf tells cupsd to use this tree.
+  # cups-files.conf tells cupsd to use this tree.
   bindir = pkgs.buildEnv {
     name = "cups-progs";
     paths =
@@ -60,6 +60,8 @@ let
 
     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
@@ -76,8 +78,6 @@ let
     '') cfg.listenAddresses}
     Listen /run/cups/cups.sock
 
-    SetEnv PATH /var/lib/cups/path/lib/cups/filter:/var/lib/cups/path/bin
-
     DefaultShared ${if cfg.defaultShared then "Yes" else "No"}
 
     Browsing ${if cfg.browsing then "Yes" else "No"}
@@ -112,6 +112,15 @@ 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 = {
@@ -127,7 +136,7 @@ in
 
       startWhenNeeded = mkOption {
         type = types.bool;
-        default = false;
+        default = true;
         description = ''
           If set, CUPS is socket-activated; that is,
           instead of having it permanently running as a daemon,
@@ -279,28 +288,42 @@ in
 
   config = mkIf config.services.printing.enable {
 
-    users.users = singleton
-      { name = "cups";
-        uid = config.ids.uids.cups;
+    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";
+    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 = map (x: replaceStrings ["localhost"] ["127.0.0.1"] (removePrefix "*:" x)) cfg.listenAddresses;
+      listenStreams = [ "/run/cups/cups.sock" ]
+        ++ map (x: replaceStrings ["localhost"] ["127.0.0.1"] (removePrefix "*:" x)) cfg.listenAddresses;
     };
 
     systemd.services.cups =
@@ -362,10 +385,10 @@ in
       { description = "CUPS Remote Printer Discovery";
 
         wantedBy = [ "multi-user.target" ];
-        wants = [ "cups.service" "avahi-daemon.service" ];
-        bindsTo = [ "cups.service" "avahi-daemon.service" ];
-        partOf = [ "cups.service" "avahi-daemon.service" ];
-        after = [ "cups.service" "avahi-daemon.service" ];
+        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 ];
 
@@ -421,4 +444,7 @@ in
     security.pam.services.cups = {};
 
   };
+
+  meta.maintainers = with lib.maintainers; [ matthewbauer ];
+
 }
diff --git a/nixos/modules/services/scheduling/atd.nix b/nixos/modules/services/scheduling/atd.nix
index a32907647a0d..93ed9231d3c5 100644
--- a/nixos/modules/services/scheduling/atd.nix
+++ b/nixos/modules/services/scheduling/atd.nix
@@ -57,17 +57,13 @@ in
 
     security.pam.services.atd = {};
 
-    users.users = singleton
-      { name = "atd";
-        uid = config.ids.uids.atd;
+    users.users.atd =
+      { uid = config.ids.uids.atd;
         description = "atd user";
         home = "/var/empty";
       };
 
-    users.groups = singleton
-      { name = "atd";
-        gid = config.ids.gids.atd;
-      };
+    users.groups.atd.gid = config.ids.gids.atd;
 
     systemd.services.atd = {
       description = "Job Execution Daemon (atd)";
diff --git a/nixos/modules/services/scheduling/fcron.nix b/nixos/modules/services/scheduling/fcron.nix
index f77b3bcd5921..42bed21bf25b 100644
--- a/nixos/modules/services/scheduling/fcron.nix
+++ b/nixos/modules/services/scheduling/fcron.nix
@@ -86,7 +86,8 @@ in
 
     services.fcron.systab = systemCronJobs;
 
-    environment.etc =
+    environment.etc = listToAttrs
+      (map (x: { name = x.target; value = x; })
       [ (allowdeny "allow" (cfg.allow))
         (allowdeny "deny" cfg.deny)
         # see man 5 fcron.conf
@@ -112,7 +113,7 @@ in
           gid = config.ids.gids.fcron;
           mode = "0644";
         }
-      ];
+      ]);
 
     environment.systemPackages = [ pkgs.fcron ];
     users.users.fcron = {
@@ -143,7 +144,6 @@ in
     };
     systemd.services.fcron = {
       description = "fcron daemon";
-      after = [ "local-fs.target" ];
       wantedBy = [ "multi-user.target" ];
 
       path = [ pkgs.fcron ];
diff --git a/nixos/modules/services/scheduling/marathon.nix b/nixos/modules/services/scheduling/marathon.nix
index 0961a67770e1..2e0d20c64b23 100644
--- a/nixos/modules/services/scheduling/marathon.nix
+++ b/nixos/modules/services/scheduling/marathon.nix
@@ -93,6 +93,6 @@ in {
       };
     };
 
-    users.users.${cfg.user} = { };
+    users.users.${cfg.user}.isSystemUser = true;
   };
 }
diff --git a/nixos/modules/services/search/elasticsearch.nix b/nixos/modules/services/search/elasticsearch.nix
index 6b688c640d59..91d8f544e16b 100644
--- a/nixos/modules/services/search/elasticsearch.nix
+++ b/nixos/modules/services/search/elasticsearch.nix
@@ -131,6 +131,7 @@ in {
       description = "Extra elasticsearch plugins";
       default = [];
       type = types.listOf types.package;
+      example = lib.literalExample "[ pkgs.elasticsearchPlugins.discovery-ec2 ]";
     };
 
   };
diff --git a/nixos/modules/services/search/hound.nix b/nixos/modules/services/search/hound.nix
index 6740928db9a7..7a44489efe61 100644
--- a/nixos/modules/services/search/hound.nix
+++ b/nixos/modules/services/search/hound.nix
@@ -88,19 +88,19 @@ in {
   };
 
   config = mkIf cfg.enable {
-    users.groups = optional (cfg.group == "hound") {
-      name = "hound";
-      gid = config.ids.gids.hound;
+    users.groups = optionalAttrs (cfg.group == "hound") {
+      hound.gid = config.ids.gids.hound;
     };
 
-    users.users = optional (cfg.user == "hound") {
-      name = "hound";
-      description = "hound code search";
-      createHome = true;
-      home = cfg.home;
-      group = cfg.group;
-      extraGroups = cfg.extraGroups;
-      uid = config.ids.uids.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 = {
diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix
index 2a6e8250850c..2beb265ee5d1 100644
--- a/nixos/modules/services/search/kibana.nix
+++ b/nixos/modules/services/search/kibana.nix
@@ -9,7 +9,7 @@ let
   lt6_6 = builtins.compareVersions cfg.package.version "6.6" < 0;
 
   cfgFile = pkgs.writeText "kibana.json" (builtins.toJSON (
-    (filterAttrsRecursive (n: v: v != null) ({
+    (filterAttrsRecursive (n: v: v != null && v != []) ({
       server.host = cfg.listenAddress;
       server.port = cfg.port;
       server.ssl.certificate = cfg.cert;
@@ -129,7 +129,7 @@ in {
 
           This defaults to the singleton list [ca] when the <option>ca</option> option is defined.
         '';
-        default = if isNull cfg.elasticsearch.ca then [] else [ca];
+        default = if cfg.elasticsearch.ca == null then [] else [ca];
         type = types.listOf types.path;
       };
 
@@ -150,7 +150,7 @@ in {
       description = "Kibana package to use";
       default = pkgs.kibana;
       defaultText = "pkgs.kibana";
-      example = "pkgs.kibana5";
+      example = "pkgs.kibana";
       type = types.package;
     };
 
@@ -198,8 +198,7 @@ in {
 
     environment.systemPackages = [ cfg.package ];
 
-    users.users = singleton {
-      name = "kibana";
+    users.users.kibana = {
       uid = config.ids.uids.kibana;
       description = "Kibana service user";
       home = cfg.dataDir;
diff --git a/nixos/modules/services/search/solr.nix b/nixos/modules/services/search/solr.nix
index 5ef7d9893a49..a8615a20a1cf 100644
--- a/nixos/modules/services/search/solr.nix
+++ b/nixos/modules/services/search/solr.nix
@@ -13,19 +13,11 @@ in
     services.solr = {
       enable = mkEnableOption "Solr";
 
-      # default to the 8.x series not forcing major version upgrade of those on the 7.x series
       package = mkOption {
         type = types.package;
-        default = if versionAtLeast config.system.stateVersion "19.09"
-          then pkgs.solr_8
-          else pkgs.solr_7
-        ;
+        default = pkgs.solr;
         defaultText = "pkgs.solr";
-        description = ''
-          Which Solr package to use. This defaults to version 7.x if
-          <literal>system.stateVersion &lt; 19.09</literal> and version 8.x
-          otherwise.
-        '';
+        description = "Which Solr package to use.";
       };
 
       port = mkOption {
@@ -100,18 +92,18 @@ in
       };
     };
 
-    users.users = optionalAttrs (cfg.user == "solr") (singleton
-      { name = "solr";
+    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") (singleton
-      { name = "solr";
-        gid = config.ids.gids.solr;
-      });
+    users.groups = optionalAttrs (cfg.group == "solr") {
+      solr.gid = config.ids.gids.solr;
+    };
 
   };
 
diff --git a/nixos/modules/services/security/bitwarden_rs/backup.sh b/nixos/modules/services/security/bitwarden_rs/backup.sh
new file mode 100644
index 000000000000..264a7da9cbb6
--- /dev/null
+++ b/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/nixos/modules/services/security/bitwarden_rs/default.nix b/nixos/modules/services/security/bitwarden_rs/default.nix
new file mode 100644
index 000000000000..d1817db07555
--- /dev/null
+++ b/nixos/modules/services/security/bitwarden_rs/default.nix
@@ -0,0 +1,129 @@
+{ 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;
+
+  configFile = pkgs.writeText "bitwarden_rs.env" (concatMapStrings (s: s + "\n") (
+    (concatLists (mapAttrsToList (name: value:
+      if value != null then [ "${nameToEnvVar name}=${if isBool value then boolToString value else toString value}" ] else []
+    ) cfg.config))));
+
+in {
+  options.services.bitwarden_rs = with types; {
+    enable = mkEnableOption "bitwarden_rs";
+
+    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 a 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/1.8.0/.env.template">the environment template file</link>.
+      '';
+      apply = config: optionalAttrs config.webVaultEnabled {
+        webVaultFolder = "${pkgs.bitwarden_rs-vault}/share/bitwarden_rs/vault";
+      } // config;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.bitwarden_rs.config = {
+      dataFolder = "/var/lib/bitwarden_rs";
+      webVaultEnabled = mkDefault true;
+    };
+
+    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 = "${pkgs.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";
+        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/nixos/modules/services/security/certmgr.nix b/nixos/modules/services/security/certmgr.nix
index e89078883ebe..94c0ba141179 100644
--- a/nixos/modules/services/security/certmgr.nix
+++ b/nixos/modules/services/security/certmgr.nix
@@ -113,7 +113,7 @@ in
         otherCert = "/var/certmgr/specs/other-cert.json";
       }
       '';
-      type = with types; attrsOf (either (submodule {
+      type = with types; attrsOf (either path (submodule {
         options = {
           service = mkOption {
             type = nullOr str;
@@ -148,7 +148,7 @@ in
             description = "certmgr spec request object.";
           };
         };
-    }) path);
+    }));
       description = ''
         Certificate specs as described by:
         <link xlink:href="https://github.com/cloudflare/certmgr#certificate-specs" />
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index 04b433f8f2bf..aaf6fb0479ba 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -30,6 +30,10 @@ let
   '';
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "clamav" "updater" "config" ] [ "services" "clamav" "updater" "extraConfig" ])
+  ];
+
   options = {
     services.clamav = {
       daemon = {
@@ -79,18 +83,15 @@ in
   config = mkIf (cfg.updater.enable || cfg.daemon.enable) {
     environment.systemPackages = [ pkg ];
 
-    users.users = singleton {
-      name = clamavUser;
+    users.users.${clamavUser} = {
       uid = config.ids.uids.clamav;
       group = clamavGroup;
       description = "ClamAV daemon user";
       home = stateDir;
     };
 
-    users.groups = singleton {
-      name = clamavGroup;
-      gid = config.ids.gids.clamav;
-    };
+    users.groups.${clamavGroup} =
+      { gid = config.ids.gids.clamav; };
 
     environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
     environment.etc."clamav/clamd.conf".source = clamdConfigFile;
diff --git a/nixos/modules/services/security/fprintd.nix b/nixos/modules/services/security/fprintd.nix
index a35b065ba815..8ece1ca19013 100644
--- a/nixos/modules/services/security/fprintd.nix
+++ b/nixos/modules/services/security/fprintd.nix
@@ -25,29 +25,31 @@ in
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.fprintd;
+        defaultText = "pkgs.fprintd";
+        example = "pkgs.fprintd-thinkpad";
+        description = ''
+          fprintd package to use.
+        '';
+      };
+
     };
-    
+
   };
-  
-  
+
+
   ###### implementation
-  
+
   config = mkIf cfg.enable {
 
     services.dbus.packages = [ pkgs.fprintd ];
 
     environment.systemPackages = [ pkgs.fprintd ];
 
-    systemd.services.fprintd = {
-      description = "Fingerprint Authentication Daemon";
-
-      serviceConfig = {
-        Type = "dbus";
-        BusName = "net.reactivated.Fprint";
-        ExecStart = "${pkgs.fprintd}/libexec/fprintd";
-      };
-    };
+    systemd.packages = [ cfg.package ];
 
   };
-  
+
 }
diff --git a/nixos/modules/services/security/fprot.nix b/nixos/modules/services/security/fprot.nix
index b1ca4ab23452..f203f2abc033 100644
--- a/nixos/modules/services/security/fprot.nix
+++ b/nixos/modules/services/security/fprot.nix
@@ -48,26 +48,22 @@ in {
     services.fprot.updater.licenseKeyfile = mkDefault "${pkgs.fprot}/opt/f-prot/license.key";
 
     environment.systemPackages = [ pkgs.fprot ];
-    environment.etc = singleton {
+    environment.etc."f-prot.conf" = {
       source = "${pkgs.fprot}/opt/f-prot/f-prot.conf";
-      target = "f-prot.conf";
     };
 
-    users.users = singleton
-      { name = fprotUser;
-        uid = config.ids.uids.fprot;
+    users.users.${fprotUser} =
+      { uid = config.ids.uids.fprot;
         description = "F-Prot daemon user";
         home = stateDir;
       };
 
-    users.groups = singleton
-      { name = fprotGroup;
-        gid = config.ids.gids.fprot;
-      };
+    users.groups.${fprotGroup} =
+      { gid = config.ids.gids.fprot; };
 
     services.cron.systemCronJobs = [ "*/${toString cfg.updater.frequency} * * * * root start fprot-updater" ];
 
-    systemd.services."fprot-updater" = {
+    systemd.services.fprot-updater = {
       serviceConfig = {
         Type = "oneshot";
         RemainAfterExit = false;
diff --git a/nixos/modules/services/security/haka.nix b/nixos/modules/services/security/haka.nix
index b64a1b4d03e0..618e689924fd 100644
--- a/nixos/modules/services/security/haka.nix
+++ b/nixos/modules/services/security/haka.nix
@@ -69,7 +69,7 @@ in
       configFile = mkOption {
         default = "empty.lua";
         example = "/srv/haka/myfilter.lua";
-        type = types.string;
+        type = types.str;
         description = ''
           Specify which configuration file Haka uses.
           It can be absolute path or a path relative to the sample directory of
@@ -80,7 +80,7 @@ in
       interfaces = mkOption {
         default = [ "eth0" ];
         example = [ "any" ];
-        type = with types; listOf string;
+        type = with types; listOf str;
         description = ''
           Specify which interface(s) Haka listens to.
           Use 'any' to listen to all interfaces.
diff --git a/nixos/modules/services/security/munge.nix b/nixos/modules/services/security/munge.nix
index 1c4f8e20552f..891788864710 100644
--- a/nixos/modules/services/security/munge.nix
+++ b/nixos/modules/services/security/munge.nix
@@ -19,7 +19,7 @@ in
 
       password = mkOption {
         default = "/etc/munge/munge.key";
-        type = types.string;
+        type = types.path;
         description = ''
           The path to a daemon's secret key.
         '';
diff --git a/nixos/modules/services/security/oauth2_proxy.nix b/nixos/modules/services/security/oauth2_proxy.nix
index 0c5fe8c0ef5f..2abb9ec32aca 100644
--- a/nixos/modules/services/security/oauth2_proxy.nix
+++ b/nixos/modules/services/security/oauth2_proxy.nix
@@ -58,11 +58,11 @@ let
       httponly = cookie.httpOnly;
     };
     set-xauthrequest = setXauthrequest;
-  } // lib.optionalAttrs (!isNull cfg.email.addresses) {
+  } // lib.optionalAttrs (cfg.email.addresses != null) {
     authenticated-emails-file = authenticatedEmailsFile;
   } // lib.optionalAttrs (cfg.passBasicAuth) {
     basic-auth-password = cfg.basicAuthPassword;
-  } // lib.optionalAttrs (!isNull cfg.htpasswd.file) {
+  } // lib.optionalAttrs (cfg.htpasswd.file != null) {
     display-htpasswd-file = cfg.htpasswd.displayForm;
   } // lib.optionalAttrs tls.enable {
     tls-cert = tls.certificate;
@@ -71,7 +71,7 @@ let
   } // (getProviderOptions cfg cfg.provider) // cfg.extraConfig;
 
   mapConfig = key: attr:
-  if (!isNull attr && attr != []) then (
+  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
@@ -284,7 +284,7 @@ in
     ####################################################
     # UPSTREAM Configuration
     upstream = mkOption {
-      type = with types; coercedTo string (x: [x]) (listOf string);
+      type = with types; coercedTo str (x: [x]) (listOf str);
       default = [];
       description = ''
         The http url(s) of the upstream endpoint or <literal>file://</literal>
@@ -523,7 +523,7 @@ in
     };
 
     keyFile = mkOption {
-      type = types.nullOr types.string;
+      type = types.nullOr types.path;
       default = null;
       description = ''
         oauth2_proxy allows passing sensitive configuration via environment variables.
@@ -538,7 +538,7 @@ in
 
   config = mkIf cfg.enable {
 
-    services.oauth2_proxy = mkIf (!isNull cfg.keyFile) {
+    services.oauth2_proxy = mkIf (cfg.keyFile != null) {
       clientID = mkDefault null;
       clientSecret = mkDefault null;
       cookie.secret = mkDefault null;
@@ -546,6 +546,7 @@ in
 
     users.users.oauth2_proxy = {
       description = "OAuth2 Proxy";
+      isSystemUser = true;
     };
 
     systemd.services.oauth2_proxy = {
diff --git a/nixos/modules/services/security/oauth2_proxy_nginx.nix b/nixos/modules/services/security/oauth2_proxy_nginx.nix
index a9ad5497a657..be6734f439f3 100644
--- a/nixos/modules/services/security/oauth2_proxy_nginx.nix
+++ b/nixos/modules/services/security/oauth2_proxy_nginx.nix
@@ -6,14 +6,14 @@ in
 {
   options.services.oauth2_proxy.nginx = {
     proxy = mkOption {
-      type = types.string;
+      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.string;
+      type = types.listOf types.str;
       default = [];
       description = ''
         A list of nginx virtual hosts to put behind the oauth2 proxy
diff --git a/nixos/modules/services/security/physlock.nix b/nixos/modules/services/security/physlock.nix
index 97fbd6aae6e0..61bcd84f2e64 100644
--- a/nixos/modules/services/security/physlock.nix
+++ b/nixos/modules/services/security/physlock.nix
@@ -99,7 +99,7 @@ in
       # for physlock -l and physlock -L
       environment.systemPackages = [ pkgs.physlock ];
 
-      systemd.services."physlock" = {
+      systemd.services.physlock = {
         enable = true;
         description = "Physlock";
         wantedBy = optional cfg.lockOn.suspend   "suspend.target"
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index 8136a5c763a9..a91060dc659a 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -108,7 +108,7 @@ in {
       hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ;
       hkpPort = builtins.toString cfg.hkpPort;
     in {
-      "sks-db" = {
+      sks-db = {
         description = "SKS database server";
         after = [ "network.target" ];
         wantedBy = [ "multi-user.target" ];
@@ -116,20 +116,22 @@ in {
           ${lib.optionalString (cfg.webroot != null)
             "ln -sfT \"${cfg.webroot}\" web"}
           mkdir -p dump
-          # Check that both database configs are symlinks before overwriting them
-          if [ -e KDB/DB_CONFIG ] && [ ! -L KBD/DB_CONFIG ]; then
-            echo "KDB/DB_CONFIG exists but is not a symlink." >&2
-            exit 1
-          fi
-          if [ -e PTree/DB_CONFIG ] && [ ! -L PTree/DB_CONFIG ]; then
-            echo "PTree/DB_CONFIG exists but is not a symlink." >&2
-            exit 1
-          fi
-          ln -sf ${dbConfig} KDB/DB_CONFIG
-          ln -sf ${dbConfig} PTree/DB_CONFIG
           ${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 = "~";
diff --git a/nixos/modules/services/security/sshguard.nix b/nixos/modules/services/security/sshguard.nix
index 3892cd5c72b8..e7a9cefdef30 100644
--- a/nixos/modules/services/security/sshguard.nix
+++ b/nixos/modules/services/security/sshguard.nix
@@ -92,8 +92,11 @@ in {
         "-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/sshg-fw-ipset"
+      BACKEND="${pkgs.sshguard}/libexec/${backend}"
       LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl ${args}"
     '';
 
@@ -104,18 +107,28 @@ in {
       after = [ "network.target" ];
       partOf = optional config.networking.firewall.enable "firewall.service";
 
-      path = with pkgs; [ iptables ipset iproute systemd ];
-
-      postStart = ''
-        ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:ip family inet
-        ${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:ip family inet6
+      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
       '';
 
-      preStop = ''
+      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)";
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index 61b751bb518b..18c105b2f576 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/nixos/modules/services/security/tor.nix
@@ -81,7 +81,7 @@ let
 
     ${optionalString (elem cfg.relay.role ["bridge" "private-bridge"]) ''
       BridgeRelay 1
-      ServerTransportPlugin obfs2,obfs3 exec ${pkgs.pythonPackages.obfsproxy}/bin/obfsproxy managed
+      ServerTransportPlugin ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${pkgs.obfs4}/bin/obfs4proxy managed
       ExtORPort auto
       ${optionalString (cfg.relay.role == "private-bridge") ''
         ExtraInfoStatistics 0
@@ -106,6 +106,12 @@ let
 
 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 {
@@ -355,7 +361,7 @@ in
                 <para>
                   Regular bridge. Works like a regular relay, but
                   doesn't list you in the public relay directory and
-                  hides your Tor node behind obfsproxy.
+                  hides your Tor node behind obfs4proxy.
                 </para>
 
                 <para>
@@ -424,6 +430,13 @@ in
           '';
         };
 
+        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";
@@ -715,7 +728,6 @@ in
     systemd.services.tor-init =
       { description = "Tor Daemon Init";
         wantedBy = [ "tor.service" ];
-        after = [ "local-fs.target" ];
         script = ''
           install -m 0700 -o tor -g tor -d ${torDirectory} ${torDirectory}/onion
           install -m 0750 -o tor -g tor -d ${torRunDirectory}
diff --git a/nixos/modules/services/security/torify.nix b/nixos/modules/services/security/torify.nix
index 08da726437ea..39551190dd33 100644
--- a/nixos/modules/services/security/torify.nix
+++ b/nixos/modules/services/security/torify.nix
@@ -25,6 +25,7 @@ in
     services.tor.tsocks = {
 
       enable = mkOption {
+        type = types.bool;
         default = false;
         description = ''
           Whether to build tsocks wrapper script to relay application traffic via Tor.
@@ -40,6 +41,7 @@ in
       };
 
       server = mkOption {
+        type = types.str;
         default = "localhost:9050";
         example = "192.168.0.20";
         description = ''
@@ -48,6 +50,7 @@ in
       };
 
       config = mkOption {
+        type = types.lines;
         default = "";
         description = ''
           Extra configuration. Contents will be added verbatim to TSocks
diff --git a/nixos/modules/services/security/torsocks.nix b/nixos/modules/services/security/torsocks.nix
index c60c745443bc..47ac95c4626e 100644
--- a/nixos/modules/services/security/torsocks.nix
+++ b/nixos/modules/services/security/torsocks.nix
@@ -112,10 +112,9 @@ in
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.torsocks (wrapTorsocks "torsocks-faster" cfg.fasterServer) ];
 
-    environment.etc =
-      [ { source = pkgs.writeText "torsocks.conf" (configFile cfg.server);
-          target = "tor/torsocks.conf";
-        }
-      ];
+    environment.etc."tor/torsocks.conf" =
+      {
+        source = pkgs.writeText "torsocks.conf" (configFile cfg.server);
+      };
   };
 }
diff --git a/nixos/modules/services/security/usbguard.nix b/nixos/modules/services/security/usbguard.nix
index 88d2f69db572..4ced5acd9bd9 100644
--- a/nixos/modules/services/security/usbguard.nix
+++ b/nixos/modules/services/security/usbguard.nix
@@ -39,6 +39,16 @@ in {
     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";
@@ -179,13 +189,13 @@ in {
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ pkgs.usbguard ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services.usbguard = {
       description = "USBGuard daemon";
 
       wantedBy = [ "basic.target" ];
-      wants = [ "systemd-udevd.service" "local-fs.target" ];
+      wants = [ "systemd-udevd.service" ];
 
       # make sure an empty rule file and required directories exist
       preStart = ''
@@ -195,7 +205,7 @@ in {
 
       serviceConfig = {
         Type = "simple";
-        ExecStart = ''${pkgs.usbguard}/bin/usbguard-daemon -P -k -c ${daemonConfFile}'';
+        ExecStart = ''${cfg.package}/bin/usbguard-daemon -P -k -c ${daemonConfFile}'';
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/security/vault.nix b/nixos/modules/services/security/vault.nix
index 8176c168ca94..6a8a3a93327e 100644
--- a/nixos/modules/services/security/vault.nix
+++ b/nixos/modules/services/security/vault.nix
@@ -70,7 +70,7 @@ in
       };
 
       storageBackend = mkOption {
-        type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" ];
+        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";
       };
@@ -119,9 +119,8 @@ in
     };
     users.groups.vault.gid = config.ids.gids.vault;
 
-    systemd.tmpfiles.rules = optional (cfg.storagePath != null) [
-      "d '${cfg.storagePath}' 0700 vault vault - -"
-    ];
+    systemd.tmpfiles.rules = optional (cfg.storagePath != null)
+      "d '${cfg.storagePath}' 0700 vault vault - -";
 
     systemd.services.vault = {
       description = "Vault server daemon";
@@ -136,6 +135,7 @@ in
         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";
diff --git a/nixos/modules/services/system/cgmanager.nix b/nixos/modules/services/system/cgmanager.nix
index 59d3deced867..d3d57aa76928 100644
--- a/nixos/modules/services/system/cgmanager.nix
+++ b/nixos/modules/services/system/cgmanager.nix
@@ -14,7 +14,6 @@ in {
   config = mkIf cfg.enable {
     systemd.services.cgmanager = {
       wantedBy = [ "multi-user.target" ];
-      after = [ "local-fs.target" ];
       description = "Cgroup management daemon";
       restartIfChanged = false;
       serviceConfig = {
diff --git a/nixos/modules/services/system/cloud-init.nix b/nixos/modules/services/system/cloud-init.nix
index 3ad555f78ef8..15fe822aec67 100644
--- a/nixos/modules/services/system/cloud-init.nix
+++ b/nixos/modules/services/system/cloud-init.nix
@@ -112,8 +112,6 @@ in
     systemd.services.cloud-init-local =
       { description = "Initial cloud-init job (pre-networking)";
         wantedBy = [ "multi-user.target" ];
-        wants = [ "local-fs.target" ];
-        after = [ "local-fs.target" ];
         path = path;
         serviceConfig =
           { Type = "oneshot";
@@ -127,9 +125,9 @@ in
     systemd.services.cloud-init =
       { description = "Initial cloud-init job (metadata service crawler)";
         wantedBy = [ "multi-user.target" ];
-        wants = [ "local-fs.target" "network-online.target" "cloud-init-local.service"
+        wants = [ "network-online.target" "cloud-init-local.service"
                   "sshd.service" "sshd-keygen.service" ];
-        after = [ "local-fs.target" "network-online.target" "cloud-init-local.service" ];
+        after = [ "network-online.target" "cloud-init-local.service" ];
         before = [ "sshd.service" "sshd-keygen.service" ];
         requires = [ "network.target "];
         path = path;
diff --git a/nixos/modules/services/system/dbus.nix b/nixos/modules/services/system/dbus.nix
index e04580218442..4a60fec1ca80 100644
--- a/nixos/modules/services/system/dbus.nix
+++ b/nixos/modules/services/system/dbus.nix
@@ -44,8 +44,10 @@ in
           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>
         '';
       };
@@ -66,10 +68,7 @@ in
 
     environment.systemPackages = [ pkgs.dbus.daemon pkgs.dbus ];
 
-    environment.etc = singleton
-      { source = configDir;
-        target = "dbus-1";
-      };
+    environment.etc."dbus-1".source = configDir;
 
     users.users.messagebus = {
       uid = config.ids.uids.messagebus;
diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix
index daa46838bfa8..39d1bf274bd2 100644
--- a/nixos/modules/services/system/earlyoom.nix
+++ b/nixos/modules/services/system/earlyoom.nix
@@ -63,6 +63,17 @@ in
           Enable debugging messages.
         '';
       };
+
+      notificationsCommand = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "sudo -u example_user DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send";
+        description = ''
+          Command used to send notifications.
+
+          See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details.
+        '';
+      };
     };
   };
 
@@ -88,7 +99,9 @@ in
           -s ${toString ecfg.freeSwapThreshold} \
           ${optionalString ecfg.useKernelOOMKiller "-k"} \
           ${optionalString ecfg.ignoreOOMScoreAdjust "-i"} \
-          ${optionalString ecfg.enableDebugInfo "-d"}
+          ${optionalString ecfg.enableDebugInfo "-d"} \
+          ${optionalString (ecfg.notificationsCommand != null)
+            "-N ${escapeShellArg ecfg.notificationsCommand}"}
         '';
       };
     };
diff --git a/nixos/modules/services/system/kerberos/default.nix b/nixos/modules/services/system/kerberos/default.nix
index 26ac85de402f..c55241c4cff1 100644
--- a/nixos/modules/services/system/kerberos/default.nix
+++ b/nixos/modules/services/system/kerberos/default.nix
@@ -1,4 +1,4 @@
-{pkgs, config, lib, ...}:
+{config, lib, ...}:
 
 let
   inherit (lib) mkOption mkIf types length attrNames;
diff --git a/nixos/modules/services/system/kerberos/heimdal.nix b/nixos/modules/services/system/kerberos/heimdal.nix
index d0f470f836ed..f0e56c7951a4 100644
--- a/nixos/modules/services/system/kerberos/heimdal.nix
+++ b/nixos/modules/services/system/kerberos/heimdal.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib) mkIf concatStringsSep concatMapStrings toList mapAttrs
-    mapAttrsToList attrValues;
+    mapAttrsToList;
   cfg = config.services.kerberos_server;
   kerberos = config.krb5.kerberos;
   stateDir = "/var/heimdal";
diff --git a/nixos/modules/services/system/kerberos/mit.nix b/nixos/modules/services/system/kerberos/mit.nix
index a53d9dd0c6b5..25d7d51e808a 100644
--- a/nixos/modules/services/system/kerberos/mit.nix
+++ b/nixos/modules/services/system/kerberos/mit.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib) mkIf concatStrings concatStringsSep concatMapStrings toList
-    mapAttrs mapAttrsToList attrValues;
+    mapAttrs mapAttrsToList;
   cfg = config.services.kerberos_server;
   kerberos = config.krb5.kerberos;
   stateDir = "/var/lib/krb5kdc";
diff --git a/nixos/modules/services/system/localtime.nix b/nixos/modules/services/system/localtime.nix
index c7e897c96448..74925c5e2c47 100644
--- a/nixos/modules/services/system/localtime.nix
+++ b/nixos/modules/services/system/localtime.nix
@@ -20,35 +20,28 @@ in {
   };
 
   config = mkIf cfg.enable {
-    services.geoclue2.enable = true;
+    services.geoclue2 = {
+      enable = true;
+      appConfig.localtime = {
+        isAllowed = true;
+        isSystem = true;
+      };
+    };
 
-    # so polkit will pick up the rules
-    environment.systemPackages = [ pkgs.localtime ];
+    # 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 = [{
-      name = "localtimed";
+    users.users.localtimed = {
       description = "Taskserver user";
-    }];
+    };
 
     systemd.services.localtime = {
-      description = "localtime service";
       wantedBy = [ "multi-user.target" ];
-      partOf = [ "geoclue.service "];
-
-      serviceConfig = {
-        Restart                 = "on-failure";
-        # TODO: make it work with dbus
-        #DynamicUser             = true;
-        Nice                    = 10;
-        User                    = "localtimed";
-        PrivateTmp              = "yes";
-        PrivateDevices          = true;
-        PrivateNetwork          = "yes";
-        NoNewPrivileges         = "yes";
-        ProtectSystem           = "strict";
-        ProtectHome             = true;
-        ExecStart               = "${pkgs.localtime}/bin/localtimed";
-      };
+      serviceConfig.Restart = "on-failure";
     };
   };
 }
diff --git a/nixos/modules/services/system/nscd.conf b/nixos/modules/services/system/nscd.conf
index 603a5d01acce..2b7523a7346d 100644
--- a/nixos/modules/services/system/nscd.conf
+++ b/nixos/modules/services/system/nscd.conf
@@ -7,46 +7,28 @@
 # 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
-threads                 1
-paranoia                no
-debug-level             0
 
 enable-cache            passwd          yes
 positive-time-to-live   passwd          0
 negative-time-to-live   passwd          0
-suggested-size          passwd          211
-check-files             passwd          yes
-persistent              passwd          no
 shared                  passwd          yes
 
 enable-cache            group           yes
 positive-time-to-live   group           0
 negative-time-to-live   group           0
-suggested-size          group           211
-check-files             group           yes
-persistent              group           no
 shared                  group           yes
 
 enable-cache            netgroup        yes
 positive-time-to-live   netgroup        0
 negative-time-to-live   netgroup        0
-suggested-size          netgroup        211
-check-files             netgroup        yes
-persistent              netgroup        no
 shared                  netgroup        yes
 
 enable-cache            hosts           yes
 positive-time-to-live   hosts           600
 negative-time-to-live   hosts           0
-suggested-size          hosts           211
-check-files             hosts           yes
-persistent              hosts           no
 shared                  hosts           yes
 
 enable-cache            services        yes
 positive-time-to-live   services        0
 negative-time-to-live   services        0
-suggested-size          services        211
-check-files             services        yes
-persistent              services        no
 shared                  services        yes
diff --git a/nixos/modules/services/system/nscd.nix b/nixos/modules/services/system/nscd.nix
index fd1570d11980..e11f7e049d8f 100644
--- a/nixos/modules/services/system/nscd.nix
+++ b/nixos/modules/services/system/nscd.nix
@@ -39,11 +39,6 @@ in
   config = mkIf cfg.enable {
     environment.etc."nscd.conf".text = cfg.config;
 
-    users.users.nscd =
-      { isSystemUser = true;
-        description = "Name service cache daemon user";
-      };
-
     systemd.services.nscd =
       { description = "Name Service Cache Daemon";
 
@@ -51,22 +46,23 @@ in
 
         environment = { LD_LIBRARY_PATH = nssModulesPath; };
 
-        preStart =
-          ''
-            mkdir -m 0755 -p /run/nscd
-            rm -f /run/nscd/nscd.pid
-            mkdir -m 0755 -p /var/db/nscd
-          '';
-
         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 = "@${pkgs.glibc.bin}/sbin/nscd nscd";
+          { ExecStart = "!@${pkgs.glibc.bin}/sbin/nscd nscd";
             Type = "forking";
+            DynamicUser = true;
+            RuntimeDirectory = "nscd";
             PIDFile = "/run/nscd/nscd.pid";
             Restart = "always";
             ExecReload =
@@ -75,15 +71,6 @@ in
                 "${pkgs.glibc.bin}/sbin/nscd --invalidate hosts"
               ];
           };
-
-        # Urgggggh... Nscd forks before opening its socket and writing
-        # its pid. So wait until it's ready.
-        postStart =
-          ''
-            while ! ${pkgs.glibc.bin}/sbin/nscd -g > /dev/null; do
-              sleep 0.2
-            done
-          '';
       };
 
   };
diff --git a/nixos/modules/services/torrent/deluge.nix b/nixos/modules/services/torrent/deluge.nix
index 84f0437b9411..0c72505395dd 100644
--- a/nixos/modules/services/torrent/deluge.nix
+++ b/nixos/modules/services/torrent/deluge.nix
@@ -5,8 +5,33 @@ with lib;
 let
   cfg = config.services.deluge;
   cfg_web = config.services.deluge.web;
+
   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
+      ln -sf ${configFile} ${configDir}/core.conf
+      ln -sf ${cfg.authFile} ${configDir}/auth
+    else
+      # Declarative for the first time, backup stateful files
+      ln -sb --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 = {
@@ -15,54 +40,215 @@ in {
 
         openFilesLimit = mkOption {
           default = openFilesLimit;
-          example = 8192;
           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, p7zip and bzip2.
+          '';
+        };
       };
 
-      deluge.web.enable = mkEnableOption "Deluge Web daemon";
+      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 {
 
+    # Provide a default set of `extraPackages`.
+    services.deluge.extraPackages = with pkgs; [ unzip gnutar xz p7zip 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 = [ pkgs.deluge ];
+      path = [ pkgs.deluge ] ++ cfg.extraPackages;
       serviceConfig = {
-        ExecStart = "${pkgs.deluge}/bin/deluged -d";
-        # To prevent "Quit & shutdown daemon" from working; we want systemd to manage it!
+        ExecStart = ''
+          ${pkgs.deluge}/bin/deluged \
+            --do-not-daemonize \
+            --config ${configDir}
+        '';
+        # To prevent "Quit & shutdown daemon" from working; we want systemd to
+        # manage it!
         Restart = "on-success";
-        User = "deluge";
-        Group = "deluge";
+        User = cfg.user;
+        Group = cfg.group;
+        UMask = "0002";
         LimitNOFILE = cfg.openFilesLimit;
       };
+      preStart = preStart;
     };
 
     systemd.services.delugeweb = mkIf cfg_web.enable {
-      after = [ "network.target" ];
+      after = [ "network.target" "deluged.service"];
+      requires = [ "deluged.service" ];
       description = "Deluge BitTorrent WebUI";
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.deluge ];
-      serviceConfig.ExecStart = "${pkgs.deluge}/bin/deluge --ui web";
-      serviceConfig.User = "deluge";
-      serviceConfig.Group = "deluge";
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.deluge}/bin/deluge-web \
+            --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 = [ pkgs.deluge ];
 
-    users.users.deluge = {
-      group = "deluge";
-      uid = config.ids.uids.deluge;
-      home = "/var/lib/deluge/";
-      createHome = true;
-      description = "Deluge Daemon user";
+    users.users = mkIf (cfg.user == "deluge") {
+      deluge = {
+        group = cfg.group;
+        uid = config.ids.uids.deluge;
+        home = cfg.dataDir;
+        description = "Deluge Daemon user";
+      };
     };
 
-    users.groups.deluge.gid = config.ids.gids.deluge;
+    users.groups = mkIf (cfg.group == "deluge") {
+      deluge = {
+        gid = config.ids.gids.deluge;
+      };
+    };
   };
 }
diff --git a/nixos/modules/services/torrent/flexget.nix b/nixos/modules/services/torrent/flexget.nix
index ca63f529a5df..6ac85f8fa178 100644
--- a/nixos/modules/services/torrent/flexget.nix
+++ b/nixos/modules/services/torrent/flexget.nix
@@ -19,7 +19,7 @@ in {
       user = mkOption {
         default = "deluge";
         example = "some_user";
-        type = types.string;
+        type = types.str;
         description = "The user under which to run flexget.";
       };
 
@@ -33,7 +33,7 @@ in {
       interval = mkOption {
         default = "10m";
         example = "1h";
-        type = types.string;
+        type = types.str;
         description = "When to perform a <command>flexget</command> run. See <command>man 7 systemd.time</command> for the format.";
       };
 
diff --git a/nixos/modules/services/torrent/magnetico.nix b/nixos/modules/services/torrent/magnetico.nix
new file mode 100644
index 000000000000..7465c10e002c
--- /dev/null
+++ b/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/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index f544928fb6b1..5ba72e8d7730 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -7,6 +7,7 @@ let
   apparmor = config.security.apparmor.enable;
 
   homeDir = cfg.home;
+  downloadDirPermissions = cfg.downloadDirPermissions;
   downloadDir = "${homeDir}/Downloads";
   incompleteDir = "${homeDir}/.incomplete";
 
@@ -16,16 +17,14 @@ let
   # for users in group "transmission" to have access to torrents
   fullSettings = { umask = 2; download-dir = downloadDir; incomplete-dir = incompleteDir; } // cfg.settings;
 
-  # Directories transmission expects to exist and be ug+rwx.
-  directoriesToManage = [ homeDir settingsDir fullSettings.download-dir fullSettings.incomplete-dir ];
-
   preStart = pkgs.writeScript "transmission-pre-start" ''
     #!${pkgs.runtimeShell}
     set -ex
-    for DIR in ${escapeShellArgs directoriesToManage}; do
+    for DIR in "${homeDir}" "${settingsDir}" "${fullSettings.download-dir}" "${fullSettings.incomplete-dir}"; do
       mkdir -p "$DIR"
-      chmod 770 "$DIR"
     done
+    chmod 700 "${homeDir}" "${settingsDir}"
+    chmod ${downloadDirPermissions} "${fullSettings.download-dir}" "${fullSettings.incomplete-dir}"
     cp -f ${settingsFile} ${settingsDir}/settings.json
   '';
 in
@@ -71,6 +70,16 @@ in
         '';
       };
 
+      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;
@@ -84,22 +93,35 @@ in
           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.services.transmission = {
       description = "Transmission BitTorrent Service";
-      after = [ "local-fs.target" "network.target" ] ++ optional apparmor "apparmor.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}";
+      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 = "transmission";
+      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";
     };
@@ -107,14 +129,23 @@ in
     # It's useful to have transmission in path, e.g. for remote control
     environment.systemPackages = [ pkgs.transmission ];
 
-    users.groups.transmission.gid = config.ids.gids.transmission;
-    users.users.transmission = {
-      group = "transmission";
-      uid = config.ids.uids.transmission;
-      description = "Transmission BitTorrent user";
-      home = homeDir;
-      createHome = true;
-    };
+    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 [
diff --git a/nixos/modules/services/ttys/agetty.nix b/nixos/modules/services/ttys/agetty.nix
index b50de496e975..f3a629f7af70 100644
--- a/nixos/modules/services/ttys/agetty.nix
+++ b/nixos/modules/services/ttys/agetty.nix
@@ -92,7 +92,7 @@ in
         restartIfChanged = false;
       };
 
-    systemd.services."console-getty" =
+    systemd.services.console-getty =
       { serviceConfig.ExecStart = [
           "" # override upstream default with an empty ExecStart
           (gettyCmd "--noclear --keep-baud console 115200,38400,9600 $TERM")
@@ -102,7 +102,7 @@ in
         enable = mkDefault config.boot.isContainer;
       };
 
-    environment.etc = singleton
+    environment.etc.issue =
       { # Friendly greeting on the virtual consoles.
         source = pkgs.writeText "issue" ''
 
@@ -110,7 +110,6 @@ in
           ${config.services.mingetty.helpLine}
 
         '';
-        target = "issue";
       };
 
   };
diff --git a/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix
index 15744d90cc79..59185fdbd36f 100644
--- a/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ b/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -142,12 +142,23 @@ in
   };
 
   config = mkIf cfg.enable {
-    users.users."${cfg.user}" = {
+    users.users.${cfg.user} = {
       isSystemUser = true;
       group = cfg.group;
     };
 
-    users.groups."${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";
@@ -167,12 +178,6 @@ in
       preStart = ''
         mkdir -p ${cfg.home}/{logs,work,temp,deploy}
 
-        mkdir -p /run/confluence
-        ln -sf ${cfg.home}/{logs,work,temp,server.xml} /run/confluence
-        ln -sf ${cfg.home} /run/confluence/home
-
-        chown ${cfg.user} ${cfg.home}
-
         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}",' \
@@ -184,7 +189,6 @@ in
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
-        PermissionsStartOnly = true;
         ExecStart = "${pkg}/bin/start-confluence.sh -fg";
         ExecStop = "${pkg}/bin/stop-confluence.sh";
       };
diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix
index c144b21bdaf2..ceab656b15e8 100644
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -110,12 +110,22 @@ in
   };
 
   config = mkIf cfg.enable {
-    users.users."${cfg.user}" = {
+    users.users.${cfg.user} = {
       isSystemUser = true;
       group = cfg.group;
     };
 
-    users.groups."${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";
@@ -136,12 +146,6 @@ in
         rm -rf ${cfg.home}/work
         mkdir -p ${cfg.home}/{logs,database,work}
 
-        mkdir -p /run/atlassian-crowd
-        ln -sf ${cfg.home}/{database,logs,work,server.xml} /run/atlassian-crowd
-
-        chown ${cfg.user}:${cfg.group} ${cfg.home}
-        chown ${cfg.user}:${cfg.group} ${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}",' \
@@ -153,7 +157,6 @@ in
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
-        PermissionsStartOnly = true;
         ExecStart = "${pkg}/start_crowd.sh -fg";
       };
     };
diff --git a/nixos/modules/services/web-apps/atlassian/jira.nix b/nixos/modules/services/web-apps/atlassian/jira.nix
index 0b3a5722d6c8..ce04982e8a9e 100644
--- a/nixos/modules/services/web-apps/atlassian/jira.nix
+++ b/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -148,12 +148,23 @@ in
   };
 
   config = mkIf cfg.enable {
-    users.users."${cfg.user}" = {
+    users.users.${cfg.user} = {
       isSystemUser = true;
       group = cfg.group;
     };
 
-    users.groups."${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";
@@ -174,12 +185,6 @@ in
       preStart = ''
         mkdir -p ${cfg.home}/{logs,work,temp,deploy}
 
-        mkdir -p /run/atlassian-jira
-        ln -sf ${cfg.home}/{logs,work,temp,server.xml} /run/atlassian-jira
-        ln -sf ${cfg.home} /run/atlassian-jira/home
-
-        chown ${cfg.user} ${cfg.home}
-
         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}",' \
@@ -191,7 +196,6 @@ in
         User = cfg.user;
         Group = cfg.group;
         PrivateTmp = true;
-        PermissionsStartOnly = true;
         ExecStart = "${pkg}/bin/start-jira.sh -fg";
         ExecStop = "${pkg}/bin/stop-jira.sh";
       };
diff --git a/nixos/modules/services/web-apps/codimd.nix b/nixos/modules/services/web-apps/codimd.nix
index 7ae7cd9c52d8..5f56f8ed5a09 100644
--- a/nixos/modules/services/web-apps/codimd.nix
+++ b/nixos/modules/services/web-apps/codimd.nix
@@ -893,6 +893,7 @@ in
       extraGroups = cfg.groups;
       home = cfg.workDir;
       createHome = true;
+      isSystemUser = true;
     };
 
     systemd.services.codimd = {
diff --git a/nixos/modules/services/web-apps/cryptpad.nix b/nixos/modules/services/web-apps/cryptpad.nix
new file mode 100644
index 000000000000..69a89107d310
--- /dev/null
+++ b/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/nixos/modules/services/web-apps/documize.nix b/nixos/modules/services/web-apps/documize.nix
index 206617b0e5ac..1b90299aa23c 100644
--- a/nixos/modules/services/web-apps/documize.nix
+++ b/nixos/modules/services/web-apps/documize.nix
@@ -3,65 +3,147 @@
 with lib;
 
 let
-
   cfg = config.services.documize;
 
-in
+  mkParams = optional: concatMapStrings (name: let
+    predicate = optional -> cfg.${name} != null;
+    template = " -${name} '${toString cfg.${name}}'";
+  in optionalString predicate template);
 
-  {
-    options.services.documize = {
-      enable = mkEnableOption "Documize Wiki";
+in {
+  options.services.documize = {
+    enable = mkEnableOption "Documize Wiki";
 
-      offline = mkEnableOption "Documize offline mode";
+    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 {
-        default = pkgs.documize-community;
-        type = types.package;
-        description = ''
-          Which package to use for documize.
-        '';
-      };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.documize-community;
+      description = ''
+        Which package to use for documize.
+      '';
+    };
 
-      db = mkOption {
-        type = types.str;
-        example = "host=localhost port=5432 sslmode=disable user=admin password=secret dbname=documize";
-        description = ''
-          The DB connection string to use for the database.
-        '';
-      };
+    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.
+      '';
+    };
 
-      dbtype = mkOption {
-        type = types.enum [ "postgresql" "percona" "mariadb" "mysql" ];
-        description = ''
-          Which database to use for storage.
-        '';
-      };
+    cert = mkOption {
+      type = types.nullOr types.str;
+      default = null;
+      description = ''
+        The <filename>cert.pem</filename> file used for https.
+      '';
+    };
 
-      port = mkOption {
-        type = types.port;
-        example = 3000;
-        description = ''
-          Which TCP port to serve.
-        '';
-      };
+    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.
+      '';
     };
 
-    config = mkIf cfg.enable {
-      systemd.services.documize-server = {
-        wantedBy = [ "multi-user.target" ];
-
-        script = ''
-          ${cfg.package}/bin/documize \
-            -db "${cfg.db}" \
-            -dbtype ${cfg.dbtype} \
-            -port ${toString cfg.port} \
-            -offline ${if cfg.offline then "1" else "0"}
-        '';
-
-        serviceConfig = {
-          Restart = "always";
-          DynamicUser = "yes";
-        };
+    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/nixos/modules/services/web-apps/dokuwiki.nix b/nixos/modules/services/web-apps/dokuwiki.nix
new file mode 100644
index 000000000000..07af7aa0dfec
--- /dev/null
+++ b/nixos/modules/services/web-apps/dokuwiki.nix
@@ -0,0 +1,272 @@
+{ config, lib, pkgs, ... }:
+
+let
+
+  inherit (lib) mkEnableOption mkForce mkIf mkMerge mkOption optionalAttrs recursiveUpdate types;
+
+  cfg = config.services.dokuwiki;
+
+  user = config.services.nginx.user;
+  group = config.services.nginx.group;
+
+  dokuwikiAclAuthConfig = pkgs.writeText "acl.auth.php" ''
+    # acl.auth.php
+    # <?php exit()?>
+    #
+    # Access Control Lists
+    #
+    ${toString cfg.acl}
+  '';
+
+  dokuwikiLocalConfig = pkgs.writeText "local.php" ''
+    <?php
+    $conf['savedir'] = '${cfg.stateDir}';
+    $conf['superuser'] = '${toString cfg.superUser}';
+    $conf['useacl'] = '${toString cfg.aclUse}';
+    ${toString cfg.extraConfig}
+  '';
+
+  dokuwikiPluginsLocalConfig = pkgs.writeText "plugins.local.php" ''
+    <?php
+    ${cfg.pluginsConfig}
+  '';
+
+in
+{
+  options.services.dokuwiki = {
+    enable = mkEnableOption "DokuWiki web application.";
+
+    hostName = mkOption {
+      type = types.str;
+      default = "localhost";
+      description = "FQDN for the instance.";
+    };
+
+    stateDir = mkOption {
+      type = types.path;
+      default = "/var/lib/dokuwiki/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.
+      '';
+    };
+
+    aclFile = mkOption {
+      type = types.nullOr types.path;
+      default = 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"/>
+      '';
+    };
+
+    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 = types.nullOr types.path;
+      default = 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"/>
+        '';
+    };
+
+    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.
+      '';
+    };
+
+    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.
+      '';
+    };
+  };
+
+  # implementation
+
+  config = mkIf cfg.enable {
+
+    warnings = mkIf (cfg.superUser == null) ["Not setting services.dokuwiki.superUser will impair your ability to administer DokuWiki"];
+
+    assertions = [ 
+      {
+        assertion = cfg.aclUse -> (cfg.acl != null || cfg.aclFile != null);
+        message = "Either services.dokuwiki.acl or services.dokuwiki.aclFile is mandatory when aclUse is true";
+      }
+      {
+        assertion = cfg.usersFile != null -> cfg.aclUse != false;
+        message = "services.dokuwiki.aclUse must be true when usersFile is not null";
+      }
+    ];
+
+    services.phpfpm.pools.dokuwiki = {
+      inherit user;
+      inherit group;
+      phpEnv = {        
+        DOKUWIKI_LOCAL_CONFIG = "${dokuwikiLocalConfig}";
+        DOKUWIKI_PLUGINS_LOCAL_CONFIG = "${dokuwikiPluginsLocalConfig}";
+      } //optionalAttrs (cfg.usersFile != null) {
+        DOKUWIKI_USERS_AUTH_CONFIG = "${cfg.usersFile}";
+      } //optionalAttrs (cfg.aclUse) {
+        DOKUWIKI_ACL_AUTH_CONFIG = if (cfg.acl != null) then "${dokuwikiAclAuthConfig}" else "${toString cfg.aclFile}";
+      };
+      
+      settings = {
+        "listen.mode" = "0660";
+        "listen.owner" = user;
+        "listen.group" = group;
+      } // cfg.poolConfig;
+    };
+
+    services.nginx = {
+      enable = true;
+      
+       virtualHosts = {
+        ${cfg.hostName} = mkMerge [ cfg.nginx {
+          root = mkForce "${pkgs.dokuwiki}/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.socket};
+              fastcgi_param HTTPS on;
+            '';
+          };
+        }];
+      };
+
+    };
+
+    systemd.tmpfiles.rules = [
+      "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} - -"
+    ];
+
+  };
+}
diff --git a/nixos/modules/services/web-apps/frab.nix b/nixos/modules/services/web-apps/frab.nix
index fb95e024817c..1b5890d6b0c7 100644
--- a/nixos/modules/services/web-apps/frab.nix
+++ b/nixos/modules/services/web-apps/frab.nix
@@ -19,7 +19,7 @@ let
     RAILS_SERVE_STATIC_FILES = "1";
   } // cfg.extraEnvironment;
 
-  frab-rake = pkgs.stdenv.mkDerivation rec {
+  frab-rake = pkgs.stdenv.mkDerivation {
     name = "frab-rake";
     buildInputs = [ package.env pkgs.makeWrapper ];
     phases = "installPhase fixupPhase";
@@ -173,14 +173,17 @@ in
   config = mkIf cfg.enable {
     environment.systemPackages = [ frab-rake ];
 
-    users.users = [
-      { name = cfg.user;
-        group = cfg.group;
+    users.users.${cfg.user} =
+      { group = cfg.group;
         home = "${cfg.statePath}";
-      }
-    ];
+        isSystemUser = true;
+      };
 
-    users.groups = [ { name = cfg.group; } ];
+    users.groups.${cfg.group} = { };
+
+    systemd.tmpfiles.rules = [
+      "d '${cfg.statePath}/system/attachments' - ${cfg.user} ${cfg.group} - -"
+    ];
 
     systemd.services.frab = {
       after = [ "network.target" "gitlab.service" ];
@@ -188,10 +191,6 @@ in
       environment = frabEnv;
 
       preStart = ''
-        mkdir -p ${cfg.statePath}/system/attachments
-        chown ${cfg.user}:${cfg.group} -R ${cfg.statePath}
-
-        mkdir /run/frab -p
         ln -sf ${pkgs.writeText "frab-database.yml" databaseConfig} /run/frab/database.yml
         ln -sf ${cfg.statePath}/system /run/frab/system
 
@@ -204,7 +203,6 @@ in
       '';
 
       serviceConfig = {
-        PermissionsStartOnly = true;
         PrivateTmp = true;
         PrivateDevices = true;
         Type = "simple";
@@ -213,6 +211,7 @@ in
         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/nixos/modules/services/web-apps/gotify-server.nix b/nixos/modules/services/web-apps/gotify-server.nix
new file mode 100644
index 000000000000..03e01f46a944
--- /dev/null
+++ b/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/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
index 644aad82df2c..d9ad7e9e3d39 100644
--- a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
+++ b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
@@ -1,14 +1,16 @@
 { config, lib, pkgs, ... }: with lib; let
   cfg = config.services.icingaweb2;
+  fpm = config.services.phpfpm.pools.${poolName};
   poolName = "icingaweb2";
-  phpfpmSocketName = "/var/run/phpfpm/${poolName}.sock";
 
   defaultConfig = {
     global = {
-      module_path = "${pkgs.icingaweb2}/modules${optionalString (builtins.length config.modulePath > 0) ":${concatStringsSep ":" config.modulePath}"}";
+      module_path = "${pkgs.icingaweb2}/modules";
     };
   };
 in {
+  meta.maintainers = with maintainers; [ das_j ];
+
   options.services.icingaweb2 = with types; {
     enable = mkEnableOption "the icingaweb2 web interface";
 
@@ -162,33 +164,32 @@ in {
   };
 
   config = mkIf cfg.enable {
-    services.phpfpm.poolConfigs = mkIf (cfg.pool == "${poolName}") {
-      "${poolName}" = ''
-        listen = "${phpfpmSocketName}"
-        listen.owner = nginx
-        listen.group = nginx
-        listen.mode = 0600
-        user = icingaweb2
-        pm = dynamic
-        pm.max_children = 75
-        pm.start_servers = 2
-        pm.min_spare_servers = 2
-        pm.max_spare_servers = 10
-      '';
+    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;
+        };
+      };
     };
 
-    services.phpfpm.phpOptions = mkIf (cfg.pool == "${poolName}")
-      ''
-        extension = ${pkgs.phpPackages.imagick}/lib/php/extensions/imagick.so
-        date.timezone = "${cfg.timezone}"
-      '';
-
     systemd.services."phpfpm-${poolName}".serviceConfig.ReadWritePaths = [ "/etc/icingaweb2" ];
 
     services.nginx = {
       enable = true;
       virtualHosts = mkIf (cfg.virtualHost != null) {
-        "${cfg.virtualHost}" = {
+        ${cfg.virtualHost} = {
           root = "${pkgs.icingaweb2}/public";
 
           extraConfig = ''
@@ -206,7 +207,7 @@ in {
             include ${config.services.nginx.package}/conf/fastcgi.conf;
             try_files $uri =404;
             fastcgi_split_path_info ^(.+\.php)(/.+)$;
-            fastcgi_pass unix:${phpfpmSocketName};
+            fastcgi_pass unix:${fpm.socket};
             fastcgi_param SCRIPT_FILENAME ${pkgs.icingaweb2}/public/index.php;
           '';
         };
@@ -215,7 +216,7 @@ in {
 
     # /etc/icingaweb2
     environment.etc = let
-      doModule = name: optionalAttrs (cfg.modules."${name}".enable) (nameValuePair "icingaweb2/enabledModules/${name}" { source = "${pkgs.icingaweb2}/modules/${name}"; });
+      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)
diff --git a/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix b/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
index 167e5e389568..e9c1d4ffe5ea 100644
--- a/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
+++ b/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
@@ -58,7 +58,7 @@ in {
     };
 
     backends = mkOption {
-      default = { "icinga" = { resource = "icinga_ido"; }; };
+      default = { icinga = { resource = "icinga_ido"; }; };
       description = "Monitoring backends to define";
       type = attrsOf (submodule ({ name, ... }: {
         options = {
diff --git a/nixos/modules/services/web-apps/ihatemoney/default.nix b/nixos/modules/services/web-apps/ihatemoney/default.nix
new file mode 100644
index 000000000000..68769ac8c031
--- /dev/null
+++ b/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/nixos/modules/services/web-apps/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
new file mode 100644
index 000000000000..e00a47191c6f
--- /dev/null
+++ b/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/per-server-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/nixos/modules/services/web-apps/matomo-doc.xml b/nixos/modules/services/web-apps/matomo-doc.xml
index 021a89be3f63..69d1170e4523 100644
--- a/nixos/modules/services/web-apps/matomo-doc.xml
+++ b/nixos/modules/services/web-apps/matomo-doc.xml
@@ -21,18 +21,18 @@
    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 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>
+# 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
@@ -86,12 +86,6 @@
   <itemizedlist>
    <listitem>
     <para>
-     Matomo's file integrity check will warn you. This is due to the patches
-     necessary for NixOS, you can safely ignore this.
-    </para>
-   </listitem>
-   <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.
@@ -105,7 +99,7 @@
   <para>
    You can use other web servers by forwarding calls for
    <filename>index.php</filename> and <filename>piwik.php</filename> to the
-   <literal>/run/phpfpm-matomo.sock</literal> fastcgi unix socket. You can use
+   <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>
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
index 14aca45a342b..75da474dc446 100644
--- a/nixos/modules/services/web-apps/matomo.nix
+++ b/nixos/modules/services/web-apps/matomo.nix
@@ -2,15 +2,13 @@
 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;
-  # it's not possible to use /run/phpfpm/${pool}.sock because /run/phpfpm/ is root:root 0770,
-  # and therefore is not accessible by the web server.
-  phpSocket = "/run/phpfpm-${pool}.sock";
   phpExecutionUnit = "phpfpm-${pool}";
   databaseService = "mysql.service";
 
@@ -20,6 +18,14 @@ let
      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
@@ -50,7 +56,7 @@ in {
         default = null;
         example = "lighttpd";
         description = ''
-          Name of the web server user that forwards requests to the ${phpSocket} fastcgi socket for Matomo if the nginx
+          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.
@@ -71,25 +77,6 @@ in {
         '';
       };
 
-      phpfpmProcessManagerConfig = mkOption {
-        type = types.str;
-        default = ''
-          ; default phpfpm process manager 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
-
-          ; log worker's stdout, but this has a performance hit
-          catch_workers_output = yes
-        '';
-        description = ''
-          Settings for phpfpm's process manager. You might need to change this depending on the load for Matomo.
-        '';
-      };
-
       nginx = mkOption {
         type = types.nullOr (types.submodule (
           recursiveUpdate
@@ -105,8 +92,8 @@ in {
         default = null;
         example = {
           serverAliases = [
-            "matomo.$\{config.networking.domain\}"
-            "stats.$\{config.networking.domain\}"
+            "matomo.\${config.networking.domain}"
+            "stats.\${config.networking.domain}"
           ];
           enableACME = false;
         };
@@ -115,7 +102,7 @@ in {
             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>,
+            <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.
         '';
@@ -176,7 +163,7 @@ in {
             # 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}/config" "${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
@@ -225,22 +212,33 @@ in {
       serviceConfig.UMask = "0007";
     };
 
-    services.phpfpm.poolConfigs = let
+    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} = ''
-        listen = "${phpSocket}"
-        listen.owner = ${socketOwner}
-        listen.group = root
-        listen.mode = 0600
-        user = ${user}
-        env[PIWIK_USER_PATH] = ${dataDir}
-        ${cfg.phpfpmProcessManagerConfig}
-      '';
+      ${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;
+      };
     };
 
 
@@ -262,18 +260,18 @@ in {
         };
         # allow index.php for webinterface
         locations."= /index.php".extraConfig = ''
-          fastcgi_pass unix:${phpSocket};
+          fastcgi_pass unix:${fpm.socket};
         '';
         # allow matomo.php for tracking
         locations."= /matomo.php".extraConfig = ''
-          fastcgi_pass unix:${phpSocket};
+          fastcgi_pass unix:${fpm.socket};
         '';
         # allow piwik.php for tracking (deprecated name)
         locations."= /piwik.php".extraConfig = ''
-          fastcgi_pass unix:${phpSocket};
+          fastcgi_pass unix:${fpm.socket};
         '';
         # Any other attempt to access any php files is forbidden
-        locations."~* ^.+\.php$".extraConfig = ''
+        locations."~* ^.+\\.php$".extraConfig = ''
           return 403;
         '';
         # Disallow access to unneeded directories
@@ -282,7 +280,7 @@ in {
           return 403;
         '';
         # Disallow access to several helper files
-        locations."~* \.(?:bat|git|ini|sh|txt|tpl|xml|md)$".extraConfig = ''
+        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.
diff --git a/nixos/modules/services/web-apps/mattermost.nix b/nixos/modules/services/web-apps/mattermost.nix
index 8c7fc4056adc..41c52b9653bf 100644
--- a/nixos/modules/services/web-apps/mattermost.nix
+++ b/nixos/modules/services/web-apps/mattermost.nix
@@ -146,17 +146,17 @@ in
 
   config = mkMerge [
     (mkIf cfg.enable {
-      users.users = optionalAttrs (cfg.user == "mattermost") (singleton {
-        name = "mattermost";
-        group = cfg.group;
-        uid = config.ids.uids.mattermost;
-        home = cfg.statePath;
-      });
-
-      users.groups = optionalAttrs (cfg.group == "mattermost") (singleton {
-        name = "mattermost";
-        gid = config.ids.gids.mattermost;
-      });
+      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;
 
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
new file mode 100644
index 000000000000..8a109b39bb57
--- /dev/null
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -0,0 +1,462 @@
+{ 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 ${v} $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 = ''
+          List of paths whose content is copied to the 'skins'
+          subdirectory of the MediaWiki installation.
+        '';
+      };
+
+      extensions = mkOption {
+        default = {};
+        type = types.attrsOf types.path;
+        description = ''
+          List of paths whose content is copied to the 'extensions'
+          subdirectory of the MediaWiki installation.
+        '';
+      };
+
+      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/per-server-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/nixos/modules/services/web-apps/miniflux.nix b/nixos/modules/services/web-apps/miniflux.nix
index 1d60004e574d..304712d0efc3 100644
--- a/nixos/modules/services/web-apps/miniflux.nix
+++ b/nixos/modules/services/web-apps/miniflux.nix
@@ -85,7 +85,7 @@ in
         DynamicUser = true;
         RuntimeDirectory = "miniflux";
         RuntimeDirectoryMode = "0700";
-        EnvironmentFile = if isNull cfg.adminCredentialsFile
+        EnvironmentFile = if cfg.adminCredentialsFile == null
         then defaultCredentials
         else cfg.adminCredentialsFile;
       };
diff --git a/nixos/modules/services/web-apps/moinmoin.nix b/nixos/modules/services/web-apps/moinmoin.nix
new file mode 100644
index 000000000000..0fee64be0bb2
--- /dev/null
+++ b/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; [ b42 ];
+}
diff --git a/nixos/modules/services/web-apps/moodle.nix b/nixos/modules/services/web-apps/moodle.nix
new file mode 100644
index 000000000000..595d070d940a
--- /dev/null
+++ b/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/per-server-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/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index d0e45e1c12a5..d79f2bb735fa 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }@args:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.services.nextcloud;
+  fpm = config.services.phpfpm.pools.nextcloud;
 
   phpPackage = pkgs.php73;
   phpPackages = pkgs.php73Packages;
@@ -30,8 +31,12 @@ let
   occ = pkgs.writeScriptBin "nextcloud-occ" ''
     #! ${pkgs.stdenv.shell}
     cd ${pkgs.nextcloud}
-    exec /run/wrappers/bin/sudo -u nextcloud \
-      NEXTCLOUD_CONFIG_DIR="${cfg.home}/config" \
+    sudo=exec
+    if [[ "$USER" != nextcloud ]]; then
+      sudo='exec /run/wrappers/bin/sudo -u nextcloud --preserve-env=NEXTCLOUD_CONFIG_DIR'
+    fi
+    export NEXTCLOUD_CONFIG_DIR="${cfg.home}/config"
+    $sudo \
       ${phpPackage}/bin/php \
       -c ${pkgs.writeText "php.ini" phpOptionsStr}\
       occ $*
@@ -57,7 +62,7 @@ in {
     https = mkOption {
       type = types.bool;
       default = false;
-      description = "Enable if there is a TLS terminating proxy in front of nextcloud.";
+      description = "Use https for generated links.";
     };
 
     maxUploadSize = mkOption {
@@ -101,10 +106,10 @@ in {
     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";
+        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";
@@ -112,23 +117,31 @@ in {
         "opcache.revalidate_freq" = "1";
         "opcache.fast_shutdown" = "1";
         "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
-        "catch_workers_output" = "yes";
+        catch_workers_output = "yes";
       };
       description = ''
         Options for PHP's php.ini file for nextcloud.
       '';
     };
 
-    poolConfig = mkOption {
-      type = types.lines;
-      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
+    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.
       '';
@@ -216,6 +229,15 @@ in {
         '';
       };
 
+      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;
@@ -257,6 +279,23 @@ in {
         '';
       };
     };
+    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 [
@@ -269,9 +308,14 @@ in {
           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.
+      '';
     }
 
-    { systemd.timers."nextcloud-cron" = {
+    { systemd.timers.nextcloud-cron = {
         wantedBy = [ "timers.target" ];
         timerConfig.OnBootSec = "5m";
         timerConfig.OnUnitActiveSec = "15m";
@@ -279,9 +323,24 @@ in {
       };
 
       systemd.services = {
-        "nextcloud-setup" = let
+        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 ],
@@ -292,19 +351,28 @@ in {
               ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
               'log_type' => 'syslog',
               'log_level' => '${builtins.toString cfg.logLevel}',
-              ${optionalString (cfg.config.overwriteProtocol != null) "'overwriteprotocol' => '${cfg.config.overwriteProtocol}',"}
+              ${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
-            c = cfg.config;
-            adminpass = if c.adminpassFile != null
-              then ''"$(<"${toString c.adminpassFile}")"''
-              else ''"${toString c.adminpass}"'';
             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}"'';
@@ -336,6 +404,7 @@ in {
         in {
           wantedBy = [ "multi-user.target" ];
           before = [ "phpfpm-nextcloud.service" ];
+          path = [ occ ];
           script = ''
             chmod og+x ${cfg.home}
             ln -sf ${pkgs.nextcloud}/apps ${cfg.home}/
@@ -356,34 +425,35 @@ in {
           '';
           serviceConfig.Type = "oneshot";
         };
-        "nextcloud-cron" = {
+        nextcloud-cron = {
           environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
           serviceConfig.Type = "oneshot";
           serviceConfig.User = "nextcloud";
           serviceConfig.ExecStart = "${phpPackage}/bin/php -f ${pkgs.nextcloud}/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 = let
-          phpAdminValues = (toKeyValue
-            (foldr (a: b: a // b) {}
-              (mapAttrsToList (k: v: { "php_admin_value[${k}]" = v; })
-                phpOptions)));
-        in {
-          phpOptions = phpOptionsExtensions;
+        pools.nextcloud = {
+          user = "nextcloud";
+          group = "nginx";
+          phpOptions = phpOptionsExtensions + phpOptionsStr;
           phpPackage = phpPackage;
-          listen = "/run/phpfpm/nextcloud";
-          extraConfig = ''
-            listen.owner = nginx
-            listen.group = nginx
-            user = nextcloud
-            group = nginx
-            ${cfg.poolConfig}
-            env[NEXTCLOUD_CONFIG_DIR] = ${cfg.home}/config
-            env[PATH] = /run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin
-            ${phpAdminValues}
-          '';
+          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;
         };
       };
 
@@ -400,7 +470,7 @@ in {
       services.nginx = {
         enable = true;
         virtualHosts = {
-          "${cfg.hostName}" = {
+          ${cfg.hostName} = {
             root = pkgs.nextcloud;
             locations = {
               "= /robots.txt" = {
@@ -413,7 +483,7 @@ in {
               };
               "/" = {
                 priority = 200;
-                extraConfig = "rewrite ^ /index.php$request_uri;";
+                extraConfig = "rewrite ^ /index.php;";
               };
               "~ ^/store-apps" = {
                 priority = 201;
@@ -440,11 +510,12 @@ in {
                 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:/run/phpfpm/nextcloud;
+                  fastcgi_pass unix:${fpm.socket};
                   fastcgi_intercept_errors on;
                   fastcgi_request_buffering off;
                   fastcgi_read_timeout 120s;
@@ -477,6 +548,7 @@ in {
               add_header X-Download-Options noopen;
               add_header X-Permitted-Cross-Domain-Policies none;
               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};
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
index dfefa55c5d5e..d66e0f0c2997 100644
--- a/nixos/modules/services/web-apps/nextcloud.xml
+++ b/nixos/modules/services/web-apps/nextcloud.xml
@@ -42,10 +42,12 @@
 
   services.postgresql = {
     <link linkend="opt-services.postgresql.enable">enable</link> = true;
-    <link linkend="opt-services.postgresql.initialScript">initialScript</link> = pkgs.writeText "psql-init" ''
-      CREATE ROLE nextcloud WITH LOGIN;
-      CREATE DATABASE nextcloud WITH OWNER nextcloud;
-    '';
+    <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
@@ -63,17 +65,22 @@
    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 for the <literal>config.php</literal> which is used for the
-   application's configuration. <emphasis>Beware: this isn't entirely pure
-   since the config is modified by the application's runtime!</emphasis>
+   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 hosts (those are checked with
+   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>)
-   those can be added using
+   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>
@@ -87,29 +94,24 @@
   </para>
 
   <para>
-   Right now changes to the <literal>services.nextcloud.config</literal>
-   attribute set won't take effect after the first install (except
-   <literal><link linkend="opt-services.nextcloud.config.extraTrustedDomains">services.nextcloud.config.extraTrustedDomains</link></literal>)
-   since the actual configuration file is generated by the NextCloud installer
-   which also sets up critical parts such as the database structure.
+   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>
 
-  <para>
-   <emphasis>Warning: don't delete <literal>config.php</literal>! This file
+  <warning>
+   <para>Don't delete <literal>config.php</literal>! This file
    tracks the application's state and a deletion can cause unwanted
-   side-effects!</emphasis>
-  </para>
+   side-effects!</para>
+  </warning>
 
-  <para>
-   <emphasis>Warning: don't rerun <literal>nextcloud-occ
+  <warning>
+   <para>Don't rerun <literal>nextcloud-occ
    maintenance:install</literal>! This command tries to install the application
-   and can cause unwanted side-effects!</emphasis>
-  </para>
-
-  <para>
-   The issues are known and reported in
-   <link xlink:href="https://github.com/NixOS/nixpkgs/issues/49783">#49783</link>,
-   for now it's unfortunately necessary to manually work around these issues.
-  </para>
+   and can cause unwanted side-effects!</para>
+  </warning>
  </section>
 </chapter>
diff --git a/nixos/modules/services/web-apps/nexus.nix b/nixos/modules/services/web-apps/nexus.nix
index 052dbed6d4f8..d4d507362c97 100644
--- a/nixos/modules/services/web-apps/nexus.nix
+++ b/nixos/modules/services/web-apps/nexus.nix
@@ -68,6 +68,7 @@ in
           -Dkaraf.data=${cfg.home}/nexus3
           -Djava.io.tmpdir=${cfg.home}/nexus3/tmp
           -Dkaraf.startLocalConsole=false
+          -Djava.endorsed.dirs=${cfg.package}/lib/endorsed
         '';
 
         description = ''
@@ -80,14 +81,14 @@ in
   };
 
   config = mkIf cfg.enable {
-    users.users."${cfg.user}" = {
+    users.users.${cfg.user} = {
       isSystemUser = true;
       group = cfg.group;
       home = cfg.home;
       createHome = true;
     };
 
-    users.groups."${cfg.group}" = {};
+    users.groups.${cfg.group} = {};
 
     systemd.services.nexus = {
       description = "Sonatype Nexus3";
diff --git a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
index 93f69bd12651..ad70ba70bbef 100644
--- a/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
+++ b/nixos/modules/services/web-apps/pgpkeyserver-lite.nix
@@ -60,7 +60,7 @@ in
     services.nginx.virtualHosts = let
       hkpPort = builtins.toString cfg.hkpPort;
     in {
-      "${cfg.hostname}" = {
+      ${cfg.hostname} = {
         root = webPkg;
         locations = {
           "/pks".extraConfig = ''
diff --git a/nixos/modules/services/web-apps/restya-board.nix b/nixos/modules/services/web-apps/restya-board.nix
index 15fd943a0826..9d0a3f65253e 100644
--- a/nixos/modules/services/web-apps/restya-board.nix
+++ b/nixos/modules/services/web-apps/restya-board.nix
@@ -9,11 +9,11 @@ with lib;
 
 let
   cfg = config.services.restya-board;
+  fpm = config.services.phpfpm.pools.${poolName};
 
   runDir = "/run/restya-board";
 
   poolName = "restya-board";
-  phpfpmSocketName = "/run/phpfpm/${poolName}.sock";
 
 in
 
@@ -116,7 +116,7 @@ in
         };
 
         passwordFile = mkOption {
-          type = types.nullOr types.str;
+          type = types.nullOr types.path;
           default = null;
           description = ''
             The database user's password. 'null' if no password is set.
@@ -178,38 +178,37 @@ in
 
   config = mkIf cfg.enable {
 
-    services.phpfpm.poolConfigs = {
-      "${poolName}" = {
-        listen = phpfpmSocketName;
+    services.phpfpm.pools = {
+      ${poolName} = {
+        inherit (cfg) user group;
+
         phpOptions = ''
           date.timezone = "CET"
 
-          ${optionalString (!isNull cfg.email.server) ''
+          ${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}
           ''}
         '';
-        extraConfig = ''
-          listen.owner = nginx
-          listen.group = nginx
-          listen.mode = 0600
-          user = ${cfg.user}
-          group = ${cfg.group}
-          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
-        '';
+        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}" = {
+    services.nginx.virtualHosts.${cfg.virtualHost.serverName} = {
       listen = [ { addr = cfg.virtualHost.listenHost; port = cfg.virtualHost.listenPort; } ];
       serverName = cfg.virtualHost.serverName;
       root = runDir;
@@ -217,7 +216,6 @@ in
         index index.html index.php;
 
         gzip on;
-        gzip_disable "msie6";
 
         gzip_comp_level 6;
         gzip_min_length  1100;
@@ -237,18 +235,18 @@ in
 
       locations."/".root = "${runDir}/client";
 
-      locations."~ \.php$" = {
+      locations."~ \\.php$" = {
         tryFiles = "$uri =404";
         extraConfig = ''
           include ${pkgs.nginx}/conf/fastcgi_params;
-          fastcgi_pass    unix:${phpfpmSocketName};
+          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)" = {
+      locations."~* \\.(css|js|less|html|ttf|woff|jpg|jpeg|gif|png|bmp|ico)" = {
         root = "${runDir}/client";
         extraConfig = ''
           if (-f $request_filename) {
@@ -282,12 +280,12 @@ in
 
         sed -i "s@^php@${config.services.phpfpm.phpPackage}/bin/php@" "${runDir}/server/php/shell/"*.sh
 
-        ${if (isNull cfg.database.host) then ''
+        ${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', '$(<${cfg.database.dbPassFile})');/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"
@@ -311,7 +309,7 @@ in
         chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/media"
         chown -R "${cfg.user}"."${cfg.group}" "${cfg.dataDir}/client/img"
 
-        ${optionalString (isNull cfg.database.host) ''
+        ${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} \
@@ -367,14 +365,14 @@ in
     };
     users.groups.restya-board = {};
 
-    services.postgresql.enable = mkIf (isNull cfg.database.host) true;
+    services.postgresql.enable = mkIf (cfg.database.host == null) true;
 
-    services.postgresql.identMap = optionalString (isNull cfg.database.host)
+    services.postgresql.identMap = optionalString (cfg.database.host == null)
       ''
         restya-board-users restya-board restya_board
       '';
 
-    services.postgresql.authentication = optionalString (isNull cfg.database.host)
+    services.postgresql.authentication = optionalString (cfg.database.host == null)
       ''
         local restya_board all ident map=restya-board-users
       '';
diff --git a/nixos/modules/services/web-apps/selfoss.nix b/nixos/modules/services/web-apps/selfoss.nix
index cd0f743a5fb8..d5a660ebf289 100644
--- a/nixos/modules/services/web-apps/selfoss.nix
+++ b/nixos/modules/services/web-apps/selfoss.nix
@@ -4,7 +4,6 @@ let
   cfg = config.services.selfoss;
 
   poolName = "selfoss_pool";
-  phpfpmSocketName = "/run/phpfpm/${poolName}.sock";
 
   dataDir = "/var/lib/selfoss";
 
@@ -115,22 +114,22 @@ in
   };
 
   config = mkIf cfg.enable {
-
-    services.phpfpm.poolConfigs = mkIf (cfg.pool == "${poolName}") {
-      "${poolName}" = ''
-        listen = "${phpfpmSocketName}";
-        listen.owner = nginx
-        listen.group = nginx
-        listen.mode = 0600
-        user = nginx
-        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.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 = {
diff --git a/nixos/modules/services/web-apps/shiori.nix b/nixos/modules/services/web-apps/shiori.nix
new file mode 100644
index 000000000000..1817a2039352
--- /dev/null
+++ b/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/nixos/modules/services/web-apps/trac.nix b/nixos/modules/services/web-apps/trac.nix
new file mode 100644
index 000000000000..207fb857438a
--- /dev/null
+++ b/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/nixos/modules/services/web-apps/trilium.nix b/nixos/modules/services/web-apps/trilium.nix
new file mode 100644
index 000000000000..6f47193c62b9
--- /dev/null
+++ b/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/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 08297c7275a4..b92e34498949 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -15,7 +15,9 @@ let
     else cfg.database.port;
 
   poolName = "tt-rss";
-  phpfpmSocketName = "/run/phpfpm/${poolName}.sock";
+
+  mysqlLocal = cfg.database.createLocally && cfg.database.type == "mysql";
+  pgsqlLocal = cfg.database.createLocally && cfg.database.type == "pgsql";
 
   tt-rss-config = pkgs.writeText "config.php" ''
     <?php
@@ -201,6 +203,12 @@ let
             and 3306 for pgsql and mysql respectively).
           '';
         };
+
+        createLocally = mkOption {
+          type = types.bool;
+          default = true;
+          description = "Create the database and database user locally.";
+        };
       };
 
       auth = {
@@ -512,21 +520,20 @@ let
     ];
 
     services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
-      "${poolName}" = {
-        listen = "/var/run/phpfpm/${poolName}.sock";
-        extraConfig = ''
-          listen.owner = nginx
-          listen.group = nginx
-          listen.mode = 0600
-          user = ${cfg.user}
-          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
-        '';
+      ${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;
+        };
       };
     };
 
@@ -534,17 +541,17 @@ let
     services.nginx = mkIf (cfg.virtualHost != null) {
       enable = true;
       virtualHosts = {
-        "${cfg.virtualHost}" = {
+        ${cfg.virtualHost} = {
           root = "${cfg.root}";
 
           locations."/" = {
             index = "index.php";
           };
 
-          locations."~ \.php$" = {
+          locations."~ \\.php$" = {
             extraConfig = ''
               fastcgi_split_path_info ^(.+\.php)(/.+)$;
-              fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.listen};
+              fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.socket};
               fastcgi_index index.php;
             '';
           };
@@ -552,9 +559,13 @@ let
       };
     };
 
-    systemd.services.tt-rss = let
-      dbService = if cfg.database.type == "pgsql" then "postgresql.service" else "mysql.service";
-    in {
+    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";
 
@@ -563,14 +574,14 @@ let
               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})"} \
-                  ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${config.services.postgresql.package}/bin/psql \
+                  ${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}' | ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${config.services.mysql.package}/bin/mysql \
+                  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}"} \
@@ -580,7 +591,6 @@ let
 
         in ''
           rm -rf "${cfg.root}/*"
-          mkdir -m 755 -p "${cfg.root}"
           cp -r "${pkgs.tt-rss}/"* "${cfg.root}"
           ${optionalString (cfg.pluginPackages != []) ''
             for plugin in ${concatStringsSep " " cfg.pluginPackages}; do
@@ -593,19 +603,10 @@ let
             done
           ''}
           ln -sf "${tt-rss-config}" "${cfg.root}/config.php"
-          chown -R "${cfg.user}" "${cfg.root}"
           chmod -R 755 "${cfg.root}"
         ''
 
         + (optionalString (cfg.database.type == "pgsql") ''
-          ${optionalString (cfg.database.host == null && cfg.database.password == null) ''
-            if ! [ -e ${cfg.root}/.db-created ]; then
-              ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createuser ${cfg.database.user}
-              ${pkgs.sudo}/bin/sudo -u ${config.services.postgresql.superUser} ${config.services.postgresql.package}/bin/createdb -O ${cfg.database.user} ${cfg.database.name}
-              touch ${cfg.root}/.db-created
-            fi
-          ''}
-
           exists=$(${callSql "select count(*) > 0 from pg_tables where tableowner = user"} \
           | tail -n+3 | head -n-2 | sed -e 's/[ \n\t]*//')
 
@@ -629,18 +630,18 @@ let
 
         serviceConfig = {
           User = "${cfg.user}";
+          Group = "tt_rss";
           ExecStart = "${pkgs.php}/bin/php ${cfg.root}/update.php --daemon";
           StandardOutput = "syslog";
           StandardError = "syslog";
-          PermissionsStartOnly = true;
         };
 
         wantedBy = [ "multi-user.target" ];
-        requires = ["${dbService}"];
-        after = ["network.target" "${dbService}"];
+        requires = optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
+        after = [ "network.target" ] ++ optional mysqlLocal "mysql.service" ++ optional pgsqlLocal "postgresql.service";
     };
 
-    services.mysql = optionalAttrs (cfg.database.type == "mysql") {
+    services.mysql = mkIf mysqlLocal {
       enable = true;
       package = mkDefault pkgs.mysql;
       ensureDatabases = [ cfg.database.name ];
@@ -654,17 +655,22 @@ let
       ];
     };
 
-    services.postgresql = optionalAttrs (cfg.database.type == "pgsql") {
+    services.postgresql = mkIf pgsqlLocal {
       enable = mkDefault true;
+      ensureDatabases = [ cfg.database.name ];
+      ensureUsers = [
+        { name = cfg.user;
+          ensurePermissions = { "DATABASE ${cfg.database.name}" = "ALL PRIVILEGES"; };
+        }
+      ];
     };
 
-    users = optionalAttrs (cfg.user == "tt_rss") {
-      users.tt_rss = {
-        description = "tt-rss service user";
-        isSystemUser = true;
-        group = "tt_rss";
-      };
-      groups.tt_rss = {};
+    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/nixos/modules/services/web-apps/virtlyst.nix b/nixos/modules/services/web-apps/virtlyst.nix
index e5c0bff2168a..37bdbb0e3b42 100644
--- a/nixos/modules/services/web-apps/virtlyst.nix
+++ b/nixos/modules/services/web-apps/virtlyst.nix
@@ -54,6 +54,7 @@ in
       home = stateDir;
       createHome = true;
       group = mkIf config.virtualisation.libvirtd.enable "libvirtd";
+      isSystemUser = true;
     };
 
     systemd.services.virtlyst = {
diff --git a/nixos/modules/services/web-apps/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
new file mode 100644
index 000000000000..ad4f39fbf52c
--- /dev/null
+++ b/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/per-server-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/nixos/modules/services/web-apps/youtrack.nix b/nixos/modules/services/web-apps/youtrack.nix
index 691cbdc8d1d5..830edac20bac 100644
--- a/nixos/modules/services/web-apps/youtrack.nix
+++ b/nixos/modules/services/web-apps/youtrack.nix
@@ -28,28 +28,28 @@ in
         The interface youtrack will listen on.
       '';
       default = "127.0.0.1";
-      type = types.string;
+      type = types.str;
     };
 
     baseUrl = mkOption {
       description = ''
         Base URL for youtrack. Will be auto-detected and stored in database.
       '';
-      type = types.nullOr types.string;
+      type = types.nullOr types.str;
       default = null;
     };
 
     extraParams = mkOption {
       default = {};
       description = ''
-        Extra parameters to pass to youtrack. See 
+        Extra parameters to pass to youtrack. See
         https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Java-Start-Parameters.html
         for more information.
       '';
       example = {
         "jetbrains.youtrack.overrideRootPassword" = "tortuga";
       };
-      type = types.attrsOf types.string;
+      type = types.attrsOf types.str;
     };
 
     package = mkOption {
@@ -73,7 +73,7 @@ in
       description = ''
         Where to keep the youtrack database.
       '';
-      type = types.string;
+      type = types.path;
       default = "/var/lib/youtrack";
     };
 
@@ -83,7 +83,7 @@ in
         If null, do not setup anything.
       '';
       default = null;
-      type = types.nullOr types.string;
+      type = types.nullOr types.str;
     };
 
     jvmOpts = mkOption {
@@ -92,7 +92,7 @@ in
         See https://www.jetbrains.com/help/youtrack/standalone/Configure-JVM-Options.html
         for more information.
       '';
-      type = types.string;
+      type = types.separatedString " ";
       example = "-XX:MetaspaceSize=250m";
       default = "";
     };
@@ -101,7 +101,7 @@ in
       description = ''
         Maximum Java heap size
       '';
-      type = types.string;
+      type = types.str;
       default = "1g";
     };
 
@@ -109,7 +109,7 @@ in
       description = ''
         Maximum java Metaspace memory.
       '';
-      type = types.string;
+      type = types.str;
       default = "350m";
     };
   };
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
new file mode 100644
index 000000000000..ee8447810c6d
--- /dev/null
+++ b/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/per-server-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/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 8f00f81b078c..fd17e4b54f0f 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -6,148 +6,54 @@ let
 
   mainCfg = config.services.httpd;
 
-  httpd = mainCfg.package.out;
+  runtimeDir = "/run/httpd";
 
-  version24 = !versionOlder httpd.version "2.4";
+  httpd = mainCfg.package.out;
 
   httpdConf = mainCfg.configFile;
 
   php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ };
 
-  phpMajorVersion = head (splitString "." php.version);
+  phpMajorVersion = lib.versions.major (lib.getVersion php);
 
   mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; };
 
-  defaultListen = cfg: if cfg.enableSSL
-    then [{ip = "*"; port = 443;}]
-    else [{ip = "*"; port = 80;}];
-
-  getListen = cfg:
-    let list = (lib.optional (cfg.port != 0) {ip = "*"; port = cfg.port;}) ++ cfg.listen;
-    in if list == []
-        then defaultListen cfg
-        else list;
-
-  listenToString = l: "${l.ip}:${toString l.port}";
-
-  extraModules = attrByPath ["extraModules"] [] mainCfg;
-  extraForeignModules = filter isAttrs extraModules;
-  extraApacheModules = filter isString extraModules;
-
-
-  makeServerInfo = cfg: {
-    # Canonical name must not include a trailing slash.
-    canonicalNames =
-      let defaultPort = (head (defaultListen cfg)).port; in
-      map (port:
-        (if cfg.enableSSL then "https" else "http") + "://" +
-        cfg.hostName +
-        (if port != defaultPort then ":${toString port}" else "")
-        ) (map (x: x.port) (getListen cfg));
-
-    # Admin address: inherit from the main server if not specified for
-    # a virtual host.
-    adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr;
-
-    vhostConfig = cfg;
-    serverConfig = mainCfg;
-    fullConfig = config; # machine config
-  };
-
-
-  allHosts = [mainCfg] ++ mainCfg.virtualHosts;
-
-
-  callSubservices = serverInfo: defs:
-    let f = svc:
-      let
-        svcFunction =
-          if svc ? function then svc.function
-          # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix;
-          else if svc ? serviceExpression then import (toString svc.serviceExpression)
-          else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix");
-        config = (evalModules
-          { modules = [ { options = res.options; config = svc.config or svc; } ];
-            check = false;
-          }).config;
-        defaults = {
-          extraConfig = "";
-          extraModules = [];
-          extraModulesPre = [];
-          extraPath = [];
-          extraServerPath = [];
-          globalEnvVars = [];
-          robotsEntries = "";
-          startupScript = "";
-          enablePHP = false;
-          enablePerl = false;
-          phpOptions = "";
-          options = {};
-          documentRoot = null;
-        };
-        res = defaults // svcFunction { inherit config lib pkgs serverInfo php; };
-      in res;
-    in map f defs;
-
+  vhosts = attrValues mainCfg.virtualHosts;
 
-  # !!! callSubservices is expensive
-  subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices;
+  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; }
+    );
 
-  mainSubservices = subservicesFor mainCfg;
+  listenInfo = unique (concatMap mkListenInfo vhosts);
 
-  allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts;
+  enableSSL = any (listen: listen.ssl) listenInfo;
 
+  enableUserDir = any (vhost: vhost.enableUserDir) vhosts;
 
-  enableSSL = any (vhost: vhost.enableSSL) allHosts;
-
-
-  # Names of modules from ${httpd}/modules that we want to load.
-  apacheModules =
-    [ # 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"
-      (if version24 then "authn_core" else "authn_alias")
-
-      # Authorization: is the user allowed access?
-      "authz_user" "authz_groupfile" "authz_host"
-
-      # Other modules.
-      "ext_filter" "include" "log_config" "env" "mime_magic"
-      "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif"
-      "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs"
-      "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling"
-      "userdir" "alias" "rewrite" "proxy" "proxy_http"
-    ]
-    ++ optionals version24 [
+  # 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_${mainCfg.multiProcessingModule}"
-      "authz_core"
-      "unixd"
-      "cache" "cache_disk"
-      "slotmem_shm"
-      "socache_shmcb"
-      # For compatibility with old configurations, the new module mod_access_compat is provided.
-      "access_compat"
     ]
     ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ])
     ++ optional enableSSL "ssl"
-    ++ extraApacheModules;
+    ++ optional enableUserDir "userdir"
+    ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
+    ++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
+    ++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
+    ++ mainCfg.extraModules;
 
 
-  allDenied = if version24 then ''
-    Require all denied
-  '' else ''
-    Order deny,allow
-    Deny from all
-  '';
-
-  allGranted = if version24 then ''
-    Require all granted
-  '' else ''
-    Order allow,deny
-    Allow from all
-  '';
+  allDenied = "Require all denied";
+  allGranted = "Require all granted";
 
 
   loggingConf = (if mainCfg.logFormat != "none" then ''
@@ -167,22 +73,24 @@ let
 
 
   browserHacks = ''
-    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 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 = ''
-    SSLSessionCache ${if version24 then "shmcb" else "shm"}:${mainCfg.stateDir}/ssl_scache(512000)
+    SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000)
 
-    ${if version24 then "Mutex" else "SSLMutex"} posixsem
+    Mutex posixsem
 
     SSLRandomSeed startup builtin
     SSLRandomSeed connect builtin
@@ -205,132 +113,144 @@ let
     </IfModule>
   '';
 
-
-  perServerConf = isMainServer: cfg: let
-
-    serverInfo = makeServerInfo cfg;
-
-    subservices = callSubservices serverInfo cfg.extraSubservices;
-
-    maybeDocumentRoot = fold (svc: acc:
-      if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc
-    ) null ([ cfg ] ++ subservices);
-
-    documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else
-      pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out";
-
-    documentRootConf = ''
-      DocumentRoot "${documentRoot}"
-
-      <Directory "${documentRoot}">
-          Options Indexes FollowSymLinks
-          AllowOverride None
-          ${allGranted}
-      </Directory>
-    '';
-
-    robotsTxt =
-      concatStringsSep "\n" (filter (x: x != "") (
-        # If this is a vhost, the include the entries for the main server as well.
-        (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices)
-        ++ [cfg.robotsEntries]
-        ++ (map (svc: svc.robotsEntries) subservices)));
-
-  in ''
-    ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)}
-
-    ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases}
-
-    ${if cfg.sslServerCert != null then ''
-      SSLCertificateFile ${cfg.sslServerCert}
-      SSLCertificateKeyFile ${cfg.sslServerKey}
-      ${if cfg.sslServerChain != null then ''
-        SSLCertificateChainFile ${cfg.sslServerChain}
-      '' else ""}
-    '' else ""}
-
-    ${if cfg.enableSSL then ''
-      SSLEngine on
-    '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */
-    ''
-      SSLEngine off
-    '' else ""}
-
-    ${if isMainServer || cfg.adminAddr != null then ''
-      ServerAdmin ${cfg.adminAddr}
-    '' else ""}
-
-    ${if !isMainServer && mainCfg.logPerVirtualHost then ''
-      ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log
-      CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat}
-    '' else ""}
-
-    ${optionalString (robotsTxt != "") ''
-      Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt}
-    ''}
-
-    ${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""}
-
-    ${if cfg.enableUserDir then ''
-
-      UserDir public_html
-      UserDir disabled root
-
-      <Directory "/home/*/public_html">
-          AllowOverride FileInfo AuthConfig Limit Indexes
-          Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec
-          <Limit GET POST OPTIONS>
-              ${allGranted}
-          </Limit>
-          <LimitExcept GET POST OPTIONS>
-              ${allDenied}
-          </LimitExcept>
-      </Directory>
-
-    '' else ""}
-
-    ${if cfg.globalRedirect != null && cfg.globalRedirect != "" then ''
-      RedirectPermanent / ${cfg.globalRedirect}
-    '' else ""}
-
-    ${
-      let makeFileConf = elem: ''
-            Alias ${elem.urlPath} ${elem.file}
-          '';
-      in concatMapStrings makeFileConf cfg.servedFiles
-    }
-
-    ${
-      let makeDirConf = elem: ''
-            Alias ${elem.urlPath} ${elem.dir}/
-            <Directory ${elem.dir}>
-                Options +Indexes
-                ${allGranted}
-                AllowOverride All
-            </Directory>
-          '';
-      in concatMapStrings makeDirConf cfg.servedDirs
-    }
-
-    ${concatMapStrings (svc: svc.extraConfig) subservices}
-
-    ${cfg.extraConfig}
-  '';
+  mkVHostConf = hostOpts:
+    let
+      adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else mainCfg.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}"}
+            ${acmeChallenge}
+            ${mkVHostCommonConf hostOpts}
+        </VirtualHost>
+      ''
+  ;
+
+  mkVHostCommonConf = hostOpts:
+    let
+      documentRoot = if hostOpts.documentRoot != null
+        then hostOpts.documentRoot
+        else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out"
+      ;
+    in
+      ''
+        ${optionalString mainCfg.logPerVirtualHost ''
+          ErrorLog ${mainCfg.logDir}/error-${hostOpts.hostName}.log
+          CustomLog ${mainCfg.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
+            ${allGranted}
+        </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 makeFileConf = elem: ''
+                Alias ${elem.urlPath} ${elem.file}
+              '';
+          in concatMapStrings makeFileConf hostOpts.servedFiles
+        }
+        ${
+          let makeDirConf = elem: ''
+                Alias ${elem.urlPath} ${elem.dir}/
+                <Directory ${elem.dir}>
+                    Options +Indexes
+                    ${allGranted}
+                    AllowOverride All
+                </Directory>
+              '';
+          in concatMapStrings makeDirConf hostOpts.servedDirs
+        }
+
+        ${hostOpts.extraConfig}
+      ''
+  ;
 
 
   confFile = pkgs.writeText "httpd.conf" ''
 
     ServerRoot ${httpd}
+    ServerName ${config.networking.hostName}
+    DefaultRuntimeDir ${runtimeDir}/runtime
 
-    ${optionalString version24 ''
-      DefaultRuntimeDir ${mainCfg.stateDir}/runtime
-    ''}
-
-    PidFile ${mainCfg.stateDir}/httpd.pid
+    PidFile ${runtimeDir}/httpd.pid
 
     ${optionalString (mainCfg.multiProcessingModule != "prefork") ''
       # mod_cgid requires this.
-      ScriptSock ${mainCfg.stateDir}/cgisock
+      ScriptSock ${runtimeDir}/cgisock
     ''}
 
     <IfModule prefork.c>
@@ -339,26 +259,21 @@ let
     </IfModule>
 
     ${let
-        listen = concatMap getListen allHosts;
-        toStr = listen: "Listen ${listenToString listen}\n";
-        uniqueListen = uniqList {inputList = map toStr listen;};
-      in concatStrings uniqueListen
+        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 ${mainCfg.user}
     Group ${mainCfg.group}
 
     ${let
-        load = {name, path}: "LoadModule ${name}_module ${path}\n";
-        allModules =
-          concatMap (svc: svc.extraModulesPre) allSubservices
-          ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules
-          ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; }
-          ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; }
-          ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; }
-          ++ concatMap (svc: svc.extraModules) allSubservices
-          ++ extraForeignModules;
-      in concatMapStrings load allModules
+        mkModule = module:
+          if isString module then { name = module; path = "${httpd}/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
@@ -394,38 +309,15 @@ let
         ${allGranted}
     </Directory>
 
-    # Generate directives for the main server.
-    ${perServerConf true mainCfg}
+    ${mainCfg.extraConfig}
 
-    # Always enable virtual hosts; it doesn't seem to hurt.
-    ${let
-        listen = concatMap getListen allHosts;
-        uniqueListen = uniqList {inputList = listen;};
-        directives = concatMapStrings (listen: "NameVirtualHost ${listenToString listen}\n") uniqueListen;
-      in optionalString (!version24) directives
-    }
-
-    ${let
-        makeVirtualHost = vhost: ''
-          <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}>
-              ${perServerConf false vhost}
-          </VirtualHost>
-        '';
-      in concatMapStrings makeVirtualHost mainCfg.virtualHosts
-    }
+    ${concatMapStringsSep "\n" mkVHostConf vhosts}
   '';
 
-
-  enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices;
-
-  enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices;
-
-
   # Generate the PHP configuration file.  Should probably be factored
   # out into a separate module.
   phpIni = pkgs.runCommand "php.ini"
-    { options = concatStringsSep "\n"
-        ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices));
+    { options = mainCfg.phpOptions;
       preferLocalBuild = true;
     }
     ''
@@ -438,6 +330,26 @@ 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.")
+
+    # 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 = {
@@ -474,7 +386,7 @@ in
         type = types.lines;
         default = "";
         description = ''
-          Cnfiguration lines appended to the generated Apache
+          Configuration lines appended to the generated Apache
           configuration file. Note that this mechanism may not work
           when <option>configFile</option> is overridden.
         '';
@@ -483,7 +395,12 @@ in
       extraModules = mkOption {
         type = types.listOf types.unspecified;
         default = [];
-        example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]'';
+        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
@@ -493,9 +410,25 @@ in
         '';
       };
 
+      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 = false;
+        default = true;
         description = ''
           If enabled, each virtual host gets its own
           <filename>access.log</filename> and
@@ -530,37 +463,29 @@ in
         '';
       };
 
-      stateDir = mkOption {
-        type = types.path;
-        default = "/run/httpd";
-        description = ''
-          Directory for Apache's transient runtime state (such as PID
-          files).  It is created automatically.  Note that the default,
-          <filename>/run/httpd</filename>, is deleted at boot time.
-        '';
-      };
-
       virtualHosts = mkOption {
-        type = types.listOf (types.submodule (
-          { options = import ./per-server-options.nix {
-              inherit lib;
-              forMainServer = false;
+        type = with types; attrsOf (submodule (import ./per-server-options.nix));
+        default = {
+          localhost = {
+            documentRoot = "${httpd}/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";
             };
-          }));
-        default = [];
-        example = [
-          { hostName = "foo";
-            documentRoot = "/data/webroot-foo";
-          }
-          { hostName = "bar";
-            documentRoot = "/data/webroot-bar";
           }
-        ];
+        '';
         description = ''
-          Specification of the virtual hosts served by Apache.  Each
+          Specification of the virtual hosts served by Apache. Each
           element should be an attribute set specifying the
-          configuration of the virtual host.  The available options
-          are the non-global options permissible for the main host.
+          configuration of the virtual host.
         '';
       };
 
@@ -642,17 +567,11 @@ in
 
       sslProtocols = mkOption {
         type = types.str;
-        default = "All -SSLv2 -SSLv3 -TLSv1";
+        default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1";
         example = "All -SSLv2 -SSLv3";
         description = "Allowed SSL/TLS protocol versions.";
       };
-    }
-
-    # Include the options shared between the main server and virtual hosts.
-    // (import ./per-server-options.nix {
-      inherit lib;
-      forMainServer = true;
-    });
+    };
 
   };
 
@@ -661,27 +580,57 @@ in
 
   config = mkIf config.services.httpd.enable {
 
-    assertions = [ { assertion = mainCfg.enableSSL == true
-                               -> mainCfg.sslServerCert != null
-                                    && mainCfg.sslServerKey != null;
-                     message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; }
-                 ];
-
-    warnings = map (cfg: ''apache-httpd's port option is deprecated. Use listen = [{/*ip = "*"; */ port = ${toString cfg.port};}]; instead'' ) (lib.filter (cfg: cfg.port != 0) allHosts);
+    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.
+        '';
+      }
+    ];
 
-    users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton
-      { name = "wwwrun";
+    users.users = optionalAttrs (mainCfg.user == "wwwrun") {
+      wwwrun = {
         group = mainCfg.group;
         description = "Apache httpd user";
         uid = config.ids.uids.wwwrun;
-      });
+      };
+    };
+
+    users.groups = optionalAttrs (mainCfg.group == "wwwrun") {
+      wwwrun.gid = config.ids.gids.wwwrun;
+    };
 
-    users.groups = optionalAttrs (mainCfg.group == "wwwrun") (singleton
-      { name = "wwwrun";
-        gid = config.ids.gids.wwwrun;
-      });
+    security.acme.certs = mapAttrs (name: hostOpts: {
+      user = mainCfg.user;
+      group = mkDefault mainCfg.group;
+      email = if hostOpts.adminAddr != null then hostOpts.adminAddr else mainCfg.adminAddr;
+      webroot = hostOpts.acmeRoot;
+      extraDomains = genAttrs hostOpts.serverAliases (alias: null);
+      postRun = "systemctl reload httpd.service";
+    }) (filterAttrs (name: hostOpts: hostOpts.enableACME) mainCfg.virtualHosts);
 
-    environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices;
+    environment.systemPackages = [httpd];
+
+    # required for "apachectl configtest"
+    environment.etc."httpd/httpd.conf".source = httpdConf;
 
     services.httpd.phpOptions =
       ''
@@ -690,37 +639,54 @@ in
 
         ; Don't advertise PHP
         expose_php = off
-      '' + optionalString (!isNull config.time.timeZone) ''
+      '' + 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.services.httpd =
+      let
+        vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts;
+      in
       { description = "Apache HTTPD";
 
         wantedBy = [ "multi-user.target" ];
-        wants = [ "keys.target" ];
-        after = [ "network.target" "fs.target" "postgresql.service" "keys.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 =
           [ httpd pkgs.coreutils pkgs.gnugrep ]
-          ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function.
-          ++ concatMap (svc: svc.extraServerPath) allSubservices;
+          ++ optional mainCfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function.
 
         environment =
-          optionalAttrs enablePHP { PHPRC = phpIni; }
-          // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH  = "${pkgs.xmlsec}/lib"; }
-          // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices));
+          optionalAttrs mainCfg.enablePHP { PHPRC = phpIni; }
+          // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH  = "${pkgs.xmlsec}/lib"; };
 
         preStart =
           ''
-            mkdir -m 0750 -p ${mainCfg.stateDir}
-            [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir}
-            ${optionalString version24 ''
-              mkdir -m 0750 -p "${mainCfg.stateDir}/runtime"
-              [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime"
-            ''}
             mkdir -m 0700 -p ${mainCfg.logDir}
 
             # Get rid of old semaphores.  These tend to accumulate across
@@ -729,21 +695,18 @@ in
             for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do
                 ${pkgs.utillinux}/bin/ipcrm -s $i
             done
-
-            # Run the startup hooks for the subservices.
-            for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do
-                echo Running Apache startup hook $i...
-                $i
-            done
           '';
 
         serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}";
         serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop";
         serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful";
+        serviceConfig.Group = mainCfg.group;
         serviceConfig.Type = "forking";
-        serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid";
+        serviceConfig.PIDFile = "${runtimeDir}/httpd.pid";
         serviceConfig.Restart = "always";
         serviceConfig.RestartSec = "5s";
+        serviceConfig.RuntimeDirectory = "httpd httpd/runtime";
+        serviceConfig.RuntimeDirectoryMode = "0750";
       };
 
   };
diff --git a/nixos/modules/services/web-servers/apache-httpd/foswiki.nix b/nixos/modules/services/web-servers/apache-httpd/foswiki.nix
deleted file mode 100644
index 8c1ac8935a47..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/foswiki.nix
+++ /dev/null
@@ -1,78 +0,0 @@
-{ config, pkgs, lib, serverInfo, ... }:
-let
-  inherit (pkgs) foswiki;
-  inherit (serverInfo.serverConfig) user group;
-  inherit (config) vardir;
-in
-{
-  options.vardir = lib.mkOption {
-    type = lib.types.path;
-    default = "/var/www/foswiki";
-    description = "The directory where variable foswiki data will be stored and served from.";
-  };
-
-  # TODO: this will probably need to be better customizable
-  extraConfig =
-    let httpd-conf = pkgs.runCommand "foswiki-httpd.conf"
-      { preferLocalBuild = true; }
-      ''
-        substitute '${foswiki}/foswiki_httpd_conf.txt' "$out" \
-          --replace /var/www/foswiki/ "${vardir}/"
-      '';
-    in
-      ''
-        RewriteEngine on
-        RewriteRule /foswiki/(.*) ${vardir}/$1
-
-        <Directory "${vardir}">
-          Require all granted
-        </Directory>
-
-        Include ${httpd-conf}
-        <Directory "${vardir}/pub">
-          Options FollowSymlinks
-        </Directory>
-      '';
-
-  /** This handles initial setup and updates.
-      It will probably need some tweaking, maybe per-site.  */
-  startupScript = pkgs.writeScript "foswiki_startup.sh" (
-    let storeLink = "${vardir}/package"; in
-    ''
-      [ -e '${storeLink}' ] || needs_setup=1
-      mkdir -p '${vardir}'
-      cd '${vardir}'
-      ln -sf -T '${foswiki}' '${storeLink}'
-
-      if [ -n "$needs_setup" ]; then # do initial setup
-        mkdir -p bin lib
-        # setup most of data/ as copies only
-        cp -r '${foswiki}'/data '${vardir}/'
-        rm -r '${vardir}'/data/{System,mime.types}
-        ln -sr -t '${vardir}/data/' '${storeLink}'/data/{System,mime.types}
-
-        ln -sr '${storeLink}/locale' .
-
-        mkdir pub
-        ln -sr '${storeLink}/pub/System' pub/
-
-        mkdir templates
-        ln -sr '${storeLink}'/templates/* templates/
-
-        ln -sr '${storeLink}/tools' .
-
-        mkdir -p '${vardir}'/working/{logs,tmp}
-        ln -sr '${storeLink}/working/README' working/ # used to check dir validity
-
-        chown -R '${user}:${group}' .
-        chmod +w -R .
-      fi
-
-      # bin/* and lib/* shall always be overwritten, in case files are added
-      ln -srf '${storeLink}'/bin/* '${vardir}/bin/'
-      ln -srf '${storeLink}'/lib/* '${vardir}/lib/'
-    ''
-    /* Symlinking bin/ one-by-one ensures that ${vardir}/lib/LocalSite.cfg
-        is used instead of ${foswiki}/... */
-  );
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix b/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix
deleted file mode 100644
index 5c387700a5d5..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix
+++ /dev/null
@@ -1,196 +0,0 @@
-{ config, lib, pkgs, serverInfo, ... }:
-
-with lib;
-
-let
-
-  httpd = serverInfo.serverConfig.package;
-
-  version24 = !versionOlder httpd.version "2.4";
-
-  allGranted = if version24 then ''
-    Require all granted
-  '' else ''
-    Order allow,deny
-    Allow from all
-  '';
-
-  limesurveyConfig = pkgs.writeText "config.php" ''
-    <?php
-    $config = array();
-    $config['name']  = "${config.siteName}";
-    $config['runtimePath'] = "${config.dataDir}/tmp/runtime";
-    $config['components'] = array();
-    $config['components']['db'] = array();
-    $config['components']['db']['connectionString'] = '${config.dbType}:host=${config.dbHost};port=${toString config.dbPort};user=${config.dbUser};password=${config.dbPassword};dbname=${config.dbName};';
-    $config['components']['db']['username'] = '${config.dbUser}';
-    $config['components']['db']['password'] = '${config.dbPassword}';
-    $config['components']['db']['charset'] = 'utf-8';
-    $config['components']['db']['tablePrefix'] = "prefix_";
-    $config['components']['assetManager'] = array();
-    $config['components']['assetManager']['basePath'] = '${config.dataDir}/tmp/assets';
-    $config['config'] = array();
-    $config['config']['debug'] = 1;
-    $config['config']['tempdir']  = "${config.dataDir}/tmp";
-    $config['config']['tempdir']  = "${config.dataDir}/tmp";
-    $config['config']['uploaddir']  = "${config.dataDir}/upload";
-    $config['config']['force_ssl'] = '${if config.forceSSL then "on" else ""}';
-    $config['config']['defaultlang'] = '${config.defaultLang}';
-    return $config;
-    ?>
-  '';
-
-  limesurveyRoot = "${pkgs.limesurvey}/share/limesurvey/";
-
-in rec {
-
-  extraConfig = ''
-    Alias ${config.urlPrefix}/tmp ${config.dataDir}/tmp
-
-    <Directory ${config.dataDir}/tmp>
-      ${allGranted}
-      Options -Indexes +FollowSymlinks
-    </Directory>
-
-    Alias ${config.urlPrefix}/upload ${config.dataDir}/upload
-
-    <Directory ${config.dataDir}/upload>
-      ${allGranted}
-      Options -Indexes
-    </Directory>
-
-    ${if config.urlPrefix != "" then ''
-      Alias ${config.urlPrefix} ${limesurveyRoot}
-    '' else ''
-      RewriteEngine On
-      RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
-      RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
-    ''}
-
-    <Directory ${limesurveyRoot}>
-      DirectoryIndex index.php
-    </Directory>
-  '';
-
-  globalEnvVars = [
-    { name = "LIMESURVEY_CONFIG"; value = limesurveyConfig; }
-  ];
-
-  documentRoot = if config.urlPrefix == "" then limesurveyRoot else null;
-
-  enablePHP = true;
-
-  options = {
-
-    id = mkOption {
-      default = "main";
-      description = ''
-        A unique identifier necessary to keep multiple Limesurvey server
-        instances on the same machine apart.  This is used to
-        disambiguate the administrative scripts, which get names like
-        mediawiki-$id-change-password.
-      '';
-    };
-
-    urlPrefix = mkOption {
-      default = "";
-      description = "Url prefix for site.";
-      type = types.str;
-    };
-
-    dbType = mkOption {
-      default = "pgsql";
-      description = "Type of database for limesurvey, for now, only pgsql.";
-      type = types.enum ["pgsql"];
-    };
-
-    dbName = mkOption {
-      default = "limesurvey";
-      description = "Name of the database that holds the limesurvey data.";
-      type = types.str;
-    };
-
-    dbHost = mkOption {
-      default = "localhost";
-      description = "Limesurvey database host.";
-      type = types.str;
-    };
-
-    dbPort = mkOption {
-      default = 5432;
-      description = "Limesurvey database port.";
-      type = types.int;
-    };
-
-    dbUser = mkOption {
-      default = "limesurvey";
-      description = "Limesurvey database user.";
-      type = types.str;
-    };
-
-    dbPassword = mkOption {
-      example = "foobar";
-      description = "Limesurvey database password.";
-      type = types.str;
-    };
-
-    adminUser = mkOption {
-      description = "Limesurvey admin username.";
-      default = "admin";
-      type = types.str;
-    };
-
-    adminPassword = mkOption {
-      description = "Default limesurvey admin password.";
-      default = "admin";
-      type = types.str;
-    };
-
-    adminEmail = mkOption {
-      description = "Limesurvey admin email.";
-      default = "admin@admin.com";
-      type = types.str;
-    };
-
-    forceSSL = mkOption {
-      default = false;
-      description = "Force use of HTTPS connection.";
-      type = types.bool;
-    };
-
-    siteName = mkOption {
-      default = "LimeSurvey";
-      description = "LimeSurvey name of the site.";
-      type = types.str;
-    };
-
-    defaultLang = mkOption {
-      default = "en";
-      description = "LimeSurvey default language.";
-      type = types.str;
-    };
-
-    dataDir = mkOption {
-      default = "/var/lib/limesurvey";
-      description = "LimeSurvey data directory.";
-      type = types.path;
-    };
-  };
-
-  startupScript = pkgs.writeScript "limesurvey_startup.sh" ''
-    if [ ! -f ${config.dataDir}/.created ]; then
-      mkdir -p ${config.dataDir}/{tmp/runtime,tmp/assets,tmp/upload,upload}
-      chmod -R ug+rw ${config.dataDir}
-      chmod -R o-rwx ${config.dataDir}
-      chown -R wwwrun:wwwrun ${config.dataDir}
-
-      ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole "${config.dbUser}" || true
-      ${pkgs.postgresql}/bin/createdb "${config.dbName}" -O "${config.dbUser}" || true
-      ${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/psql -U postgres -d postgres -c "alter user ${config.dbUser} with password '${config.dbPassword}';" || true
-
-      ${pkgs.limesurvey}/bin/limesurvey-console install '${config.adminUser}' '${config.adminPassword}' '${config.adminUser}' '${config.adminEmail}'
-
-      touch ${config.dataDir}/.created
-    fi
-  '';
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix b/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
deleted file mode 100644
index 6234478014ce..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
+++ /dev/null
@@ -1,349 +0,0 @@
-{ config, lib, pkgs, serverInfo, php, ... }:
-
-with lib;
-
-let
-
-  httpd = serverInfo.serverConfig.package;
-
-  version24 = !versionOlder httpd.version "2.4";
-
-  allGranted = if version24 then ''
-    Require all granted
-  '' else ''
-    Order allow,deny
-    Allow from all
-  '';
-
-  mediawikiConfig = pkgs.writeText "LocalSettings.php"
-    ''
-      <?php
-        # Copied verbatim from the default (generated) LocalSettings.php.
-        if( defined( 'MW_INSTALL_PATH' ) ) {
-                $IP = MW_INSTALL_PATH;
-        } else {
-                $IP = dirname( __FILE__ );
-        }
-
-        $path = array( $IP, "$IP/includes", "$IP/languages" );
-        set_include_path( implode( PATH_SEPARATOR, $path ) . PATH_SEPARATOR . get_include_path() );
-
-        require_once( "$IP/includes/DefaultSettings.php" );
-
-        if ( $wgCommandLineMode ) {
-                if ( isset( $_SERVER ) && array_key_exists( 'REQUEST_METHOD', $_SERVER ) ) {
-                        die( "This script must be run from the command line\n" );
-                }
-        }
-
-        $wgScriptPath = "${config.urlPrefix}";
-
-        # We probably need to set $wgSecretKey and $wgCacheEpoch.
-
-        # Paths to external programs.
-        $wgDiff3 = "${pkgs.diffutils}/bin/diff3";
-        $wgDiff = "${pkgs.diffutils}/bin/diff";
-        $wgImageMagickConvertCommand = "${pkgs.imagemagick.out}/bin/convert";
-
-        #$wgDebugLogFile = "/tmp/mediawiki_debug_log.txt";
-
-        # Database configuration.
-        $wgDBtype = "${config.dbType}";
-        $wgDBserver = "${config.dbServer}";
-        $wgDBuser = "${config.dbUser}";
-        $wgDBpassword = "${config.dbPassword}";
-        $wgDBname = "${config.dbName}";
-
-        # E-mail.
-        $wgEmergencyContact = "${config.emergencyContact}";
-        $wgPasswordSender = "${config.passwordSender}";
-
-        $wgSitename = "${config.siteName}";
-
-        ${optionalString (config.logo != "") ''
-          $wgLogo = "${config.logo}";
-        ''}
-
-        ${optionalString (config.articleUrlPrefix != "") ''
-          $wgArticlePath = "${config.articleUrlPrefix}/$1";
-        ''}
-
-        ${optionalString config.enableUploads ''
-          $wgEnableUploads = true;
-          $wgUploadDirectory = "${config.uploadDir}";
-        ''}
-
-        ${optionalString (config.defaultSkin != "") ''
-          $wgDefaultSkin = "${config.defaultSkin}";
-        ''}
-
-        ${config.extraConfig}
-      ?>
-    '';
-
-  # Unpack Mediawiki and put the config file in its root directory.
-  mediawikiRoot = pkgs.stdenv.mkDerivation rec {
-    name= "mediawiki-1.31.1";
-
-    src = pkgs.fetchurl {
-      url = "https://releases.wikimedia.org/mediawiki/1.31/${name}.tar.gz";
-      sha256 = "13x48clij21cmysjkpnx68vggchrdasqp7b290j87xlfgjhdhnnf";
-    };
-
-    skins = config.skins;
-    extensions = config.extensions;
-
-    buildPhase =
-      ''
-        for skin in $skins; do
-          cp -prvd $skin/* skins/
-        done
-        for extension in $extensions; do
-          cp -prvd $extension/* extensions/
-        done
-      ''; # */
-
-    installPhase =
-      ''
-        mkdir -p $out
-        cp -r * $out
-        cp ${mediawikiConfig} $out/LocalSettings.php
-        sed -i \
-        -e 's|/bin/bash|${pkgs.bash}/bin/bash|g' \
-        -e 's|/usr/bin/timeout|${pkgs.coreutils}/bin/timeout|g' \
-          $out/includes/shell/limit.sh \
-          $out/includes/GlobalFunctions.php
-      '';
-  };
-
-  mediawikiScripts = pkgs.runCommand "mediawiki-${config.id}-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 ${php}/bin/php $out/bin/mediawiki-${config.id}-$(basename $i .php) \
-          --add-flags ${mediawikiRoot}/maintenance/$i
-      done
-    '';
-
-in
-
-{
-
-  extraConfig =
-    ''
-      ${optionalString config.enableUploads ''
-        Alias ${config.urlPrefix}/images ${config.uploadDir}
-
-        <Directory ${config.uploadDir}>
-            ${allGranted}
-            Options -Indexes
-        </Directory>
-      ''}
-
-      ${if config.urlPrefix != "" then "Alias ${config.urlPrefix} ${mediawikiRoot}" else ''
-        RewriteEngine On
-        RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
-        RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
-        ${concatMapStringsSep "\n" (u: "RewriteCond %{REQUEST_URI} !^${u.urlPath}") serverInfo.vhostConfig.servedDirs}
-        ${concatMapStringsSep "\n" (u: "RewriteCond %{REQUEST_URI} !^${u.urlPath}") serverInfo.vhostConfig.servedFiles}
-        RewriteRule ${if config.enableUploads
-          then "!^/images"
-          else "^.*\$"
-        } %{DOCUMENT_ROOT}/${if config.articleUrlPrefix == ""
-          then ""
-          else "${config.articleUrlPrefix}/"
-        }index.php [L]
-      ''}
-
-      <Directory ${mediawikiRoot}>
-          ${allGranted}
-          DirectoryIndex index.php
-      </Directory>
-
-      ${optionalString (config.articleUrlPrefix != "") ''
-        Alias ${config.articleUrlPrefix} ${mediawikiRoot}/index.php
-      ''}
-    '';
-
-  documentRoot = if config.urlPrefix == "" then mediawikiRoot else null;
-
-  enablePHP = true;
-
-  options = {
-
-    id = mkOption {
-      default = "main";
-      description = ''
-        A unique identifier necessary to keep multiple MediaWiki server
-        instances on the same machine apart.  This is used to
-        disambiguate the administrative scripts, which get names like
-        mediawiki-$id-change-password.
-      '';
-    };
-
-    dbType = mkOption {
-      default = "postgres";
-      example = "mysql";
-      description = "Database type.";
-    };
-
-    dbName = mkOption {
-      default = "mediawiki";
-      description = "Name of the database that holds the MediaWiki data.";
-    };
-
-    dbServer = mkOption {
-      default = ""; # use a Unix domain socket
-      example = "10.0.2.2";
-      description = ''
-        The location of the database server.  Leave empty to use a
-        database server running on the same machine through a Unix
-        domain socket.
-      '';
-    };
-
-    dbUser = mkOption {
-      default = "mediawiki";
-      description = "The user name for accessing the database.";
-    };
-
-    dbPassword = mkOption {
-      default = "";
-      example = "foobar";
-      description = ''
-        The password of the database user.  Warning: this is stored in
-        cleartext in the Nix store!
-      '';
-    };
-
-    emergencyContact = mkOption {
-      default = serverInfo.serverConfig.adminAddr;
-      example = "admin@example.com";
-      description = ''
-        Emergency contact e-mail address.  Defaults to the Apache
-        admin address.
-      '';
-    };
-
-    passwordSender = mkOption {
-      default = serverInfo.serverConfig.adminAddr;
-      example = "password@example.com";
-      description = ''
-        E-mail address from which password confirmations originate.
-        Defaults to the Apache admin address.
-      '';
-    };
-
-    siteName = mkOption {
-      default = "MediaWiki";
-      example = "Foobar Wiki";
-      description = "Name of the wiki";
-    };
-
-    logo = mkOption {
-      default = "";
-      example = "/images/logo.png";
-      description = "The URL of the site's logo (which should be a 135x135px image).";
-    };
-
-    urlPrefix = mkOption {
-      default = "/w";
-      description = ''
-        The URL prefix under which the Mediawiki service appears.
-      '';
-    };
-
-    articleUrlPrefix = mkOption {
-      default = "/wiki";
-      example = "";
-      description = ''
-        The URL prefix under which article pages appear,
-        e.g. http://server/wiki/Page.  Leave empty to use the main URL
-        prefix, e.g. http://server/w/index.php?title=Page.
-      '';
-    };
-
-    enableUploads = mkOption {
-      default = false;
-      description = "Whether to enable file uploads.";
-    };
-
-    uploadDir = mkOption {
-      default = throw "You must specify `uploadDir'.";
-      example = "/data/mediawiki-upload";
-      description = "The directory that stores uploaded files.";
-    };
-
-    defaultSkin = mkOption {
-      default = "";
-      example = "nostalgia";
-      description = "Set this value to change the default skin used by MediaWiki.";
-    };
-
-    skins = mkOption {
-      default = [];
-      type = types.listOf types.path;
-      description =
-        ''
-          List of paths whose content is copied to the ‘skins’
-          subdirectory of the MediaWiki installation.
-        '';
-    };
-
-    extensions = mkOption {
-      default = [];
-      type = types.listOf types.path;
-      description =
-        ''
-          List of paths whose content is copied to the 'extensions'
-          subdirectory of the MediaWiki installation.
-        '';
-    };
-
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      example =
-        ''
-          $wgEnableEmail = false;
-        '';
-      description = ''
-        Any additional text to be appended to MediaWiki's
-        configuration file.  This is a PHP script.  For configuration
-        settings, see <link xlink:href='https://www.mediawiki.org/wiki/Manual:Configuration_settings'/>.
-      '';
-    };
-
-  };
-
-  extraPath = [ mediawikiScripts ];
-
-  # !!! Need to specify that Apache has a dependency on PostgreSQL!
-
-  startupScript = pkgs.writeScript "mediawiki_startup.sh"
-    # Initialise the database automagically if we're using a Postgres
-    # server on localhost.
-    (optionalString (config.dbType == "postgres" && config.dbServer == "") ''
-      if ! ${pkgs.postgresql}/bin/psql -l | grep -q ' ${config.dbName} ' ; then
-          ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole "${config.dbUser}" || true
-          ${pkgs.postgresql}/bin/createdb "${config.dbName}" -O "${config.dbUser}"
-          ( echo 'CREATE LANGUAGE plpgsql;'
-            cat ${mediawikiRoot}/maintenance/postgres/tables.sql
-            echo 'CREATE TEXT SEARCH CONFIGURATION public.default ( COPY = pg_catalog.english );'
-            echo COMMIT
-          ) | ${pkgs.postgresql}/bin/psql -U "${config.dbUser}" "${config.dbName}"
-      fi
-      ${php}/bin/php ${mediawikiRoot}/maintenance/update.php
-    '');
-
-  robotsEntries = optionalString (config.articleUrlPrefix != "")
-    ''
-      User-agent: *
-      Disallow: ${config.urlPrefix}/
-      Disallow: ${config.articleUrlPrefix}/Special:Search
-      Disallow: ${config.articleUrlPrefix}/Special:Random
-    '';
-
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/mercurial.nix b/nixos/modules/services/web-servers/apache-httpd/mercurial.nix
deleted file mode 100644
index 4b8ee2b17ea7..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/mercurial.nix
+++ /dev/null
@@ -1,75 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-let
-  inherit (pkgs) mercurial;
-  inherit (lib) mkOption;
-
-  urlPrefix = config.urlPrefix;
-
-  cgi = pkgs.stdenv.mkDerivation {
-    name = "mercurial-cgi";
-    buildCommand = ''
-      mkdir -p $out
-      cp -v ${mercurial}/share/cgi-bin/hgweb.cgi $out
-      sed -i "s|/path/to/repo/or/config|$out/hgweb.config|" $out/hgweb.cgi
-      echo "
-      [collections]
-      ${config.dataDir} = ${config.dataDir}
-      [web]
-      style = gitweb
-      allow_push = *
-      " > $out/hgweb.config
-    '';
-  };
-
-in {
-
-  extraConfig = ''
-    RewriteEngine on
-    RewriteRule /(.*) ${cgi}/hgweb.cgi/$1
-
-    <Location "${urlPrefix}">
-        AuthType Basic
-        AuthName "Mercurial repositories"
-        AuthUserFile ${config.dataDir}/hgusers
-        <LimitExcept GET>
-            Require valid-user
-        </LimitExcept>
-    </Location>
-    <Directory "${cgi}">
-        Order allow,deny
-        Allow from all
-        AllowOverride All
-        Options ExecCGI
-        AddHandler cgi-script .cgi
-        PassEnv PYTHONPATH
-    </Directory>
-  '';
-
-  robotsEntries = ''
-    User-agent: *
-    Disallow: ${urlPrefix}
-  '';
-
-  extraServerPath = [ pkgs.python ];
-
-  globalEnvVars = [ { name = "PYTHONPATH"; value = "${mercurial}/lib/${pkgs.python.libPrefix}/site-packages"; } ];
-
-  options = {
-    urlPrefix = mkOption {
-      default = "/hg";
-      description = "
-        The URL prefix under which the Mercurial service appears.
-        Use the empty string to have it appear in the server root.
-      ";
-    };
-
-    dataDir = mkOption {
-      example = "/data/mercurial";
-      description = "
-        Path to the directory that holds the repositories.
-      ";
-    };
-  };
-
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
index 4bbd041b6e04..f2e92cda05f6 100644
--- a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix
@@ -1,188 +1,235 @@
-# This file defines the options that can be used both for the Apache
-# 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.)
-
-{ forMainServer, lib }:
-
-with lib;
-
+{ config, lib, name, ... }:
+let
+  inherit (lib) mkOption 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.";
+    };
+
+    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.
+      '';
+    };
+
+    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.
+      '';
+    };
 
-  hostName = mkOption {
-    type = types.str;
-    default = "localhost";
-    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.
-    '';
   };
-
-  port = mkOption {
-    type = types.int;
-    default = 0;
-    description = ''
-      Port for the server. Option will be removed, use <option>listen</option> instead.
-  '';
-  };
-
-  listen = mkOption {
-     type = types.listOf (types.submodule (
-          {
-            options = {
-              port = mkOption {
-                type = types.int;
-                description = "port to listen on";
-              };
-              ip = mkOption {
-                type = types.string;
-                default = "*";
-                description = "Ip to listen on. 0.0.0.0 for ipv4 only, * for all.";
-              };
-            };
-          } ));
-    description = ''
-      List of { /* ip: "*"; */ port = 80;} to listen on
-    '';
-
-    default = [];
-  };
-
-  enableSSL = mkOption {
-    type = types.bool;
-    default = false;
-    description = "Whether to enable SSL (https) support.";
-  };
-
-  # Note: sslServerCert and sslServerKey can be left empty, but this
-  # only makes sense for virtual hosts (they will inherit from the
-  # main server).
-
-  sslServerCert = mkOption {
-    type = types.nullOr types.path;
-    default = null;
-    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.";
-  };
-
-  adminAddr = mkOption ({
-    type = types.nullOr types.str;
-    example = "admin@example.org";
-    description = "E-mail address of the server administrator.";
-  } // (if forMainServer then {} else {default = null;}));
-
-  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.
-    '';
-  };
-
-  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.
-    '';
-  };
-
-  extraSubservices = mkOption {
-    type = types.listOf types.unspecified;
-    default = [];
-    description = "Extra subservices to enable in the webserver.";
-  };
-
-  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.
-    '';
-  };
-
 }
diff --git a/nixos/modules/services/web-servers/apache-httpd/phabricator.nix b/nixos/modules/services/web-servers/apache-httpd/phabricator.nix
deleted file mode 100644
index efd4a7b5f0fb..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/phabricator.nix
+++ /dev/null
@@ -1,50 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  phabricatorRoot = pkgs.phabricator;
-in {
-
-  enablePHP = true;
-  extraApacheModules = [ "mod_rewrite" ];
-  DocumentRoot = "${phabricatorRoot}/phabricator/webroot";
-
-  options = {
-      git = mkOption {
-          default = true;
-          description = "Enable git repositories.";
-      };
-      mercurial = mkOption {
-          default = true;
-          description = "Enable mercurial repositories.";
-      };
-      subversion = mkOption {
-          default = true;
-          description = "Enable subversion repositories.";
-      };
-  };
-
-  extraConfig = ''
-      DocumentRoot ${phabricatorRoot}/phabricator/webroot
-
-      RewriteEngine on
-      RewriteRule ^/rsrc/(.*) - [L,QSA]
-      RewriteRule ^/favicon.ico - [L,QSA]
-      RewriteRule ^(.*)$ /index.php?__path__=$1 [B,L,QSA]
-  '';
-
-  extraServerPath = [
-      "${pkgs.which}"
-      "${pkgs.diffutils}"
-      ] ++
-      (if config.mercurial then ["${pkgs.mercurial}"] else []) ++
-      (if config.subversion then ["${pkgs.subversion.out}"] else []) ++
-      (if config.git then ["${pkgs.git}"] else []);
-
-  startupScript = pkgs.writeScript "activatePhabricator" ''
-      mkdir -p /var/repo
-      chown wwwrun /var/repo
-  '';
-
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix b/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix
deleted file mode 100644
index a883bb2b3433..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/tomcat-connector.nix
+++ /dev/null
@@ -1,103 +0,0 @@
-{ config, pkgs, serverInfo, lib, ... }:
-
-let
-  extraWorkersProperties = lib.optionalString (config ? extraWorkersProperties) config.extraWorkersProperties;
-  
-  workersProperties = pkgs.writeText "workers.properties" ''
-# Define list of workers that will be used
-# for mapping requests
-# The configuration directives are valid
-# for the mod_jk version 1.2.18 and later
-#
-worker.list=loadbalancer,status
-
-# Define Node1
-# modify the host as your host IP or DNS name.
-worker.node1.port=8009
-worker.node1.host=localhost
-worker.node1.type=ajp13
-worker.node1.lbfactor=1
-
-# Load-balancing behaviour
-worker.loadbalancer.type=lb
-worker.loadbalancer.balance_workers=node1
-
-# Status worker for managing load balancer
-worker.status.type=status
-
-${extraWorkersProperties}
-  '';
-in
-{
-
-  options = {
-    extraWorkersProperties = lib.mkOption {
-      default = "";
-      description = "Additional configuration for the workers.properties file.";
-    };
-  };
-
-  extraModules = [
-    { name = "jk"; path = "${pkgs.tomcat_connectors}/modules/mod_jk.so"; }
-  ];
-
-  extraConfig = ''
-# Where to find workers.properties
-JkWorkersFile ${workersProperties}
-
-# Where to put jk logs
-JkLogFile ${serverInfo.serverConfig.logDir}/mod_jk.log
-
-# Set the jk log level [debug/error/info]
-JkLogLevel info
-
-# Select the log format
-JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
-
-# JkOptions indicates to send SSK KEY SIZE
-# Note: Changed from +ForwardURICompat.
-# See http://tomcat.apache.org/security-jk.html
-JkOptions +ForwardKeySize +ForwardURICompatUnparsed -ForwardDirectories
-
-# JkRequestLogFormat
-JkRequestLogFormat "%w %V %T"
-
-# Mount your applications
-JkMount /__application__/* loadbalancer
-
-# You can use external file for mount points.
-# It will be checked for updates each 60 seconds.
-# The format of the file is: /url=worker
-# /examples/*=loadbalancer
-#JkMountFile uriworkermap.properties
-
-# Add shared memory.
-# This directive is present with 1.2.10 and
-# later versions of mod_jk, and is needed for
-# for load balancing to work properly
-# Note: Replaced JkShmFile logs/jk.shm due to SELinux issues. Refer to
-# https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=225452
-JkShmFile ${serverInfo.serverConfig.stateDir}/jk.shm
-
-# Static files in all Tomcat webapp context directories are served by apache
-JkAutoAlias /var/tomcat/webapps
-
-# All requests go to worker by default
-JkMount /* loadbalancer
-# Serve some static files using httpd
-#JkUnMount /*.html loadbalancer
-#JkUnMount /*.jpg  loadbalancer
-#JkUnMount /*.gif  loadbalancer
-#JkUnMount /*.css  loadbalancer
-#JkUnMount /*.png  loadbalancer
-#JkUnMount /*.js  loadbalancer
-
-# Add jkstatus for managing runtime data
-<Location /jkstatus/>
-JkMount status
-Order deny,allow
-Deny from all
-Allow from 127.0.0.1
-</Location>
-  '';
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/trac.nix b/nixos/modules/services/web-servers/apache-httpd/trac.nix
deleted file mode 100644
index 28b411a64b6f..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/trac.nix
+++ /dev/null
@@ -1,121 +0,0 @@
-{ config, lib, pkgs, serverInfo, ... }:
-
-with lib;
-
-let
-
-  # Build a Subversion instance with Apache modules and Swig/Python bindings.
-  subversion = pkgs.subversion.override {
-    bdbSupport = true;
-    httpServer = true;
-    pythonBindings = true;
-    apacheHttpd = httpd;
-  };
-
-  httpd = serverInfo.serverConfig.package;
-
-  versionPre24 = versionOlder httpd.version "2.4";
-
-in
-
-{
-
-  options = {
-
-    projectsLocation = mkOption {
-      description = "URL path in which Trac projects can be accessed";
-      default = "/projects";
-    };
-
-    projects = mkOption {
-      description = "List of projects that should be provided by Trac. If they are not defined yet empty projects are created.";
-      default = [];
-      example =
-        [ { identifier = "myproject";
-            name = "My Project";
-            databaseURL="postgres://root:password@/tracdb";
-            subversionRepository="/data/subversion/myproject";
-          }
-        ];
-    };
-
-    user = mkOption {
-      default = "wwwrun";
-      description = "User account under which Trac runs.";
-    };
-
-    group = mkOption {
-      default = "wwwrun";
-      description = "Group under which Trac runs.";
-    };
-
-    ldapAuthentication = {
-      enable = mkOption {
-        default = false;
-        description = "Enable the ldap authentication in trac";
-      };
-
-      url = mkOption {
-        default = "ldap://127.0.0.1/dc=example,dc=co,dc=ke?uid?sub?(objectClass=inetOrgPerson)";
-        description = "URL of the LDAP authentication";
-      };
-
-      name = mkOption {
-        default = "Trac server";
-        description = "AuthName";
-      };
-    };
-
-  };
-
-  extraModules = singleton
-    { name = "python"; path = "${pkgs.mod_python}/modules/mod_python.so"; };
-
-  extraConfig = ''
-    <Location ${config.projectsLocation}>
-      SetHandler mod_python
-      PythonHandler trac.web.modpython_frontend
-      PythonOption TracEnvParentDir /var/trac/projects
-      PythonOption TracUriRoot ${config.projectsLocation}
-      PythonOption PYTHON_EGG_CACHE /var/trac/egg-cache
-    </Location>
-    ${if config.ldapAuthentication.enable then ''
-      <LocationMatch "^${config.projectsLocation}[^/]+/login$">
-        AuthType Basic
-        AuthName "${config.ldapAuthentication.name}"
-        AuthBasicProvider "ldap"
-        AuthLDAPURL "${config.ldapAuthentication.url}"
-        ${if versionPre24 then "authzldapauthoritative Off" else ""}
-        require valid-user
-      </LocationMatch>
-    '' else ""}
-  '';
-
-  globalEnvVars = singleton
-    { name = "PYTHONPATH";
-      value =
-        makeSearchPathOutput "lib" "lib/${pkgs.python.libPrefix}/site-packages"
-          [ pkgs.mod_python
-            pkgs.pythonPackages.trac
-            pkgs.pythonPackages.setuptools
-            pkgs.pythonPackages.genshi
-            pkgs.pythonPackages.psycopg2
-            subversion
-          ];
-    };
-
-  startupScript = pkgs.writeScript "activateTrac" ''
-    mkdir -p /var/trac
-    chown ${config.user}:${config.group} /var/trac
-
-    ${concatMapStrings (project:
-      ''
-        if [ ! -d /var/trac/${project.identifier} ]
-        then
-            export PYTHONPATH=${pkgs.pythonPackages.psycopg2}/lib/${pkgs.python.libPrefix}/site-packages
-            ${pkgs.pythonPackages.trac}/bin/trac-admin /var/trac/${project.identifier} initenv "${project.name}" "${project.databaseURL}" svn "${project.subversionRepository}"
-        fi
-      '' ) (config.projects)}
-  '';
-
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/wordpress.nix b/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
deleted file mode 100644
index c68bfd25f6a8..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
+++ /dev/null
@@ -1,285 +0,0 @@
-{ config, lib, pkgs, serverInfo, ... }:
-# http://codex.wordpress.org/Hardening_WordPress
-
-with lib;
-
-let
-  # Our bare-bones wp-config.php file using the above settings
-  wordpressConfig = pkgs.writeText "wp-config.php" ''
-    <?php
-    define('DB_NAME',     '${config.dbName}');
-    define('DB_USER',     '${config.dbUser}');
-    define('DB_PASSWORD', file_get_contents('${config.dbPasswordFile}'));
-    define('DB_HOST',     '${config.dbHost}');
-    define('DB_CHARSET',  'utf8');
-    $table_prefix  = '${config.tablePrefix}';
-    define('AUTOMATIC_UPDATER_DISABLED', true);
-    ${config.extraConfig}
-    if ( !defined('ABSPATH') )
-    	define('ABSPATH', dirname(__FILE__) . '/');
-    require_once(ABSPATH . 'wp-settings.php');
-  '';
-
-  # .htaccess to support pretty URLs
-  htaccess = pkgs.writeText "htaccess" ''
-    <IfModule mod_rewrite.c>
-    RewriteEngine On
-    RewriteBase /
-    RewriteRule ^index\.php$ - [L]
-
-    # add a trailing slash to /wp-admin
-    RewriteRule ^wp-admin$ wp-admin/ [R=301,L]
-
-    RewriteCond %{REQUEST_FILENAME} -f [OR]
-    RewriteCond %{REQUEST_FILENAME} -d
-    RewriteRule ^ - [L]
-    RewriteRule ^(wp-(content|admin|includes).*) $1 [L]
-    RewriteRule ^(.*\.php)$ $1 [L]
-    RewriteRule . index.php [L]
-    </IfModule>
-
-    ${config.extraHtaccess}
-  '';
-
-  # WP translation can be found here:
-  #   https://github.com/nixcloud/wordpress-translations
-  supportedLanguages = {
-    en_GB = { revision="d6c005372a5318fd758b710b77a800c86518be13"; sha256="0qbbsi87k47q4rgczxx541xz4z4f4fr49hw4lnaxkdsf5maz8p9p"; };
-    de_DE = { revision="3c62955c27baaae98fd99feb35593d46562f4736"; sha256="1shndgd11dk836dakrjlg2arwv08vqx6j4xjh4jshvwmjab6ng6p"; };
-    zh_ZN = { revision="12b9f811e8cae4b6ee41de343d35deb0a8fdda6d"; sha256="1339ggsxh0g6lab37jmfxicsax4h702rc3fsvv5azs7mcznvwh47"; };
-    fr_FR = { revision="688c8b1543e3d38d9e8f57e0a6f2a2c3c8b588bd"; sha256="1j41iak0i6k7a4wzyav0yrllkdjjskvs45w53db8vfm8phq1n014"; };
-  };
-
-  downloadLanguagePack = language: revision: sha256s:
-    pkgs.stdenv.mkDerivation rec {
-      name = "wp_${language}";
-      src = pkgs.fetchFromGitHub {
-        owner = "nixcloud";
-        repo = "wordpress-translations";
-        rev = revision;
-        sha256 = sha256s;
-      };
-      installPhase = "mkdir -p $out; cp -R * $out/";
-    };
-
-  selectedLanguages = map (lang: downloadLanguagePack lang supportedLanguages.${lang}.revision supportedLanguages.${lang}.sha256) (config.languages);
-
-  # The wordpress package itself
-  wordpressRoot = pkgs.stdenv.mkDerivation rec {
-    name = "wordpress";
-    src = config.package;
-    installPhase = ''
-      mkdir -p $out
-      # copy all the wordpress files we downloaded
-      cp -R * $out/
-
-      # symlink the wordpress config
-      ln -s ${wordpressConfig} $out/wp-config.php
-      # symlink custom .htaccess
-      ln -s ${htaccess} $out/.htaccess
-      # symlink uploads directory
-      ln -s ${config.wordpressUploads} $out/wp-content/uploads
-
-      # remove bundled plugins(s) coming with wordpress
-      rm -Rf $out/wp-content/plugins/*
-      # remove bundled themes(s) coming with wordpress
-      rm -Rf $out/wp-content/themes/*
-
-      # copy additional theme(s)
-      ${concatMapStrings (theme: "cp -r ${theme} $out/wp-content/themes/${theme.name}\n") config.themes}
-      # copy additional plugin(s)
-      ${concatMapStrings (plugin: "cp -r ${plugin} $out/wp-content/plugins/${plugin.name}\n") (config.plugins) }
-
-      # symlink additional translation(s)
-      mkdir -p $out/wp-content/languages
-      ${concatMapStrings (language: "ln -s ${language}/*.mo ${language}/*.po $out/wp-content/languages/\n") (selectedLanguages) }
-    '';
-  };
-
-in
-
-{
-
-  # And some httpd extraConfig to make things work nicely
-  extraConfig = ''
-    <Directory ${wordpressRoot}>
-      DirectoryIndex index.php
-      Allow from *
-      Options FollowSymLinks
-      AllowOverride All
-    </Directory>
-  '';
-
-  enablePHP = true;
-
-  options = {
-    package = mkOption {
-      type = types.path;
-      default = pkgs.wordpress;
-      description = ''
-        Path to the wordpress sources.
-        Upgrading? We have a test! nix-build ./nixos/tests/wordpress.nix
-      '';
-    };
-    dbHost = mkOption {
-      default = "localhost";
-      description = "The location of the database server.";
-      example = "localhost";
-    };
-    dbName = mkOption {
-      default = "wordpress";
-      description = "Name of the database that holds the Wordpress data.";
-      example = "localhost";
-    };
-    dbUser = mkOption {
-      default = "wordpress";
-      description = "The dbUser, read: the username, for the database.";
-      example = "wordpress";
-    };
-    dbPassword = mkOption {
-      default = "wordpress";
-      description = ''
-        The mysql password to the respective dbUser.
-
-        Warning: this password is stored in the world-readable Nix store. It's
-        recommended to use the $dbPasswordFile option since that gives you control over
-        the security of the password. $dbPasswordFile also takes precedence over $dbPassword.
-      '';
-      example = "wordpress";
-    };
-    dbPasswordFile = mkOption {
-      type = types.str;
-      default = toString (pkgs.writeTextFile {
-        name = "wordpress-dbpassword";
-        text = config.dbPassword;
-      });
-      example = "/run/keys/wordpress-dbpassword";
-      description = ''
-        Path to a file that contains the mysql password to the respective dbUser.
-        The file should be readable by the user: config.services.httpd.user.
-
-        $dbPasswordFile takes precedence over the $dbPassword option.
-
-        This defaults to a file in the world-readable Nix store that contains the value
-        of the $dbPassword option. It's recommended to override this with a path not in
-        the Nix store. Tip: use nixops key management:
-        <link xlink:href='https://nixos.org/nixops/manual/#idm140737318306400'/>
-      '';
-    };
-    tablePrefix = mkOption {
-      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='http://codex.wordpress.org/Editing_wp-config.php#table_prefix'/>.
-      '';
-    };
-    wordpressUploads = mkOption {
-    default = "/data/uploads";
-      description = ''
-        This directory is used for uploads of pictures and must be accessible (read: owned) by the httpd running user. The directory passed here is automatically created and permissions are given to the httpd running user.
-      '';
-    };
-    plugins = mkOption {
-      default = [];
-      type = types.listOf types.path;
-      description =
-        ''
-          List of path(s) to respective plugin(s) which are symlinked from the 'plugins' directory. Note: These plugins need to be packaged before use, see example.
-        '';
-      example = ''
-        # Wordpress plugin 'akismet' installation example
-        akismetPlugin = pkgs.stdenv.mkDerivation {
-          name = "akismet-plugin";
-          # Download the theme from the wordpress site
-          src = pkgs.fetchurl {
-            url = https://downloads.wordpress.org/plugin/akismet.3.1.zip;
-            sha256 = "1i4k7qyzna08822ncaz5l00wwxkwcdg4j9h3z2g0ay23q640pclg";
-          };
-          # 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 = [ akismetPlugin ];
-      '';
-    };
-    themes = mkOption {
-      default = [];
-      type = types.listOf types.path;
-      description =
-        ''
-          List of path(s) to respective theme(s) which are symlinked from the 'theme' directory. Note: These themes need to be packaged before use, see example.
-        '';
-      example = ''
-        # For shits and giggles, let's package the responsive theme
-        responsiveTheme = pkgs.stdenv.mkDerivation {
-          name = "responsive-theme";
-          # Download the theme from the wordpress site
-          src = pkgs.fetchurl {
-            url = http://wordpress.org/themes/download/responsive.1.9.7.6.zip;
-            sha256 = "06i26xlc5kdnx903b1gfvnysx49fb4kh4pixn89qii3a30fgd8r8";
-          };
-          # 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 ];
-      '';
-    };
-    languages = mkOption {
-          default = [];
-          description = "Installs wordpress language packs based on the list, see wordpress.nix for possible translations.";
-          example = "[ \"en_GB\" \"de_DE\" ];";
-    };
-    extraConfig = mkOption {
-      type = types.lines;
-      default = "";
-      example =
-        ''
-          define( 'AUTOSAVE_INTERVAL', 60 ); // Seconds
-        '';
-      description = ''
-        Any additional text to be appended to Wordpress's wp-config.php
-        configuration file.  This is a PHP script.  For configuration
-        settings, see <link xlink:href='http://codex.wordpress.org/Editing_wp-config.php'/>.
-      '';
-    };
-    extraHtaccess = mkOption {
-      default = "";
-      example =
-        ''
-          php_value upload_max_filesize 20M
-          php_value post_max_size 20M
-        '';
-      description = ''
-        Any additional text to be appended to Wordpress's .htaccess file.
-      '';
-    };
-  };
-
-  documentRoot = wordpressRoot;
-
-  # FIXME adding the user has to be done manually for the time being
-  startupScript = pkgs.writeScript "init-wordpress.sh" ''
-    #!/bin/sh
-    mkdir -p ${config.wordpressUploads}
-    chown ${serverInfo.serverConfig.user} ${config.wordpressUploads}
-
-    # we should use systemd dependencies here
-    if [ ! -d ${serverInfo.fullConfig.services.mysql.dataDir}/${config.dbName} ]; then
-      echo "Need to create the database '${config.dbName}' and grant permissions to user named '${config.dbUser}'."
-      # Wait until MySQL is up
-      while [ ! -e ${serverInfo.fullConfig.services.mysql.pidDir}/mysqld.pid ]; do
-        sleep 1
-      done
-      ${pkgs.mysql}/bin/mysql -e 'CREATE DATABASE ${config.dbName};'
-      ${pkgs.mysql}/bin/mysql -e "GRANT ALL ON ${config.dbName}.* TO ${config.dbUser}@localhost IDENTIFIED BY \"$(cat ${config.dbPasswordFile})\";"
-    else
-      echo "Good, no need to do anything database related."
-    fi
-  '';
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/zabbix.nix b/nixos/modules/services/web-servers/apache-httpd/zabbix.nix
deleted file mode 100644
index cab16593bcbc..000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/zabbix.nix
+++ /dev/null
@@ -1,84 +0,0 @@
-{ config, lib, pkgs, serverInfo, ... }:
-
-with lib;
-
-let
-
-  # The Zabbix PHP frontend needs to be able to write its
-  # configuration settings (the connection info to the database) to
-  # the "conf" subdirectory.  So symlink $out/conf to some directory
-  # outside of the Nix store where we want to keep this stateful info.
-  # Note that different instances of the frontend will therefore end
-  # up with their own copies of the PHP sources.  !!! Alternatively,
-  # we could generate zabbix.conf.php declaratively.
-  zabbixPHP = pkgs.runCommand "${pkgs.zabbix.server.name}-php" {}
-    ''
-      cp -rs ${pkgs.zabbix.server}/share/zabbix/php "$out"
-      chmod -R u+w $out
-      ln -s "${if config.configFile == null
-               then "${config.stateDir}/zabbix.conf.php"
-               else config.configFile}" "$out/conf/zabbix.conf.php"
-    '';
-
-in
-
-{
-
-  enablePHP = true;
-
-  phpOptions =
-    ''
-      post_max_size = 32M
-      max_execution_time = 300
-      max_input_time = 300
-    '';
-
-  extraConfig = ''
-    Alias ${config.urlPrefix}/ ${zabbixPHP}/
-
-    <Directory ${zabbixPHP}>
-      DirectoryIndex index.php
-      Order deny,allow
-      Allow from *
-    </Directory>
-  '';
-
-  startupScript = pkgs.writeScript "zabbix-startup-hook" ''
-    mkdir -p ${config.stateDir}
-    chown -R ${serverInfo.serverConfig.user} ${config.stateDir}
-  '';
-
-  # The frontend needs "ps" to find out whether zabbix_server is running.
-  extraServerPath = [ pkgs.procps ];
-
-  options = {
-
-    urlPrefix = mkOption {
-      default = "/zabbix";
-      description = "
-        The URL prefix under which the Zabbix service appears.
-        Use the empty string to have it appear in the server root.
-      ";
-    };
-
-    configFile = mkOption {
-      default = null;
-      type = types.nullOr types.path;
-      description = ''
-        The configuration file (zabbix.conf.php) which contains the database
-        connection settings. If not set, the configuration settings will created
-        by the web installer.
-      '';
-    };
-
-    stateDir = mkOption {
-      default = "/var/lib/zabbix/frontend";
-      description = "
-        Directory where the dynamically generated configuration data
-        of the PHP frontend will be stored.
-      ";
-    };
-
-  };
-
-}
diff --git a/nixos/modules/services/web-servers/caddy.nix b/nixos/modules/services/web-servers/caddy.nix
index d0b936cb019f..132c50735d96 100644
--- a/nixos/modules/services/web-servers/caddy.nix
+++ b/nixos/modules/services/web-servers/caddy.nix
@@ -27,13 +27,13 @@ in {
     ca = mkOption {
       default = "https://acme-v02.api.letsencrypt.org/directory";
       example = "https://acme-staging-v02.api.letsencrypt.org/directory";
-      type = types.string;
+      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.string;
+      type = types.str;
       description = "Email address (for Let's Encrypt certificate)";
     };
 
@@ -70,7 +70,7 @@ in {
         { CADDYPATH = cfg.dataDir; };
       serviceConfig = {
         ExecStart = ''
-          ${cfg.package.bin}/bin/caddy -root=/var/tmp -conf=${configFile} \
+          ${cfg.package}/bin/caddy -root=/var/tmp -conf=${configFile} \
             -ca=${cfg.ca} -email=${cfg.email} ${optionalString cfg.agree "-agree"}
         '';
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
diff --git a/nixos/modules/services/web-servers/darkhttpd.nix b/nixos/modules/services/web-servers/darkhttpd.nix
new file mode 100644
index 000000000000..d6649fd472d9
--- /dev/null
+++ b/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/nixos/modules/services/web-servers/hitch/default.nix b/nixos/modules/services/web-servers/hitch/default.nix
index a6c4cbea1225..1812f225b74d 100644
--- a/nixos/modules/services/web-servers/hitch/default.nix
+++ b/nixos/modules/services/web-servers/hitch/default.nix
@@ -102,7 +102,10 @@ with lib;
 
     environment.systemPackages = [ pkgs.hitch ];
 
-    users.users.hitch.group = "hitch";
+    users.users.hitch = {
+      group = "hitch";
+      isSystemUser = true;
+    };
     users.groups.hitch = {};
   };
 }
diff --git a/nixos/modules/services/web-servers/hydron.nix b/nixos/modules/services/web-servers/hydron.nix
index ed63230bc784..a4a5a435b2e6 100644
--- a/nixos/modules/services/web-servers/hydron.nix
+++ b/nixos/modules/services/web-servers/hydron.nix
@@ -2,7 +2,6 @@
 
 let
   cfg = config.services.hydron;
-  postgres = config.services.postgresql;
 in with lib; {
   options.services.hydron = {
     enable = mkEnableOption "hydron";
@@ -81,40 +80,40 @@ in with lib; {
   };
 
   config = mkIf cfg.enable {
-    security.sudo.enable = cfg.enable;
-    services.postgresql.enable = 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} dbname=hydron sslmode=disable"
+        "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" ];
 
-      preStart = ''
-        # Ensure folder exists or create it and permissions are correct
-        mkdir -p ${escapeShellArg cfg.dataDir}/{.hydron,images}
-        ln -sf ${escapeShellArg cfg.postgresArgsFile} ${escapeShellArg cfg.dataDir}/.hydron/db_conf.json
-        chmod 750 ${escapeShellArg cfg.dataDir}
-        chown -R hydron:hydron ${escapeShellArg cfg.dataDir}
-
-        # Ensure the database is correct or create it
-        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createuser \
-          -SDR hydron || true
-        ${pkgs.sudo}/bin/sudo -u ${postgres.superUser} ${postgres.package}/bin/createdb \
-          -T template0 -E UTF8 -O hydron hydron || true
-        ${pkgs.sudo}/bin/sudo -u hydron ${postgres.package}/bin/psql \
-          -c "ALTER ROLE hydron WITH PASSWORD '$(cat ${escapeShellArg cfg.passwordFile})';" || true
-      '';
-
       serviceConfig = {
-        PermissionsStartOnly = true;
         User = "hydron";
         Group = "hydron";
         ExecStart = "${pkgs.hydron}/bin/hydron serve"
@@ -139,7 +138,7 @@ in with lib; {
       description = "Automatically import paths into hydron and possibly fetch tags";
       after = [ "network.target" "hydron.service" ];
       wantedBy = [ "timers.target" ];
-      
+
       timerConfig = {
         Persistent = true;
         OnCalendar = cfg.interval;
@@ -148,11 +147,10 @@ in with lib; {
 
     users = {
       groups.hydron.gid = config.ids.gids.hydron;
-      
+
       users.hydron = {
         description = "hydron server service user";
         home = cfg.dataDir;
-        createHome = true;
         group = "hydron";
         uid = config.ids.uids.hydron;
       };
diff --git a/nixos/modules/services/web-servers/lighttpd/cgit.nix b/nixos/modules/services/web-servers/lighttpd/cgit.nix
index e6a054c296dc..9f25dc34f3f0 100644
--- a/nixos/modules/services/web-servers/lighttpd/cgit.nix
+++ b/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -42,6 +42,8 @@ in
     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
       '';
diff --git a/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixos/modules/services/web-servers/lighttpd/collectd.nix
index e70c980d5243..3f262451c2cb 100644
--- a/nixos/modules/services/web-servers/lighttpd/collectd.nix
+++ b/nixos/modules/services/web-servers/lighttpd/collectd.nix
@@ -12,7 +12,7 @@ let
 
   defaultCollectionCgi = config.services.collectd.package.overrideDerivation(old: {
     name = "collection.cgi";
-    configurePhase = "true";
+    dontConfigure = true;
     buildPhase = "true";
     installPhase = ''
       substituteInPlace contrib/collection.cgi --replace '"/etc/collection.conf"' '$ENV{COLLECTION_CONF}'
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index 3a154ab75ba9..c8602e5975b3 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -4,23 +4,25 @@ 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;
-      acmeDirectory = config.security.acme.directory;
     in
     vhostConfig // {
       inherit serverName;
     } // (optionalAttrs vhostConfig.enableACME {
-      sslCertificate = "${acmeDirectory}/${serverName}/fullchain.pem";
-      sslCertificateKey = "${acmeDirectory}/${serverName}/key.pem";
-      sslTrustedCertificate = "${acmeDirectory}/${serverName}/full.pem";
+      sslCertificate = "${certs.${serverName}.directory}/fullchain.pem";
+      sslCertificateKey = "${certs.${serverName}.directory}/key.pem";
+      sslTrustedCertificate = "${certs.${serverName}.directory}/full.pem";
     }) // (optionalAttrs (vhostConfig.useACMEHost != null) {
-      sslCertificate = "${acmeDirectory}/${vhostConfig.useACMEHost}/fullchain.pem";
-      sslCertificateKey = "${acmeDirectory}/${vhostConfig.useACMEHost}/key.pem";
-      sslTrustedCertificate = "${acmeDirectory}/${vhostConfig.useACMEHost}/full.pem";
+      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;
@@ -45,7 +47,7 @@ let
   ''));
 
   configFile = pkgs.writers.writeNginxConfig "nginx.conf" ''
-    user ${cfg.user} ${cfg.group};
+    pid /run/nginx/nginx.pid;
     error_log ${cfg.logError};
     daemon off;
 
@@ -59,12 +61,15 @@ let
 
     ${optionalString (cfg.httpConfig == "" && cfg.config == "") ''
     http {
-      include ${cfg.package}/conf/mime.types;
+      # 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;
 
       ${optionalString (cfg.resolver.addresses != []) ''
-        resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"};
+        resolver ${toString cfg.resolver.addresses} ${optionalString (cfg.resolver.valid != "") "valid=${cfg.resolver.valid}"} ${optionalString (!cfg.resolver.ipv6) "ipv6=off"};
       ''}
       ${upstreamConfig}
 
@@ -92,7 +97,6 @@ let
 
       ${optionalString (cfg.recommendedGzipSettings) ''
         gzip on;
-        gzip_disable "msie6";
         gzip_proxied any;
         gzip_comp_level 5;
         gzip_types
@@ -118,6 +122,14 @@ let
         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;
@@ -162,6 +174,12 @@ let
     ${cfg.appendConfig}
   '';
 
+  configPath = if cfg.enableReload
+    then "/etc/nginx/nginx.conf"
+    else configFile;
+
+  execCommand = "${cfg.package}/bin/nginx -c '${configPath}' -p '${cfg.stateDir}'";
+
   vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost:
     let
         onlySSL = vhost.onlySSL || vhost.enableSSL;
@@ -269,17 +287,6 @@ let
       ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"}
     }
   '') (sortProperties (mapAttrsToList (k: v: v // { location = k; }) locations)));
-  mkBasicAuth = vhostName: authDef: let
-    htpasswdFile = pkgs.writeText "${vhostName}.htpasswd" (
-      concatStringsSep "\n" (mapAttrsToList (user: password: ''
-        ${user}:{PLAIN}${password}
-      '') authDef)
-    );
-  in ''
-    auth_basic secured;
-    auth_basic_user_file ${htpasswdFile};
-  '';
-
   mkHtpasswd = vhostName: authDef: pkgs.writeText "${vhostName}.htpasswd" (
     concatStringsSep "\n" (mapAttrsToList (user: password: ''
       ${user}:{PLAIN}${password}
@@ -361,12 +368,7 @@ in
 
       preStart =  mkOption {
         type = types.lines;
-        default = ''
-          test -d ${cfg.stateDir}/logs || mkdir -m 750 -p ${cfg.stateDir}/logs
-          test `stat -c %a ${cfg.stateDir}` = "750" || chmod 750 ${cfg.stateDir}
-          test `stat -c %a ${cfg.stateDir}/logs` = "750" || chmod 750 ${cfg.stateDir}/logs
-          chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
-        '';
+        default = "";
         description = "
           Shell commands executed before the service's nginx is started.
         ";
@@ -442,6 +444,16 @@ in
         ";
       };
 
+      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>.
+        '';
+      };
+
       stateDir = mkOption {
         default = "/var/spool/nginx";
         description = "
@@ -468,7 +480,7 @@ in
       };
 
       clientMaxBodySize = mkOption {
-        type = types.string;
+        type = types.str;
         default = "10m";
         description = "Set nginx global client_max_body_size.";
       };
@@ -503,6 +515,23 @@ in
         '';
       };
 
+      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 = {
@@ -521,6 +550,15 @@ in
                 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 = ''
@@ -632,29 +670,63 @@ in
       }
     ];
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -"
+      "d '${cfg.stateDir}/logs' 0750 ${cfg.user} ${cfg.group} - -"
+      "Z '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.nginx = {
       description = "Nginx Web Server";
-      after = [ "network.target" ];
       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 =
-        ''
+      preStart = ''
         ${cfg.preStart}
-        ${cfg.package}/bin/nginx -c ${configFile} -p ${cfg.stateDir} -t
-        '';
+        ${execCommand} -t
+      '';
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/nginx -c ${configFile} -p ${cfg.stateDir}";
+        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";
+        # Capabilities
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ];
       };
     };
 
+    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 ${pkgs.systemd}/bin/systemctl -q is-active nginx.service ; then
+          ${execCommand} -t && \
+            ${pkgs.systemd}/bin/systemctl reload nginx.service
+        fi
+      '';
+      serviceConfig.RemainAfterExit = true;
+    };
+
     security.acme.certs = filterAttrs (n: v: v != {}) (
       let
-        vhostsConfigs = mapAttrsToList (vhostName: vhostConfig: vhostConfig) virtualHosts;
-        acmeEnabledVhosts = filter (vhostConfig: vhostConfig.enableACME && vhostConfig.useACMEHost == null) vhostsConfigs;
         acmePairs = map (vhostConfig: { name = vhostConfig.serverName; value = {
             user = cfg.user;
             group = lib.mkDefault cfg.group;
@@ -668,15 +740,16 @@ in
         listToAttrs acmePairs
     );
 
-    users.users = optionalAttrs (cfg.user == "nginx") (singleton
-      { name = "nginx";
+    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;
+    };
 
-    users.groups = optionalAttrs (cfg.group == "nginx") (singleton
-      { name = "nginx";
-        gid = config.ids.gids.nginx;
-      });
   };
 }
diff --git a/nixos/modules/services/web-servers/nginx/gitweb.nix b/nixos/modules/services/web-servers/nginx/gitweb.nix
index 272fd1480185..f7fb07bb7975 100644
--- a/nixos/modules/services/web-servers/nginx/gitweb.nix
+++ b/nixos/modules/services/web-servers/nginx/gitweb.nix
@@ -3,8 +3,9 @@
 with lib;
 
 let
-  cfg = config.services.gitweb;
-  package = pkgs.gitweb.override (optionalAttrs cfg.gitwebTheme {
+  cfg = config.services.nginx.gitweb;
+  gitwebConfig = config.services.gitweb;
+  package = pkgs.gitweb.override (optionalAttrs gitwebConfig.gitwebTheme {
     gitwebTheme = true;
   });
 
@@ -17,13 +18,45 @@ in
       default = false;
       type = types.bool;
       description = ''
-        If true, enable gitweb in nginx. Access it at http://yourserver/gitweb
+        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 config.services.nginx.gitweb.enable {
+  config = mkIf cfg.enable {
 
     systemd.services.gitweb = {
       description = "GitWeb service";
@@ -32,22 +65,22 @@ in
         FCGI_SOCKET_PATH = "/run/gitweb/gitweb.sock";
       };
       serviceConfig = {
-        User = "nginx";
-        Group = "nginx";
+        User = cfg.user;
+        Group = cfg.group;
         RuntimeDirectory = [ "gitweb" ];
       };
       wantedBy = [ "multi-user.target" ];
     };
 
     services.nginx = {
-      virtualHosts.default = {
-        locations."/gitweb/static/" = {
+      virtualHosts.${cfg.virtualHost} = {
+        locations."${cfg.location}/static/" = {
           alias = "${package}/static/";
         };
-        locations."/gitweb/" = {
+        locations."${cfg.location}/" = {
           extraConfig = ''
             include ${pkgs.nginx}/conf/fastcgi_params;
-            fastcgi_param GITWEB_CONFIG ${cfg.gitwebConfigFile};
+            fastcgi_param GITWEB_CONFIG ${gitwebConfig.gitwebConfigFile};
             fastcgi_pass unix:/run/gitweb/gitweb.sock;
           '';
         };
diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix
index aeb9b1dd79ef..3d9e391ecf20 100644
--- a/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -1,4 +1,4 @@
-# This file defines the options that can be used both for the Apache
+# 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.)
@@ -67,7 +67,7 @@ with lib;
     return = mkOption {
       type = types.nullOr types.str;
       default = null;
-      example = "301 http://example.com$request_uri;";
+      example = "301 http://example.com$request_uri";
       description = ''
         Adds a return directive, for e.g. redirections.
       '';
@@ -92,4 +92,3 @@ with lib;
     };
   };
 }
-
diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix
index 15b933c984a6..455854e2a965 100644
--- a/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -1,4 +1,4 @@
-# This file defines the options that can be used both for the Apache
+# 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.)
@@ -207,6 +207,7 @@ with lib;
       default = null;
       description = ''
         Basic Auth password file for a vhost.
+        Can be created via: <command>htpasswd -c &lt;filename&gt; &lt;username&gt;</command>
       '';
     };
 
diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix
index ffafbc5e92f2..2c73da103946 100644
--- a/nixos/modules/services/web-servers/phpfpm/default.nix
+++ b/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -4,57 +4,171 @@ with lib;
 
 let
   cfg = config.services.phpfpm;
-  enabled = cfg.poolConfigs != {} || cfg.pools != {};
 
-  stateDir = "/run/phpfpm";
+  runtimeDir = "/run/phpfpm";
 
-  poolConfigs =
-    (mapAttrs mapPoolConfig cfg.poolConfigs) //
-    (mapAttrs mapPool cfg.pools);
+  toStr = value:
+    if true == value then "yes"
+    else if false == value then "no"
+    else toString value;
 
-  mapPoolConfig = n: p: {
-    phpPackage = cfg.phpPackage;
-    phpOptions = cfg.phpOptions;
-    config = p;
-  };
-
-  mapPool = n: p: {
-    phpPackage = p.phpPackage;
-    phpOptions = p.phpOptions;
-    config = ''
-      listen = ${p.listen}
-      ${p.extraConfig}
-    '';
-  };
-
-  fpmCfgFile = pool: conf: pkgs.writeText "phpfpm-${pool}.conf" ''
+  fpmCfgFile = pool: poolOpts: pkgs.writeText "phpfpm-${pool}.conf" ''
     [global]
-    error_log = syslog
-    daemonize = no
-    ${cfg.extraConfig}
+    ${concatStringsSep "\n" (mapAttrsToList (n: v: "${n} = ${toStr v}") cfg.settings)}
+    ${optionalString (cfg.extraConfig != null) cfg.extraConfig}
 
     [${pool}]
-    ${conf}
+    ${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 = pool: pkgs.runCommand "php.ini" {
-    inherit (pool) phpPackage phpOptions;
+  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 $phpPackage/etc/php.ini $nixDefaultsPath $phpOptionsPath > $out
+    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>
+          '';
+        };
+
+        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 = types.lines;
-        default = "";
+        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
@@ -80,64 +194,56 @@ in {
           ''
             date.timezone = "CET"
           '';
-        description =
-          "Options appended to the PHP configuration file <filename>php.ini</filename>.";
-      };
-
-      poolConfigs = mkOption {
-        default = {};
-        type = types.attrsOf types.lines;
-        example = literalExample ''
-          { mypool = '''
-              listen = /run/phpfpm/mypool
-              user = nobody
-              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 = ''
-          A mapping between PHP-FPM pool names and their configurations.
-          See the documentation on <literal>php-fpm.conf</literal> for
-          details on configuration directives. If no pools are defined,
-          the phpfpm service is disabled.
+          Options appended to the PHP configuration file <filename>php.ini</filename>.
         '';
       };
 
       pools = mkOption {
-        type = types.attrsOf (types.submodule (import ./pool-options.nix {
-          inherit lib config;
-        }));
+        type = types.attrsOf (types.submodule poolOpts);
         default = {};
         example = literalExample ''
          {
            mypool = {
-             listen = "/path/to/unix/socket";
+             user = "php";
+             group = "php";
              phpPackage = pkgs.php;
-             extraConfig = '''
-               user = nobody
-               pm = dynamic
-               pm.max_children = 75
-               pm.start_servers = 10
-               pm.min_spare_servers = 5
-               pm.max_spare_servers = 20
-               pm.max_requests = 500
+             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 or poolConfigs are defined, the PHP-FPM
+          PHP-FPM pools. If no pools are defined, the PHP-FPM
           service is disabled.
         '';
       };
     };
   };
 
-  config = mkIf enabled {
+  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";
@@ -148,30 +254,30 @@ in {
       wantedBy = [ "multi-user.target" ];
     };
 
-    systemd.services = flip mapAttrs' poolConfigs (pool: poolConfig:
+    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" ];
-        preStart = ''
-          mkdir -p ${stateDir}
-        '';
         serviceConfig = let
-          cfgFile = fpmCfgFile pool poolConfig.config;
-          iniFile = phpIni poolConfig;
+          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 = "${poolConfig.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
+          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/nixos/modules/services/web-servers/phpfpm/pool-options.nix b/nixos/modules/services/web-servers/phpfpm/pool-options.nix
deleted file mode 100644
index d9ad7eff71f2..000000000000
--- a/nixos/modules/services/web-servers/phpfpm/pool-options.nix
+++ /dev/null
@@ -1,57 +0,0 @@
-{ lib, config }:
-
-let
-  fpmCfg = config.services.phpfpm;
-in
-
-with lib; {
-
-  options = {
-
-    listen = mkOption {
-      type = types.str;
-      example = "/path/to/unix/socket";
-      description = ''
-        The address on which to accept FastCGI requests.
-      '';
-    };
-
-    phpPackage = mkOption {
-      type = types.package;
-      default = fpmCfg.phpPackage;
-      defaultText = "config.services.phpfpm.phpPackage";
-      description = ''
-        The PHP package to use for running this PHP-FPM pool.
-      '';
-    };
-
-    phpOptions = mkOption {
-      type = types.lines;
-      default = fpmCfg.phpOptions;
-      defaultText = "config.services.phpfpm.phpOptions";
-      description = ''
-        "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
-      '';
-    };
-
-    extraConfig = mkOption {
-      type = types.lines;
-      example = ''
-        user = nobody
-        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 = ''
-        Extra lines that go into the pool configuration.
-        See the documentation on <literal>php-fpm.conf</literal> for
-        details on configuration directives.
-      '';
-    };
-  };
-}
-
diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix
index 68261c50324d..6d12925829f7 100644
--- a/nixos/modules/services/web-servers/tomcat.nix
+++ b/nixos/modules/services/web-servers/tomcat.nix
@@ -194,14 +194,10 @@ in
 
   config = mkIf config.services.tomcat.enable {
 
-    users.groups = singleton
-      { name = "tomcat";
-        gid = config.ids.gids.tomcat;
-      };
+    users.groups.tomcat.gid = config.ids.gids.tomcat;
 
-    users.users = singleton
-      { name = "tomcat";
-        uid = config.ids.uids.tomcat;
+    users.users.tomcat =
+      { uid = config.ids.uids.tomcat;
         description = "Tomcat user";
         home = "/homeless-shelter";
         extraGroups = cfg.extraGroups;
diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix
index 5bac895d43ac..5b0fc467ea46 100644
--- a/nixos/modules/services/web-servers/traefik.nix
+++ b/nixos/modules/services/web-servers/traefik.nix
@@ -67,7 +67,7 @@ in {
 
     group = mkOption {
       default = "traefik";
-      type = types.string;
+      type = types.str;
       example = "docker";
       description = ''
         Set the group that traefik runs under.
@@ -117,6 +117,7 @@ in {
       group = "traefik";
       home = cfg.dataDir;
       createHome = true;
+      isSystemUser = true;
     };
 
     users.groups.traefik = {};
diff --git a/nixos/modules/services/web-servers/ttyd.nix b/nixos/modules/services/web-servers/ttyd.nix
new file mode 100644
index 000000000000..01a01d97a234
--- /dev/null
+++ b/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/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix
index a4a9d370d644..f8a18954fc99 100644
--- a/nixos/modules/services/web-servers/unit/default.nix
+++ b/nixos/modules/services/web-servers/unit/default.nix
@@ -85,7 +85,7 @@ in {
     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";
@@ -93,33 +93,52 @@ in {
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [ curl ];
       preStart = ''
-        test -f '/run/unit/control.unit.sock' || rm -f '/run/unit/control.unit.sock'
+        test -f '${cfg.stateDir}/conf.json' || rm -f '${cfg.stateDir}/conf.json'
       '';
       postStart = ''
         curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config'
       '';
       serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID";
-        CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID";
         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}' --no-daemon \
                                    --user ${cfg.user} --group ${cfg.group}
         '';
+        # User and group
+        User = cfg.user;
+        Group = cfg.group;
+        # Capabilities
+        AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SETGID" "CAP_SETUID" ];
+        # Security
+        NoNewPrivileges = true;
+        # Sandboxing
+        ProtectSystem = "full";
+        ProtectHome = true;
         RuntimeDirectory = "unit";
         RuntimeDirectoryMode = "0750";
+        PrivateTmp = true;
+        PrivateDevices = true;
+        ProtectHostname = true;
+        ProtectKernelTunables = true;
+        ProtectKernelModules = true;
+        ProtectControlGroups = true;
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        RestrictRealtime = true;
+        PrivateMounts = true;
       };
     };
 
-    users.users = optionalAttrs (cfg.user == "unit") (singleton {
-      name = "unit";
-      group = cfg.group;
-    });
+    users.users = optionalAttrs (cfg.user == "unit") {
+      unit = {
+        group = cfg.group;
+        isSystemUser = true;
+      };
+    };
+
+    users.groups = optionalAttrs (cfg.group == "unit") {
+      unit = { };
+    };
 
-    users.groups = optionalAttrs (cfg.group == "unit") (singleton {
-      name = "unit";
-    });
   };
 }
diff --git a/nixos/modules/services/web-servers/uwsgi.nix b/nixos/modules/services/web-servers/uwsgi.nix
index 3f858d90fa46..3481b5e60403 100644
--- a/nixos/modules/services/web-servers/uwsgi.nix
+++ b/nixos/modules/services/web-servers/uwsgi.nix
@@ -5,10 +5,6 @@ with lib;
 let
   cfg = config.services.uwsgi;
 
-  uwsgi = pkgs.uwsgi.override {
-    plugins = cfg.plugins;
-  };
-
   buildCfg = name: c:
     let
       plugins =
@@ -23,8 +19,8 @@ let
       python =
         if hasPython2 && hasPython3 then
           throw "`plugins` attribute in UWSGI configuration shouldn't contain both python2 and python3"
-        else if hasPython2 then uwsgi.python2
-        else if hasPython3 then uwsgi.python3
+        else if hasPython2 then cfg.package.python2
+        else if hasPython3 then cfg.package.python3
         else null;
 
       pythonEnv = python.withPackages (c.pythonPackages or (self: []));
@@ -72,11 +68,16 @@ in {
       };
 
       runDir = mkOption {
-        type = types.string;
+        type = types.path;
         default = "/run/uwsgi";
         description = "Where uWSGI communication sockets can live";
       };
 
+      package = mkOption {
+        type = types.package;
+        internal = true;
+      };
+
       instance = mkOption {
         type = types.attrs;
         default = {
@@ -138,7 +139,7 @@ in {
       '';
       serviceConfig = {
         Type = "notify";
-        ExecStart = "${uwsgi}/bin/uwsgi --uid ${cfg.user} --gid ${cfg.group} --json ${buildCfg "server" cfg.instance}/server.json";
+        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";
@@ -146,15 +147,19 @@ in {
       };
     };
 
-    users.users = optionalAttrs (cfg.user == "uwsgi") (singleton
-      { name = "uwsgi";
+    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;
+    };
 
-    users.groups = optionalAttrs (cfg.group == "uwsgi") (singleton
-      { name = "uwsgi";
-        gid = config.ids.gids.uwsgi;
-      });
+    services.uwsgi.package = pkgs.uwsgi.override {
+      inherit (cfg) plugins;
+    };
   };
 }
diff --git a/nixos/modules/services/web-servers/varnish/default.nix b/nixos/modules/services/web-servers/varnish/default.nix
index 63f967185c2d..01fe3d12917a 100644
--- a/nixos/modules/services/web-servers/varnish/default.nix
+++ b/nixos/modules/services/web-servers/varnish/default.nix
@@ -15,8 +15,8 @@ in
 
       package = mkOption {
         type = types.package;
-        default = pkgs.varnish5;
-        defaultText = "pkgs.varnish5";
+        default = pkgs.varnish;
+        defaultText = "pkgs.varnish";
         description = ''
           The package to use
         '';
@@ -48,7 +48,7 @@ in
       extraModules = mkOption {
         type = types.listOf types.package;
         default = [];
-        example = literalExample "[ pkgs.varnish5Packages.geoip ]";
+        example = literalExample "[ pkgs.varnishPackages.geoip ]";
         description = "
           Varnish modules (except 'std').
         ";
diff --git a/nixos/modules/services/web-servers/winstone.nix b/nixos/modules/services/web-servers/winstone.nix
deleted file mode 100644
index 064ead5ce4bb..000000000000
--- a/nixos/modules/services/web-servers/winstone.nix
+++ /dev/null
@@ -1,129 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  cfg = config.services.winstone;
-
-  winstoneOpts = { name, ... }: {
-    options = {
-      name = mkOption {
-        default = name;
-        internal = true;
-      };
-
-      serviceName = mkOption {
-        type = types.str;
-        description = ''
-          The name of the systemd service. By default, it is
-          derived from the winstone instance name.
-        '';
-      };
-
-      warFile = mkOption {
-        type = types.str;
-        description = ''
-          The WAR file that Winstone should serve.
-        '';
-      };
-
-      javaPackage = mkOption {
-        type = types.package;
-        default = pkgs.jre;
-        defaultText = "pkgs.jre";
-        description = ''
-          Which Java derivation to use for running Winstone.
-        '';
-      };
-
-      user = mkOption {
-        type = types.str;
-        description = ''
-          The user that should run this Winstone process and
-          own the working directory.
-        '';
-      };
-
-      group = mkOption {
-        type = types.str;
-        description = ''
-          The group that will own the working directory.
-        '';
-      };
-
-      workDir = mkOption {
-        type = types.str;
-        description = ''
-          The working directory for this Winstone instance. Will
-          contain extracted webapps etc. The directory will be
-          created if it doesn't exist.
-        '';
-      };
-
-      extraJavaOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          Extra command line options given to the java process running
-          Winstone.
-        '';
-      };
-
-      extraOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          Extra command line options given to the Winstone process.
-        '';
-      };
-    };
-
-    config = {
-      workDir = mkDefault "/run/winstone/${name}";
-      serviceName = mkDefault "winstone-${name}";
-    };
-  };
-
-  mkService = cfg: let
-    opts = concatStringsSep " " (cfg.extraOptions ++ [
-      "--warfile ${cfg.warFile}"
-    ]);
-
-    javaOpts = concatStringsSep " " (cfg.extraJavaOptions ++ [
-      "-Djava.io.tmpdir=${cfg.workDir}"
-      "-jar ${pkgs.winstone}/lib/winstone.jar"
-    ]);
-  in {
-    wantedBy = [ "multi-user.target" ];
-    description = "winstone service for ${cfg.name}";
-    preStart = ''
-      mkdir -p "${cfg.workDir}"
-      chown ${cfg.user}:${cfg.group} "${cfg.workDir}"
-    '';
-    serviceConfig = {
-      ExecStart = "${cfg.javaPackage}/bin/java ${javaOpts} ${opts}";
-      User = cfg.user;
-      PermissionsStartOnly = true;
-    };
-  };
-
-in {
-
-  options = {
-    services.winstone = mkOption {
-      default = {};
-      type = with types; attrsOf (submodule winstoneOpts);
-      description = ''
-        Defines independent Winstone services, each serving one WAR-file.
-      '';
-    };
-  };
-
-  config = mkIf (cfg != {}) {
-
-    systemd.services = mapAttrs' (n: c: nameValuePair c.serviceName (mkService c)) cfg;
-
-  };
-
-}
diff --git a/nixos/modules/services/web-servers/zope2.nix b/nixos/modules/services/web-servers/zope2.nix
index 4cad2a2ff777..3abd506827c0 100644
--- a/nixos/modules/services/web-servers/zope2.nix
+++ b/nixos/modules/services/web-servers/zope2.nix
@@ -11,7 +11,7 @@ let
 
       name = mkOption {
         default = "${name}";
-        type = types.string;
+        type = types.str;
         description = "The name of the zope2 instance. If undefined, the name of the attribute set will be used.";
       };
 
@@ -23,19 +23,19 @@ let
 
       http_address = mkOption {
         default = "localhost:8080";
-        type = types.string;
+        type = types.str;
         description = "Give a port and address for the HTTP server.";
       };
 
       user = mkOption {
         default = "zope2";
-        type = types.string;
+        type = types.str;
         description = "The name of the effective user for the Zope process.";
       };
 
       clientHome = mkOption {
         default = "/var/lib/zope2/${name}";
-        type = types.string;
+        type = types.path;
         description = "Home directory of zope2 instance.";
       };
       extra = mkOption {
@@ -52,7 +52,7 @@ let
             </blobstorage>
           </zodb_db>
           '';
-        type = types.string;
+        type = types.lines;
         description = "Extra zope.conf";
       };
 
diff --git a/nixos/modules/services/x11/clight.nix b/nixos/modules/services/x11/clight.nix
new file mode 100644
index 000000000000..4daf6d8d9db7
--- /dev/null
+++ b/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/nixos/modules/services/x11/colord.nix b/nixos/modules/services/x11/colord.nix
index 17568df091d4..cf113ad2af8c 100644
--- a/nixos/modules/services/x11/colord.nix
+++ b/nixos/modules/services/x11/colord.nix
@@ -29,6 +29,7 @@ in {
     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";
     };
diff --git a/nixos/modules/services/x11/desktop-managers/cde.nix b/nixos/modules/services/x11/desktop-managers/cde.nix
new file mode 100644
index 000000000000..c1b6d3bf064a
--- /dev/null
+++ b/nixos/modules/services/x11/desktop-managers/cde.nix
@@ -0,0 +1,55 @@
+{ 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";
+  };
+
+  config = mkIf (xcfg.enable && cfg.enable) {
+    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/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix
index 2b1e9169e5f6..ea6aac9f6c92 100644
--- a/nixos/modules/services/x11/desktop-managers/default.nix
+++ b/nixos/modules/services/x11/desktop-managers/default.nix
@@ -20,7 +20,7 @@ in
   imports = [
     ./none.nix ./xterm.nix ./xfce.nix ./plasma5.nix ./lumina.nix
     ./lxqt.nix ./enlightenment.nix ./gnome3.nix ./kodi.nix ./maxx.nix
-    ./mate.nix ./pantheon.nix
+    ./mate.nix ./pantheon.nix ./surf-display.nix ./cde.nix
   ];
 
   options = {
@@ -68,46 +68,31 @@ in
           scripts before forwarding the value to the
           <varname>displayManager</varname>.
         '';
-        apply = list: {
-          list = 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
-              else
-                # Use a solid black background as fallback
-                ${pkgs.xorg.xsetroot}/bin/xsetroot -solid black
-              fi
-            '';
-          }) list;
-          needBGPackages = [] != filter needBGCond list;
-        };
+        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.str;
-        default = "";
+        type = types.nullOr types.str;
+        default = null;
         example = "none";
-        description = "Default desktop manager loaded if none have been chosen.";
-        apply = defaultDM:
-          if defaultDM == "" && cfg.session.list != [] then
-            (head cfg.session.list).name
-          else if any (w: w.name == defaultDM) cfg.session.list then
-            defaultDM
-          else
-            builtins.trace ''
-              Default desktop manager (${defaultDM}) not found at evaluation time.
-              These are the known valid session names:
-                ${concatMapStringsSep "\n  " (w: "services.xserver.desktopManager.default = \"${w.name}\";") cfg.session.list}
-              It's also possible the default can be found in one of these packages:
-                ${concatMapStringsSep "\n  " (p: p.name) config.services.xserver.displayManager.extraSessionFilePackages}
-            '' defaultDM;
+        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.list;
+  config.services.xserver.displayManager.session = cfg.session;
 }
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 04e380b61530..26b662a2a643 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -16,6 +16,10 @@ let
 in
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "xserver" "desktopManager" "e19" "enable" ] [ "services" "xserver" "desktopManager" "enlightenment" "enable" ])
+  ];
+
   options = {
 
     services.xserver.desktopManager.enlightenment.enable = mkOption {
@@ -25,33 +29,29 @@ in
 
   };
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     environment.systemPackages = [
       e.efl e.enlightenment
       e.terminology e.econnman
       pkgs.xorg.xauth # used by kdesu
-      pkgs.gtk2 # To get GTK+'s themes.
+      pkgs.gtk2 # To get GTK's themes.
       pkgs.tango-icon-theme
 
-      pkgs.gnome2.gnomeicontheme
+      pkgs.gnome2.gnome_icon_theme
       pkgs.xorg.xcursorthemes
     ];
 
-    environment.pathsToLink = [ 
-      "/etc/enlightenment" 
-      "/share/enlightenment" 
-      "/share/elementary" 
+    environment.pathsToLink = [
+      "/etc/enlightenment"
+      "/share/enlightenment"
+      "/share/elementary"
       "/share/locale"
     ];
 
     services.xserver.desktopManager.session = [
     { name = "Enlightenment";
       start = ''
-        # Set GTK_DATA_PREFIX so that GTK+ can find the themes
-        export GTK_DATA_PREFIX=${config.system.path}
-        # find theme engines
-        export GTK_PATH=${config.system.path}/lib/gtk-3.0:${config.system.path}/lib/gtk-2.0
         export XDG_MENU_PREFIX=e-
 
         export GST_PLUGIN_PATH="${GST_PLUGIN_PATH}"
@@ -68,10 +68,7 @@ in
 
     security.wrappers = (import "${e.enlightenment}/e-wrappers.nix").security.wrappers;
 
-    environment.etc = singleton
-      { source = xcfg.xkbDir;
-        target = "X11/xkb";
-      };
+    environment.etc."X11/xkb".source = xcfg.xkbDir;
 
     fonts.fonts = [ pkgs.dejavu_fonts pkgs.ubuntu_font_family ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index 9bf03a494701..5756cf14ed94 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -3,7 +3,9 @@
 with lib;
 
 let
+
   cfg = config.services.xserver.desktopManager.gnome3;
+  serviceCfg = config.services.gnome3;
 
   # Prioritize nautilus by default when opening directories
   mimeAppsList = pkgs.writeTextFile {
@@ -15,20 +17,33 @@ let
     '';
   };
 
-  nixos-gsettings-desktop-schemas = pkgs.runCommand "nixos-gsettings-desktop-schemas" { preferLocalBuild = true; }
+  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
-     cp -rf ${pkgs.gnome3.gsettings-desktop-schemas}/share/gsettings-schemas/gsettings-desktop-schemas*/glib-2.0/schemas/*.xml $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") cfg.extraGSettingsOverridePackages}
+     ${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='${pkgs.nixos-artwork.wallpapers.simple-dark-gray}/share/artwork/gnome/nix-wallpaper-simple-dark-gray.png'
+       picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray}/share/artwork/gnome/nix-wallpaper-simple-dark-gray.png'
 
        [org.gnome.desktop.screensaver]
-       picture-uri='${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png'
+       picture-uri='file://${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png'
+
+       [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
@@ -38,10 +53,19 @@ let
 
   flashbackEnabled = cfg.flashback.enableMetacity || length cfg.flashback.customSessions > 0;
 
-in {
+in
+
+{
 
   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 {
         default = false;
@@ -114,62 +138,40 @@ in {
 
   };
 
-  config = mkIf cfg.enable {
-
-    # Enable helpful DBus services.
-    security.polkit.enable = true;
-    services.udisks2.enable = true;
-    services.accounts-daemon.enable = true;
-    services.geoclue2.enable = mkDefault true;
-    # GNOME should have its own geoclue agent
-    services.geoclue2.enableDemoAgent = false;
-    services.dleyna-renderer.enable = mkDefault true;
-    services.dleyna-server.enable = mkDefault true;
-    services.gnome3.at-spi2-core.enable = true;
-    services.gnome3.evince.enable = mkDefault true;
-    services.gnome3.evolution-data-server.enable = true;
-    services.gnome3.file-roller.enable = mkDefault true;
-    services.gnome3.glib-networking.enable = true;
-    services.gnome3.gnome-disks.enable = mkDefault true;
-    services.gnome3.gnome-documents.enable = mkDefault true;
-    services.gnome3.gnome-keyring.enable = true;
-    services.gnome3.gnome-online-accounts.enable = mkDefault true;
-    services.gnome3.gnome-remote-desktop.enable = mkDefault true;
-    services.gnome3.gnome-settings-daemon.enable = true;
-    services.gnome3.gnome-terminal-server.enable = mkDefault true;
-    services.gnome3.gnome-user-share.enable = mkDefault true;
-    services.gnome3.gvfs.enable = true;
-    services.gnome3.rygel.enable = mkDefault true;
-    services.gnome3.seahorse.enable = mkDefault true;
-    services.gnome3.sushi.enable = mkDefault true;
-    services.gnome3.tracker.enable = mkDefault true;
-    services.gnome3.tracker-miners.enable = mkDefault true;
-    hardware.pulseaudio.enable = mkDefault true;
-    services.telepathy.enable = mkDefault true;
-    networking.networkmanager.enable = mkDefault true;
-    services.upower.enable = config.powerManagement.enable;
-    services.dbus.packages =
-      optional config.services.printing.enable pkgs.system-config-printer ++
-      optional flashbackEnabled pkgs.gnome3.gnome-screensaver;
-    services.colord.enable = mkDefault true;
-    services.packagekit.enable = mkDefault true;
-    hardware.bluetooth.enable = mkDefault true;
-    services.hardware.bolt.enable = mkDefault true;
-    services.xserver.libinput.enable = mkDefault true; # for controlling touchpad settings via gnome control center
-    systemd.packages = [ pkgs.gnome3.vino ];
-    services.flatpak.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
-
-    # If gnome3 is installed, build vim for gtk3 too.
-    nixpkgs.config.vim.gui = "gtk3";
-
-    fonts.fonts = [
-      pkgs.dejavu_fonts pkgs.cantarell-fonts
-      pkgs.source-sans-pro
-      pkgs.source-code-pro # Default monospace font in 3.32
-    ];
-
-    services.xserver.displayManager.extraSessionFilePackages = [ pkgs.gnome3.gnome-session ]
-      ++ map
+  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 {
@@ -178,57 +180,212 @@ in {
               wmCommand = "${pkgs.gnome3.metacity}/bin/metacity";
             } ++ cfg.flashback.customSessions);
 
-    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.variables.GNOME_SESSION_DEBUG = optionalString cfg.debug "1";
-
-    # Override default mimeapps
-    environment.variables.XDG_DATA_DIRS = [ "${mimeAppsList}/share" ];
-
-    # Override GSettings schemas
-    environment.variables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
-
-    # Let nautilus find extensions
-    # TODO: Create nautilus-with-extensions package
-    environment.variables.NAUTILUS_EXTENSION_DIR = "${config.system.path}/lib/nautilus/extensions-3.0";
+      security.pam.services.gnome-screensaver = {
+        enableGnomeKeyring = true;
+      };
 
-    services.xserver.updateDbusEnvironment = true;
+      systemd.packages = with pkgs.gnome3; [
+        gnome-flashback
+      ] ++ (map
+        (wm: gnome-flashback.mkSystemdTargetForWm {
+          inherit (wm) wmName;
+        }) cfg.flashback.customSessions);
+
+      services.dbus.packages = [
+        pkgs.gnome3.gnome-screensaver
+      ];
+    })
+
+    (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
+        vino
+      ];
+
+      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;
+      };
 
-    environment.variables.GIO_EXTRA_MODULES = [ "${lib.getLib pkgs.gnome3.dconf}/lib/gio/modules"
-                                                "${pkgs.gnome3.gvfs}/lib/gio/modules" ];
-    environment.systemPackages = pkgs.gnome3.corePackages ++ cfg.sessionPath
-      ++ (pkgs.gnome3.removePackagesByName pkgs.gnome3.optionalPackages config.environment.gnome3.excludePackages) ++ [
-      pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
-    ];
+      fonts.fonts = with pkgs; [
+        cantarell-fonts
+        dejavu_fonts
+        source-code-pro # Default monospace font in 3.32
+        source-sans-pro
+      ];
 
-    # Use the correct gnome3 packageSet
-    networking.networkmanager.basePackages =
-      { inherit (pkgs) networkmanager modemmanager wpa_supplicant;
-        inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
-                              networkmanager-openconnect networkmanager-fortisslvpn
-                              networkmanager-iodine networkmanager-l2tp; };
+      ## Enable soft realtime scheduling, only supported on wayland ##
 
-    # Needed for themes and backgrounds
-    environment.pathsToLink = [
-      "/share"
-      "/share/nautilus-python/extensions"
-    ];
+      security.wrappers.".gnome-shell-wrapped" = {
+        source = "${pkgs.gnome3.gnome-shell}/bin/.gnome-shell-wrapped";
+        capabilities = "cap_sys_nice=ep";
+      };
 
-    security.pam.services.gnome-screensaver = mkIf flashbackEnabled {
-      enableGnomeKeyring = true;
-    };
-  };
+      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-32/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.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/
+        vino
+      ];
+    })
+
+    # Adapt from https://gitlab.gnome.org/GNOME/gnome-build-meta/blob/gnome-3-32/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/nixos/modules/services/x11/desktop-managers/lumina.nix b/nixos/modules/services/x11/desktop-managers/lumina.nix
index 43fed2572b51..2224bcd5a2a5 100644
--- a/nixos/modules/services/x11/desktop-managers/lumina.nix
+++ b/nixos/modules/services/x11/desktop-managers/lumina.nix
@@ -21,23 +21,18 @@ in
   };
 
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     services.xserver.desktopManager.session = singleton {
       name = "lumina";
       start = ''
-        exec ${pkgs.lumina}/bin/start-lumina-desktop
+        exec ${pkgs.lumina.lumina}/bin/start-lumina-desktop
       '';
     };
 
-    environment.systemPackages = [
-      pkgs.fluxbox
-      pkgs.libsForQt5.kwindowsystem
-      pkgs.lumina
-      pkgs.numlockx
-      pkgs.qt5.qtsvg
-      pkgs.xscreensaver
-    ];
+    environment.systemPackages =
+      pkgs.lumina.preRequisitePackages ++
+      pkgs.lumina.corePackages;
 
     # Link some extra directories in /run/current-system/software/share
     environment.pathsToLink = [
diff --git a/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixos/modules/services/x11/desktop-managers/lxqt.nix
index 686bbd0dcf98..bf53082b267d 100644
--- a/nixos/modules/services/x11/desktop-managers/lxqt.nix
+++ b/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -26,7 +26,7 @@ in
 
   };
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     services.xserver.desktopManager.session = singleton {
       name = "lxqt";
@@ -58,7 +58,8 @@ in
     # Link some extra directories in /run/current-system/software/share
     environment.pathsToLink = [ "/share" ];
 
-    environment.variables.GIO_EXTRA_MODULES = [ "${pkgs.gvfs}/lib/gio/modules" ];
+    services.gvfs.enable = true;
+    services.gvfs.package = pkgs.gvfs;
 
     services.upower.enable = config.powerManagement.enable;
   };
diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix
index bf6685ff7eac..4a6f2ca727d9 100644
--- a/nixos/modules/services/x11/desktop-managers/mate.nix
+++ b/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -42,18 +42,12 @@ in
 
   };
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     services.xserver.desktopManager.session = singleton {
       name = "mate";
       bgSupport = true;
       start = ''
-        # Set GTK_DATA_PREFIX so that GTK+ can find the themes
-        export GTK_DATA_PREFIX=${config.system.path}
-
-        # Find theme engines
-        export GTK_PATH=${config.system.path}/lib/gtk-3.0:${config.system.path}/lib/gtk-2.0
-
         export XDG_MENU_PREFIX=mate-
 
         # Let caja find extensions
@@ -91,19 +85,24 @@ in
         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
       ];
 
     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.gnome3.gnome-settings-daemon.enable = true;
-    services.gnome3.gnome-settings-daemon.package = pkgs.mate.mate-settings-daemon;
-    services.gnome3.gvfs.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.variables.GIO_EXTRA_MODULES = [ "${pkgs.gnome3.gvfs}/lib/gio/modules" ];
+    security.pam.services.mate-screensaver.unixAuth = true;
 
     environment.pathsToLink = [ "/share" ];
   };
diff --git a/nixos/modules/services/x11/desktop-managers/maxx.nix b/nixos/modules/services/x11/desktop-managers/maxx.nix
index 6a698658bdd6..1c04104df41e 100644
--- a/nixos/modules/services/x11/desktop-managers/maxx.nix
+++ b/nixos/modules/services/x11/desktop-managers/maxx.nix
@@ -10,7 +10,7 @@ in {
     enable = mkEnableOption "MaXX desktop environment";
   };
 
-  config = mkIf (xcfg.enable && cfg.enable) {
+  config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.maxx ];
 
     # there is hardcoded path in binaries
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index e1eeb32aa1a0..b46a2d189ef9 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -5,6 +5,7 @@ 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;
@@ -19,6 +20,16 @@ in
 
   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;
@@ -41,6 +52,18 @@ in
         ];
       };
 
+      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;
@@ -67,139 +90,204 @@ in
   };
 
 
-  config = mkIf cfg.enable {
+  config = mkMerge [
+    (mkIf cfg.enable {
 
-    services.xserver.displayManager.extraSessionFilePackages = [ pkgs.pantheon.elementary-session-settings ];
+      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
+      # 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.
+        '';
 
-    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 = "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
       '';
 
-    services.xserver.displayManager.lightdm.enable = mkDefault true;
-    services.xserver.displayManager.lightdm.greeters.gtk.enable = mkDefault true;
-
-    # If not set manually Pantheon session cannot be started
-    # Known issue of https://github.com/NixOS/nixpkgs/pull/43992
-    services.xserver.desktopManager.default = mkForce "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}
-
-          # Makes qt applications look less alien
-          export QT_QPA_PLATFORMTHEME=gtk3
-          export QT_STYLE_OVERRIDE=adwaita
-      fi
-    '';
-
-    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.pantheon.files.enable = mkDefault true;
-    services.tumbler.enable = mkDefault true;
-    services.dbus.packages = mkMerge [
-      ([ pkgs.pantheon.switchboard-plug-power ])
-      (mkIf config.services.printing.enable  ([pkgs.system-config-printer]) )
-    ];
-    services.pantheon.contractor.enable = mkDefault true;
-    services.geoclue2.enable = mkDefault true;
-    # pantheon has pantheon-agent-geoclue2
-    services.geoclue2.enableDemoAgent = false;
-    services.gnome3.at-spi2-core.enable = true;
-    services.gnome3.evince.enable = mkDefault true;
-    services.gnome3.evolution-data-server.enable = true;
-    services.gnome3.file-roller.enable = mkDefault true;
-    # TODO: gnome-keyring's xdg autostarts will still be in the environment (from elementary-session-settings) if disabled forcefully
-    services.gnome3.glib-networking.enable = true;
-    services.gnome3.gnome-keyring.enable = true;
-    services.gnome3.gnome-settings-daemon.enable = true;
-    services.gnome3.gnome-settings-daemon.package = pkgs.pantheon.elementary-settings-daemon;
-    services.gnome3.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;
-
-    networking.networkmanager.enable = mkDefault true;
-    networking.networkmanager.basePackages =
-      { inherit (pkgs) networkmanager modemmanager wpa_supplicant;
-        inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
-                              networkmanager-openconnect networkmanager-fortisslvpn
-                              networkmanager-iodine networkmanager-l2tp; };
-
-    # Override GSettings schemas
-    environment.variables.NIX_GSETTINGS_OVERRIDES_DIR = "${nixos-gsettings-desktop-schemas}/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas";
-
-    environment.variables.GNOME_SESSION_DEBUG = optionalString cfg.debug "1";
-
-    environment.variables.GIO_EXTRA_MODULES = [
-      "${lib.getLib pkgs.gnome3.dconf}/lib/gio/modules"
-      "${pkgs.gnome3.gvfs}/lib/gio/modules"
-    ];
-
-    environment.pathsToLink = [
-      # FIXME: modules should link subdirs of `/share` rather than relying on this
-      "/share"
-    ];
-
-    environment.systemPackages =
-      pkgs.pantheon.artwork ++ pkgs.pantheon.desktop ++ pkgs.pantheon.services ++ cfg.sessionPath
-      ++ (with pkgs; gnome3.removePackagesByName
-      ([
-        gnome3.geary
-        gnome3.epiphany
-        gnome3.gnome-font-viewer
-      ] ++ pantheon.apps) config.environment.pantheon.excludePackages)
-      ++ (with pkgs;
-      [
-        adwaita-qt
+      # 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
-        glib-networking
         gnome-menus
         gnome3.adwaita-icon-theme
-        gnome3.dconf
         gtk3.out
         hicolor-icon-theme
         lightlocker
+        onboard
         plank
         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-session-settings
+        elementary-shortcut-overlay
+        gala
+        (switchboard-with-plugs.override {
+          plugs = cfg.extraSwitchboardPlugs;
+        })
+        (wingpanel-with-indicators.override {
+          indicators = cfg.extraWingpanelIndicators;
+        })
+
+        # Services
+        cerbere
+        elementary-capnet-assist
+        elementary-dpms-helper
+        elementary-settings-daemon
+        pantheon-agent-geoclue2
+        pantheon-agent-polkit
+      ]) ++ (gnome3.removePackagesByName [
+        gnome3.geary
+        gnome3.epiphany
+        gnome3.gnome-font-viewer
+      ] config.environment.pantheon.excludePackages);
 
-    fonts.fonts = with pkgs; [
-      opensans-ttf
-      roboto-mono
-      pantheon.elementary-redacted-script # needed by screenshot-tool
-    ];
+      programs.evince.enable = mkDefault true;
+      programs.file-roller.enable = mkDefault true;
 
-    fonts.fontconfig.defaultFonts = {
-      monospace = [ "Roboto Mono" ];
-      sansSerif = [ "Open Sans" ];
-    };
+      # 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/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index a9e55eb846ca..2538858ac0f7 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -21,22 +21,23 @@ in
         description = "Enable the Plasma 5 (KDE 5) desktop environment.";
       };
 
-      enableQt4Support = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Enable support for Qt 4-based applications. Particularly, install a
-          default backend for Phonon.
-        '';
+      phononBackend = mkOption {
+        type = types.enum [ "gstreamer" "vlc" ];
+        default = "gstreamer";
+        example = "vlc";
+        description = "Phonon audio backend to install.";
       };
-
     };
 
   };
 
+  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 (xcfg.enable && cfg.enable) {
+    (mkIf cfg.enable {
       services.xserver.desktopManager.session = singleton {
         name = "plasma5";
         bgSupport = true;
@@ -64,8 +65,8 @@ in
       };
 
       security.wrappers = {
-        kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/lib/libexec/kcheckpass";
-        "start_kdeinit".source = "${lib.getBin pkgs.kinit}/lib/libexec/kf5/start_kdeinit";
+        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";
@@ -161,31 +162,28 @@ in
 
           qtvirtualkeyboard
 
-          libsForQt56.phonon-backend-gstreamer
-          libsForQt5.phonon-backend-gstreamer
-
           xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
         ]
 
-        ++ lib.optionals cfg.enableQt4Support [ pkgs.phonon-backend-gstreamer ]
+        # 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.optional config.hardware.bluetooth.enable bluedevil
+        ++ 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.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 = singleton {
-        source = xcfg.xkbDir;
-        target = "X11/xkb";
-      };
+      environment.etc."X11/xkb".source = xcfg.xkbDir;
 
       # Enable GTK applications to load SVG icons
       services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
@@ -202,8 +200,8 @@ in
       # Enable helpful DBus services.
       services.udisks2.enable = true;
       services.upower.enable = config.powerManagement.enable;
-      services.dbus.packages =
-        mkIf config.services.printing.enable [ pkgs.system-config-printer ];
+      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 = [
@@ -223,7 +221,9 @@ in
       security.pam.services.kdm.enableKwallet = true;
       security.pam.services.lightdm.enableKwallet = true;
       security.pam.services.sddm.enableKwallet = true;
-      security.pam.services.slim.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 = ''
diff --git a/nixos/modules/services/x11/desktop-managers/surf-display.nix b/nixos/modules/services/x11/desktop-managers/surf-display.nix
new file mode 100644
index 000000000000..9aeb0bbd2a88
--- /dev/null
+++ b/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/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 6852154378d7..21f59074f3ae 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -7,6 +7,33 @@ let
 in
 
 {
+
+  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 {
@@ -30,14 +57,6 @@ in
         description = "Don't install XFCE desktop components (xfdesktop, panel and notification daemon).";
       };
 
-      extraSessionCommands = mkOption {
-        default = "";
-        type = types.lines;
-        description = ''
-          Shell commands executed just before XFCE is started.
-        '';
-      };
-
       enableXfwm = mkOption {
         type = types.bool;
         default = true;
@@ -48,85 +67,96 @@ in
 
   config = mkIf cfg.enable {
     environment.systemPackages = with pkgs.xfce // pkgs; [
-      # Get GTK+ themes and gtk-update-icon-cache
-      gtk2.out
+      glib # for gsettings
+      gtk3.out # gtk-update-icon-cache
 
-      # Supplies some abstract icons such as:
-      # utilities-terminal, accessories-text-editor
+      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
-      # TODO: replace with command -v
-      which
+      xdg-user-dirs # Update user dirs as described in https://freedesktop.org/wiki/Software/xdg-user-dirs/
 
       exo
       garcon
-      gtk-xfce-engine
-      gvfs
       libxfce4ui
-      tumbler
       xfconf
 
       mousepad
+      parole
       ristretto
       xfce4-appfinder
       xfce4-screenshooter
       xfce4-session
       xfce4-settings
+      xfce4-taskmanager
       xfce4-terminal
 
       (thunar.override { thunarPlugins = cfg.thunarPlugins; })
-      thunar-volman # TODO: drop
-    ] ++ (if config.hardware.pulseaudio.enable
-          then [ xfce4-mixer-pulse xfce4-volumed-pulse ]
-          else [ xfce4-mixer xfce4-volumed ])
-      # TODO: NetworkManager doesn't belong here
-      ++ optionals config.networking.networkmanager.enable [ networkmanagerapplet ]
-      ++ optionals config.powerManagement.enable [ xfce4-power-manager ]
-      ++ optionals cfg.enableXfwm [ xfwm4 ]
-      ++ optionals (!cfg.noDesktop) [
-        xfce4-panel
+    ] # 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"
-      "/share/themes"
-      "/share/gtksourceview-2.0"
+      "/lib/xfce4"
+      "/share/gtksourceview-3.0"
+      "/share/gtksourceview-4.0"
     ];
 
-    environment.variables = {
-      GIO_EXTRA_MODULES = [ "${pkgs.xfce.gvfs}/lib/gio/modules" ];
-    };
-
-    services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
-
-    services.xserver.desktopManager.session = [{
-      name = "xfce";
-      bgSupport = true;
-      start = ''
-        ${cfg.extraSessionCommands}
-
-        # Set GTK_PATH so that GTK+ can find the theme engines.
-        export GTK_PATH="${config.system.path}/lib/gtk-2.0:${config.system.path}/lib/gtk-3.0"
-
-        # Set GTK_DATA_PREFIX so that GTK+ can find the Xfce themes.
-        export GTK_DATA_PREFIX=${config.system.path}
-
-        ${pkgs.runtimeShell} ${pkgs.xfce.xinitrc} &
-        waitPID=$!
-      '';
-    }];
+    services.xserver.displayManager.sessionPackages = [
+      pkgs.xfce.xfce4-session
+    ];
 
     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/nixos/modules/services/x11/desktop-managers/xterm.nix b/nixos/modules/services/x11/desktop-managers/xterm.nix
index 6ff1ef0e4c9a..f76db278a927 100644
--- a/nixos/modules/services/x11/desktop-managers/xterm.nix
+++ b/nixos/modules/services/x11/desktop-managers/xterm.nix
@@ -5,6 +5,7 @@ with lib;
 let
 
   cfg = config.services.xserver.desktopManager.xterm;
+  xSessionEnabled = config.services.xserver.enable;
 
 in
 
@@ -13,13 +14,14 @@ in
 
     services.xserver.desktopManager.xterm.enable = mkOption {
       type = types.bool;
-      default = true;
+      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 (config.services.xserver.enable && cfg.enable) {
+  config = mkIf cfg.enable {
 
     services.xserver.desktopManager.session = singleton
       { name = "xterm";
diff --git a/nixos/modules/services/x11/display-managers/account-service-util.nix b/nixos/modules/services/x11/display-managers/account-service-util.nix
new file mode 100644
index 000000000000..1dbe703b5662
--- /dev/null
+++ b/nixos/modules/services/x11/display-managers/account-service-util.nix
@@ -0,0 +1,39 @@
+{ accountsservice
+, glib
+, gobject-introspection
+, python3
+, wrapGAppsHook
+}:
+
+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
+  '';
+}
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index 0e87e6adbab8..1efd07393765 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -1,9 +1,9 @@
 # This module declares the options to define a *display manager*, the
-# program responsible for handling X logins (such as xdm, gdb, or
-# SLiM).  The display manager allows the user to select a *session
-# type*.  When the user logs in, the display manager starts 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*
+# 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).
 
@@ -27,16 +27,7 @@ let
     Xft.hintstyle: hintslight
   '';
 
-  mkCases = session:
-    concatStrings (
-      mapAttrsToList (name: starts: ''
-                       (${name})
-                         ${concatMapStringsSep "\n  " (n: n.start) starts}
-                         ;;
-                     '') (lib.groupBy (n: n.name) session)
-    );
-
-  # file provided by services.xserver.displayManager.session.wrapper
+  # file provided by services.xserver.displayManager.sessionData.wrapper
   xsessionWrapper = pkgs.writeScript "xsession-wrapper"
     ''
       #! ${pkgs.bash}/bin/bash
@@ -109,106 +100,54 @@ let
 
       # Allow the user to setup a custom session type.
       if test -x ~/.xsession; then
-          exec ~/.xsession
+          eval exec ~/.xsession "$@"
       fi
 
       if test "$1"; then
           # Run the supplied session command. Remove any double quotes with eval.
           eval exec "$@"
       else
-          # Fall back to the default window/desktopManager
-          exec ${cfg.displayManager.session.script}
+          # TODO: Do we need this? Should not the session always exist?
+          echo "error: unknown session $1" 1>&2
+          exit 1
       fi
     '';
 
-  # file provided by services.xserver.displayManager.session.script
-  xsession = wm: dm: pkgs.writeScript "xsession"
-    ''
-      #! ${pkgs.bash}/bin/bash
-
-      # Legacy session script used to construct .desktop files from
-      # `services.xserver.displayManager.session` entries. Called from
-      # `sessionWrapper`.
-
-      # Expected parameters:
-      #   $1 = <desktop-manager>+<window-manager>
-
-      # The first argument of this script is the session type.
-      sessionType="$1"
-      if [ "$sessionType" = default ]; then sessionType=""; fi
-
-      # The session type is "<desktop-manager>+<window-manager>", so
-      # extract those (see:
-      # http://wiki.bash-hackers.org/syntax/pe#substring_removal).
-      windowManager="''${sessionType##*+}"
-      : ''${windowManager:=${cfg.windowManager.default}}
-      desktopManager="''${sessionType%%+*}"
-      : ''${desktopManager:=${cfg.desktopManager.default}}
-
-      # Start the window manager.
-      case "$windowManager" in
-        ${mkCases wm}
-        (*) echo "$0: Window manager '$windowManager' not found.";;
-      esac
-
-      # Start the desktop manager.
-      case "$desktopManager" in
-        ${mkCases dm}
-        (*) echo "$0: Desktop manager '$desktopManager' not found.";;
-      esac
-
-      ${optionalString cfg.updateDbusEnvironment ''
-        ${lib.getBin pkgs.dbus}/bin/dbus-update-activation-environment --systemd --all
-      ''}
-
-      test -n "$waitPID" && wait "$waitPID"
-
-      ${config.systemd.package}/bin/systemctl --user stop graphical-session.target
-
-      exit 0
-    '';
-
-  # Desktop Entry Specification:
-  # - https://standards.freedesktop.org/desktop-entry-spec/latest/
-  # - https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
-  mkDesktops = names: pkgs.runCommand "desktops"
+  installedSessions = pkgs.runCommand "desktops"
     { # trivial derivation
       preferLocalBuild = true;
       allowSubstitutes = false;
     }
     ''
-      mkdir -p "$out/share/xsessions"
-      ${concatMapStrings (n: ''
-        cat - > "$out/share/xsessions/${n}.desktop" << EODESKTOP
-        [Desktop Entry]
-        Version=1.0
-        Type=XSession
-        TryExec=${cfg.displayManager.session.script}
-        Exec=${cfg.displayManager.session.script} "${n}"
-        Name=${n}
-        Comment=
-        EODESKTOP
-      '') names}
+      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
-      '') cfg.displayManager.extraSessionFilePackages}
-
-      
-      ${concatMapStrings (pkg: ''
         if test -d ${pkg}/share/wayland-sessions; then
-          mkdir -p "$out/share/wayland-sessions"
           ${xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions
         fi
-      '') cfg.displayManager.extraSessionFilePackages}
+      '') cfg.displayManager.sessionPackages}
     '';
 
+  dmDefault = cfg.desktopManager.default;
+  wmDefault = cfg.windowManager.default;
+
+  defaultSessionFromLegacyOptions = concatStringsSep "+" (filter (s: s != null) ([ dmDefault ] ++ optional (wmDefault != "none") wmDefault));
+
 in
 
 {
-
   options = {
 
     services.xserver.displayManager = {
@@ -263,11 +202,24 @@ in
         '';
       };
 
-      extraSessionFilePackages = mkOption {
-        type = types.listOf types.package;
+      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 xsession files to be passed to the display manager.
+          A list of packages containing x11 or wayland session files to be passed to the display manager.
         '';
       };
 
@@ -298,18 +250,50 @@ in
           inside the display manager with the desktop manager name
           followed by the window manager name.
         '';
-        apply = list: rec {
-          wm = filter (s: s.manage == "window") list;
-          dm = filter (s: s.manage == "desktop") list;
-          names = flip concatMap dm
-            (d: map (w: d.name + optionalString (w.name != "none") ("+" + w.name))
-              (filter (w: d.name != "none" || w.name != "none") wm));
-          desktops = mkDesktops names;
-          script = xsession wm dm;
+      };
+
+      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 {
@@ -322,7 +306,7 @@ in
         execCmd = mkOption {
           type = types.str;
           example = literalExample ''
-            "''${pkgs.slim}/bin/slim"
+            "''${pkgs.lightdm}/bin/lightdm"
           '';
           description = "Command to start the display manager.";
         };
@@ -330,7 +314,6 @@ in
         environment = mkOption {
           type = types.attrsOf types.unspecified;
           default = {};
-          example = { SLIM_CFGFILE = "/etc/slim.conf"; };
           description = "Additional environment variables needed by the display manager.";
         };
 
@@ -359,6 +342,27 @@ in
   };
 
   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 = "${concatStringsSep "+" (filter (s: s != null) [ dmDefault wmDefault ])}";
+          instead.
+        ''
+      ];
+
     services.xserver.displayManager.xserverBin = "${xorg.xorgserver.out}/bin/X";
 
     systemd.user.targets.graphical-session = {
@@ -367,11 +371,75 @@ in
         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"
+
+          ${config.systemd.package}/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;
+            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}
+                  '';
+                } // {
+                  providedSessions = [ sessionName ];
+                })
+            )
+            [dms wms]
+          );
   };
 
   imports = [
-   (mkRemovedOptionModule [ "services" "xserver" "displayManager" "desktopManagerHandlesLidAndPower" ]
+    (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/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index 3edf7c8d9cab..325023f4121a 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -14,6 +14,26 @@ let
       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
 
 {
@@ -25,10 +45,7 @@ in
     services.xserver.displayManager.gdm = {
 
       enable = mkEnableOption ''
-        GDM as the display manager.
-        <emphasis>GDM in NixOS is not well-tested with desktops other
-        than GNOME, so use with caution, as it could render the
-        system unusable.</emphasis>
+        GDM, the GNOME Display Manager
       '';
 
       debug = mkEnableOption ''
@@ -74,7 +91,25 @@ in
       wayland = mkOption {
         default = true;
         description = ''
-          Allow GDM run on Wayland instead of Xserver
+          Allow GDM to run on Wayland instead of Xserver.
+          Note to enable Wayland with Nvidia you need to
+          enable the <option>nvidiaWayland</option>.
+        '';
+        type = types.bool;
+      };
+
+      nvidiaWayland = mkOption {
+        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;
       };
@@ -116,9 +151,7 @@ in
         environment = {
           GDM_X_SERVER_EXTRA_ARGS = toString
             (filter (arg: arg != "-terminate") cfg.xserverArgs);
-          XDG_DATA_DIRS = "${cfg.session.desktops}/share/";
-          # Find the mouse
-          XCURSOR_PATH = "~/.icons:${pkgs.gnome3.adwaita-icon-theme}/share/icons";
+          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
@@ -126,14 +159,47 @@ in
           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}
+        '';
       };
 
-    # Because sd_login_monitor_new requires /run/systemd/machines
-    systemd.services.display-manager.wants = [ "systemd-machined.service" ];
+    systemd.tmpfiles.rules = [
+      "d /run/gdm/.config 0711 gdm gdm -"
+    ] ++ optionals config.hardware.pulseaudio.enable [
+      "L+ /run/gdm/.config/pulse - - - - ${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"
+    ];
+
     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.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 = {
@@ -143,6 +209,9 @@ in
       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 ];
@@ -152,12 +221,55 @@ in
 
     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 = pkgs.writeText "dconf-gdm-profile" ''
-      system-db:local
-      ${gdm}/share/dconf/profile/gdm
-    '';
+    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
@@ -188,7 +300,7 @@ in
       ${optionalString cfg.gdm.debug "Enable=true"}
     '';
 
-    environment.etc."gdm/Xsession".source = config.services.xserver.displayManager.session.wrapper;
+    environment.etc."gdm/Xsession".source = config.services.xserver.displayManager.sessionData.wrapper;
 
     # GDM LFS PAM modules, adapted somehow to NixOS
     security.pam.services = {
@@ -202,7 +314,7 @@ in
         password required       pam_deny.so
 
         session  required       pam_succeed_if.so audit quiet_success user = gdm
-        session  required       pam_env.so envfile=${config.system.build.pamEnvironment}
+        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
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
index de128809ce30..129df139c61a 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
@@ -10,32 +10,6 @@ let
   icons = cfg.iconTheme.package;
   cursors = cfg.cursorTheme.package;
 
-  # We need a few things in the environment for the greeter to run with
-  # fonts/icons.
-  wrappedEnsoGreeter = pkgs.runCommand "lightdm-enso-os-greeter" {
-      buildInputs = [ pkgs.makeWrapper ];
-      preferLocalBuild = true;
-    } ''
-      # This wrapper ensures that we actually get themes
-      makeWrapper ${pkgs.lightdm-enso-os-greeter}/bin/pantheon-greeter \
-        $out/greeter \
-        --prefix PATH : "${pkgs.glibc.bin}/bin" \
-        --set GDK_PIXBUF_MODULE_FILE "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" \
-        --set GTK_PATH "${theme}:${pkgs.gtk3.out}" \
-        --set GTK_EXE_PREFIX "${theme}" \
-        --set GTK_DATA_PREFIX "${theme}" \
-        --set XDG_DATA_DIRS "${theme}/share:${icons}/share:${cursors}/share" \
-        --set XDG_CONFIG_HOME "${theme}/share"
-
-      cat - > $out/lightdm-enso-os-greeter.desktop << EOF
-      [Desktop Entry]
-      Name=LightDM Greeter
-      Comment=This runs the LightDM Greeter
-      Exec=$out/greeter
-      Type=Application
-      EOF
-    '';
-
   ensoGreeterConf = pkgs.writeText "lightdm-enso-os-greeter.conf" ''
     [greeter]
     default-wallpaper=${ldmcfg.background}
@@ -144,10 +118,16 @@ in {
   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 = wrappedEnsoGreeter;
-        name = "lightdm-enso-os-greeter";
+        package = pkgs.lightdm-enso-os-greeter.xgreeters;
+        name = "pantheon-greeter";
       };
 
       greeters = {
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
index 5b280b024233..de932e6e840a 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
@@ -15,34 +15,6 @@ let
   icons = cfg.iconTheme.package;
   cursors = cfg.cursorTheme.package;
 
-  # The default greeter provided with this expression is the GTK greeter.
-  # Again, we need a few things in the environment for the greeter to run with
-  # fonts/icons.
-  wrappedGtkGreeter = pkgs.runCommand "lightdm-gtk-greeter" {
-      buildInputs = [ pkgs.makeWrapper ];
-      preferLocalBuild = true;
-    } ''
-      # This wrapper ensures that we actually get themes
-      makeWrapper ${pkgs.lightdm_gtk_greeter}/sbin/lightdm-gtk-greeter \
-        $out/greeter \
-        --prefix PATH : "${lib.getBin pkgs.stdenv.cc.libc}/bin" \
-        --set GDK_PIXBUF_MODULE_FILE "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" \
-        --set GTK_PATH "${theme}:${pkgs.gtk3.out}" \
-        --set GTK_EXE_PREFIX "${theme}" \
-        --set GTK_DATA_PREFIX "${theme}" \
-        --set XDG_DATA_DIRS "${theme}/share:${icons}/share" \
-        --set XDG_CONFIG_HOME "${theme}/share" \
-        --set XCURSOR_PATH "${cursors}/share/icons"
-
-      cat - > $out/lightdm-gtk-greeter.desktop << EOF
-      [Desktop Entry]
-      Name=LightDM Greeter
-      Comment=This runs the LightDM Greeter
-      Exec=$out/greeter
-      Type=Application
-      EOF
-    '';
-
   gtkGreeterConf = writeText "lightdm-gtk-greeter.conf"
     ''
     [greeter]
@@ -185,10 +157,16 @@ in
   config = mkIf (ldmcfg.enable && cfg.enable) {
 
     services.xserver.displayManager.lightdm.greeter = mkDefault {
-      package = wrappedGtkGreeter;
+      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/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
index ba8151a60f20..0025f9b36037 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/mini.nix
@@ -8,11 +8,6 @@ let
   ldmcfg = dmcfg.lightdm;
   cfg = ldmcfg.greeters.mini;
 
-  xgreeters = pkgs.linkFarm "lightdm-mini-greeter-xgreeters" [{
-    path = "${pkgs.lightdm-mini-greeter}/share/xgreeters/lightdm-mini-greeter.desktop";
-    name = "lightdm-mini-greeter.desktop";
-  }];
-
   miniGreeterConf = pkgs.writeText "lightdm-mini-greeter.conf"
     ''
     [greeter]
@@ -58,9 +53,8 @@ in
           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 by
-          <option>services.xserver.desktopManager.default</option> and
-          <option>services.xserver.windowManager.default</option>.
+          You can configure the default X session using
+          <xref linkend="opt-services.xserver.displayManager.defaultSession"/>.
         '';
       };
 
@@ -90,7 +84,7 @@ in
     services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
 
     services.xserver.displayManager.lightdm.greeter = mkDefault {
-      package = xgreeters;
+      package = pkgs.lightdm-mini-greeter.xgreeters;
       name = "lightdm-mini-greeter";
     };
 
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
index bfba174144a1..77c94114e6d9 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/pantheon.nix
@@ -8,11 +8,6 @@ let
   ldmcfg = dmcfg.lightdm;
   cfg = ldmcfg.greeters.pantheon;
 
-  xgreeters = pkgs.linkFarm "pantheon-greeter-xgreeters" [{
-    path = "${pkgs.pantheon.elementary-greeter}/share/xgreeters/io.elementary.greeter.desktop";
-    name = "io.elementary.greeter.desktop";
-  }];
-
 in
 {
   options = {
@@ -33,20 +28,16 @@ in
 
   config = mkIf (ldmcfg.enable && cfg.enable) {
 
-    warnings = [
-      ''
-        The Pantheon greeter is suboptimal in NixOS and can possibly put you in
-        a situation where you cannot start a session when switching desktopManagers.
-      ''
-    ];
-
     services.xserver.displayManager.lightdm.greeters.gtk.enable = false;
 
     services.xserver.displayManager.lightdm.greeter = mkDefault {
-      package = xgreeters;
+      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/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index 3ab4f26399f3..f7face0adb7e 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -6,12 +6,11 @@ let
 
   xcfg = config.services.xserver;
   dmcfg = xcfg.displayManager;
-  xEnv = config.systemd.services."display-manager".environment;
+  xEnv = config.systemd.services.display-manager.environment;
   cfg = dmcfg.lightdm;
+  sessionData = dmcfg.sessionData;
 
-  dmDefault = xcfg.desktopManager.default;
-  wmDefault = xcfg.windowManager.default;
-  hasDefaultUserSession = dmDefault != "none" || wmDefault != "none";
+  setSessionScript = pkgs.callPackage ./account-service-util.nix { };
 
   inherit (pkgs) lightdm writeScript writeText;
 
@@ -45,22 +44,19 @@ let
         greeter-user = ${config.users.users.lightdm.name}
         greeters-directory = ${cfg.greeter.package}
       ''}
-      sessions-directory = ${dmcfg.session.desktops}/share/xsessions
+      sessions-directory = ${dmcfg.sessionData.desktops}/share/xsessions:${dmcfg.sessionData.desktops}/share/wayland-sessions
       ${cfg.extraConfig}
 
       [Seat:*]
       xserver-command = ${xserverWrapper}
-      session-wrapper = ${dmcfg.session.wrapper}
+      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 = ${defaultSessionName}
-      ''}
-      ${optionalString hasDefaultUserSession ''
-        user-session=${defaultSessionName}
+        autologin-session = ${sessionData.autologinSession}
       ''}
       ${optionalString (dmcfg.setupCommands != "") ''
         display-setup-script=${pkgs.writeScript "lightdm-display-setup" ''
@@ -71,7 +67,6 @@ let
       ${cfg.extraSeatDefaults}
     '';
 
-  defaultSessionName = dmDefault + optionalString (wmDefault != "none") ("+" + wmDefault);
 in
 {
   # Note: the order in which lightdm greeter modules are imported
@@ -114,7 +109,7 @@ in
 
         };
         name = mkOption {
-          type = types.string;
+          type = types.str;
           description = ''
             The name of a .desktop file in the directory specified
             in the 'package' option.
@@ -189,16 +184,19 @@ in
   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 -> dmDefault != "none" || wmDefault != "none";
+      { assertion = cfg.autoLogin.enable -> sessionData.autologinSession != null;
         message = ''
-          LightDM auto-login requires that services.xserver.desktopManager.default and
-          services.xserver.windowManager.default are set to valid values. The current
-          default session: ${defaultSessionName} is not valid.
+          LightDM auto-login requires that services.xserver.displayManager.defaultSession is set.
         '';
       }
       { assertion = !cfg.greeter.enable -> (cfg.autoLogin.enable && cfg.autoLogin.timeout == 0);
@@ -209,12 +207,63 @@ in
       }
     ];
 
+    # 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;
 
@@ -227,36 +276,41 @@ in
     # Enable the accounts daemon to find lightdm's dbus interface
     environment.systemPackages = [ lightdm ];
 
-    security.pam.services.lightdm = {
-      allowNullPassword = true;
-      startSession = true;
-    };
-    security.pam.services.lightdm-greeter = {
-      allowNullPassword = true;
-      startSession = true;
-      text = ''
-        auth     required pam_env.so envfile=${config.system.build.pamEnvironment}
-        auth     required pam_permit.so
+    security.pam.services.lightdm.text = ''
+        auth      substack      login
+        account   include       login
+        password  substack      login
+        session   include       login
+    '';
 
-        account  required pam_permit.so
+    security.pam.services.lightdm-greeter.text = ''
+        auth     required       pam_succeed_if.so audit quiet_success user = lightdm
+        auth     optional       pam_permit.so
 
-        password required pam_deny.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
+    '';
 
-        session  required pam_env.so envfile=${config.system.build.pamEnvironment}
-        session  required pam_unix.so
-        session  optional ${pkgs.systemd}/lib/security/pam_systemd.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
+        auth      requisite     pam_nologin.so
+
+        auth      required      pam_succeed_if.so uid >= 1000 quiet
+        auth      required      pam_permit.so
 
-        account  include   lightdm
+        account   sufficient    pam_unix.so
 
-        password include   lightdm
+        password  requisite     pam_unix.so nullok sha512
 
-        session  include   lightdm
+        session   optional      pam_keyinit.so revoke
+        session   include       login
     '';
 
     users.users.lightdm = {
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 05830e325d51..4224c557ed63 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -7,14 +7,14 @@ let
   xcfg = config.services.xserver;
   dmcfg = xcfg.displayManager;
   cfg = dmcfg.sddm;
-  xEnv = config.systemd.services."display-manager".environment;
+  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 ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} "$@"
+    exec systemd-cat -t xserver-wrapper ${dmcfg.xserverBin} ${toString dmcfg.xserverArgs} "$@"
   '';
 
   Xsetup = pkgs.writeScript "Xsetup" ''
@@ -50,8 +50,8 @@ let
     MinimumVT=${toString (if xcfg.tty != null then xcfg.tty else 7)}
     ServerPath=${xserverWrapper}
     XephyrPath=${pkgs.xorg.xorgserver.out}/bin/Xephyr
-    SessionCommand=${dmcfg.session.wrapper}
-    SessionDir=${dmcfg.session.desktops}/share/xsessions
+    SessionCommand=${dmcfg.sessionData.wrapper}
+    SessionDir=${dmcfg.sessionData.desktops}/share/xsessions
     XauthPath=${pkgs.xorg.xauth}/bin/xauth
     DisplayCommand=${Xsetup}
     DisplayStopCommand=${Xstop}
@@ -59,26 +59,27 @@ let
 
     [Wayland]
     EnableHidpi=${if cfg.enableHidpi then "true" else "false"}
-    SessionDir=${dmcfg.session.desktops}/share/wayland-sessions
+    SessionDir=${dmcfg.sessionData.desktops}/share/wayland-sessions
 
     ${optionalString cfg.autoLogin.enable ''
     [Autologin]
     User=${cfg.autoLogin.user}
-    Session=${defaultSessionName}.desktop
+    Session=${autoLoginSessionName}.desktop
     Relogin=${boolToString cfg.autoLogin.relogin}
     ''}
 
     ${cfg.extraConfig}
   '';
 
-  defaultSessionName =
-    let
-      dm = xcfg.desktopManager.default;
-      wm = xcfg.windowManager.default;
-    in dm + optionalString (wm != "none") ("+" + wm);
+  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 = {
@@ -195,16 +196,19 @@ in
   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 -> elem defaultSessionName dmcfg.session.names;
+      { assertion = cfg.autoLogin.enable -> autoLoginSessionName != null;
         message = ''
-          SDDM auto-login requires that services.xserver.desktopManager.default and
-          services.xserver.windowManager.default are set to valid values. The current
-          default session: ${defaultSessionName} is not valid.
+          SDDM auto-login requires that services.xserver.displayManager.defaultSession is set.
         '';
       }
     ];
@@ -214,8 +218,6 @@ in
         # 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;
-
-        XDG_DATA_DIRS = "/run/current-system/sw/share";
       };
 
       execCmd = "exec /run/current-system/sw/bin/sddm";
@@ -237,7 +239,7 @@ in
         password required       pam_deny.so
 
         session  required       pam_succeed_if.so audit quiet_success user = sddm
-        session  required       pam_env.so envfile=${config.system.build.pamEnvironment}
+        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
@@ -264,8 +266,8 @@ in
     };
 
     environment.etc."sddm.conf".source = cfgFile;
-    environment.pathsToLink = [ 
-      "/share/sddm" 
+    environment.pathsToLink = [
+      "/share/sddm"
     ];
 
     users.groups.sddm.gid = config.ids.gids.sddm;
diff --git a/nixos/modules/services/x11/display-managers/set-session.py b/nixos/modules/services/x11/display-managers/set-session.py
new file mode 100755
index 000000000000..0cca80af44e8
--- /dev/null
+++ b/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/nixos/modules/services/x11/display-managers/slim.nix b/nixos/modules/services/x11/display-managers/slim.nix
index 124660a43f07..4b0948a5b7a5 100644
--- a/nixos/modules/services/x11/display-managers/slim.nix
+++ b/nixos/modules/services/x11/display-managers/slim.nix
@@ -2,155 +2,15 @@
 
 with lib;
 
-let
-
-  dmcfg = config.services.xserver.displayManager;
-
-  cfg = dmcfg.slim;
-
-  slimConfig = pkgs.writeText "slim.cfg"
-    ''
-      xauth_path ${dmcfg.xauthBin}
-      default_xserver ${dmcfg.xserverBin}
-      xserver_arguments ${toString dmcfg.xserverArgs}
-      sessiondir ${dmcfg.session.desktops}/share/xsessions
-      login_cmd exec ${pkgs.runtimeShell} ${dmcfg.session.wrapper} "%session"
-      halt_cmd ${config.systemd.package}/sbin/shutdown -h now
-      reboot_cmd ${config.systemd.package}/sbin/shutdown -r now
-      logfile /dev/stderr
-      ${optionalString (cfg.defaultUser != null) ("default_user " + cfg.defaultUser)}
-      ${optionalString (cfg.defaultUser != null) ("focus_password yes")}
-      ${optionalString cfg.autoLogin "auto_login yes"}
-      ${optionalString (cfg.consoleCmd != null) "console_cmd ${cfg.consoleCmd}"}
-      ${cfg.extraConfig}
-    '';
-
-  # Unpack the SLiM theme, or use the default.
-  slimThemesDir =
-    let
-      unpackedTheme = pkgs.runCommand "slim-theme" { preferLocalBuild = true; }
-        ''
-          mkdir -p $out
-          cd $out
-          unpackFile ${cfg.theme}
-          ln -s * default
-        '';
-    in if cfg.theme == null then "${pkgs.slim}/share/slim/themes" else unpackedTheme;
-
-in
-
 {
-
-  ###### interface
-
-  options = {
-
-    services.xserver.displayManager.slim = {
-
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to enable SLiM as the display manager.
-        '';
-      };
-
-      theme = mkOption {
-        type = types.nullOr types.path;
-        default = pkgs.fetchurl {
-          url = "https://github.com/jagajaga/nixos-slim-theme/archive/2.0.tar.gz";
-          sha256 = "0lldizhigx7bjhxkipii87y432hlf5wdvamnfxrryf9z7zkfypc8";
-        };
-        defaultText = ''pkgs.fetchurl {
-          url = "https://github.com/jagajaga/nixos-slim-theme/archive/2.0.tar.gz";
-          sha256 = "0lldizhigx7bjhxkipii87y432hlf5wdvamnfxrryf9z7zkfypc8";
-        }'';
-        example = literalExample ''
-          pkgs.fetchurl {
-            url = "mirror://sourceforge/slim.berlios/slim-wave.tar.gz";
-            sha256 = "0ndr419i5myzcylvxb89m9grl2xyq6fbnyc3lkd711mzlmnnfxdy";
-          }
-        '';
-        description = ''
-          The theme for the SLiM login manager.  If not specified, SLiM's
-          default theme is used.  See <link
-          xlink:href='http://slim.berlios.de/themes01.php'/> for a
-          collection of themes. TODO: berlios shut down.
-        '';
-      };
-
-      defaultUser = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "login";
-        description = ''
-          The default user to load. If you put a username here you
-          get it automatically loaded into the username field, and
-          the focus is placed on the password.
-        '';
-      };
-
-      autoLogin = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Automatically log in as the default user.
-        '';
-      };
-
-      extraConfig = mkOption {
-        type = types.lines;
-        default = "";
-        description = ''
-          Extra configuration options for SLiM login manager. Do not
-          add options that can be configured directly.
-        '';
-      };
-
-      consoleCmd = mkOption {
-        type = types.nullOr types.str;
-        default = ''
-          ${pkgs.xterm}/bin/xterm -C -fg white -bg black +sb -T "Console login" -e ${pkgs.shadow}/bin/login
-        '';
-        defaultText = ''
-          ''${pkgs.xterm}/bin/xterm -C -fg white -bg black +sb -T "Console login" -e ''${pkgs.shadow}/bin/login
-        '';
-        description = ''
-          The command to run when "console" is given as the username.
-        '';
-      };
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf cfg.enable {
-
-    services.xserver.displayManager.job =
-      { environment =
-          { SLIM_CFGFILE = slimConfig;
-            SLIM_THEMESDIR = slimThemesDir;
-          };
-        execCmd = "exec ${pkgs.slim}/bin/slim";
-      };
-
-    services.xserver.displayManager.sessionCommands =
-      ''
-        # Export the config/themes for slimlock.
-        export SLIM_THEMESDIR=${slimThemesDir}
-      '';
-
-    # Allow null passwords so that the user can login as root on the
-    # installation CD.
-    security.pam.services.slim = { allowNullPassword = true; startSession = true; };
-
-    # Allow slimlock to work.
-    security.pam.services.slimlock = {};
-
-    environment.systemPackages = [ pkgs.slim ];
-
-  };
-
+  # 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/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix
new file mode 100644
index 000000000000..f48216ff446f
--- /dev/null
+++ b/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/nixos/modules/services/x11/gdk-pixbuf.nix b/nixos/modules/services/x11/gdk-pixbuf.nix
index 2dc8eabd95a7..9ad926369ec7 100644
--- a/nixos/modules/services/x11/gdk-pixbuf.nix
+++ b/nixos/modules/services/x11/gdk-pixbuf.nix
@@ -5,21 +5,21 @@ 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);
+  # 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}"
+        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.gdk_pixbuf.dev}/bin/gdk-pixbuf-query-loaders
+          ${pkgs.gdk-pixbuf.dev}/bin/gdk-pixbuf-query-loaders
       done
     ) > "$out"
   '';
diff --git a/nixos/modules/services/x11/hardware/cmt.nix b/nixos/modules/services/x11/hardware/cmt.nix
new file mode 100644
index 000000000000..5ac824c5e419
--- /dev/null
+++ b/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/nixos/modules/services/x11/hardware/digimend.nix b/nixos/modules/services/x11/hardware/digimend.nix
new file mode 100644
index 000000000000..a9f5640905aa
--- /dev/null
+++ b/nixos/modules/services/x11/hardware/digimend.nix
@@ -0,0 +1,43 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.digimend;
+
+  pkg = config.boot.kernelPackages.digimend;
+
+in
+
+{
+
+  options = {
+
+    services.xserver.digimend = {
+
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable 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/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index 58fe702d4969..f6b0e7c09f51 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -41,13 +41,13 @@ in {
       };
 
       accelSpeed = mkOption {
-        type = types.nullOr types.string;
+        type = types.nullOr types.str;
         default = null;
         description = "Cursor acceleration (how fast speed increases from minSpeed to maxSpeed).";
       };
 
       buttonMapping = mkOption {
-        type = types.nullOr types.string;
+        type = types.nullOr types.str;
         default = null;
         description =
           ''
@@ -61,7 +61,7 @@ in {
       };
 
       calibrationMatrix = mkOption {
-        type = types.nullOr types.string;
+        type = types.nullOr types.str;
         default = null;
         description =
           ''
@@ -122,7 +122,7 @@ in {
         description =
           ''
             Specify the scrolling method: <literal>twofinger</literal>, <literal>edge</literal>,
-            or <literal>none</literal>
+            <literal>button</literal>, or <literal>none</literal>
           '';
       };
 
@@ -178,7 +178,7 @@ in {
       };
 
       additionalOptions = mkOption {
-        type = types.str;
+        type = types.lines;
         default = "";
         example =
         ''
@@ -198,23 +198,24 @@ in {
 
     environment.systemPackages = [ pkgs.xorg.xf86inputlibinput ];
 
-    environment.etc = [
-      (let cfgPath = "X11/xorg.conf.d/40-libinput.conf"; in {
-        source = pkgs.xorg.xf86inputlibinput.out + "/share/" + cfgPath;
-        target = cfgPath;
-      })
-    ];
+    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 =
       ''
-        # Automatically enable the libinput driver for all touchpads.
+        # General libinput configuration.
+        # See CONFIGURATION DETAILS section of man:libinput(4).
         Section "InputClass"
           Identifier "libinputConfiguration"
-          MatchIsTouchpad "on"
+          MatchDriver "libinput"
           ${optionalString (cfg.dev != null) ''MatchDevicePath "${cfg.dev}"''}
-          Driver "libinput"
           Option "AccelProfile" "${cfg.accelProfile}"
           ${optionalString (cfg.accelSpeed != null) ''Option "AccelSpeed" "${cfg.accelSpeed}"''}
           ${optionalString (cfg.buttonMapping != null) ''Option "ButtonMapping" "${cfg.buttonMapping}"''}
diff --git a/nixos/modules/services/x11/hardware/multitouch.nix b/nixos/modules/services/x11/hardware/multitouch.nix
deleted file mode 100644
index c03bb3b494fb..000000000000
--- a/nixos/modules/services/x11/hardware/multitouch.nix
+++ /dev/null
@@ -1,94 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let cfg = config.services.xserver.multitouch;
-    disabledTapConfig = ''
-      Option "MaxTapTime" "0"
-      Option "MaxTapMove" "0"
-      Option "TapButton1" "0"
-      Option "TapButton2" "0"
-      Option "TapButton3" "0"
-    '';
-in {
-
-  options = {
-
-    services.xserver.multitouch = {
-
-      enable = mkOption {
-        default = false;
-        description = "Whether to enable multitouch touchpad support.";
-      };
-
-      invertScroll = mkOption {
-        default = false;
-        type = types.bool;
-        description = "Whether to invert scrolling direction à la OSX Lion";
-      };
-
-      ignorePalm = mkOption {
-        default = false;
-        type = types.bool;
-        description = "Whether to ignore touches detected as being the palm (i.e when typing)";
-      };
-
-      tapButtons = mkOption {
-        type = types.bool;
-        default = true;
-        description = "Whether to enable tap buttons.";
-      };
-
-      buttonsMap = mkOption {
-        type = types.listOf types.int;
-        default = [3 2 0];
-        example = [1 3 2];
-        description = "Remap touchpad buttons.";
-        apply = map toString;
-      };
-
-      additionalOptions = mkOption {
-        type = types.str;
-        default = "";
-        example = ''
-          Option "ScaleDistance" "50"
-          Option "RotateDistance" "60"
-        '';
-        description = ''
-          Additional options for mtrack touchpad driver.
-        '';
-      };
-
-    };
-
-  };
-
-  config = mkIf cfg.enable {
-
-    services.xserver.modules = [ pkgs.xf86_input_mtrack ];
-
-    services.xserver.config =
-      ''
-        # Automatically enable the multitouch driver
-        Section "InputClass"
-          MatchIsTouchpad "on"
-          Identifier "Touchpads"
-          Driver "mtrack"
-          Option "IgnorePalm" "${boolToString cfg.ignorePalm}"
-          Option "ClickFinger1" "${builtins.elemAt cfg.buttonsMap 0}"
-          Option "ClickFinger2" "${builtins.elemAt cfg.buttonsMap 1}"
-          Option "ClickFinger3" "${builtins.elemAt cfg.buttonsMap 2}"
-          ${optionalString (!cfg.tapButtons) disabledTapConfig}
-          ${optionalString cfg.invertScroll ''
-            Option "ScrollUpButton" "5"
-            Option "ScrollDownButton" "4"
-            Option "ScrollLeftButton" "7"
-            Option "ScrollRightButton" "6"
-          ''}
-          ${cfg.additionalOptions}
-        EndSection
-      '';
-
-  };
-
-}
diff --git a/nixos/modules/services/x11/hardware/synaptics.nix b/nixos/modules/services/x11/hardware/synaptics.nix
index f032c5938852..22af869f1f8a 100644
--- a/nixos/modules/services/x11/hardware/synaptics.nix
+++ b/nixos/modules/services/x11/hardware/synaptics.nix
@@ -44,19 +44,19 @@ in {
       };
 
       accelFactor = mkOption {
-        type = types.nullOr types.string;
+        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.string;
+        type = types.nullOr types.str;
         default = "0.6";
         description = "Cursor speed factor for precision finger motion.";
       };
 
       maxSpeed = mkOption {
-        type = types.nullOr types.string;
+        type = types.nullOr types.str;
         default = "1.0";
         description = "Cursor speed factor for highest-speed finger motion.";
       };
@@ -167,7 +167,7 @@ in {
 
     services.xserver.modules = [ pkg.out ];
 
-    environment.etc."${etcFile}".source =
+    environment.etc.${etcFile}.source =
       "${pkg.out}/share/X11/xorg.conf.d/70-synaptics.conf";
 
     environment.systemPackages = [ pkg ];
diff --git a/nixos/modules/services/x11/imwheel.nix b/nixos/modules/services/x11/imwheel.nix
new file mode 100644
index 000000000000..3923df498e79
--- /dev/null
+++ b/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/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/picom.nix
index cafd8d88ec4d..e3bd21be73e4 100644
--- a/nixos/modules/services/x11/compton.nix
+++ b/nixos/modules/services/x11/picom.nix
@@ -5,58 +5,50 @@ with builtins;
 
 let
 
-  cfg = config.services.compton;
+  cfg = config.services.picom;
+
+  pairOf = x: with types; addCheck (listOf x) (y: length y == 2);
 
   floatBetween = a: b: with lib; with types;
     addCheck str (x: versionAtLeast x a && versionOlder x b);
 
-  pairOf = x: with types; addCheck (listOf x) (y: lib.length y == 2);
-
-  opacityRules = optionalString (length cfg.opacityRules != 0)
-    (concatMapStringsSep ",\n" (rule: ''"${rule}"'') cfg.opacityRules);
-
-  configFile = pkgs.writeText "compton.conf"
-    (optionalString cfg.fade ''
-      # fading
-      fading = true;
-      fade-delta    = ${toString cfg.fadeDelta};
-      fade-in-step  = ${elemAt cfg.fadeSteps 0};
-      fade-out-step = ${elemAt cfg.fadeSteps 1};
-      fade-exclude  = ${toJSON cfg.fadeExclude};
-    '' + 
-    optionalString cfg.shadow ''
-
-      # shadows
-      shadow = true;
-      shadow-offset-x = ${toString (elemAt cfg.shadowOffsets 0)};
-      shadow-offset-y = ${toString (elemAt cfg.shadowOffsets 1)};
-      shadow-opacity  = ${cfg.shadowOpacity};
-      shadow-exclude  = ${toJSON cfg.shadowExclude};
-    '' + ''
-
-      # opacity
-      active-opacity   = ${cfg.activeOpacity};
-      inactive-opacity = ${cfg.inactiveOpacity};
-      menu-opacity     = ${cfg.menuOpacity};
-
-      opacity-rule = [
-        ${opacityRules}
-      ];
-
-      # other options
-      backend = ${toJSON cfg.backend};
-      vsync = ${toJSON cfg.vSync};
-      refresh-rate = ${toString cfg.refreshRate};
-    '' + cfg.extraOptions);
+  toConf = attrs: concatStringsSep "\n"
+    (mapAttrsToList
+      (k: v: let
+        sep = if isAttrs v then ":" else "=";
+        # Basically a tinkered lib.generators.mkKeyValueDefault
+        mkValueString = v:
+          if isBool v        then boolToString v
+          else if isInt v    then toString v
+          else if isFloat v  then toString v
+          else if isString v then ''"${escape [ ''"'' ] v}"''
+          else if isList v   then "[ "
+            + concatMapStringsSep " , " mkValueString v
+            + " ]"
+          else if isAttrs v  then "{ "
+            + concatStringsSep " "
+              (mapAttrsToList
+                (key: value: "${toString key}=${mkValueString value};")
+                v)
+            + " }"
+          else abort "picom.mkValueString: unexpected type (v = ${v})";
+      in "${escape [ sep ] k}${sep}${mkValueString v};")
+      attrs);
+
+  configFile = pkgs.writeText "picom.conf" (toConf cfg.settings);
 
 in {
 
-  options.services.compton = {
+  imports = [
+    (mkAliasOptionModule [ "services" "compton" ] [ "services" "picom" ])
+  ];
+
+  options.services.picom = {
     enable = mkOption {
       type = types.bool;
       default = false;
       description = ''
-        Whether of not to enable Compton as the X.org composite manager.
+        Whether of not to enable Picom as the X.org composite manager.
       '';
     };
 
@@ -93,11 +85,11 @@ in {
       example = [
         "window_type *= 'menu'"
         "name ~= 'Firefox$'"
-        "focused = 1" 
+        "focused = 1"
       ];
       description = ''
         List of conditions of windows that should not be faded.
-        See <literal>compton(1)</literal> man page for more examples.
+        See <literal>picom(1)</literal> man page for more examples.
       '';
     };
 
@@ -133,11 +125,11 @@ in {
       example = [
         "window_type *= 'menu'"
         "name ~= 'Firefox$'"
-        "focused = 1" 
+        "focused = 1"
       ];
       description = ''
         List of conditions of windows that should have no shadow.
-        See <literal>compton(1)</literal> man page for more examples.
+        See <literal>picom(1)</literal> man page for more examples.
       '';
     };
 
@@ -168,6 +160,15 @@ in {
       '';
     };
 
+    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 = [];
@@ -189,15 +190,22 @@ in {
     };
 
     vSync = mkOption {
-      type = types.enum [
-        "none" "drm" "opengl"
-        "opengl-oml" "opengl-swc" "opengl-mswc"
-      ];
-      default = "none";
-      example = "opengl-swc";
+      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 using the specified method.
-        See <literal>compton(1)</literal> man page an explanation.
+        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.
       '';
     };
 
@@ -210,32 +218,58 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.compton;
-      defaultText = "pkgs.compton";
-      example = literalExample "pkgs.compton";
+    settings = let
+      configTypes = with types; oneOf [ bool int float str ];
+      # types.loaOf converts lists to sets
+      loaOf = t: with types; either (listOf t) (attrsOf t);
+    in mkOption {
+      type = loaOf (types.either configTypes (loaOf (types.either configTypes (loaOf configTypes))));
+      default = {};
       description = ''
-        Compton derivation to use.
-      '';
-    };
-
-    extraOptions = mkOption {
-      type = types.lines;
-      default = "";
-      example = ''
-        unredir-if-possible = true;
-        dbe = true;
-      '';
-      description = ''
-        Additional Compton configuration.
+        Additional Picom configuration.
       '';
     };
   };
 
   config = mkIf cfg.enable {
-    systemd.user.services.compton = {
-      description = "Compton composite manager";
+    services.picom.settings = let
+      # Hard conversion to float, literally lib.toInt but toFloat
+      toFloat = str: let
+        may_be_float = builtins.fromJSON str;
+      in if builtins.isFloat may_be_float
+        then may_be_float
+        else throw "Could not convert ${str} to float.";
+    in {
+      # fading
+      fading           = mkDefault cfg.fade;
+      fade-delta       = mkDefault cfg.fadeDelta;
+      fade-in-step     = mkDefault (toFloat (elemAt cfg.fadeSteps 0));
+      fade-out-step    = mkDefault (toFloat (elemAt cfg.fadeSteps 1));
+      fade-exclude     = mkDefault cfg.fadeExclude;
+
+      # shadows
+      shadow           = mkDefault cfg.shadow;
+      shadow-offset-x  = mkDefault (elemAt cfg.shadowOffsets 0);
+      shadow-offset-y  = mkDefault (elemAt cfg.shadowOffsets 1);
+      shadow-opacity   = mkDefault (toFloat cfg.shadowOpacity);
+      shadow-exclude   = mkDefault cfg.shadowExclude;
+
+      # opacity
+      active-opacity   = mkDefault (toFloat cfg.activeOpacity);
+      inactive-opacity = mkDefault (toFloat cfg.inactiveOpacity);
+
+      wintypes         = mkDefault cfg.wintypes;
+
+      opacity-rule     = mkDefault cfg.opacityRules;
+
+      # other options
+      backend          = mkDefault cfg.backend;
+      vsync            = mkDefault cfg.vSync;
+      refresh-rate     = mkDefault cfg.refreshRate;
+    };
+
+    systemd.user.services.picom = {
+      description = "Picom composite manager";
       wantedBy = [ "graphical-session.target" ];
       partOf = [ "graphical-session.target" ];
 
@@ -245,13 +279,15 @@ in {
       };
 
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/compton --config ${configFile}";
+        ExecStart = "${pkgs.picom}/bin/picom --config ${configFile}";
         RestartSec = 3;
         Restart = "always";
       };
     };
 
-    environment.systemPackages = [ cfg.package ];
+    environment.systemPackages = [ pkgs.picom ];
   };
 
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
 }
diff --git a/nixos/modules/services/x11/redshift.nix b/nixos/modules/services/x11/redshift.nix
index b7dd7debcb63..21b0b33553ac 100644
--- a/nixos/modules/services/x11/redshift.nix
+++ b/nixos/modules/services/x11/redshift.nix
@@ -5,9 +5,26 @@ 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;
@@ -18,35 +35,6 @@ in {
       '';
     };
 
-    latitude = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        Your current latitude, between
-        <literal>-90.0</literal> and <literal>90.0</literal>. Must be provided
-        along with longitude.
-      '';
-    };
-
-    longitude = mkOption {
-      type = types.nullOr types.str;
-      default = null;
-      description = ''
-        Your current longitude, between
-        between <literal>-180.0</literal> and <literal>180.0</literal>. Must be
-        provided along with latitude.
-      '';
-    };
-
-    provider = mkOption {
-      type = types.enum [ "manual" "geoclue2" ];
-      default = "manual";
-      description = ''
-        The location provider to use for determining your location. If set to
-        <literal>manual</literal> you must also provide latitude/longitude.
-      '';
-    };
-
     temperature = {
       day = mkOption {
         type = types.int;
@@ -106,27 +94,19 @@ in {
   };
 
   config = mkIf cfg.enable {
-    assertions = [ 
-      {
-        assertion = 
-          if cfg.provider == "manual"
-          then (cfg.latitude != null && cfg.longitude != null) 
-          else (cfg.latitude == null && cfg.longitude == null);
-        message = "Latitude and longitude must be provided together, and with provider set to null.";
-      }
-    ];
-
     # needed so that .desktop files are installed, which geoclue cares about
     environment.systemPackages = [ cfg.package ];
 
-    services.geoclue2.enable = mkIf (cfg.provider == "geoclue2") true;
+    services.geoclue2.appConfig.redshift = {
+      isAllowed = true;
+      isSystem = true;
+    };
 
-    systemd.user.services.redshift = 
+    systemd.user.services.redshift =
     let
-      providerString = 
-        if cfg.provider == "manual"
-        then "${cfg.latitude}:${cfg.longitude}"
-        else cfg.provider;
+      providerString = if lcfg.provider == "manual"
+        then "${toString lcfg.latitude}:${toString lcfg.longitude}"
+        else lcfg.provider;
     in
     {
       description = "Redshift colour temperature adjuster";
diff --git a/nixos/modules/services/x11/unclutter.nix b/nixos/modules/services/x11/unclutter.nix
index 5f16a680050d..c0868604a688 100644
--- a/nixos/modules/services/x11/unclutter.nix
+++ b/nixos/modules/services/x11/unclutter.nix
@@ -32,7 +32,7 @@ in {
       default = 1;
     };
 
-    threeshold = mkOption {
+    threshold = mkOption {
       description = "Minimum number of pixels considered cursor movement";
       type = types.int;
       default = 1;
@@ -71,4 +71,12 @@ in {
       serviceConfig.Restart = "always";
     };
   };
+
+  imports = [
+    (mkRenamedOptionModule [ "services" "unclutter" "threeshold" ]
+                           [ "services"  "unclutter" "threshold" ])
+  ];
+
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
 }
diff --git a/nixos/modules/services/x11/urxvtd.nix b/nixos/modules/services/x11/urxvtd.nix
index d916fa5bb393..9bfcfa9b065d 100644
--- a/nixos/modules/services/x11/urxvtd.nix
+++ b/nixos/modules/services/x11/urxvtd.nix
@@ -45,4 +45,6 @@ in {
     environment.variables.RXVT_SOCKET = "/run/user/$(id -u)/urxvtd-socket";
   };
 
+  meta.maintainers = with lib.maintainers; [ rnhmjoj ];
+
 }
diff --git a/nixos/modules/services/x11/window-managers/cwm.nix b/nixos/modules/services/x11/window-managers/cwm.nix
new file mode 100644
index 000000000000..03375a226bb6
--- /dev/null
+++ b/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/nixos/modules/services/x11/window-managers/default.nix b/nixos/modules/services/x11/window-managers/default.nix
index 06c59342b45a..04a9fc46628c 100644
--- a/nixos/modules/services/x11/window-managers/default.nix
+++ b/nixos/modules/services/x11/window-managers/default.nix
@@ -11,6 +11,7 @@ in
     ./2bwm.nix
     ./afterstep.nix
     ./bspwm.nix
+    ./cwm.nix
     ./dwm.nix
     ./evilwm.nix
     ./exwm.nix
@@ -19,6 +20,7 @@ in
     ./herbstluftwm.nix
     ./i3.nix
     ./jwm.nix
+    ./leftwm.nix
     ./metacity.nix
     ./mwm.nix
     ./openbox.nix
@@ -57,15 +59,14 @@ in
       };
 
       default = mkOption {
-        type = types.str;
-        default = "none";
+        type = types.nullOr types.str;
+        default = null;
         example = "wmii";
-        description = "Default window manager loaded if none have been chosen.";
-        apply = defaultWM:
-          if any (w: w.name == defaultWM) cfg.session then
-            defaultWM
-          else
-            throw "Default window manager (${defaultWM}) not found.";
+        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.
+        '';
       };
 
     };
diff --git a/nixos/modules/services/x11/window-managers/i3.nix b/nixos/modules/services/x11/window-managers/i3.nix
index c9b0669e7ba5..0ef55d5f2c03 100644
--- a/nixos/modules/services/x11/window-managers/i3.nix
+++ b/nixos/modules/services/x11/window-managers/i3.nix
@@ -60,12 +60,15 @@ in
         ${cfg.extraSessionCommands}
 
         ${cfg.package}/bin/i3 ${optionalString (cfg.configFile != null)
-          "-c \"${cfg.configFile}\""
+          "-c /etc/i3/config"
         } &
         waitPID=$!
       '';
     }];
     environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages;
+    environment.etc."i3/config" = mkIf (cfg.configFile != null) {
+      source = cfg.configFile;
+    };
   };
 
   imports = [
diff --git a/nixos/modules/services/x11/window-managers/leftwm.nix b/nixos/modules/services/x11/window-managers/leftwm.nix
new file mode 100644
index 000000000000..3ef40df95df2
--- /dev/null
+++ b/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/nixos/modules/services/x11/window-managers/xmonad.nix b/nixos/modules/services/x11/window-managers/xmonad.nix
index 43de746ab1f1..30c59b88f82f 100644
--- a/nixos/modules/services/x11/window-managers/xmonad.nix
+++ b/nixos/modules/services/x11/window-managers/xmonad.nix
@@ -10,6 +10,14 @@ let
                      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 = {
@@ -48,14 +56,37 @@ in
         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 = ''
-          ${xmonad}/bin/xmonad &
+        start = if (cfg.config != null) then ''
+          ${xmonadBin}
+          waitPID=$!
+        '' else ''
+          systemd-cat -t xmonad ${xmonad}/bin/xmonad &
           waitPID=$!
         '';
       }];
diff --git a/nixos/modules/services/x11/xautolock.nix b/nixos/modules/services/x11/xautolock.nix
index cbe000058dc6..3e03131ca114 100644
--- a/nixos/modules/services/x11/xautolock.nix
+++ b/nixos/modules/services/x11/xautolock.nix
@@ -129,10 +129,10 @@ in
           assertion = cfg.killer != null -> cfg.killtime >= 10;
           message = "killtime has to be at least 10 minutes according to `man xautolock`";
         }
-      ] ++ (lib.flip map [ "locker" "notifier" "nowlocker" "killer" ]
+      ] ++ (lib.forEach [ "locker" "notifier" "nowlocker" "killer" ]
         (option:
         {
-          assertion = cfg."${option}" != null -> builtins.substring 0 1 cfg."${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/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index e767b0eda312..7029919170aa 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -4,8 +4,6 @@ with lib;
 
 let
 
-  kernelPackages = config.boot.kernelPackages;
-
   # Abbreviations.
   cfg = config.services.xserver;
   xorg = pkgs.xorg;
@@ -16,6 +14,9 @@ let
     # 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 = {};
   };
@@ -77,7 +78,7 @@ let
   in imap1 mkHead cfg.xrandrHeads;
 
   xrandrDeviceSection = let
-    monitors = flip map xrandrHeads (h: ''
+    monitors = forEach xrandrHeads (h: ''
       Option "monitor-${h.config.output}" "${h.name}"
     '');
     # First option is indented through the space in the config but any
@@ -148,6 +149,8 @@ in
     [ ./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.")
     ];
 
 
@@ -243,7 +246,7 @@ in
       videoDrivers = mkOption {
         type = types.listOf types.str;
         # !!! We'd like "nv" here, but it segfaults the X server.
-        default = [ "ati" "cirrus" "vesa" "vmware" "modesetting" ];
+        default = [ "radeon" "cirrus" "vesa" "vmware" "modesetting" ];
         example = [
           "ati_unfree" "amdgpu" "amdgpu-pro"
           "nv" "nvidia" "nvidiaLegacy390" "nvidiaLegacy340" "nvidiaLegacy304"
@@ -328,9 +331,9 @@ in
       };
 
       xkbOptions = mkOption {
-        type = types.str;
+        type = types.commas;
         default = "terminate:ctrl_alt_bksp";
-        example = "grp:caps_toggle, grp_led:scroll";
+        example = "grp:caps_toggle,grp_led:scroll";
         description = ''
           X keyboard options; layout switching goes here.
         '';
@@ -347,6 +350,7 @@ in
 
       xkbDir = mkOption {
         type = types.path;
+        default = "${pkgs.xkeyboard_config}/etc/X11/xkb";
         description = ''
           Path used for -xkbdir xserver parameter.
         '';
@@ -555,7 +559,6 @@ in
           default = !( dmconf.auto.enable
                     || dmconf.gdm.enable
                     || dmconf.sddm.enable
-                    || dmconf.slim.enable
                     || dmconf.xpra.enable );
       in mkIf (default) true;
 
@@ -587,19 +590,15 @@ in
     ];
 
     environment.etc =
-      (optionals cfg.exportConfiguration
-        [ { source = "${configFile}";
-            target = "X11/xorg.conf";
-          }
+      (optionalAttrs cfg.exportConfiguration
+        {
+          "X11/xorg.conf".source = "${configFile}";
           # -xkbdir command line option does not seems to be passed to xkbcomp.
-          { source = "${cfg.xkbDir}";
-            target = "X11/xkb";
-          }
-        ])
+          "X11/xkb".source = "${cfg.xkbDir}";
+        })
       # localectl looks into 00-keyboard.conf
-      ++ [
-        {
-          text = ''
+      //{
+          "X11/xorg.conf.d/00-keyboard.conf".text = ''
             Section "InputClass"
               Identifier "Keyboard catchall"
               MatchIsKeyboard "on"
@@ -609,16 +608,12 @@ in
               Option "XkbVariant" "${cfg.xkbVariant}"
             EndSection
           '';
-          target = "X11/xorg.conf.d/00-keyboard.conf";
         }
-      ]
       # 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
-        [{
-          source = xorg.xf86inputevdev.out + "/share" + cfgPath;
-          target = cfgPath;
-        }]
-      );
+      // (let cfgPath = "/X11/xorg.conf.d/10-evdev.conf"; in
+        {
+          ${cfgPath}.source = xorg.xf86inputevdev.out + "/share" + cfgPath;
+        });
 
     environment.systemPackages =
       [ xorg.xorgserver.out
@@ -657,16 +652,15 @@ in
     systemd.services.display-manager =
       { description = "X11 Server";
 
-        after = [ "systemd-udev-settle.service" "local-fs.target" "acpid.service" "systemd-logind.service" ];
+        after = [ "systemd-udev-settle.service" "acpid.service" "systemd-logind.service" ];
         wants = [ "systemd-udev-settle.service" ];
 
         restartIfChanged = false;
 
         environment =
-          {
-            LD_LIBRARY_PATH = concatStringsSep ":" ([ "/run/opengl-driver/lib" ]
-              ++ concatLists (catAttrs "libPath" cfg.drivers));
-          } // cfg.displayManager.job.environment;
+          optionalAttrs config.hardware.opengl.setLdLibraryPath
+            { LD_LIBRARY_PATH = pkgs.addOpenGLRunpath.driverLink; }
+          // cfg.displayManager.job.environment;
 
         preStart =
           ''
@@ -708,14 +702,12 @@ in
         xorg.xf86inputevdev.out
       ];
 
-    services.xserver.xkbDir = mkDefault "${pkgs.xkeyboard_config}/etc/X11/xkb";
-
     system.extraDependencies = singleton (pkgs.runCommand "xkb-validated" {
       inherit (cfg) xkbModel layout xkbVariant xkbOptions;
       nativeBuildInputs = [ pkgs.xkbvalidate ];
       preferLocalBuild = true;
     } ''
-      validate "$xkbModel" "$layout" "$xkbVariant" "$xkbOptions"
+      xkbvalidate "$xkbModel" "$layout" "$xkbVariant" "$xkbOptions"
       touch "$out"
     '');
 
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index 74c150a848d1..495d77dfd497 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -162,6 +162,16 @@ in
         <literal>/usr/bin/env</literal>.
       '';
     };
+
+    environment.ld-linux = mkOption {
+      default = false;
+      type = types.bool;
+      visible = false;
+      description = ''
+        Install symlink to ld-linux(8) system-wide to allow running unmodified ELF binaries.
+        It might be useful to run games or executables distributed inside jar files.
+      '';
+    };
   };
 
 
@@ -195,9 +205,30 @@ in
       ''
       else ''
         rm -f /usr/bin/env
-        rmdir --ignore-fail-on-non-empty /usr/bin /usr
+        rmdir -p /usr/bin || true
       '';
 
+    system.activationScripts.ld-linux =
+      concatStrings (
+        mapAttrsToList
+          (target: source:
+            if config.environment.ld-linux then ''
+              mkdir -m 0755 -p $(dirname ${target})
+              ln -sfn ${escapeShellArg source} ${target}.tmp
+              mv -f ${target}.tmp ${target} # atomically replace
+            '' else ''
+              rm -f ${target}
+              rmdir $(dirname ${target}) || true
+            '')
+          {
+            "i686-linux"   ."/lib/ld-linux.so.2"          = "${pkgs.glibc.out}/lib/ld-linux.so.2";
+            "x86_64-linux" ."/lib/ld-linux.so.2"          = "${pkgs.pkgsi686Linux.glibc.out}/lib/ld-linux.so.2";
+            "x86_64-linux" ."/lib64/ld-linux-x86-64.so.2" = "${pkgs.glibc.out}/lib64/ld-linux-x86-64.so.2";
+            "aarch64-linux"."/lib/ld-linux-aarch64.so.1"  = "${pkgs.glibc.out}/lib/ld-linux-aarch64.so.1";
+            "armv7l-linux" ."/lib/ld-linux-armhf.so.3"    = "${pkgs.glibc.out}/lib/ld-linux-armhf.so.3";
+          }.${pkgs.stdenv.system} or {}
+      );
+
     system.activationScripts.specialfs =
       ''
         specialMount() {
@@ -218,7 +249,7 @@ in
 
     systemd.user = {
       services.nixos-activation = {
-        description = "Run user specific NixOS activation";
+        description = "Run user-specific NixOS activation";
         script = config.system.userActivationScripts.script;
         unitConfig.ConditionUser = "!@system";
         serviceConfig.Type = "oneshot";
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index 397b308b7311..641cf9faadc9 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -10,6 +10,9 @@ use Cwd 'abs_path';
 
 my $out = "@out@";
 
+# FIXME: maybe we should use /proc/1/exe to get the current systemd.
+my $curSystemd = abs_path("/run/current-system/sw/bin");
+
 # To be robust against interruption, record what units need to be started etc.
 my $startListFile = "/run/systemd/start-list";
 my $restartListFile = "/run/systemd/restart-list";
@@ -166,24 +169,6 @@ while (my ($unit, $state) = each %{$activePrev}) {
 
     if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) {
         if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") {
-            # Ignore (i.e. never stop) these units:
-            if ($unit eq "system.slice") {
-                # TODO: This can be removed a few months after 18.09 is out
-                # (i.e. after everyone switched away from 18.03).
-                # Problem: Restarting (stopping) system.slice would not only
-                # stop X11 but also most system units/services. We obviously
-                # don't want this happening to users when they switch from 18.03
-                # to 18.09 or nixos-unstable.
-                # Reason: The following change in systemd:
-                # https://github.com/systemd/systemd/commit/d8e5a9338278d6602a0c552f01f298771a384798
-                # The commit adds system.slice to the perpetual units, which
-                # means removing the unit file and adding it to the source code.
-                # This is done so that system.slice can't be stopped anymore but
-                # in our case it ironically would cause this script to stop
-                # system.slice because the unit was removed (and an older
-                # systemd version is still running).
-                next;
-            }
             my $unitInfo = parseUnit($prevUnitFile);
             $unitsToStop{$unit} = 1 if boolIsTrue($unitInfo->{'X-StopOnRemoval'} // "yes");
         }
@@ -285,7 +270,7 @@ while (my ($unit, $state) = each %{$activePrev}) {
 sub pathToUnitName {
     my ($path) = @_;
     # Use current version of systemctl binary before daemon is reexeced.
-    open my $cmd, "-|", "/run/current-system/sw/bin/systemd-escape", "--suffix=mount", "-p", $path
+    open my $cmd, "-|", "$curSystemd/systemd-escape", "--suffix=mount", "-p", $path
         or die "Unable to escape $path!\n";
     my $escaped = join "", <$cmd>;
     chomp $escaped;
@@ -388,7 +373,7 @@ if (scalar (keys %unitsToStop) > 0) {
     print STDERR "stopping the following units: ", join(", ", @unitsToStopFiltered), "\n"
         if scalar @unitsToStopFiltered;
     # Use current version of systemctl binary before daemon is reexeced.
-    system("/run/current-system/sw/bin/systemctl", "stop", "--", sort(keys %unitsToStop)); # FIXME: ignore errors?
+    system("$curSystemd/systemctl", "stop", "--", sort(keys %unitsToStop)); # FIXME: ignore errors?
 }
 
 print STDERR "NOT restarting the following changed units: ", join(", ", sort(keys %unitsToSkip)), "\n"
@@ -400,10 +385,12 @@ my $res = 0;
 print STDERR "activating the configuration...\n";
 system("$out/activate", "$out") == 0 or $res = 2;
 
-# Restart systemd if necessary.
+# Restart systemd if necessary. Note that this is done using the
+# current version of systemd, just in case the new one has trouble
+# communicating with the running pid 1.
 if ($restartSystemd) {
     print STDERR "restarting systemd...\n";
-    system("@systemd@/bin/systemctl", "daemon-reexec") == 0 or $res = 2;
+    system("$curSystemd/systemctl", "daemon-reexec") == 0 or $res = 2;
 }
 
 # Forget about previously failed services.
@@ -419,8 +406,10 @@ while (my $f = <$listActiveUsers>) {
     my ($uid, $name) = ($+{uid}, $+{user});
     print STDERR "reloading user units for $name...\n";
 
-    system("@su@", "-s", "@shell@", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user daemon-reload");
-    system("@su@", "-s", "@shell@", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user start nixos-activation.service");
+    system("@su@", "-s", "@shell@", "-l", $name, "-c",
+           "export XDG_RUNTIME_DIR=/run/user/$uid; " .
+           "$curSystemd/systemctl --user daemon-reexec; " .
+           "@systemd@/bin/systemctl --user start nixos-activation.service");
 }
 
 close $listActiveUsers;
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index 5c88d27b6c65..f67d29005616 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -46,8 +46,8 @@ let
 
         ln -s ${kernelPath} $out/kernel
         ln -s ${config.system.modulesTree} $out/kernel-modules
-        ${optionalString (pkgs.stdenv.hostPlatform.platform.kernelDTB or false) ''
-          ln -s ${config.boot.kernelPackages.kernel}/dtbs $out/dtbs
+        ${optionalString (config.hardware.deviceTree.package != null) ''
+          ln -s ${config.hardware.deviceTree.package} $out/dtbs
         ''}
 
         echo -n "$kernelParams" > $out/kernel-params
diff --git a/nixos/modules/system/boot/binfmt.nix b/nixos/modules/system/boot/binfmt.nix
index 15e84dc021e2..a677ab4cb71a 100644
--- a/nixos/modules/system/boot/binfmt.nix
+++ b/nixos/modules/system/boot/binfmt.nix
@@ -1,8 +1,8 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 let
   inherit (lib) mkOption types optionalString;
 
-  cfg = config.boot.binfmtMiscRegistrations;
+  cfg = config.boot.binfmt;
 
   makeBinfmtLine = name: { recognitionType, offset, magicOrExtension
                          , mask, preserveArgvZero, openBinary
@@ -13,125 +13,262 @@ let
     mask' = toString mask;
     interpreter = "/run/binfmt/${name}";
     flags = if !(matchCredentials -> openBinary)
-              then throw "boot.binfmtMiscRegistrations.${name}: you can't specify openBinary = false when matchCredentials = true."
+              then throw "boot.binfmt.registrations.${name}: you can't specify openBinary = false when matchCredentials = true."
             else optionalString preserveArgvZero "P" +
                  optionalString (openBinary && !matchCredentials) "O" +
                  optionalString matchCredentials "C" +
                  optionalString fixBinary "F";
   in ":${name}:${type}:${offset'}:${magicOrExtension}:${mask'}:${interpreter}:${flags}";
 
-  binfmtFile = builtins.toFile "binfmt_nixos.conf"
-    (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine cfg));
-
   activationSnippet = name: { interpreter, ... }:
     "ln -sf ${interpreter} /run/binfmt/${name}";
-  activationScript = ''
-    mkdir -p -m 0755 /run/binfmt
-    ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet cfg)}
-  '';
+
+  getEmulator = system: (lib.systems.elaborate { inherit system; }).emulator pkgs;
+
+  # Mapping of systems to “magicOrExtension” and “mask”. Mostly taken from:
+  # - https://github.com/cleverca22/nixos-configs/blob/master/qemu.nix
+  # and
+  # - https://github.com/qemu/qemu/blob/master/scripts/qemu-binfmt-conf.sh
+  # TODO: maybe put these in a JSON file?
+  magics = {
+    armv6l-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+    };
+    armv7l-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+    };
+    aarch64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\x00\xff\xfe\xff\xff\xff'';
+    };
+    aarch64_be-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    i386-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    i486-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    i586-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    i686-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    x86_64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    alpha-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90'';
+      mask = ''\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    sparc64-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    sparc-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    powerpc-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    powerpc64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    powerpc64le-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00'';
+    };
+    mips-linux = {
+      magicOrExtension = ''\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    mipsel-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    mips64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'';
+    };
+    mips64el-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    riscv32-linux = {
+      magicOrExtension = ''\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    riscv64-linux = {
+      magicOrExtension = ''\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00'';
+      mask = ''\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'';
+    };
+    wasm32-wasi = {
+      magicOrExtension = ''\x00asm'';
+      mask = ''\xff\xff\xff\xff'';
+    };
+    wasm64-wasi = {
+      magicOrExtension = ''\x00asm'';
+      mask = ''\xff\xff\xff\xff'';
+    };
+    x86_64-windows = {
+      magicOrExtension = ".exe";
+      recognitionType = "extension";
+    };
+    i686-windows = {
+      magicOrExtension = ".exe";
+      recognitionType = "extension";
+    };
+  };
+
 in {
+  imports = [
+    (lib.mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
+  ];
+
   options = {
-    boot.binfmtMiscRegistrations = mkOption {
-      default = {};
-
-      description = ''
-        Extra binary formats to register with the kernel.
-        See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
-      '';
-
-      type = types.attrsOf (types.submodule ({ config, ... }: {
-        options = {
-          recognitionType = mkOption {
-            default = "magic";
-            description = "Whether to recognize executables by magic number or extension.";
-            type = types.enum [ "magic" "extension" ];
-          };
+    boot.binfmt = {
+      registrations = mkOption {
+        default = {};
 
-          offset = mkOption {
-            default = null;
-            description = "The byte offset of the magic number used for recognition.";
-            type = types.nullOr types.int;
-          };
+        description = ''
+          Extra binary formats to register with the kernel.
+          See https://www.kernel.org/doc/html/latest/admin-guide/binfmt-misc.html for more details.
+        '';
 
-          magicOrExtension = mkOption {
-            description = "The magic number or extension to match on.";
-            type = types.str;
-          };
+        type = types.attrsOf (types.submodule ({ config, ... }: {
+          options = {
+            recognitionType = mkOption {
+              default = "magic";
+              description = "Whether to recognize executables by magic number or extension.";
+              type = types.enum [ "magic" "extension" ];
+            };
 
-          mask = mkOption {
-            default = null;
-            description =
-              "A mask to be ANDed with the byte sequence of the file before matching";
-            type = types.nullOr types.str;
-          };
+            offset = mkOption {
+              default = null;
+              description = "The byte offset of the magic number used for recognition.";
+              type = types.nullOr types.int;
+            };
 
-          interpreter = mkOption {
-            description = ''
-              The interpreter to invoke to run the program.
+            magicOrExtension = mkOption {
+              description = "The magic number or extension to match on.";
+              type = types.str;
+            };
 
-              Note that the actual registration will point to
-              /run/binfmt/''${name}, so the kernel interpreter length
-              limit doesn't apply.
-            '';
-            type = types.path;
-          };
+            mask = mkOption {
+              default = null;
+              description =
+                "A mask to be ANDed with the byte sequence of the file before matching";
+              type = types.nullOr types.str;
+            };
 
-          preserveArgvZero = mkOption {
-            default = false;
-            description = ''
-              Whether to pass the original argv[0] to the interpreter.
+            interpreter = mkOption {
+              description = ''
+                The interpreter to invoke to run the program.
 
-              See the description of the 'P' flag in the kernel docs
-              for more details;
-            '';
-            type = types.bool;
-          };
+                Note that the actual registration will point to
+                /run/binfmt/''${name}, so the kernel interpreter length
+                limit doesn't apply.
+              '';
+              type = types.path;
+            };
 
-          openBinary = mkOption {
-            default = config.matchCredentials;
-            description = ''
-              Whether to pass the binary to the interpreter as an open
-              file descriptor, instead of a path.
-            '';
-            type = types.bool;
-          };
+            preserveArgvZero = mkOption {
+              default = false;
+              description = ''
+                Whether to pass the original argv[0] to the interpreter.
 
-          matchCredentials = mkOption {
-            default = false;
-            description = ''
-              Whether to launch with the credentials and security
-              token of the binary, not the interpreter (e.g. setuid
-              bit).
+                See the description of the 'P' flag in the kernel docs
+                for more details;
+              '';
+              type = types.bool;
+            };
 
-              See the description of the 'C' flag in the kernel docs
-              for more details.
+            openBinary = mkOption {
+              default = config.matchCredentials;
+              description = ''
+                Whether to pass the binary to the interpreter as an open
+                file descriptor, instead of a path.
+              '';
+              type = types.bool;
+            };
 
-              Implies/requires openBinary = true.
-            '';
-            type = types.bool;
-          };
+            matchCredentials = mkOption {
+              default = false;
+              description = ''
+                Whether to launch with the credentials and security
+                token of the binary, not the interpreter (e.g. setuid
+                bit).
 
-          fixBinary = mkOption {
-            default = false;
-            description = ''
-              Whether to open the interpreter file as soon as the
-              registration is loaded, rather than waiting for a
-              relevant file to be invoked.
-
-              See the description of the 'F' flag in the kernel docs
-              for more details.
-            '';
-            type = types.bool;
+                See the description of the 'C' flag in the kernel docs
+                for more details.
+
+                Implies/requires openBinary = true.
+              '';
+              type = types.bool;
+            };
+
+            fixBinary = mkOption {
+              default = false;
+              description = ''
+                Whether to open the interpreter file as soon as the
+                registration is loaded, rather than waiting for a
+                relevant file to be invoked.
+
+                See the description of the 'F' flag in the kernel docs
+                for more details.
+              '';
+              type = types.bool;
+            };
           };
-        };
-      }));
+        }));
+      };
+
+      emulatedSystems = mkOption {
+        default = [];
+        example = [ "wasm32-wasi" "x86_64-windows" "aarch64-linux" ];
+        description = ''
+          List of systems to emulate. Will also configure Nix to
+          support your new systems.
+        '';
+        type = types.listOf types.str;
+      };
     };
   };
 
-  config = lib.mkIf (cfg != {}) {
-    environment.etc."binfmt.d/nixos.conf".source = binfmtFile;
-    system.activationScripts.binfmt = activationScript;
-    systemd.additionalUpstreamSystemUnits =
+  config = {
+    boot.binfmt.registrations = builtins.listToAttrs (map (system: {
+      name = system;
+      value = {
+        interpreter = getEmulator system;
+      } // (magics.${system} or (throw "Cannot create binfmt registration for system ${system}"));
+    }) cfg.emulatedSystems);
+    # TODO: add a nix.extraPlatforms option to NixOS!
+    nix.extraOptions = lib.mkIf (cfg.emulatedSystems != []) ''
+      extra-platforms = ${toString (cfg.emulatedSystems ++ lib.optional pkgs.stdenv.hostPlatform.isx86_64 "i686-linux")}
+    '';
+    nix.sandboxPaths = lib.mkIf (cfg.emulatedSystems != [])
+      ([ "/run/binfmt" ] ++ (map (system: dirOf (dirOf (getEmulator system))) cfg.emulatedSystems));
+
+    environment.etc."binfmt.d/nixos.conf".source = builtins.toFile "binfmt_nixos.conf"
+      (lib.concatStringsSep "\n" (lib.mapAttrsToList makeBinfmtLine config.boot.binfmt.registrations));
+    system.activationScripts.binfmt = ''
+      mkdir -p -m 0755 /run/binfmt
+      ${lib.concatStringsSep "\n" (lib.mapAttrsToList activationSnippet config.boot.binfmt.registrations)}
+    '';
+    systemd.additionalUpstreamSystemUnits = lib.mkIf (config.boot.binfmt.registrations != {})
       [ "proc-sys-fs-binfmt_misc.automount"
         "proc-sys-fs-binfmt_misc.mount"
       ];
diff --git a/nixos/modules/system/boot/coredump.nix b/nixos/modules/system/boot/coredump.nix
deleted file mode 100644
index 30f367da7666..000000000000
--- a/nixos/modules/system/boot/coredump.nix
+++ /dev/null
@@ -1,66 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-{
-
-  options = {
-
-    systemd.coredump = {
-
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Enables storing core dumps in systemd.
-          Note that this alone is not enough to enable core dumps. The maximum
-          file size for core dumps must be specified in limits.conf as well. See
-          <option>security.pam.loginLimits</option> and the limits.conf(5)
-          man page (these specify the core dump limits for user login sessions)
-          and <option>systemd.extraConfig</option> (where e.g.
-          <literal>DefaultLimitCORE=1000000</literal> can be specified to set
-          the core dump limit for systemd system-level services).
-        '';
-      };
-
-      extraConfig = mkOption {
-        default = "";
-        type = types.lines;
-        example = "Storage=journal";
-        description = ''
-          Extra config options for systemd-coredump. See coredump.conf(5) man page
-          for available options.
-        '';
-      };
-    };
-
-  };
-
-  config = mkMerge [
-    (mkIf config.systemd.coredump.enable {
-
-      systemd.additionalUpstreamSystemUnits = [ "systemd-coredump.socket" "systemd-coredump@.service" ];
-
-      environment.etc."systemd/coredump.conf".text =
-        ''
-          [Coredump]
-          ${config.systemd.coredump.extraConfig}
-        '';
-
-      # Have the kernel pass core dumps to systemd's coredump helper binary.
-      # From systemd's 50-coredump.conf file. See:
-      # <https://github.com/systemd/systemd/blob/v218/sysctl.d/50-coredump.conf.in>
-      boot.kernel.sysctl."kernel.core_pattern" = "|${pkgs.systemd}/lib/systemd/systemd-coredump %P %u %g %s %t %c %e";
-    })
-
-    (mkIf (!config.systemd.coredump.enable) {
-      boot.kernel.sysctl."kernel.core_pattern" = mkDefault "core";
-
-      systemd.extraConfig =
-        ''
-          DefaultLimitCORE=0:infinity
-        '';
-    })
-  ];
-
-}
diff --git a/nixos/modules/system/boot/grow-partition.nix b/nixos/modules/system/boot/grow-partition.nix
index 8c9b1502558a..71a86c74772e 100644
--- a/nixos/modules/system/boot/grow-partition.nix
+++ b/nixos/modules/system/boot/grow-partition.nix
@@ -7,6 +7,9 @@
 with lib;
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ])
+  ];
 
   options = {
     boot.growPartition = mkEnableOption "grow the root partition on boot";
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index ab919099d112..6edb9082e75a 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -36,6 +36,7 @@ in
 
     boot.kernelPackages = mkOption {
       default = pkgs.linuxPackages;
+      type = types.unspecified // { merge = mergeEqualOption; };
       apply = kernelPackages: kernelPackages.extend (self: super: {
         kernel = super.kernel.override {
           inherit randstructSeed;
@@ -107,7 +108,7 @@ in
     boot.extraModulePackages = mkOption {
       type = types.listOf types.package;
       default = [];
-      example = literalExample "[ pkgs.linuxPackages.nvidia_x11 ]";
+      example = literalExample "[ config.boot.kernelPackages.nvidia_x11 ]";
       description = "A list of additional packages supplying kernel modules.";
     };
 
@@ -196,7 +197,7 @@ in
     # (so you don't need to reboot to have changes take effect).
     boot.kernelParams =
       [ "loglevel=${toString config.boot.consoleLogLevel}" ] ++
-      optionals config.boot.vesa [ "vga=0x317" ];
+      optionals config.boot.vesa [ "vga=0x317" "nomodeset" ];
 
     boot.kernel.sysctl."kernel.printk" = mkDefault config.boot.consoleLogLevel;
 
@@ -255,12 +256,11 @@ in
 
     # Create /etc/modules-load.d/nixos.conf, which is read by
     # systemd-modules-load.service to load required kernel modules.
-    environment.etc = singleton
-      { target = "modules-load.d/nixos.conf";
-        source = kernelModulesConf;
+    environment.etc =
+      { "modules-load.d/nixos.conf".source = kernelModulesConf;
       };
 
-    systemd.services."systemd-modules-load" =
+    systemd.services.systemd-modules-load =
       { wantedBy = [ "multi-user.target" ];
         restartTriggers = [ kernelModulesConf ];
         serviceConfig =
diff --git a/nixos/modules/system/boot/kernel_config.nix b/nixos/modules/system/boot/kernel_config.nix
index fbbd0982b2c6..a316782dfc57 100644
--- a/nixos/modules/system/boot/kernel_config.nix
+++ b/nixos/modules/system/boot/kernel_config.nix
@@ -9,7 +9,6 @@ let
   mergeAnswer = winners: locs: defs:
     let
       values = map (x: x.value) defs;
-      freeformAnswer = intersectLists values winners;
       inter = intersectLists values winners;
       winner = head winners;
     in
diff --git a/nixos/modules/system/boot/kexec.nix b/nixos/modules/system/boot/kexec.nix
index 61f9c6d0e7eb..27a8e0217c55 100644
--- a/nixos/modules/system/boot/kexec.nix
+++ b/nixos/modules/system/boot/kexec.nix
@@ -4,7 +4,7 @@
   config = lib.mkIf (lib.any (lib.meta.platformMatch pkgs.stdenv.hostPlatform) pkgs.kexectools.meta.platforms) {
     environment.systemPackages = [ pkgs.kexectools ];
 
-    systemd.services."prepare-kexec" =
+    systemd.services.prepare-kexec =
       { description = "Preparation for kexec";
         wantedBy = [ "kexec.target" ];
         before = [ "systemd-kexec.service" ];
@@ -13,8 +13,18 @@
         path = [ pkgs.kexectools ];
         script =
           ''
+            # Don't load the current system profile if we already have a kernel loaded
+            if [[ 1 = "$(</sys/kernel/kexec_loaded)" ]] ; then
+              echo "kexec kernel has already been loaded, prepare-kexec skipped"
+              exit 0
+            fi
+
             p=$(readlink -f /nix/var/nix/profiles/system)
-            if ! [ -d $p ]; then exit 1; fi
+            if ! [[ -d $p ]]; then
+              echo "Could not find system profile for prepare-kexec"
+              exit 1
+            fi
+            echo "Loading NixOS system via kexec."
             exec kexec --load $p/kernel --initrd=$p/initrd --append="$(cat $p/kernel-params) init=$p/init"
           '';
       };
diff --git a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
index ff90a9b46179..2d27611946e2 100644
--- a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
+++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -7,8 +7,8 @@ let
   generationsDirBuilder = pkgs.substituteAll {
     src = ./generations-dir-builder.sh;
     isExecutable = true;
-    inherit (pkgs.buildPackages) bash;
-    path = with pkgs.buildPackages; [coreutils gnused gnugrep];
+    inherit (pkgs) bash;
+    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
     inherit (config.boot.loader.generationsDir) copyKernels;
   };
 
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
index 5f5dbe1092d0..af39c7bb6841 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -8,7 +8,7 @@ let
 
   timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
 
-  builder = import ./extlinux-conf-builder.nix { pkgs = pkgs.buildPackages; };
+  builder = import ./extlinux-conf-builder.nix { inherit pkgs; };
 in
 {
   options = {
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
index 9ac6b6b12242..576a07c1d272 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
@@ -3,6 +3,6 @@
 pkgs.substituteAll {
   src = ./extlinux-conf-builder.sh;
   isExecutable = true;
-  path = [pkgs.buildPackages.coreutils pkgs.buildPackages.gnused pkgs.buildPackages.gnugrep];
-  inherit (pkgs.buildPackages) bash;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  inherit (pkgs) bash;
 }
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
index c780a89b102c..0092ee92b62f 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.sh
@@ -75,9 +75,8 @@ addEntry() {
 
     copyToKernelsDir "$path/kernel"; kernel=$result
     copyToKernelsDir "$path/initrd"; initrd=$result
-    # XXX UGLY: maybe the system config should have a top-level "dtbs" entry?
-    dtbDir=$(readlink -m "$path/kernel/../dtbs")
-    if [ -d "$dtbDir" ]; then
+    dtbDir=$(readlink -m "$path/dtbs")
+    if [ -e "$dtbDir" ]; then
         copyToKernelsDir "$dtbDir"; dtbs=$result
     fi
 
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 99aa7759c954..9a4db84f7b73 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -8,7 +8,7 @@ let
 
   efi = config.boot.loader.efi;
 
-  grubPkgs = 
+  grubPkgs =
     # Package set of targeted architecture
     if cfg.forcei686 then pkgs.pkgsi686Linux else pkgs;
 
@@ -47,8 +47,8 @@ let
       grub = f grub;
       grubTarget = f (grub.grubTarget or "");
       shell = "${pkgs.runtimeShell}";
-      fullName = (builtins.parseDrvName realGrub.name).name;
-      fullVersion = (builtins.parseDrvName realGrub.name).version;
+      fullName = lib.getName realGrub;
+      fullVersion = lib.getVersion realGrub;
       grubEfi = f grubEfi;
       grubTargetEfi = if cfg.efiSupport && (cfg.version == 2) then f (grubEfi.grubTarget or "") else "";
       bootPath = args.path;
@@ -72,7 +72,7 @@ let
              else "${convertedFont}");
     });
 
-  bootDeviceCounters = fold (device: attr: attr // { "${device}" = (attr."${device}" or 0) + 1; }) {}
+  bootDeviceCounters = fold (device: attr: attr // { ${device} = (attr.${device} or 0) + 1; }) {}
     (concatMap (args: args.devices) cfg.mirroredBoots);
 
   convertedFont = (pkgs.runCommand "grub-font-converted.pf2" {}
@@ -333,7 +333,7 @@ in
       };
 
       backgroundColor = mkOption {
-        type = types.nullOr types.string;
+        type = types.nullOr types.str;
         example = "#7EBAE4";
         default = null;
         description = ''
@@ -360,6 +360,7 @@ in
       font = mkOption {
         type = types.nullOr types.path;
         default = "${realGrub}/share/grub/unicode.pf2";
+        defaultText = ''"''${pkgs.grub2}/share/grub/unicode.pf2"'';
         description = ''
           Path to a TrueType, OpenType, or pf2 font to be used by Grub.
         '';
@@ -398,7 +399,7 @@ in
         example = "text";
         type = types.str;
         description = ''
-          The gfxpayload to pass to GRUB when loading a graphical boot interface under EFI. 
+          The gfxpayload to pass to GRUB when loading a graphical boot interface under EFI.
         '';
       };
 
@@ -407,7 +408,7 @@ in
         example = "keep";
         type = types.str;
         description = ''
-          The gfxpayload to pass to GRUB when loading a graphical boot interface under BIOS. 
+          The gfxpayload to pass to GRUB when loading a graphical boot interface under BIOS.
         '';
       };
 
@@ -534,7 +535,7 @@ in
         default = false;
         type = types.bool;
         description = ''
-          Whether to force the use of a ia32 boot loader on x64 systems. Required 
+          Whether to force the use of a ia32 boot loader on x64 systems. Required
           to install and run NixOS on 64bit x86 systems with 32bit (U)EFI.
         '';
       };
@@ -553,7 +554,7 @@ in
         systemHasTPM = mkOption {
           default = "";
           example = "YES_TPM_is_activated";
-          type = types.string;
+          type = types.str;
           description = ''
             Assertion that the target system has an activated TPM. It is a safety
             check before allowing the activation of 'trustedBoot.enable'. TrustedBoot
@@ -683,7 +684,7 @@ in
           assertion = if args.efiSysMountPoint == null then true else hasPrefix "/" args.efiSysMountPoint;
           message = "EFI paths must be absolute, not ${args.efiSysMountPoint}";
         }
-      ] ++ flip map args.devices (device: {
+      ] ++ forEach args.devices (device: {
         assertion = device == "nodev" || hasPrefix "/" device;
         message = "GRUB devices must be absolute paths, not ${device} in ${args.path}";
       }));
diff --git a/nixos/modules/system/boot/loader/grub/install-grub.pl b/nixos/modules/system/boot/loader/grub/install-grub.pl
index a36b3c180eb4..a09c5dc47618 100644
--- a/nixos/modules/system/boot/loader/grub/install-grub.pl
+++ b/nixos/modules/system/boot/loader/grub/install-grub.pl
@@ -407,6 +407,29 @@ addEntry("NixOS - Default", $defaultConfig);
 
 $conf .= "$extraEntries\n" unless $extraEntriesBeforeNixOS;
 
+# Find all the children of the current default configuration
+# Do not search for grand children
+my @links = sort (glob "$defaultConfig/fine-tune/*");
+foreach my $link (@links) {
+
+    my $entryName = "";
+
+    my $cfgName = readFile("$link/configuration-name");
+
+    my $date = strftime("%F", localtime(lstat($link)->mtime));
+    my $version =
+        -e "$link/nixos-version"
+        ? readFile("$link/nixos-version")
+        : basename((glob(dirname(Cwd::abs_path("$link/kernel")) . "/lib/modules/*"))[0]);
+
+    if ($cfgName) {
+        $entryName = $cfgName;
+    } else {
+        $entryName = "($date - $version)";
+    }
+    addEntry("NixOS - $entryName", $link);
+}
+
 my $grubBootPath = $grubBoot->path;
 # extraEntries could refer to @bootRoot@, which we have to substitute
 $conf =~ s/\@bootRoot\@/$grubBootPath/g;
diff --git a/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixos/modules/system/boot/loader/init-script/init-script.nix
index 385a26036784..374d9524ff1e 100644
--- a/nixos/modules/system/boot/loader/init-script/init-script.nix
+++ b/nixos/modules/system/boot/loader/init-script/init-script.nix
@@ -7,8 +7,8 @@ let
   initScriptBuilder = pkgs.substituteAll {
     src = ./init-script-builder.sh;
     isExecutable = true;
-    inherit (pkgs.buildPackages) bash;
-    path = with pkgs.buildPackages; [coreutils gnused gnugrep];
+    inherit (pkgs) bash;
+    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
   };
 
 in
diff --git a/nixos/modules/system/boot/loader/loader.nix b/nixos/modules/system/boot/loader/loader.nix
index 7fbda9ef0f57..01475f79b9c2 100644
--- a/nixos/modules/system/boot/loader/loader.nix
+++ b/nixos/modules/system/boot/loader/loader.nix
@@ -3,6 +3,11 @@
 with lib;
 
 {
+  imports = [
+    (mkRenamedOptionModule [ "boot" "loader" "grub" "timeout" ] [ "boot" "loader" "timeout" ])
+    (mkRenamedOptionModule [ "boot" "loader" "gummiboot" "timeout" ] [ "boot" "loader" "timeout" ])
+  ];
+
     options = {
         boot.loader.timeout =  mkOption {
             default = 5;
@@ -12,4 +17,4 @@ with lib;
             '';
         };
     };
-}
\ No newline at end of file
+}
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
index 0fb07de10c04..c8b5bf2e61af 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
@@ -71,7 +71,7 @@ addEntry() {
 
     local kernel=$(readlink -f $path/kernel)
     local initrd=$(readlink -f $path/initrd)
-    local dtb_path=$(readlink -f $path/kernel-modules/dtbs)
+    local dtb_path=$(readlink -f $path/dtbs)
 
     if test -n "@copyKernels@"; then
         copyToKernelsDir $kernel; kernel=$result
@@ -113,10 +113,18 @@ done
 fwdir=@firmware@/share/raspberrypi/boot/
 copyForced $fwdir/bootcode.bin  $target/bootcode.bin
 copyForced $fwdir/fixup.dat     $target/fixup.dat
+copyForced $fwdir/fixup4.dat    $target/fixup4.dat
+copyForced $fwdir/fixup4cd.dat  $target/fixup4cd.dat
+copyForced $fwdir/fixup4db.dat  $target/fixup4db.dat
+copyForced $fwdir/fixup4x.dat   $target/fixup4x.dat
 copyForced $fwdir/fixup_cd.dat  $target/fixup_cd.dat
 copyForced $fwdir/fixup_db.dat  $target/fixup_db.dat
 copyForced $fwdir/fixup_x.dat   $target/fixup_x.dat
 copyForced $fwdir/start.elf     $target/start.elf
+copyForced $fwdir/start4.elf    $target/start4.elf
+copyForced $fwdir/start4cd.elf  $target/start4cd.elf
+copyForced $fwdir/start4db.elf  $target/start4db.elf
+copyForced $fwdir/start4x.elf   $target/start4x.elf
 copyForced $fwdir/start_cd.elf  $target/start_cd.elf
 copyForced $fwdir/start_db.elf  $target/start_db.elf
 copyForced $fwdir/start_x.elf   $target/start_x.elf
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
index 7db60daa60b8..337afe9ef628 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -10,7 +10,7 @@ let
   builderUboot = import ./uboot-builder.nix { inherit pkgs configTxt; inherit (cfg) version; };
   builderGeneric = import ./raspberrypi-builder.nix { inherit pkgs configTxt; };
 
-  builder = 
+  builder =
     if cfg.uboot.enable then
       "${builderUboot} -g ${toString cfg.uboot.configurationLimit} -t ${timeoutStr} -c"
     else
@@ -59,7 +59,7 @@ in
 
       version = mkOption {
         default = 2;
-        type = types.enum [ 0 1 2 3 ];
+        type = types.enum [ 0 1 2 3 4 ];
         description = ''
         '';
       };
@@ -86,7 +86,7 @@ in
 
       firmwareConfig = mkOption {
         default = null;
-        type = types.nullOr types.string;
+        type = types.nullOr types.lines;
         description = ''
           Extra options that will be appended to <literal>/boot/config.txt</literal> file.
           For possible values, see: https://www.raspberrypi.org/documentation/configuration/config-txt/
@@ -97,8 +97,8 @@ in
 
   config = mkIf cfg.enable {
     assertions = singleton {
-      assertion = !pkgs.stdenv.hostPlatform.isAarch64 || cfg.version == 3;
-      message = "Only Raspberry Pi 3 supports aarch64.";
+      assertion = !pkgs.stdenv.hostPlatform.isAarch64 || cfg.version >= 3;
+      message = "Only Raspberry Pi >= 3 supports aarch64.";
     };
 
     system.build.installBootLoader = builder;
diff --git a/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
index 94599a0081c6..1dc397e521b4 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
@@ -10,25 +10,28 @@ let
       pkgs.ubootRaspberryPi
     else if version == 2 then
       pkgs.ubootRaspberryPi2
-    else
+    else if version == 3 then
       if isAarch64 then
         pkgs.ubootRaspberryPi3_64bit
       else
-        pkgs.ubootRaspberryPi3_32bit;
+        pkgs.ubootRaspberryPi3_32bit
+    else
+      throw "U-Boot is not yet supported on the raspberry pi 4.";
 
   extlinuxConfBuilder =
     import ../generic-extlinux-compatible/extlinux-conf-builder.nix {
-      pkgs = pkgs.buildPackages;
+      inherit pkgs;
     };
 in
 pkgs.substituteAll {
   src = ./uboot-builder.sh;
   isExecutable = true;
-  inherit (pkgs.buildPackages) bash;
-  path = with pkgs.buildPackages; [coreutils gnused gnugrep];
+  inherit (pkgs) bash;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
   firmware = pkgs.raspberrypifw;
   inherit uboot;
   inherit configTxt;
   inherit extlinuxConfBuilder;
   inherit version;
 }
+
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index 6016a85ea061..f48a085ce57a 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
@@ -33,6 +33,15 @@ initrd {initrd}
 options {kernel_params}
 """
 
+# The boot loader entry for memtest86.
+#
+# TODO: This is hard-coded to use the 64-bit EFI app, but it could probably
+# be updated to use the 32-bit EFI app on 32-bit systems.  The 32-bit EFI
+# app filename is BOOTIA32.efi.
+MEMTEST_BOOT_ENTRY = """title MemTest86
+efi /efi/memtest86/BOOTX64.efi
+"""
+
 def write_loader_conf(profile, generation):
     with open("@efiSysMountPoint@/loader/loader.conf.tmp", 'w') as f:
         if "@timeout@" != "":
@@ -124,7 +133,9 @@ def get_generations(profile=None):
         universal_newlines=True)
     gen_lines = gen_list.split('\n')
     gen_lines.pop()
-    return [ (profile, int(line.split()[0])) for line in gen_lines ]
+
+    configurationLimit = @configurationLimit@
+    return [ (profile, int(line.split()[0])) for line in gen_lines ][-configurationLimit:]
 
 def remove_old_entries(gens):
     rex_profile = re.compile("^@efiSysMountPoint@/loader/entries/nixos-(.*)-generation-.*\.conf$")
@@ -145,7 +156,7 @@ def remove_old_entries(gens):
         except ValueError:
             pass
     for path in glob.iglob("@efiSysMountPoint@/efi/nixos/*"):
-        if not path in known_paths:
+        if not path in known_paths and not os.path.isdir(path):
             os.unlink(path)
 
 def get_profiles():
@@ -199,6 +210,24 @@ def main():
         if os.readlink(system_dir(*gen)) == args.default_config:
             write_loader_conf(*gen)
 
+    memtest_entry_file = "@efiSysMountPoint@/loader/entries/memtest86.conf"
+    if os.path.exists(memtest_entry_file):
+        os.unlink(memtest_entry_file)
+    shutil.rmtree("@efiSysMountPoint@/efi/memtest86", ignore_errors=True)
+    if "@memtest86@" != "":
+        mkdir_p("@efiSysMountPoint@/efi/memtest86")
+        for path in glob.iglob("@memtest86@/*"):
+            if os.path.isdir(path):
+                shutil.copytree(path, os.path.join("@efiSysMountPoint@/efi/memtest86", os.path.basename(path)))
+            else:
+                shutil.copy(path, "@efiSysMountPoint@/efi/memtest86/")
+
+        memtest_entry_file = "@efiSysMountPoint@/loader/entries/memtest86.conf"
+        memtest_entry_file_tmp_path = "%s.tmp" % memtest_entry_file
+        with open(memtest_entry_file_tmp_path, 'w') as f:
+            f.write(MEMTEST_BOOT_ENTRY)
+        os.rename(memtest_entry_file_tmp_path, memtest_entry_file)
+
     # Since fat32 provides little recovery facilities after a crash,
     # it can leave the system in an unbootable state, when a crash/outage
     # happens shortly after an update. To decrease the likelihood of this
diff --git a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
index 9ad2a2779e18..22d459ceb04f 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -12,7 +12,7 @@ let
 
     isExecutable = true;
 
-    inherit (pkgs.buildPackages) python3;
+    inherit (pkgs) python3;
 
     systemd = config.systemd.package;
 
@@ -22,9 +22,13 @@ let
 
     editor = if cfg.editor then "True" else "False";
 
+    configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit;
+
     inherit (cfg) consoleMode;
 
     inherit (efi) efiSysMountPoint canTouchEfiVariables;
+
+    memtest86 = if cfg.memtest86.enable then pkgs.memtest86-efi else "";
   };
 in {
 
@@ -55,6 +59,19 @@ in {
       '';
     };
 
+    configurationLimit = mkOption {
+      default = null;
+      example = 120;
+      type = types.nullOr types.int;
+      description = ''
+        Maximum number of latest generations in the boot menu. 
+        Useful to prevent boot partition running out of disk space.
+
+        <literal>null</literal> means no limit i.e. all generations 
+        that were not garbage collected yet.
+      '';
+    };
+
     consoleMode = mkOption {
       default = "keep";
 
@@ -62,8 +79,7 @@ in {
 
       description = ''
         The resolution of the console. The following values are valid:
-        </para>
-        <para>
+
         <itemizedlist>
           <listitem><para>
             <literal>"0"</literal>: Standard UEFI 80x25 mode
@@ -86,6 +102,19 @@ in {
         </itemizedlist>
       '';
     };
+
+    memtest86 = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Make MemTest86 available from the systemd-boot menu. MemTest86 is a
+          program for testing memory.  MemTest86 is an unfree program, so
+          this requires <literal>allowUnfree</literal> to be set to
+          <literal>true</literal>.
+        '';
+      };
+    };
   };
 
   config = mkIf cfg.enable {
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index aa4a5f8abcce..31f1e22cda32 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   luks = config.boot.initrd.luks;
+  kernelPackages = config.boot.kernelPackages;
 
   commonFunctions = ''
     die() {
@@ -76,6 +77,33 @@ let
         fi
         return 0
     }
+
+    wait_gpgcard() {
+        local secs="''${1:-10}"
+
+        gpg --card-status > /dev/null 2> /dev/null
+        if [ $? != 0 ]; then
+            echo -n "Waiting $secs seconds for GPG Card to appear"
+            local success=false
+            for try in $(seq $secs); do
+                echo -n .
+                sleep 1
+                gpg --card-status > /dev/null 2> /dev/null
+                if [ $? == 0 ]; then
+                    success=true
+                    break
+                fi
+            done
+            if [ $success == true ]; then
+                echo " - success";
+                return 0
+            else
+                echo " - failure";
+                return 1
+            fi
+        fi
+        return 0
+    }
   '';
 
   preCommands = ''
@@ -87,9 +115,19 @@ let
     mkdir -p /crypt-ramfs
     mount -t ramfs none /crypt-ramfs
 
+    # Cryptsetup locking directory
+    mkdir -p /run/cryptsetup
+
     # For Yubikey salt storage
     mkdir -p /crypt-storage
 
+    ${optionalString luks.gpgSupport ''
+    export GPG_TTY=$(tty)
+    export GNUPGHOME=/crypt-ramfs/.gnupg
+
+    gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null
+    ''}
+
     # Disable all input echo for the whole stage. We could use read -s
     # instead but that would ocasionally leak characters between read
     # invocations.
@@ -102,7 +140,7 @@ let
     umount /crypt-ramfs 2>/dev/null
   '';
 
-  openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name;
+  openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, fallbackToPassword, ... }: assert name' == name;
   let
     csopen   = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}";
     cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}";
@@ -179,7 +217,7 @@ let
         ''}
     }
 
-    ${if luks.yubikeySupport && (yubikey != null) then ''
+    ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
     # Yubikey
     rbtohex() {
         ( od -An -vtx1 | tr -d ' \n' )
@@ -275,7 +313,7 @@ let
         umount /crypt-storage
     }
 
-    open_yubikey() {
+    open_with_hardware() {
         if wait_yubikey ${toString yubikey.gracePeriod}; then
             do_open_yubikey
         else
@@ -283,8 +321,99 @@ let
             open_normally
         fi
     }
+    ''}
+
+    ${optionalString (luks.gpgSupport && (gpgCard != null)) ''
+
+    do_open_gpg_card() {
+        # Make all of these local to this function
+        # to prevent their values being leaked
+        local pin
+        local opened
 
-    open_yubikey
+        gpg --import /gpg-keys/${device}/pubkey.asc > /dev/null 2> /dev/null
+
+        gpg --card-status > /dev/null 2> /dev/null
+
+        for try in $(seq 3); do
+            echo -n "PIN for GPG Card associated with device ${device}: "
+            pin=
+            while true; do
+                if [ -e /crypt-ramfs/passphrase ]; then
+                    echo "reused"
+                    pin=$(cat /crypt-ramfs/passphrase)
+                    break
+                else
+                    # and try reading it from /dev/console with a timeout
+                    IFS= read -t 1 -r pin
+                    if [ -n "$pin" ]; then
+                       ${if luks.reusePassphrases then ''
+                         # remember it for the next device
+                         echo -n "$pin" > /crypt-ramfs/passphrase
+                       '' else ''
+                         # Don't save it to ramfs. We are very paranoid
+                       ''}
+                       echo
+                       break
+                    fi
+                fi
+            done
+            echo -n "Verifying passphrase for ${device}..."
+            echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null
+            if [ $? == 0 ]; then
+                echo " - success"
+                ${if luks.reusePassphrases then ''
+                  # we don't rm here because we might reuse it for the next device
+                '' else ''
+                  rm -f /crypt-ramfs/passphrase
+                ''}
+                break
+            else
+                echo " - failure"
+                # ask for a different one
+                rm -f /crypt-ramfs/passphrase
+            fi
+        done
+
+        [ "$opened" == false ] && die "Maximum authentication errors reached"
+    }
+
+    open_with_hardware() {
+        if wait_gpgcard ${toString gpgCard.gracePeriod}; then
+            do_open_gpg_card
+        else
+            echo "No GPG Card found, falling back to normal open procedure"
+            open_normally
+        fi
+    }
+    ''}
+
+    ${optionalString (luks.fido2Support && (fido2.credential != null)) ''
+
+    open_with_hardware() {
+      local passsphrase
+
+        ${if fido2.passwordLess then ''
+          export passphrase=""
+        '' else ''
+          read -rsp "FIDO2 salt for ${device}: " passphrase
+          echo
+        ''}
+        ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") ''
+          echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest."
+          echo "Please move your mouse to create needed randomness."
+        ''}
+          echo "Waiting for your FIDO2 device..."
+          fido2luks -i open ${device} ${name} ${fido2.credential} --await-dev ${toString fido2.gracePeriod} --salt string:$passphrase
+        if [ $? -ne 0 ]; then
+          echo "No FIDO2 key found, falling back to normal open procedure"
+          open_normally
+        fi
+    }
+    ''}
+
+    ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) then ''
+    open_with_hardware
     '' else ''
     open_normally
     ''}
@@ -313,6 +442,9 @@ let
 
 in
 {
+  imports = [
+    (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
+  ];
 
   options = {
 
@@ -334,6 +466,7 @@ in
       default =
         [ "aes" "aes_generic" "blowfish" "twofish"
           "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
+          "af_alg" "algif_skcipher"
 
           (if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "aes_x86_64" else "aes_i586")
         ];
@@ -371,7 +504,7 @@ in
 
     boot.initrd.luks.devices = mkOption {
       default = { };
-      example = { "luksroot".device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
+      example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
       description = ''
         The encrypted disk that should be opened before the root
         filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
@@ -470,6 +603,61 @@ in
             '';
           };
 
+          gpgCard = mkOption {
+            default = null;
+            description = ''
+              The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
+              If null (the default), GPG-Smartcard will be disabled for this device.
+            '';
+
+            type = with types; nullOr (submodule {
+              options = {
+                gracePeriod = mkOption {
+                  default = 10;
+                  type = types.int;
+                  description = "Time in seconds to wait for the GPG Smartcard.";
+                };
+
+                encryptedPass = mkOption {
+                  default = "";
+                  type = types.path;
+                  description = "Path to the GPG encrypted passphrase.";
+                };
+
+                publicKey = mkOption {
+                  default = "";
+                  type = types.path;
+                  description = "Path to the Public Key.";
+                };
+              };
+            });
+          };
+
+          fido2 = {
+            credential = mkOption {
+              default = null;
+              example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
+              type = types.str;
+              description = "The FIDO2 credential ID.";
+            };
+
+            gracePeriod = mkOption {
+              default = 10;
+              type = types.int;
+              description = "Time in seconds to wait for the FIDO2 key.";
+            };
+
+            passwordLess = mkOption {
+              default = false;
+              type = types.bool;
+              description = ''
+                Defines whatever to use an empty string as a default salt.
+
+                Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>.
+              '';
+            };
+          };
+
           yubikey = mkOption {
             default = null;
             description = ''
@@ -551,6 +739,14 @@ in
       }));
     };
 
+    boot.initrd.luks.gpgSupport = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enables support for authenticating with a GPG encrypted password.
+      '';
+    };
+
     boot.initrd.luks.yubikeySupport = mkOption {
       default = false;
       type = types.bool;
@@ -560,10 +756,33 @@ in
             and a Yubikey to work with this feature.
           '';
     };
+
+    boot.initrd.luks.fido2Support = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Enables support for authenticating with FIDO2 devices.
+      '';
+    };
+
   };
 
   config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
 
+    assertions =
+      [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
+          message = "Yubikey and GPG Card may not be used at the same time.";
+        }
+
+        { assertion = !(luks.gpgSupport && luks.fido2Support);
+          message = "FIDO2 and GPG Card may not be used at the same time.";
+        }
+
+        { assertion = !(luks.fido2Support && luks.yubikeySupport);
+          message = "FIDO2 and Yubikey may not be used at the same time.";
+        }
+      ];
+
     # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
     boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
       ["firewire_ohci" "firewire_core" "firewire_sbp2"];
@@ -600,6 +819,28 @@ in
         EOF
         chmod +x $out/bin/openssl-wrap
       ''}
+
+      ${optionalString luks.fido2Support ''
+        copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks
+      ''}
+
+
+      ${optionalString luks.gpgSupport ''
+        copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
+        copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
+        copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
+
+        ${concatMapStringsSep "\n" (x:
+          if x.gpgCard != null then
+            ''
+              mkdir -p $out/secrets/gpg-keys/${x.device}
+              cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
+              cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
+            ''
+          else ""
+          ) (attrValues luks.devices)
+        }
+      ''}
     '';
 
     boot.initrd.extraUtilsCommandsTest = ''
@@ -609,6 +850,14 @@ in
         $out/bin/ykinfo -V
         $out/bin/openssl-wrap version
       ''}
+      ${optionalString luks.gpgSupport ''
+        $out/bin/gpg --version
+        $out/bin/gpg-agent --version
+        $out/bin/scdaemon --version
+      ''}
+      ${optionalString luks.fido2Support ''
+        $out/bin/fido2luks --version
+      ''}
     '';
 
     boot.initrd.preFailCommands = postCommands;
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 63a6f7fbe099..56a9d6b11380 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -10,8 +10,8 @@ let
 
   checkLink = checkUnitConfig "Link" [
     (assertOnlyFields [
-      "Description" "Alias" "MACAddressPolicy" "MACAddress" "NamePolicy" "Name"
-      "MTUBytes" "BitsPerSecond" "Duplex" "AutoNegotiation" "WakeOnLan" "Port"
+      "Description" "Alias" "MACAddressPolicy" "MACAddress" "NamePolicy" "Name" "OriginalName"
+      "MTUBytes" "BitsPerSecond" "Duplex" "AutoNegotiation" "WakeOnLan" "Port" "Advertise"
       "TCPSegmentationOffload" "TCP6SegmentationOffload" "GenericSegmentationOffload"
       "GenericReceiveOffload" "LargeReceiveOffload" "RxChannels" "TxChannels"
       "OtherChannels" "CombinedChannels"
@@ -49,12 +49,33 @@ let
     (assertValueOneOf "Kind" [
       "bond" "bridge" "dummy" "gre" "gretap" "ip6gre" "ip6tnl" "ip6gretap" "ipip"
       "ipvlan" "macvlan" "macvtap" "sit" "tap" "tun" "veth" "vlan" "vti" "vti6"
-      "vxlan" "geneve" "vrf" "vcan" "vxcan" "wireguard" "netdevsim"
+      "vxlan" "geneve" "vrf" "vcan" "vxcan" "wireguard" "netdevsim" "xfrm"
     ])
     (assertByteFormat "MTUBytes")
     (assertMacAddress "MACAddress")
   ];
 
+  # NOTE The PrivateKey directive is missing on purpose here, please
+  # do not add it to this list. The nix store is world-readable let's
+  # refrain ourselves from providing a footgun.
+  checkWireGuard = checkUnitConfig "WireGuard" [
+    (assertOnlyFields [
+      "PrivateKeyFile" "ListenPort" "FwMark"
+    ])
+    (assertRange "FwMark" 1 4294967295)
+  ];
+
+  # NOTE The PresharedKey directive is missing on purpose here, please
+  # do not add it to this list. The nix store is world-readable,let's
+  # refrain ourselves from providing a footgun.
+  checkWireGuardPeer = checkUnitConfig "WireGuardPeer" [
+    (assertOnlyFields [
+      "PublicKey" "PresharedKeyFile" "AllowedIPs"
+      "Endpoint" "PersistentKeepalive"
+    ])
+    (assertRange "PersistentKeepalive" 1 65535)
+  ];
+
   checkVlan = checkUnitConfig "VLAN" [
     (assertOnlyFields ["Id" "GVRP" "MVRP" "LooseBinding" "ReorderHeader"])
     (assertRange "Id" 0 4094)
@@ -151,6 +172,14 @@ let
     (assertValueOneOf "AllSlavesActive" boolValues)
   ];
 
+  checkXfrm = checkUnitConfig "Xfrm" [
+    (assertOnlyFields [
+      "InterfaceId" "Independent"
+    ])
+    (assertRange "InterfaceId" 1 4294967295)
+    (assertValueOneOf "Independent" boolValues)
+  ];
+
   checkNetwork = checkUnitConfig "Network" [
     (assertOnlyFields [
       "Description" "DHCP" "DHCPServer" "LinkLocalAddressing" "IPv4LLRoute"
@@ -161,12 +190,12 @@ let
       "IPv6HopLimit" "IPv4ProxyARP" "IPv6ProxyNDP" "IPv6ProxyNDPAddress"
       "IPv6PrefixDelegation" "IPv6MTUBytes" "Bridge" "Bond" "VRF" "VLAN"
       "IPVLAN" "MACVLAN" "VXLAN" "Tunnel" "ActiveSlave" "PrimarySlave"
-      "ConfigureWithoutCarrier"
+      "ConfigureWithoutCarrier" "Xfrm"
     ])
     # Note: For DHCP the values both, none, v4, v6 are deprecated
     (assertValueOneOf "DHCP" ["yes" "no" "ipv4" "ipv6" "both" "none" "v4" "v6"])
     (assertValueOneOf "DHCPServer" boolValues)
-    (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6"])
+    (assertValueOneOf "LinkLocalAddressing" ["yes" "no" "ipv4" "ipv6" "ipv4-fallback" "fallback"])
     (assertValueOneOf "IPv4LLRoute" boolValues)
     (assertValueOneOf "LLMNR" ["yes" "resolve" "no"])
     (assertValueOneOf "MulticastDNS" ["yes" "resolve" "no"])
@@ -180,7 +209,7 @@ let
     (assertValueOneOf "IPv6AcceptRA" boolValues)
     (assertValueOneOf "IPv4ProxyARP" boolValues)
     (assertValueOneOf "IPv6ProxyNDP" boolValues)
-    (assertValueOneOf "IPv6PrefixDelegation" boolValues)
+    (assertValueOneOf "IPv6PrefixDelegation" (boolValues ++ [ "dhcpv6" "static" ]))
     (assertValueOneOf "ActiveSlave" boolValues)
     (assertValueOneOf "PrimarySlave" boolValues)
     (assertValueOneOf "ConfigureWithoutCarrier" boolValues)
@@ -203,7 +232,7 @@ let
 
   checkRoute = checkUnitConfig "Route" [
     (assertOnlyFields [
-      "Gateway" "GatewayOnlink" "Destination" "Source" "Metric"
+      "Gateway" "GatewayOnLink" "Destination" "Source" "Metric"
       "IPv6Preference" "Scope" "PreferredSource" "Table" "Protocol" "Type"
       "InitialCongestionWindow" "InitialAdvertisedReceiveWindow" "QuickAck"
       "MTUBytes"
@@ -255,7 +284,7 @@ let
     (assertValueOneOf "ARP" boolValues)
     (assertValueOneOf "Multicast" boolValues)
     (assertValueOneOf "Unmanaged" boolValues)
-    (assertValueOneOf "RequiredForOnline" boolValues)
+    (assertValueOneOf "RequiredForOnline" (boolValues ++ ["off" "no-carrier" "dormant" "degraded-carrier" "carrier" "degraded" "enslaved" "routable"]))
   ];
 
 
@@ -320,6 +349,46 @@ let
       '';
     };
 
+    wireguardConfig = mkOption {
+      default = {};
+      example = {
+        PrivateKeyFile = "/etc/wireguard/secret.key";
+        ListenPort = 51820;
+        FwMark = 42;
+      };
+      type = types.addCheck (types.attrsOf unitOption) checkWireGuard;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[WireGuard]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+        Use <literal>PrivateKeyFile</literal> instead of
+        <literal>PrivateKey</literal>: the nix store is
+        world-readable.
+      '';
+    };
+
+    wireguardPeers = mkOption {
+      default = [];
+      example = [ { wireguardPeerConfig={
+        Endpoint = "192.168.1.1:51820";
+        PublicKey = "27s0OvaBBdHoJYkH9osZpjpgSOVNw+RaKfboT/Sfq0g=";
+        PresharedKeyFile = "/etc/wireguard/psk.key";
+        AllowedIPs = [ "10.0.0.1/32" ];
+        PersistentKeepalive = 15;
+      };}];
+      type = with types; listOf (submodule wireguardPeerOptions);
+      description = ''
+        Each item in this array specifies an option in the
+        <literal>[WireGuardPeer]</literal> section of the unit. See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+        Use <literal>PresharedKeyFile</literal> instead of
+        <literal>PresharedKey</literal>: the nix store is
+        world-readable.
+      '';
+    };
+
     vlanConfig = mkOption {
       default = {};
       example = { Id = "4"; };
@@ -416,6 +485,18 @@ let
       '';
     };
 
+    xfrmConfig = mkOption {
+      default = {};
+      example = { InterfaceId = 1; };
+      type = types.addCheck (types.attrsOf unitOption) checkXfrm;
+      description = ''
+        Each attribute in this set specifies an option in the
+        <literal>[Xfrm]</literal> section of the unit.  See
+        <citerefentry><refentrytitle>systemd.netdev</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
   };
 
   addressOptions = {
@@ -450,6 +531,23 @@ let
     };
   };
 
+  wireguardPeerOptions = {
+    options = {
+      wireguardPeerConfig = mkOption {
+        default = {};
+        example = { };
+        type = types.addCheck (types.attrsOf unitOption) checkWireGuardPeer;
+        description = ''
+          Each attribute in this set specifies an option in the
+          <literal>[WireGuardPeer]</literal> section of the unit.  See
+          <citerefentry><refentrytitle>systemd.network</refentrytitle>
+          <manvolnum>5</manvolnum></citerefentry> for details.
+        '';
+      };
+    };
+  };
+
+
   networkOptions = commonNetworkOptions // {
 
     networkConfig = mkOption {
@@ -634,6 +732,16 @@ let
       '';
     };
 
+    xfrm = mkOption {
+      default = [ ];
+      type = types.listOf types.str;
+      description = ''
+        A list of xfrm interfaces to be added to the network section of the
+        unit.  See <citerefentry><refentrytitle>systemd.network</refentrytitle>
+        <manvolnum>5</manvolnum></citerefentry> for details.
+      '';
+    };
+
     addresses = mkOption {
       default = [ ];
       type = with types; listOf (submodule addressOptions);
@@ -669,7 +777,7 @@ let
     };
   };
 
-  commonMatchText = def: ''
+  commonMatchText = def: optionalString (def.matchConfig != {}) ''
     [Match]
     ${attrsToSection def.matchConfig}
   '';
@@ -732,6 +840,21 @@ let
             ${attrsToSection def.bondConfig}
 
           ''}
+          ${optionalString (def.xfrmConfig != { }) ''
+            [Xfrm]
+            ${attrsToSection def.xfrmConfig}
+
+          ''}
+          ${optionalString (def.wireguardConfig != { }) ''
+            [WireGuard]
+            ${attrsToSection def.wireguardConfig}
+
+          ''}
+          ${flip concatMapStrings def.wireguardPeers (x: ''
+            [WireGuardPeer]
+            ${attrsToSection x.wireguardPeerConfig}
+
+          '')}
           ${def.extraConfig}
         '';
     };
@@ -759,6 +882,7 @@ let
           ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)}
           ${concatStringsSep "\n" (map (s: "VXLAN=${s}") def.vxlan)}
           ${concatStringsSep "\n" (map (s: "Tunnel=${s}") def.tunnel)}
+          ${concatStringsSep "\n" (map (s: "Xfrm=${s}") def.xfrm)}
 
           ${optionalString (def.dhcpConfig != { }) ''
             [DHCP]
@@ -784,10 +908,10 @@ let
         '';
     };
 
-  unitFiles = map (name: {
-    target = "systemd/network/${name}";
-    source = "${cfg.units.${name}.unit}/${name}";
-  }) (attrNames cfg.units);
+  unitFiles = listToAttrs (map (name: {
+    name = "systemd/network/${name}";
+    value.source = "${cfg.units.${name}.unit}/${name}";
+  }) (attrNames cfg.units));
 in
 
 {
@@ -836,6 +960,8 @@ in
 
   config = mkIf config.systemd.network.enable {
 
+    users.users.systemd-network.group = "systemd-network";
+
     systemd.additionalUpstreamSystemUnits = [
       "systemd-networkd.service" "systemd-networkd-wait-online.service"
     ];
@@ -848,7 +974,7 @@ in
 
     systemd.services.systemd-networkd = {
       wantedBy = [ "multi-user.target" ];
-      restartTriggers = map (f: f.source) (unitFiles);
+      restartTriggers = attrNames unitFiles;
       # prevent race condition with interface renaming (#39069)
       requires = [ "systemd-udev-settle.service" ];
       after = [ "systemd-udev-settle.service" ];
diff --git a/nixos/modules/system/boot/plymouth.nix b/nixos/modules/system/boot/plymouth.nix
index e4223bae7d32..23fce22366d8 100644
--- a/nixos/modules/system/boot/plymouth.nix
+++ b/nixos/modules/system/boot/plymouth.nix
@@ -5,23 +5,27 @@ with lib;
 let
 
   inherit (pkgs) plymouth;
+  inherit (pkgs) nixos-icons;
 
   cfg = config.boot.plymouth;
 
-  breezePlymouth = pkgs.breeze-plymouth.override {
-    nixosBranding = true;
-    nixosVersion = config.system.nixos.release;
+  nixosBreezePlymouth = pkgs.breeze-plymouth.override {
+    logoFile = cfg.logo;
+    logoName = "nixos";
+    osName = "NixOS";
+    osVersion = config.system.nixos.release;
   };
 
   themesEnv = pkgs.buildEnv {
     name = "plymouth-themes";
-    paths = [ plymouth breezePlymouth ] ++ cfg.themePackages;
+    paths = [ plymouth ] ++ cfg.themePackages;
   };
 
   configFile = pkgs.writeText "plymouthd.conf" ''
     [Daemon]
     ShowDelay=0
     Theme=${cfg.theme}
+    ${cfg.extraConfig}
   '';
 
 in
@@ -35,7 +39,7 @@ in
       enable = mkEnableOption "Plymouth boot splash screen";
 
       themePackages = mkOption {
-        default = [];
+        default = [ nixosBreezePlymouth ];
         type = types.listOf types.package;
         description = ''
           Extra theme packages for plymouth.
@@ -52,10 +56,7 @@ in
 
       logo = mkOption {
         type = types.path;
-        default = pkgs.fetchurl {
-          url = "https://nixos.org/logo/nixos-hires.png";
-          sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
-        };
+        default = "${nixos-icons}/share/icons/hicolor/128x128/apps/nix-snowflake.png";
         defaultText = ''pkgs.fetchurl {
           url = "https://nixos.org/logo/nixos-hires.png";
           sha256 = "1ivzgd7iz0i06y36p8m5w48fd8pjqwxhdaavc0pxs7w1g7mcy5si";
@@ -65,6 +66,15 @@ in
         '';
       };
 
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Literal string to append to <literal>configFile</literal>
+          and the config file generated by the plymouth module.
+        '';
+      };
+
     };
 
   };
@@ -88,10 +98,7 @@ in
     systemd.services.plymouth-kexec.wantedBy = [ "kexec.target" ];
     systemd.services.plymouth-halt.wantedBy = [ "halt.target" ];
     systemd.services.plymouth-quit-wait.wantedBy = [ "multi-user.target" ];
-    systemd.services.plymouth-quit = {
-      wantedBy = [ "multi-user.target" ];
-      after = [ "display-manager.service" ];
-    };
+    systemd.services.plymouth-quit.wantedBy = [ "multi-user.target" ];
     systemd.services.plymouth-poweroff.wantedBy = [ "poweroff.target" ];
     systemd.services.plymouth-reboot.wantedBy = [ "reboot.target" ];
     systemd.services.plymouth-read-write.wantedBy = [ "sysinit.target" ];
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
index fc68904ae080..da61c64faf8b 100644
--- a/nixos/modules/system/boot/resolved.nix
+++ b/nixos/modules/system/boot/resolved.nix
@@ -3,6 +3,10 @@
 with lib;
 let
   cfg = config.services.resolved;
+
+  dnsmasqResolve = config.services.dnsmasq.enable &&
+                   config.services.dnsmasq.resolveLocalQueries;
+
 in
 {
 
@@ -35,7 +39,7 @@ in
         when resolving single-label host names (domain names which
         contain no dot), in order to qualify them into fully-qualified
         domain names (FQDNs).
-        </para><para>
+
         For compatibility reasons, if this setting is not specified,
         the search domains listed in
         <filename>/etc/resolv.conf</filename> are used instead, if
@@ -50,8 +54,9 @@ in
       description = ''
         Controls Link-Local Multicast Name Resolution support
         (RFC 4795) on the local host.
-        </para><para>
+
         If set to
+
         <variablelist>
         <varlistentry>
           <term><literal>"true"</literal></term>
@@ -125,6 +130,14 @@ in
 
   config = mkIf cfg.enable {
 
+    assertions = [
+      { assertion = !config.networking.useHostResolvConf;
+        message = "Using host resolv.conf is not supported with systemd-resolved";
+      }
+    ];
+
+    users.users.resolved.group = "systemd-resolve";
+
     systemd.additionalUpstreamSystemUnits = [
       "systemd-resolved.service"
     ];
@@ -134,21 +147,30 @@ in
       restartTriggers = [ config.environment.etc."systemd/resolved.conf".source ];
     };
 
-    environment.etc."systemd/resolved.conf".text = ''
-      [Resolve]
-      ${optionalString (config.networking.nameservers != [])
-        "DNS=${concatStringsSep " " config.networking.nameservers}"}
-      ${optionalString (cfg.fallbackDns != [])
-        "FallbackDNS=${concatStringsSep " " cfg.fallbackDns}"}
-      ${optionalString (cfg.domains != [])
-        "Domains=${concatStringsSep " " cfg.domains}"}
-      LLMNR=${cfg.llmnr}
-      DNSSEC=${cfg.dnssec}
-      ${config.services.resolved.extraConfig}
-    '';
+    environment.etc = {
+      "systemd/resolved.conf".text = ''
+        [Resolve]
+        ${optionalString (config.networking.nameservers != [])
+          "DNS=${concatStringsSep " " config.networking.nameservers}"}
+        ${optionalString (cfg.fallbackDns != [])
+          "FallbackDNS=${concatStringsSep " " cfg.fallbackDns}"}
+        ${optionalString (cfg.domains != [])
+          "Domains=${concatStringsSep " " cfg.domains}"}
+        LLMNR=${cfg.llmnr}
+        DNSSEC=${cfg.dnssec}
+        ${config.services.resolved.extraConfig}
+      '';
+
+      # symlink the dynamic stub resolver of resolv.conf as recommended by upstream:
+      # https://www.freedesktop.org/software/systemd/man/systemd-resolved.html#/etc/resolv.conf
+      "resolv.conf".source = "/run/systemd/resolve/stub-resolv.conf";
+    } // optionalAttrs dnsmasqResolve {
+      "dnsmasq-resolv.conf".source = "/run/systemd/resolve/resolv.conf";
+    };
 
     # If networkmanager is enabled, ask it to interface with resolved.
     networking.networkmanager.dns = "systemd-resolved";
+
   };
 
 }
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index 67cbe720ddc3..f520bf54ad1b 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -44,13 +44,13 @@ EOF
   *) to ignore the error and continue
 EOF
 
-    read reply
+    read -n 1 reply
 
     if [ -n "$allowShell" -a "$reply" = f ]; then
         exec setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console"
     elif [ -n "$allowShell" -a "$reply" = i ]; then
         echo "Starting interactive shell..."
-        setsid @shell@ -c "@shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail
+        setsid @shell@ -c "exec @shell@ < /dev/$console >/dev/$console 2>/dev/$console" || fail
     elif [ "$reply" = r ]; then
         echo "Rebooting..."
         reboot -f
@@ -183,6 +183,12 @@ for o in $(cat /proc/cmdline); do
         copytoram)
             copytoram=1
             ;;
+        findiso=*)
+            # if an iso name is supplied, try to find the device where
+            # the iso resides on
+            set -- $(IFS==; echo $o)
+            isoPath=$2
+            ;;
     esac
 done
 
@@ -442,6 +448,27 @@ if test -e /sys/power/resume -a -e /sys/power/disk; then
     fi
 fi
 
+# If we have a path to an iso file, find the iso and link it to /dev/root
+if [ -n "$isoPath" ]; then
+  mkdir -p /findiso
+
+  for delay in 5 10; do
+    blkid | while read -r line; do
+      device=$(echo "$line" | sed 's/:.*//')
+      type=$(echo "$line" | sed 's/.*TYPE="\([^"]*\)".*/\1/')
+
+      mount -t "$type" "$device" /findiso
+      if [ -e "/findiso$isoPath" ]; then
+        ln -sf "/findiso$isoPath" /dev/root
+        break 2
+      else
+        umount /findiso
+      fi
+    done
+
+    sleep "$delay"
+  done
+fi
 
 # Try to find and mount the root device.
 mkdir -p $targetRoot
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index 788e3f4a2ab1..4c2d130d5a5d 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -217,13 +217,11 @@ let
             --replace ata_id ${extraUtils}/bin/ata_id \
             --replace scsi_id ${extraUtils}/bin/scsi_id \
             --replace cdrom_id ${extraUtils}/bin/cdrom_id \
-            --replace ${pkgs.utillinux}/sbin/blkid ${extraUtils}/bin/blkid \
-            --replace /sbin/blkid ${extraUtils}/bin/blkid \
+            --replace ${pkgs.coreutils}/bin/basename ${extraUtils}/bin/basename \
+            --replace ${pkgs.utillinux}/bin/blkid ${extraUtils}/bin/blkid \
             --replace ${pkgs.lvm2}/sbin ${extraUtils}/bin \
-            --replace /sbin/mdadm ${extraUtils}/bin/mdadm \
+            --replace ${pkgs.mdadm}/sbin ${extraUtils}/sbin \
             --replace ${pkgs.bash}/bin/sh ${extraUtils}/bin/sh \
-            --replace /usr/bin/readlink ${extraUtils}/bin/readlink \
-            --replace /usr/bin/basename ${extraUtils}/bin/basename \
             --replace ${udev}/bin/udevadm ${extraUtils}/bin/udevadm
       done
 
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index 03daafa1ce4f..d1de7920df98 100644
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -142,7 +142,7 @@ fi
 # Record the boot configuration.
 ln -sfn "$systemConfig" /run/booted-system
 
-# Prevent the booted system form being garbage-collected If it weren't
+# Prevent the booted system from being garbage-collected. If it weren't
 # a gcroot, if we were running a different kernel, switched system,
 # and garbage collected all, we could not load kernel modules anymore.
 ln -sfn /run/booted-system /nix/var/nix/gcroots/booted-system
diff --git a/nixos/modules/system/boot/stage-2.nix b/nixos/modules/system/boot/stage-2.nix
index 55e6b19c67fd..6b0b47227301 100644
--- a/nixos/modules/system/boot/stage-2.nix
+++ b/nixos/modules/system/boot/stage-2.nix
@@ -4,19 +4,20 @@ with lib;
 
 let
 
+  useHostResolvConf = config.networking.resolvconf.enable && config.networking.useHostResolvConf;
+
   bootStage2 = pkgs.substituteAll {
     src = ./stage-2-init.sh;
     shellDebug = "${pkgs.bashInteractive}/bin/bash";
     shell = "${pkgs.bash}/bin/bash";
     isExecutable = true;
     inherit (config.nix) readOnlyStore;
-    inherit (config.networking) useHostResolvConf;
+    inherit useHostResolvConf;
     inherit (config.system.build) earlyMountScript;
-    path = lib.makeBinPath [
+    path = lib.makeBinPath ([
       pkgs.coreutils
       pkgs.utillinux
-      pkgs.openresolv
-    ];
+    ] ++ lib.optional useHostResolvConf pkgs.openresolv);
     fsPackagesPath = lib.makeBinPath config.system.fsPackages;
     postBootCommands = pkgs.writeText "local-cmds"
       ''
diff --git a/nixos/modules/system/boot/systemd-lib.nix b/nixos/modules/system/boot/systemd-lib.nix
index 28ad4f121bbe..fd1a5b9f62c5 100644
--- a/nixos/modules/system/boot/systemd-lib.nix
+++ b/nixos/modules/system/boot/systemd-lib.nix
@@ -147,7 +147,13 @@ in rec {
       done
 
       # Symlink all units provided listed in systemd.packages.
-      for i in ${toString cfg.packages}; do
+      packages="${toString cfg.packages}"
+
+      # Filter duplicate directories
+      declare -A unique_packages
+      for k in $packages ; do unique_packages[$k]=1 ; done
+
+      for i in ''${!unique_packages[@]}; do
         for fn in $i/etc/systemd/${type}/* $i/lib/systemd/${type}/*; do
           if ! [[ "$fn" =~ .wants$ ]]; then
             if [[ -d "$fn" ]]; then
diff --git a/nixos/modules/system/boot/systemd-nspawn.nix b/nixos/modules/system/boot/systemd-nspawn.nix
index 34a34091a7dc..1e2435e36f0c 100644
--- a/nixos/modules/system/boot/systemd-nspawn.nix
+++ b/nixos/modules/system/boot/systemd-nspawn.nix
@@ -113,11 +113,21 @@ in {
   config =
     let
       units = mapAttrs' (n: v: let nspawnFile = "${n}.nspawn"; in nameValuePair nspawnFile (instanceToUnit nspawnFile v)) cfg;
-    in mkIf (cfg != {}) {
-
-      environment.etc."systemd/nspawn".source = generateUnits "nspawn" units [] [];
-
-      systemd.targets."multi-user".wants = [ "machines.target" ];
-  };
-
+    in 
+      mkMerge [
+        (mkIf (cfg != {}) { 
+          environment.etc."systemd/nspawn".source = mkIf (cfg != {}) (generateUnits "nspawn" units [] []);
+        })
+        {
+          systemd.targets.multi-user.wants = [ "machines.target" ];
+
+          # Workaround for https://github.com/NixOS/nixpkgs/pull/67232#issuecomment-531315437 and https://github.com/systemd/systemd/issues/13622
+          # Once systemd fixes this upstream, we can re-enable -U
+          systemd.services."systemd-nspawn@".serviceConfig.ExecStart = [ 
+            ""  # deliberately empty. signals systemd to override the ExecStart
+            # Only difference between upstream is that we do not pass the -U flag
+            "${config.systemd.package}/bin/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth --settings=override --machine=%i"
+          ];
+        }
+      ];
 }
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index 63f974b704f3..bee21f1a8f36 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -6,7 +6,7 @@ with import ./systemd-lib.nix { inherit config lib pkgs; };
 let
   checkService = checkUnitConfig "Service" [
     (assertValueOneOf "Type" [
-      "simple" "forking" "oneshot" "dbus" "notify" "idle"
+      "exec" "simple" "forking" "oneshot" "dbus" "notify" "idle"
     ])
     (assertValueOneOf "Restart" [
       "no" "on-success" "on-failure" "on-abnormal" "on-abort" "always"
@@ -24,7 +24,7 @@ in rec {
       in
         if isList (head defs'')
         then concatLists defs''
-        else mergeOneOption loc defs';
+        else mergeEqualOption loc defs';
   };
 
   sharedOptions = {
@@ -226,7 +226,7 @@ in rec {
 
     environment = mkOption {
       default = {};
-      type = with types; attrsOf (nullOr (either str (either path package)));
+      type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       example = { PATH = "/foo/bar/bin"; LANG = "nl_NL.UTF-8"; };
       description = "Environment variables passed to the service's processes.";
     };
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 18ee2ef1b8f5..941df5797c66 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -63,6 +63,7 @@ let
       "systemd-logind.service"
       "autovt@.service"
       "systemd-user-sessions.service"
+      "dbus-org.freedesktop.import1.service"
       "dbus-org.freedesktop.machine1.service"
       "user@.service"
       "user-runtime-dir@.service"
@@ -76,6 +77,10 @@ let
       "systemd-journald-dev-log.socket"
       "syslog.socket"
 
+      # Coredumps.
+      "systemd-coredump.socket"
+      "systemd-coredump@.service"
+
       # SysV init compatibility.
       "systemd-initctl.socket"
       "systemd-initctl.service"
@@ -108,11 +113,13 @@ let
       # Hibernate / suspend.
       "hibernate.target"
       "suspend.target"
+      "suspend-then-hibernate.target"
       "sleep.target"
       "hybrid-sleep.target"
       "systemd-hibernate.service"
       "systemd-hybrid-sleep.service"
       "systemd-suspend.service"
+      "systemd-suspend-then-hibernate.service"
 
       # Reboot stuff.
       "reboot.target"
@@ -139,6 +146,7 @@ let
       "user.slice"
       "machine.slice"
       "machines.target"
+      "systemd-importd.service"
       "systemd-machined.service"
       "systemd-nspawn@.service"
 
@@ -186,6 +194,9 @@ let
       "sockets.target"
       "sound.target"
       "systemd-exit.service"
+      "systemd-tmpfiles-clean.service"
+      "systemd-tmpfiles-clean.timer"
+      "systemd-tmpfiles-setup.service"
       "timers.target"
     ];
 
@@ -229,7 +240,7 @@ let
   serviceConfig = { name, config, ... }: {
     config = mkMerge
       [ { # Default path for systemd services.  Should be quite minimal.
-          path =
+          path = mkAfter
             [ pkgs.coreutils
               pkgs.findutils
               pkgs.gnugrep
@@ -319,7 +330,7 @@ let
           [Service]
           ${let env = cfg.globalEnvironment // def.environment;
             in concatMapStrings (n:
-              let s = optionalString (env."${n}" != null)
+              let s = optionalString (env.${n} != null)
                 "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n";
               # systemd max line length is now 1MiB
               # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
@@ -397,7 +408,6 @@ let
 in
 
 {
-
   ###### interface
 
   options = {
@@ -424,7 +434,8 @@ in
     systemd.packages = mkOption {
       default = [];
       type = types.listOf types.package;
-      description = "Packages providing systemd units.";
+      example = literalExample "[ pkgs.systemd-cryptsetup-generator ]";
+      description = "Packages providing systemd units and hooks.";
     };
 
     systemd.targets = mkOption {
@@ -486,7 +497,7 @@ in
     systemd.generators = mkOption {
       type = types.attrsOf types.path;
       default = {};
-      example = { "systemd-gpt-auto-generator" = "/dev/null"; };
+      example = { systemd-gpt-auto-generator = "/dev/null"; };
       description = ''
         Definition of systemd generators.
         For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
@@ -494,11 +505,14 @@ in
       '';
     };
 
-    systemd.generator-packages = mkOption {
-      default = [];
-      type = types.listOf types.package;
-      example = literalExample "[ pkgs.systemd-cryptsetup-generator ]";
-      description = "Packages providing systemd generators.";
+    systemd.shutdown = mkOption {
+      type = types.attrsOf types.path;
+      default = {};
+      description = ''
+        Definition of systemd shutdown executables.
+        For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
+        <literal>/etc/systemd/system-shutdown/NAME</literal> to <literal>VALUE</literal>.
+      '';
     };
 
     systemd.defaultUnit = mkOption {
@@ -517,7 +531,7 @@ in
     };
 
     systemd.globalEnvironment = mkOption {
-      type = with types; attrsOf (nullOr (either str (either path package)));
+      type = with types; attrsOf (nullOr (oneOf [ str path package ]));
       default = {};
       example = { TZ = "CET"; };
       description = ''
@@ -526,13 +540,33 @@ in
     };
 
     systemd.enableCgroupAccounting = mkOption {
-      default = false;
+      default = true;
       type = types.bool;
       description = ''
         Whether to enable cgroup accounting.
       '';
     };
 
+    systemd.coredump.enable = mkOption {
+      default = true;
+      type = types.bool;
+      description = ''
+        Whether core dumps should be processed by
+        <command>systemd-coredump</command>. If disabled, core dumps
+        appear in the current directory of the crashing process.
+      '';
+    };
+
+    systemd.coredump.extraConfig = mkOption {
+      default = "";
+      type = types.lines;
+      example = "Storage=journal";
+      description = ''
+        Extra config options for systemd-coredump. See coredump.conf(5) man page
+        for available options.
+      '';
+    };
+
     systemd.extraConfig = mkOption {
       default = "";
       type = types.lines;
@@ -758,18 +792,21 @@ in
     environment.systemPackages = [ systemd ];
 
     environment.etc = let
-      # generate contents for /etc/systemd/system-generators from
-      # systemd.generators and systemd.generator-packages
-      generators = pkgs.runCommand "system-generators" {
+      # generate contents for /etc/systemd/system-${type} from attrset of links and packages
+      hooks = type: links: pkgs.runCommand "system-${type}" {
           preferLocalBuild = true;
-          packages = cfg.generator-packages;
-        } ''
+          packages = cfg.packages;
+      } ''
+        set -e
         mkdir -p $out
         for package in $packages
         do
-          ln -s $package/lib/systemd/system-generators/* $out/
-        done;
-        ${concatStrings (mapAttrsToList (generator: target: "ln -s ${target} $out/${generator};\n") cfg.generators)}
+          for hook in $package/lib/systemd/system-${type}/*
+          do
+            ln -s $hook $out/
+          done
+        done
+        ${concatStrings (mapAttrsToList (exec: target: "ln -s ${target} $out/${exec};\n") links)}
       '';
     in ({
       "systemd/system".source = generateUnits "system" cfg.units upstreamSystemUnits upstreamSystemWants;
@@ -780,11 +817,12 @@ in
         [Manager]
         ${optionalString config.systemd.enableCgroupAccounting ''
           DefaultCPUAccounting=yes
+          DefaultBlockIOAccounting=yes
           DefaultIOAccounting=yes
           DefaultBlockIOAccounting=yes
-          DefaultMemoryAccounting=yes
-          DefaultTasksAccounting=yes
+          DefaultIPAccounting=yes
         ''}
+        DefaultLimitCORE=infinity
         ${config.systemd.extraConfig}
       '';
 
@@ -808,6 +846,12 @@ in
         ${config.services.journald.extraConfig}
       '';
 
+      "systemd/coredump.conf".text =
+        ''
+          [Coredump]
+          ${config.systemd.coredump.extraConfig}
+        '';
+
       "systemd/logind.conf".text = ''
         [Login]
         KillUserProcesses=${if config.services.logind.killUserProcesses then "yes" else "no"}
@@ -821,7 +865,20 @@ in
         [Sleep]
       '';
 
+      # install provided sysctl snippets
+      "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
+      "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
+
+      "tmpfiles.d/home.conf".source = "${systemd}/example/tmpfiles.d/home.conf";
+      "tmpfiles.d/journal-nocow.conf".source = "${systemd}/example/tmpfiles.d/journal-nocow.conf";
+      "tmpfiles.d/portables.conf".source = "${systemd}/example/tmpfiles.d/portables.conf";
+      "tmpfiles.d/static-nodes-permissions.conf".source = "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf";
       "tmpfiles.d/systemd.conf".source = "${systemd}/example/tmpfiles.d/systemd.conf";
+      "tmpfiles.d/systemd-nologin.conf".source = "${systemd}/example/tmpfiles.d/systemd-nologin.conf";
+      "tmpfiles.d/systemd-nspawn.conf".source = "${systemd}/example/tmpfiles.d/systemd-nspawn.conf";
+      "tmpfiles.d/systemd-tmp.conf".source = "${systemd}/example/tmpfiles.d/systemd-tmp.conf";
+      "tmpfiles.d/tmp.conf".source = "${systemd}/example/tmpfiles.d/tmp.conf";
+      "tmpfiles.d/var.conf".source = "${systemd}/example/tmpfiles.d/var.conf";
       "tmpfiles.d/x11.conf".source = "${systemd}/example/tmpfiles.d/x11.conf";
 
       "tmpfiles.d/nixos.conf".text = ''
@@ -831,7 +888,8 @@ in
         ${concatStringsSep "\n" cfg.tmpfiles.rules}
       '';
 
-      "systemd/system-generators" = { source = generators; };
+      "systemd/system-generators" = { source = hooks "generators" cfg.generators; };
+      "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; };
     });
 
     services.dbus.enable = true;
@@ -941,6 +999,9 @@ in
     systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
     systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
 
+    boot.kernel.sysctl = mkIf (!cfg.coredump.enable) {
+      "kernel.core_pattern" = "core";
+    };
   };
 
   # FIXME: Remove these eventually.
@@ -948,6 +1009,7 @@ in
     [ (mkRenamedOptionModule [ "boot" "systemd" "sockets" ] [ "systemd" "sockets" ])
       (mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ])
       (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ])
+      (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ])
+      (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
     ];
-
 }
diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix
index 8d8bfe5900a9..9e2f36ca01f8 100644
--- a/nixos/modules/system/boot/timesyncd.nix
+++ b/nixos/modules/system/boot/timesyncd.nix
@@ -20,6 +20,18 @@ with lib;
           The set of NTP servers from which to synchronise.
         '';
       };
+      extraConfig = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''
+          PollIntervalMaxSec=180
+        '';
+        description = ''
+          Extra config options for systemd-timesyncd. See
+          <link xlink:href="https://www.freedesktop.org/software/systemd/man/timesyncd.conf.html">
+          timesyncd.conf(5)</link> for available options.
+        '';
+      };
     };
   };
 
@@ -35,11 +47,24 @@ with lib;
     environment.etc."systemd/timesyncd.conf".text = ''
       [Time]
       NTP=${concatStringsSep " " config.services.timesyncd.servers}
+      ${config.services.timesyncd.extraConfig}
     '';
 
-    users.users.systemd-timesync.uid = config.ids.uids.systemd-timesync;
+    users.users.systemd-timesync = {
+      uid = config.ids.uids.systemd-timesync;
+      group = "systemd-timesync";
+    };
     users.groups.systemd-timesync.gid = config.ids.gids.systemd-timesync;
 
+    system.activationScripts.systemd-timesyncd-migration = mkIf (versionOlder config.system.stateVersion "19.09") ''
+      # workaround an issue of systemd-timesyncd not starting due to upstream systemd reverting their dynamic users changes
+      #  - https://github.com/NixOS/nixpkgs/pull/61321#issuecomment-492423742
+      #  - https://github.com/systemd/systemd/issues/12131
+      if [ -L /var/lib/systemd/timesync ]; then
+        rm /var/lib/systemd/timesync
+        mv /var/lib/private/systemd/timesync /var/lib/systemd/timesync
+      fi
+    '';
   };
 
 }
diff --git a/nixos/modules/tasks/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix
index 91f4ae79ee91..7fe066991918 100644
--- a/nixos/modules/tasks/auto-upgrade.nix
+++ b/nixos/modules/tasks/auto-upgrade.nix
@@ -53,6 +53,16 @@ let cfg = config.system.autoUpgrade; in
         '';
       };
 
+      allowReboot = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Reboot the system into the new generation instead of a switch
+          if the new generation uses a different kernel, kernel modules
+          or initrd than the booted system.
+        '';
+      };
+
     };
 
   };
@@ -78,11 +88,23 @@ let cfg = config.system.autoUpgrade; in
           HOME = "/root";
         } // config.networking.proxy.envVars;
 
-      path = [ pkgs.gnutar pkgs.xz.bin pkgs.gitMinimal config.nix.package.out ];
-
-      script = ''
-        ${config.system.build.nixos-rebuild}/bin/nixos-rebuild switch ${toString cfg.flags}
-      '';
+      path = with pkgs; [ coreutils gnutar xz.bin gzip gitMinimal config.nix.package.out ];
+
+      script = let
+          nixos-rebuild = "${config.system.build.nixos-rebuild}/bin/nixos-rebuild";
+        in
+        if cfg.allowReboot then ''
+            ${nixos-rebuild} boot ${toString cfg.flags}
+            booted="$(readlink /run/booted-system/{initrd,kernel,kernel-modules})"
+            built="$(readlink /nix/var/nix/profiles/system/{initrd,kernel,kernel-modules})"
+            if [ "$booted" = "$built" ]; then
+              ${nixos-rebuild} switch ${toString cfg.flags}
+            else
+              /run/current-system/sw/bin/shutdown -r +1
+            fi
+          '' else ''
+            ${nixos-rebuild} switch ${toString cfg.flags}
+        '';
 
       startAt = cfg.dates;
     };
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index 43764bb82f1f..688c77cb22d1 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -12,7 +12,7 @@ let
 
   fileSystems' = toposort fsBefore (attrValues config.fileSystems);
 
-  fileSystems = if fileSystems' ? "result"
+  fileSystems = if fileSystems' ? result
                 then # use topologically sorted fileSystems everywhere
                      fileSystems'.result
                 else # the assertion below will catch this,
@@ -211,7 +211,7 @@ in
       ls = sep: concatMapStringsSep sep (x: x.mountPoint);
       notAutoResizable = fs: fs.autoResize && !(hasPrefix "ext" fs.fsType || fs.fsType == "f2fs");
     in [
-      { assertion = ! (fileSystems' ? "cycle");
+      { assertion = ! (fileSystems' ? cycle);
         message = "The ‘fileSystems’ option can't be topologically sorted: mountpoint dependency path ${ls " -> " fileSystems'.cycle} loops to ${ls ", " fileSystems'.loops}";
       }
       { assertion = ! (any notAutoResizable fileSystems);
diff --git a/nixos/modules/tasks/filesystems/nfs.nix b/nixos/modules/tasks/filesystems/nfs.nix
index d3a558738f4b..ddcc0ed8f5a4 100644
--- a/nixos/modules/tasks/filesystems/nfs.nix
+++ b/nixos/modules/tasks/filesystems/nfs.nix
@@ -25,6 +25,9 @@ let
   '';
 
   nfsConfFile = pkgs.writeText "nfs.conf" cfg.extraConfig;
+  requestKeyConfFile = pkgs.writeText "request-key.conf" ''
+    create id_resolver * * ${pkgs.nfs-utils}/bin/nfsidmap -t 600 %k %d
+  '';
 
   cfg = config.services.nfs;
 
@@ -56,11 +59,13 @@ in
     boot.initrd.kernelModules = mkIf inInitrd [ "nfs" ];
 
     systemd.packages = [ pkgs.nfs-utils ];
-    systemd.generator-packages = [ pkgs.nfs-utils ];
+
+    environment.systemPackages = [ pkgs.keyutils ];
 
     environment.etc = {
       "idmapd.conf".source = idmapdConfFile;
       "nfs.conf".source = nfsConfFile;
+      "request-key.conf".source = requestKeyConfFile;
     };
 
     systemd.services.nfs-blkmap =
diff --git a/nixos/modules/tasks/filesystems/xfs.nix b/nixos/modules/tasks/filesystems/xfs.nix
index c6a90bcf1a51..98038701ca58 100644
--- a/nixos/modules/tasks/filesystems/xfs.nix
+++ b/nixos/modules/tasks/filesystems/xfs.nix
@@ -18,6 +18,7 @@ in
     boot.initrd.extraUtilsCommands = mkIf inInitrd
       ''
         copy_bin_and_libs ${pkgs.xfsprogs.bin}/bin/fsck.xfs
+        copy_bin_and_libs ${pkgs.xfsprogs.bin}/bin/xfs_repair
       '';
 
     # Trick just to set 'sh' after the extraUtils nuke-refs.
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 37a19fb9fc8c..d14ba98ec48b 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -1,8 +1,6 @@
 { config, lib, pkgs, utils, ... }:
 #
-# todo:
-#   - crontab for scrubs, etc
-#   - zfs tunables
+# TODO: zfs tunables
 
 with utils;
 with lib;
@@ -13,22 +11,20 @@ let
   cfgSnapshots = config.services.zfs.autoSnapshot;
   cfgSnapFlags = cfgSnapshots.flags;
   cfgScrub = config.services.zfs.autoScrub;
+  cfgTrim = config.services.zfs.trim;
+  cfgZED = config.services.zfs.zed;
 
   inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems;
   inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems;
 
-  enableAutoSnapshots = cfgSnapshots.enable;
-  enableAutoScrub = cfgScrub.enable;
-  enableZfs = inInitrd || inSystem || enableAutoSnapshots || enableAutoScrub;
+  enableZfs = inInitrd || inSystem;
 
   kernel = config.boot.kernelPackages;
 
   packages = if config.boot.zfs.enableUnstable then {
-    spl = null;
     zfs = kernel.zfsUnstable;
     zfsUser = pkgs.zfsUnstable;
   } else {
-    spl = kernel.spl;
     zfs = kernel.zfs;
     zfsUser = pkgs.zfs;
   };
@@ -92,10 +88,25 @@ let
     }
   '';
 
+  zedConf = generators.toKeyValue {
+    mkKeyValue = generators.mkKeyValueDefault {
+      mkValueString = v:
+        if isInt           v then toString v
+        else if isString   v then "\"${v}\""
+        else if true  ==   v then "1"
+        else if false ==   v then "0"
+        else if isList     v then "\"" + (concatStringsSep " " v) + "\""
+        else err "this value is" (toString v);
+    } "=";
+  } cfgZED.settings;
 in
 
 {
 
+  imports = [
+    (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.")
+  ];
+
   ###### interface
 
   options = {
@@ -181,10 +192,9 @@ in
 
       requestEncryptionCredentials = mkOption {
         type = types.bool;
-        default = config.boot.zfs.enableUnstable;
+        default = true;
         description = ''
           Request encryption keys or passwords for all encrypted datasets on import.
-          Dataset encryption is only supported in zfsUnstable at the moment.
           For root pools the encryption key can be supplied via both an
           interactive prompt (keylocation=prompt) and from a file
           (keylocation=file://). Note that for data pools the encryption key can
@@ -271,14 +281,31 @@ in
       };
     };
 
-    services.zfs.autoScrub = {
+    services.zfs.trim = {
       enable = mkOption {
-        default = false;
+        description = "Whether to enable periodic TRIM on all ZFS pools.";
+        default = true;
+        example = false;
         type = types.bool;
+      };
+
+      interval = mkOption {
+        default = "weekly";
+        type = types.str;
+        example = "daily";
         description = ''
-          Enables periodic scrubbing of ZFS pools.
+          How often we run trim. 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>.
         '';
       };
+    };
+
+    services.zfs.autoScrub = {
+      enable = mkEnableOption "Enables periodic scrubbing of ZFS pools.";
 
       interval = mkOption {
         default = "Sun, 02:00";
@@ -301,6 +328,32 @@ in
         '';
       };
     };
+
+    services.zfs.zed.settings = mkOption {
+      type = with types; attrsOf (oneOf [ str int bool (listOf str) ]);
+      example = literalExample ''
+        {
+          ZED_DEBUG_LOG = "/tmp/zed.debug.log";
+
+          ZED_EMAIL_ADDR = [ "root" ];
+          ZED_EMAIL_PROG = "mail";
+          ZED_EMAIL_OPTS = "-s '@SUBJECT@' @ADDRESS@";
+
+          ZED_NOTIFY_INTERVAL_SECS = 3600;
+          ZED_NOTIFY_VERBOSE = false;
+
+          ZED_USE_ENCLOSURE_LEDS = true;
+          ZED_SCRUB_AFTER_RESILVER = false;
+        }
+      '';
+      description = ''
+        ZFS Event Daemon /etc/zfs/zed.d/zed.rc content
+
+        See
+        <citerefentry><refentrytitle>zed</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        for details on ZED and the scripts in /etc/zfs/zed.d to find the possible variables
+      '';
+    };
   };
 
   ###### implementation
@@ -316,17 +369,13 @@ in
           assertion = !cfgZfs.forceImportAll || cfgZfs.forceImportRoot;
           message = "If you enable boot.zfs.forceImportAll, you must also enable boot.zfs.forceImportRoot";
         }
-        {
-          assertion = cfgZfs.requestEncryptionCredentials -> cfgZfs.enableUnstable;
-          message = "This feature is only available for zfs unstable. Set the NixOS option boot.zfs.enableUnstable.";
-        }
       ];
 
       virtualisation.lxd.zfsSupport = true;
 
       boot = {
-        kernelModules = [ "zfs" ] ++ optional (!cfgZfs.enableUnstable) "spl";
-        extraModulePackages = with packages; [ zfs ] ++ optional (!cfgZfs.enableUnstable) spl;
+        kernelModules = [ "zfs" ];
+        extraModulePackages = with packages; [ zfs ];
       };
 
       boot.initrd = mkIf inInitrd {
@@ -382,11 +431,36 @@ in
         zfsSupport = true;
       };
 
-      environment.etc."zfs/zed.d".source = "${packages.zfsUser}/etc/zfs/zed.d/";
+      services.zfs.zed.settings = {
+        ZED_EMAIL_PROG = mkDefault "${pkgs.mailutils}/bin/mail";
+      };
+
+      environment.etc = genAttrs
+        (map
+          (file: "zfs/zed.d/${file}")
+          [
+            "all-syslog.sh"
+            "pool_import-led.sh"
+            "resilver_finish-start-scrub.sh"
+            "statechange-led.sh"
+            "vdev_attach-led.sh"
+            "zed-functions.sh"
+            "data-notify.sh"
+            "resilver_finish-notify.sh"
+            "scrub_finish-notify.sh"
+            "statechange-notify.sh"
+            "vdev_clear-led.sh"
+          ]
+        )
+        (file: { source = "${packages.zfsUser}/etc/${file}"; })
+      // {
+        "zfs/zed.d/zed.rc".text = zedConf;
+        "zfs/zpool.d".source = "${packages.zfsUser}/etc/zfs/zpool.d/";
+      };
 
       system.fsPackages = [ packages.zfsUser ]; # XXX: needed? zfs doesn't have (need) a fsck
       environment.systemPackages = [ packages.zfsUser ]
-        ++ optional enableAutoSnapshots autosnapPkg; # so the user can run the command to see flags
+        ++ optional cfgSnapshots.enable autosnapPkg; # so the user can run the command to see flags
 
       services.udev.packages = [ packages.zfsUser ]; # to hook zvol naming, etc.
       systemd.packages = [ packages.zfsUser ];
@@ -465,7 +539,7 @@ in
                       map createSyncService allPools ++
                       map createZfsService [ "zfs-mount" "zfs-share" "zfs-zed" ]);
 
-      systemd.targets."zfs-import" =
+      systemd.targets.zfs-import =
         let
           services = map (pool: "zfs-import-${pool}.service") dataPools;
         in
@@ -475,10 +549,10 @@ in
             wantedBy = [ "zfs.target" ];
           };
 
-      systemd.targets."zfs".wantedBy = [ "multi-user.target" ];
+      systemd.targets.zfs.wantedBy = [ "multi-user.target" ];
     })
 
-    (mkIf enableAutoSnapshots {
+    (mkIf (enableZfs && cfgSnapshots.enable) {
       systemd.services = let
                            descr = name: if name == "frequent" then "15 mins"
                                     else if name == "hourly" then "hour"
@@ -516,7 +590,7 @@ in
                             }) snapshotNames);
     })
 
-    (mkIf enableAutoScrub {
+    (mkIf (enableZfs && cfgScrub.enable) {
       systemd.services.zfs-scrub = {
         description = "ZFS pools scrubbing";
         after = [ "zfs-import.target" ];
@@ -542,5 +616,15 @@ in
         };
       };
     })
+
+    (mkIf (enableZfs && cfgTrim.enable) {
+      systemd.services.zpool-trim = {
+        description = "ZFS pools trim";
+        after = [ "zfs-import.target" ];
+        path = [ packages.zfsUser ];
+        startAt = cfgTrim.interval;
+        serviceConfig.ExecStart = "${pkgs.runtimeShell} -c 'zpool list -H -o name | xargs --no-run-if-empty -n1 zpool trim'";
+      };
+    })
   ];
 }
diff --git a/nixos/modules/tasks/kbd.nix b/nixos/modules/tasks/kbd.nix
deleted file mode 100644
index 6d34f897d189..000000000000
--- a/nixos/modules/tasks/kbd.nix
+++ /dev/null
@@ -1,127 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-
-  makeColor = n: value: "COLOR_${toString n}=${value}";
-  makeColorCS =
-    let positions = [ "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F" ];
-    in n: value: "\\033]P${elemAt positions (n - 1)}${value}";
-  colors = concatImapStringsSep "\n" makeColor config.i18n.consoleColors;
-
-  isUnicode = hasSuffix "UTF-8" (toUpper config.i18n.defaultLocale);
-
-  optimizedKeymap = pkgs.runCommand "keymap" {
-    nativeBuildInputs = [ pkgs.buildPackages.kbd ];
-    LOADKEYS_KEYMAP_PATH = "${kbdEnv}/share/keymaps/**";
-    preferLocalBuild = true;
-  } ''
-    loadkeys -b ${optionalString isUnicode "-u"} "${config.i18n.consoleKeyMap}" > $out
-  '';
-
-  # Sadly, systemd-vconsole-setup doesn't support binary keymaps.
-  vconsoleConf = pkgs.writeText "vconsole.conf" ''
-    KEYMAP=${config.i18n.consoleKeyMap}
-    FONT=${config.i18n.consoleFont}
-    ${colors}
-  '';
-
-  kbdEnv = pkgs.buildEnv {
-    name = "kbd-env";
-    paths = [ pkgs.kbd ] ++ config.i18n.consolePackages;
-    pathsToLink = [ "/share/consolefonts" "/share/consoletrans" "/share/keymaps" "/share/unimaps" ];
-  };
-
-  setVconsole = !config.boot.isContainer;
-in
-
-{
-  ###### interface
-
-  options = {
-
-    # most options are defined in i18n.nix
-
-    # FIXME: still needed?
-    boot.extraTTYs = mkOption {
-      default = [];
-      type = types.listOf types.str;
-      example = ["tty8" "tty9"];
-      description = ''
-        Tty (virtual console) devices, in addition to the consoles on
-        which mingetty and syslogd run, that must be initialised.
-        Only useful if you have some program that you want to run on
-        some fixed console.  For example, the NixOS installation CD
-        opens the manual in a web browser on console 7, so it sets
-        <option>boot.extraTTYs</option> to <literal>["tty7"]</literal>.
-      '';
-    };
-
-    boot.earlyVconsoleSetup = mkOption {
-      default = false;
-      type = types.bool;
-      description = ''
-        Enable setting font as early as possible (in initrd).
-      '';
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkMerge [
-    (mkIf (!setVconsole) {
-      systemd.services."systemd-vconsole-setup".enable = false;
-    })
-
-    (mkIf setVconsole (mkMerge [
-      { environment.systemPackages = [ pkgs.kbd ];
-
-        # Let systemd-vconsole-setup.service do the work of setting up the
-        # virtual consoles.
-        environment.etc."vconsole.conf".source = vconsoleConf;
-        # Provide kbd with additional packages.
-        environment.etc."kbd".source = "${kbdEnv}/share";
-
-        boot.initrd.preLVMCommands = mkBefore ''
-          kbd_mode ${if isUnicode then "-u" else "-a"} -C /dev/console
-          printf "\033%%${if isUnicode then "G" else "@"}" >> /dev/console
-          loadkmap < ${optimizedKeymap}
-
-          ${optionalString config.boot.earlyVconsoleSetup ''
-            setfont -C /dev/console $extraUtils/share/consolefonts/font.psf
-          ''}
-
-          ${concatImapStringsSep "\n" (n: color: ''
-            printf "${makeColorCS n color}" >> /dev/console
-          '') config.i18n.consoleColors}
-        '';
-
-        systemd.services."systemd-vconsole-setup" =
-          { before = [ "display-manager.service" ];
-            after = [ "systemd-udev-settle.service" ];
-            restartTriggers = [ vconsoleConf kbdEnv ];
-          };
-      }
-
-      (mkIf config.boot.earlyVconsoleSetup {
-        boot.initrd.extraUtilsCommands = ''
-          mkdir -p $out/share/consolefonts
-          ${if substring 0 1 config.i18n.consoleFont == "/" then ''
-            font="${config.i18n.consoleFont}"
-          '' else ''
-            font="$(echo ${kbdEnv}/share/consolefonts/${config.i18n.consoleFont}.*)"
-          ''}
-          if [[ $font == *.gz ]]; then
-            gzip -cd $font > $out/share/consolefonts/font.psf
-          else
-            cp -L $font $out/share/consolefonts/font.psf
-          fi
-        '';
-      })
-    ]))
-  ];
-
-}
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index c12ada7a030a..1726d05115ea 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -103,7 +103,7 @@ let
 
             script =
               ''
-                ${optionalString (!config.environment.etc?"resolv.conf") ''
+                ${optionalString config.networking.resolvconf.enable ''
                   # Set the static DNS configuration, if given.
                   ${pkgs.openresolv}/sbin/resolvconf -m 1 -a static <<EOF
                   ${optionalString (cfg.nameservers != [] && cfg.domain != null) ''
@@ -498,8 +498,8 @@ let
          // mapAttrs' createSitDevice cfg.sits
          // mapAttrs' createVlanDevice cfg.vlans
          // {
-           "network-setup" = networkSetup;
-           "network-local-commands" = networkLocalCommands;
+           network-setup = networkSetup;
+           network-local-commands = networkLocalCommands;
          };
 
     services.udev.extraRules =
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index 2318bdd1d582..e25dc0c0b39a 100644
--- a/nixos/modules/tasks/network-interfaces-systemd.nix
+++ b/nixos/modules/tasks/network-interfaces-systemd.nix
@@ -12,7 +12,7 @@ let
     i.ipv4.addresses
     ++ optionals cfg.enableIPv6 i.ipv6.addresses;
 
-  dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "both" else "none";
+  dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no";
 
   slaves =
     concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
@@ -31,13 +31,19 @@ in
       message = "networking.defaultGatewayWindowSize is not supported by networkd.";
     } {
       assertion = cfg.vswitches == {};
-      message = "networking.vswichtes are not supported by networkd.";
+      message = "networking.vswitches are not supported by networkd.";
     } {
       assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null;
       message = "networking.defaultGateway.interface is not supported by networkd.";
     } {
       assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null;
       message = "networking.defaultGateway6.interface is not supported by networkd.";
+    } {
+      assertion = cfg.useDHCP == false;
+      message = ''
+        networking.useDHCP is not supported by networkd.
+        Please use per interface configuration and set the global option to false.
+      '';
     } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: {
       assertion = !rstp;
       message = "networking.bridges.${n}.rstp is not supported by networkd.";
@@ -54,20 +60,24 @@ in
       let
         domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain);
         genericNetwork = override:
-          let gateway = optional (cfg.defaultGateway != null) cfg.defaultGateway.address
-            ++ optional (cfg.defaultGateway6 != null) cfg.defaultGateway6.address;
-          in {
-            DHCP = override (dhcpStr cfg.useDHCP);
-          } // optionalAttrs (gateway != [ ]) {
-            gateway = override gateway;
+          let gateway = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address
+            ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address;
+          in optionalAttrs (gateway != [ ]) {
+            routes = override [
+              {
+                routeConfig = {
+                  Gateway = gateway;
+                  GatewayOnLink = false;
+                };
+              }
+            ];
           } // optionalAttrs (domains != [ ]) {
             domains = override domains;
           };
       in mkMerge [ {
         enable = true;
-        networks."99-main" = genericNetwork mkDefault;
       }
-      (mkMerge (flip map interfaces (i: {
+      (mkMerge (forEach interfaces (i: {
         netdevs = mkIf i.virtual ({
           "40-${i.name}" = {
             netdevConfig = {
@@ -82,8 +92,8 @@ in
         networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) {
           name = mkDefault i.name;
           DHCP = mkForce (dhcpStr
-            (if i.useDHCP != null then i.useDHCP else cfg.useDHCP && interfaceIps i == [ ]));
-          address = flip map (interfaceIps i)
+            (if i.useDHCP != null then i.useDHCP else false));
+          address = forEach (interfaceIps i)
             (ip: "${ip.address}/${toString ip.prefixLength}");
           networkConfig.IPv6PrivacyExtensions = "kernel";
         } ];
@@ -95,7 +105,7 @@ in
             Kind = "bridge";
           };
         };
-        networks = listToAttrs (flip map bridge.interfaces (bi:
+        networks = listToAttrs (forEach bridge.interfaces (bi:
           nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
             DHCP = mkOverride 0 (dhcpStr false);
             networkConfig.Bridge = name;
@@ -153,20 +163,20 @@ in
                             (mapAttrsToList (k: _: k) do); "";
             # get those driverOptions that have been set
             filterSystemdOptions = filterAttrs (sysDOpt: kOpts:
-                                     any (kOpt: do ? "${kOpt}") kOpts.optNames);
+                                     any (kOpt: do ? ${kOpt}) kOpts.optNames);
             # build final set of systemd options to bond values
             buildOptionSet = mapAttrs (_: kOpts: with kOpts;
                                # we simply take the first set kernel bond option
                                # (one option has multiple names, which is silly)
-                               head (map (optN: valTransform (do."${optN}"))
+                               head (map (optN: valTransform (do.${optN}))
                                  # only map those that exist
-                                 (filter (o: do ? "${o}") optNames)));
+                                 (filter (o: do ? ${o}) optNames)));
             in seq assertNoUnknownOption
                    (buildOptionSet (filterSystemdOptions driverOptionMapping));
 
         };
 
-        networks = listToAttrs (flip map bond.interfaces (bi:
+        networks = listToAttrs (forEach bond.interfaces (bi:
           nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) {
             DHCP = mkOverride 0 (dhcpStr false);
             networkConfig.Bond = name;
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index f9b0eb330bf8..31e2ed1cd1ea 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -305,7 +305,7 @@ let
             optional (defined ipv6Address && defined ipv6PrefixLength)
             { address = ipv6Address; prefixLength = ipv6PrefixLength; }))
 
-        ({ options.warnings = options.warnings; })
+        ({ options.warnings = options.warnings; options.assertions = options.assertions; })
       ];
 
   };
@@ -799,19 +799,19 @@ in
     networking.wlanInterfaces = mkOption {
       default = { };
       example = literalExample {
-        "wlan-station0" = {
+        wlan-station0 = {
             device = "wlp6s0";
         };
-        "wlan-adhoc0" = {
+        wlan-adhoc0 = {
             type = "ibss";
             device = "wlp6s0";
             mac = "02:00:00:00:00:01";
         };
-        "wlan-p2p0" = {
+        wlan-p2p0 = {
             device = "wlp6s0";
             mac = "02:00:00:00:00:02";
         };
-        "wlan-ap0" = {
+        wlan-ap0 = {
             device = "wlp6s0";
             mac = "02:00:00:00:00:03";
         };
@@ -836,7 +836,7 @@ in
         options = {
 
           device = mkOption {
-            type = types.string;
+            type = types.str;
             example = "wlp6s0";
             description = "The name of the underlying hardware WLAN device as assigned by <literal>udev</literal>.";
           };
@@ -852,7 +852,7 @@ in
           };
 
           meshID = mkOption {
-            type = types.nullOr types.string;
+            type = types.nullOr types.str;
             default = null;
             description = "MeshID of interface with type <literal>mesh</literal>.";
           };
@@ -903,6 +903,11 @@ in
         Whether to use DHCP to obtain an IP address and other
         configuration for all network interfaces that are not manually
         configured.
+
+        Using this option is highly discouraged and also incompatible with
+        <option>networking.useNetworkd</option>. Please use
+        <option>networking.interfaces.&lt;name&gt;.useDHCP</option> instead
+        and set this to false.
       '';
     };
 
@@ -926,7 +931,7 @@ in
     warnings = concatMap (i: i.warnings) interfaces;
 
     assertions =
-      (flip map interfaces (i: {
+      (forEach interfaces (i: {
         # With the linux kernel, interface name length is limited by IFNAMSIZ
         # to 16 bytes, including the trailing null byte.
         # See include/linux/if.h in the kernel sources
@@ -934,12 +939,12 @@ in
         message = ''
           The name of networking.interfaces."${i.name}" is too long, it needs to be less than 16 characters.
         '';
-      })) ++ (flip map slaveIfs (i: {
+      })) ++ (forEach slaveIfs (i: {
         assertion = i.ipv4.addresses == [ ] && i.ipv6.addresses == [ ];
         message = ''
           The networking.interfaces."${i.name}" must not have any defined ips when it is a slave.
         '';
-      })) ++ (flip map interfaces (i: {
+      })) ++ (forEach interfaces (i: {
         assertion = i.preferTempAddress -> cfg.enableIPv6;
         message = ''
           Temporary addresses are only needed when IPv6 is enabled.
@@ -967,9 +972,9 @@ in
       "net.ipv6.conf.default.disable_ipv6" = mkDefault (!cfg.enableIPv6);
       "net.ipv6.conf.all.forwarding" = mkDefault (any (i: i.proxyARP) interfaces);
     } // listToAttrs (flip concatMap (filter (i: i.proxyARP) interfaces)
-        (i: flip map [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${i.name}.proxy_arp" true)))
-      // listToAttrs (flip map (filter (i: i.preferTempAddress) interfaces)
-        (i: nameValuePair "net.ipv6.conf.${i.name}.use_tempaddr" 2));
+        (i: forEach [ "4" "6" ] (v: nameValuePair "net.ipv${v}.conf.${replaceChars ["."] ["/"] i.name}.proxy_arp" true)))
+      // listToAttrs (forEach (filter (i: i.preferTempAddress) interfaces)
+        (i: nameValuePair "net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr" 2));
 
     # Capabilities won't work unless we have at-least a 4.3 Linux
     # kernel because we need the ambient capability
@@ -994,7 +999,7 @@ in
         domainname "${cfg.domain}"
       '';
 
-    environment.etc."hostid" = mkIf (cfg.hostId != null)
+    environment.etc.hostid = mkIf (cfg.hostId != null)
       { source = pkgs.runCommand "gen-hostid" { preferLocalBuild = true; } ''
           hi="${cfg.hostId}"
           ${if pkgs.stdenv.isBigEndian then ''
@@ -1007,7 +1012,7 @@ in
 
     # static hostname configuration needed for hostnamectl and the
     # org.freedesktop.hostname1 dbus service (both provided by systemd)
-    environment.etc."hostname" = mkIf (cfg.hostName != "")
+    environment.etc.hostname = mkIf (cfg.hostName != "")
       {
         text = cfg.hostName + "\n";
       };
@@ -1017,7 +1022,6 @@ in
         pkgs.iproute
         pkgs.iputils
         pkgs.nettools
-        pkgs.openresolv
       ]
       ++ optionals config.networking.wireless.enable [
         pkgs.wirelesstools # FIXME: obsolete?
@@ -1028,7 +1032,7 @@ in
 
     # The network-interfaces target is kept for backwards compatibility.
     # New modules must NOT use it.
-    systemd.targets."network-interfaces" =
+    systemd.targets.network-interfaces =
       { description = "All Network Interfaces (deprecated)";
         wantedBy = [ "network.target" ];
         before = [ "network.target" ];
@@ -1051,7 +1055,7 @@ in
           ${cfg.localCommands}
         '';
       };
-    } // (listToAttrs (flip map interfaces (i:
+    } // (listToAttrs (forEach interfaces (i:
       let
         deviceDependency = if (config.boot.isContainer || i.name == "lo")
           then []
@@ -1087,7 +1091,24 @@ in
 
     virtualisation.vswitch = mkIf (cfg.vswitches != { }) { enable = true; };
 
-    services.udev.packages = mkIf (cfg.wlanInterfaces != {}) [
+    services.udev.packages =  [
+      (pkgs.writeTextFile rec {
+        name = "ipv6-privacy-extensions.rules";
+        destination = "/etc/udev/rules.d/98-${name}";
+        text = ''
+          # enable and prefer IPv6 privacy addresses by default
+          ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.bash}/bin/sh -c 'echo 2 > /proc/sys/net/ipv6/conf/%k/use_tempaddr'"
+        '';
+      })
+      (pkgs.writeTextFile rec {
+        name = "ipv6-privacy-extensions.rules";
+        destination = "/etc/udev/rules.d/99-${name}";
+        text = concatMapStrings (i: ''
+          # enable IPv6 privacy addresses but prefer EUI-64 addresses for ${i.name}
+          ACTION=="add", SUBSYSTEM=="net", RUN+="${pkgs.procps}/bin/sysctl net.ipv6.conf.${replaceChars ["."] ["/"] i.name}.use_tempaddr=1"
+        '') (filter (i: !i.preferTempAddress) interfaces);
+      })
+    ] ++ lib.optional (cfg.wlanInterfaces != {})
       (pkgs.writeTextFile {
         name = "99-zzz-40-wlanInterfaces.rules";
         destination = "/etc/udev/rules.d/99-zzz-40-wlanInterfaces.rules";
@@ -1146,13 +1167,13 @@ in
           in
           flip (concatMapStringsSep "\n") (attrNames wlanDeviceInterfaces) (device:
             let
-              interfaces = wlanListDeviceFirst device wlanDeviceInterfaces."${device}";
+              interfaces = wlanListDeviceFirst device wlanDeviceInterfaces.${device};
               curInterface = elemAt interfaces 0;
               newInterfaces = drop 1 interfaces;
             in ''
             # It is important to have that rule first as overwriting the NAME attribute also prevents the
             # next rules from matching.
-            ${flip (concatMapStringsSep "\n") (wlanListDeviceFirst device wlanDeviceInterfaces."${device}") (interface:
+            ${flip (concatMapStringsSep "\n") (wlanListDeviceFirst device wlanDeviceInterfaces.${device}) (interface:
             ''ACTION=="add", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", ENV{INTERFACE}=="${interface._iName}", ${systemdAttrs interface._iName}, RUN+="${newInterfaceScript device interface}"'')}
 
             # Add the required, new WLAN interfaces to the default WLAN interface with the
@@ -1161,8 +1182,7 @@ in
             # Generate the same systemd events for both 'add' and 'move' udev events.
             ACTION=="move", SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", NAME=="${device}", ${systemdAttrs curInterface._iName}
           '');
-      }) ];
-
+      });
   };
 
 }
diff --git a/nixos/modules/tasks/powertop.nix b/nixos/modules/tasks/powertop.nix
index 609831506e16..e8064f9fa80c 100644
--- a/nixos/modules/tasks/powertop.nix
+++ b/nixos/modules/tasks/powertop.nix
@@ -15,6 +15,7 @@ in {
     systemd.services = {
       powertop = {
         wantedBy = [ "multi-user.target" ];
+        after = [ "multi-user.target" ];
         description = "Powertop tunings";
         path = [ pkgs.kmod ];
         serviceConfig = {
diff --git a/nixos/modules/tasks/swraid.nix b/nixos/modules/tasks/swraid.nix
index 93e03c44c868..8fa19194bed4 100644
--- a/nixos/modules/tasks/swraid.nix
+++ b/nixos/modules/tasks/swraid.nix
@@ -6,51 +6,12 @@
 
   services.udev.packages = [ pkgs.mdadm ];
 
+  systemd.packages = [ pkgs.mdadm ];
+
   boot.initrd.availableKernelModules = [ "md_mod" "raid0" "raid1" "raid10" "raid456" ];
 
   boot.initrd.extraUdevRulesCommands = ''
     cp -v ${pkgs.mdadm}/lib/udev/rules.d/*.rules $out/
   '';
 
-  systemd.services.mdadm-shutdown = {
-    wantedBy = [ "final.target"];
-    after = [ "umount.target" ];
-
-    unitConfig = {
-      DefaultDependencies = false;
-    };
-
-    serviceConfig = {
-      Type = "oneshot";
-      ExecStart = ''${pkgs.mdadm}/bin/mdadm --wait-clean --scan'';
-    };
-  };
-
-  systemd.services."mdmon@" = {
-    description = "MD Metadata Monitor on /dev/%I";
-
-    unitConfig.DefaultDependencies = false;
-
-    serviceConfig = {
-      Type = "forking";
-      Environment = "IMSM_NO_PLATFORM=1";
-      ExecStart = ''${pkgs.mdadm}/bin/mdmon --offroot --takeover %I'';
-      KillMode = "none";
-    };
-  };
-
-  systemd.services."mdadm-grow-continue@" = {
-    description = "Manage MD Reshape on /dev/%I";
-
-    unitConfig.DefaultDependencies = false;
-
-    serviceConfig = {
-      ExecStart = ''${pkgs.mdadm}/bin/mdadm --grow --continue /dev/%I'';
-      StandardInput = "null";
-      StandardOutput = "null";
-      StandardError = "null";
-      KillMode = "none";
-    };
-  };
- 
 }
diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index ed4cfa7805e2..1baeab53b0c2 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -15,7 +15,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
   #
   # One particular example are the boot tests where we want instrumentation
   # within the images but not other stuff like setting up 9p filesystems.
-  options.virtualisation.qemu.program = mkOption { type = types.path; };
+  options.virtualisation.qemu = { };
 
   config = {
 
@@ -129,9 +129,6 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; };
     users.users.root.initialHashedPassword = mkOverride 150 "";
 
     services.xserver.displayManager.job.logToJournal = true;
-
-    # set default stateVersion to avoid warnings during eval
-    system.stateVersion = mkDefault "18.03";
   };
 
 }
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index d67790702f1f..20d48add7129 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixos/modules/virtualisation/amazon-image.nix
@@ -25,6 +25,9 @@ in
       { assertion = cfg.hvm;
         message = "Paravirtualized EC2 instances are no longer supported.";
       }
+      { assertion = cfg.efi -> cfg.hvm;
+        message = "EC2 instances using EFI must be HVM instances.";
+      }
     ];
 
     boot.growPartition = cfg.hvm;
@@ -35,10 +38,14 @@ in
       autoResize = true;
     };
 
-    boot.extraModulePackages =
-      [ config.boot.kernelPackages.ixgbevf
-        config.boot.kernelPackages.ena
-      ];
+    fileSystems."/boot" = mkIf cfg.efi {
+      device = "/dev/disk/by-label/ESP";
+      fsType = "vfat";
+    };
+
+    boot.extraModulePackages = [
+      config.boot.kernelPackages.ena
+    ];
     boot.initrd.kernelModules = [ "xen-blkfront" "xen-netfront" ];
     boot.initrd.availableKernelModules = [ "ixgbevf" "ena" "nvme" ];
     boot.kernelParams = mkIf cfg.hvm [ "console=ttyS0" ];
@@ -51,8 +58,10 @@ in
 
     # Generate a GRUB menu.  Amazon's pv-grub uses this to boot our kernel/initrd.
     boot.loader.grub.version = if cfg.hvm then 2 else 1;
-    boot.loader.grub.device = if cfg.hvm then "/dev/xvda" else "nodev";
+    boot.loader.grub.device = if (cfg.hvm && !cfg.efi) then "/dev/xvda" else "nodev";
     boot.loader.grub.extraPerEntryConfig = mkIf (!cfg.hvm) "root (hd0)";
+    boot.loader.grub.efiSupport = cfg.efi;
+    boot.loader.grub.efiInstallAsRemovable = cfg.efi;
     boot.loader.timeout = 0;
 
     boot.initrd.network.enable = true;
@@ -126,6 +135,9 @@ in
     services.openssh.enable = true;
     services.openssh.permitRootLogin = "prohibit-password";
 
+    # Creates symlinks for block device names.
+    services.udev.packages = [ pkgs.ec2-utils ];
+
     # Force getting the hostname from EC2.
     networking.hostName = mkDefault "";
 
@@ -138,7 +150,7 @@ in
     networking.timeServers = [ "169.254.169.123" ];
 
     # udisks has become too bloated to have in a headless system
-    # (e.g. it depends on GTK+).
+    # (e.g. it depends on GTK).
     services.udisks2.enable = false;
   };
 }
diff --git a/nixos/modules/virtualisation/amazon-init.nix b/nixos/modules/virtualisation/amazon-init.nix
index 8032b2c6d7ca..8c12e0e49bf5 100644
--- a/nixos/modules/virtualisation/amazon-init.nix
+++ b/nixos/modules/virtualisation/amazon-init.nix
@@ -7,8 +7,8 @@ let
     echo "attempting to fetch configuration from EC2 user data..."
 
     export HOME=/root
-    export PATH=${pkgs.lib.makeBinPath [ config.nix.package pkgs.systemd pkgs.gnugrep pkgs.gnused config.system.build.nixos-rebuild]}:$PATH
-    export NIX_PATH=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels
+    export PATH=${pkgs.lib.makeBinPath [ config.nix.package pkgs.systemd pkgs.gnugrep pkgs.git pkgs.gnutar pkgs.gzip pkgs.gnused config.system.build.nixos-rebuild]}:$PATH
+    export NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels
 
     userData=/etc/ec2-metadata/user-data
 
@@ -18,9 +18,9 @@ let
       # that as the channel.
       if sed '/^\(#\|SSH_HOST_.*\)/d' < "$userData" | grep -q '\S'; then
         channels="$(grep '^###' "$userData" | sed 's|###\s*||')"
-        printf "%s" "$channels" | while read channel; do
+        while IFS= read -r channel; do
           echo "writing channel: $channel"
-        done
+        done < <(printf "%s\n" "$channels")
 
         if [[ -n "$channels" ]]; then
           printf "%s" "$channels" > /root/.nix-channels
@@ -48,7 +48,7 @@ in {
     wantedBy = [ "multi-user.target" ];
     after = [ "multi-user.target" ];
     requires = [ "network-online.target" ];
- 
+
     restartIfChanged = false;
     unitConfig.X-StopOnRemoval = false;
 
@@ -58,4 +58,3 @@ in {
     };
   };
 }
-
diff --git a/nixos/modules/virtualisation/amazon-options.nix b/nixos/modules/virtualisation/amazon-options.nix
index 15de8638bbab..2e807131e938 100644
--- a/nixos/modules/virtualisation/amazon-options.nix
+++ b/nixos/modules/virtualisation/amazon-options.nix
@@ -1,4 +1,4 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 {
   options = {
     ec2 = {
@@ -9,6 +9,13 @@
           Whether the EC2 instance is a HVM instance.
         '';
       };
+      efi = lib.mkOption {
+        default = pkgs.stdenv.hostPlatform.isAarch64;
+        internal = true;
+        description = ''
+          Whether the EC2 instance is using EFI.
+        '';
+      };
     };
   };
 }
diff --git a/nixos/modules/virtualisation/anbox.nix b/nixos/modules/virtualisation/anbox.nix
index 9cb89e7b2926..da5df3580734 100644
--- a/nixos/modules/virtualisation/anbox.nix
+++ b/nixos/modules/virtualisation/anbox.nix
@@ -56,7 +56,7 @@ in
 
       dns = mkOption {
         default = "1.1.1.1";
-        type = types.string;
+        type = types.str;
         description = ''
           Container DNS server.
         '';
@@ -100,12 +100,7 @@ in
       wantedBy = [ "multi-user.target" ];
       after = [ "systemd-udev-settle.service" ];
       preStart = let
-        initsh = let
-          ip = cfg.ipv4.container.address;
-          gw = cfg.ipv4.gateway.address;
-          dns = cfg.ipv4.dns;
-        in
-        pkgs.writeText "nixos-init" (''
+        initsh = pkgs.writeText "nixos-init" (''
           #!/system/bin/sh
           setprop nixos.version ${config.system.nixos.version}
 
diff --git a/nixos/modules/virtualisation/azure-agent.nix b/nixos/modules/virtualisation/azure-agent.nix
index 770cefbcd511..036b1036f92a 100644
--- a/nixos/modules/virtualisation/azure-agent.nix
+++ b/nixos/modules/virtualisation/azure-agent.nix
@@ -166,7 +166,6 @@ in
 
       wantedBy = [ "sshd.service" "waagent.service" ];
       before = [ "sshd.service" "waagent.service" ];
-      after = [ "local-fs.target" ];
 
       path  = [ pkgs.coreutils ];
       script =
diff --git a/nixos/modules/virtualisation/azure-image.nix b/nixos/modules/virtualisation/azure-image.nix
index dd2108ccc379..e91dd72ff5d4 100644
--- a/nixos/modules/virtualisation/azure-image.nix
+++ b/nixos/modules/virtualisation/azure-image.nix
@@ -26,7 +26,6 @@ in
 
       wantedBy = [ "sshd.service" "waagent.service" ];
       before = [ "sshd.service" "waagent.service" ];
-      after = [ "local-fs.target" ];
 
       path  = [ pkgs.coreutils ];
       script =
diff --git a/nixos/modules/virtualisation/brightbox-image.nix b/nixos/modules/virtualisation/brightbox-image.nix
index e716982c510a..d0efbcc808aa 100644
--- a/nixos/modules/virtualisation/brightbox-image.nix
+++ b/nixos/modules/virtualisation/brightbox-image.nix
@@ -111,7 +111,7 @@ in
   # Always include cryptsetup so that NixOps can use it.
   environment.systemPackages = [ pkgs.cryptsetup ];
 
-  systemd.services."fetch-ec2-data" =
+  systemd.services.fetch-ec2-data =
     { description = "Fetch EC2 Data";
 
       wantedBy = [ "multi-user.target" "sshd.service" ];
diff --git a/nixos/modules/virtualisation/cloudstack-config.nix b/nixos/modules/virtualisation/cloudstack-config.nix
index 81c545676277..78afebdc5dd3 100644
--- a/nixos/modules/virtualisation/cloudstack-config.nix
+++ b/nixos/modules/virtualisation/cloudstack-config.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ lib, pkgs, ... }:
 
 with lib;
 
diff --git a/nixos/modules/virtualisation/container-config.nix b/nixos/modules/virtualisation/container-config.nix
index 604fb8a75932..6ff6bdd30c20 100644
--- a/nixos/modules/virtualisation/container-config.nix
+++ b/nixos/modules/virtualisation/container-config.nix
@@ -7,8 +7,10 @@ with lib;
   config = mkIf config.boot.isContainer {
 
     # Disable some features that are not useful in a container.
+    nix.optimise.automatic = mkDefault false; # the store is host managed
     services.udisks2.enable = mkDefault false;
     powerManagement.enable = mkDefault false;
+    documentation.nixos.enable = mkDefault false;
 
     networking.useHostResolvConf = mkDefault true;
 
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index d10c4feecb43..02de5801da25 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -138,7 +138,7 @@ let
         --bind-ro=/nix/var/nix/daemon-socket \
         --bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles" \
         --bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots" \
-        --link-journal=try-guest \
+        ${optionalString (!cfg.ephemeral) "--link-journal=try-guest"} \
         --setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \
         --setenv HOST_BRIDGE="$HOST_BRIDGE" \
         --setenv HOST_ADDRESS="$HOST_ADDRESS" \
@@ -147,8 +147,9 @@ let
         --setenv LOCAL_ADDRESS6="$LOCAL_ADDRESS6" \
         --setenv HOST_PORT="$HOST_PORT" \
         --setenv PATH="$PATH" \
+        ${optionalString cfg.ephemeral "--ephemeral"} \
         ${if cfg.additionalCapabilities != null && cfg.additionalCapabilities != [] then
-          ''--capability="${concatStringsSep " " cfg.additionalCapabilities}"'' else ""
+          ''--capability="${concatStringsSep "," cfg.additionalCapabilities}"'' else ""
         } \
         ${if cfg.tmpfs != null && cfg.tmpfs != [] then
           ''--tmpfs=${concatStringsSep " --tmpfs=" cfg.tmpfs}'' else ""
@@ -224,12 +225,6 @@ let
           fi
           ${concatStringsSep "\n" (mapAttrsToList renderExtraVeth cfg.extraVeths)}
         fi
-
-        # Get the leader PID so that we can signal it in
-        # preStop. We can't use machinectl there because D-Bus
-        # might be shutting down. FIXME: in systemd 219 we can
-        # just signal systemd-nspawn to do a clean shutdown.
-        machinectl show "$INSTANCE" | sed 's/Leader=\(.*\)/\1/;t;d' > "/run/containers/$INSTANCE.pid"
       ''
   );
 
@@ -247,12 +242,18 @@ let
 
     Type = "notify";
 
+    RuntimeDirectory = lib.optional cfg.ephemeral "containers/%i";
+
     # Note that on reboot, systemd-nspawn returns 133, so this
     # unit will be restarted. On poweroff, it returns 0, so the
     # unit won't be restarted.
     RestartForceExitStatus = "133";
     SuccessExitStatus = "133";
 
+    # Some containers take long to start
+    # especially when you automatically start many at once
+    TimeoutStartSec = cfg.timeoutStartSec;
+
     Restart = "on-failure";
 
     Slice = "machine.slice";
@@ -330,7 +331,7 @@ let
 
   networkOptions = {
     hostBridge = mkOption {
-      type = types.nullOr types.string;
+      type = types.nullOr types.str;
       default = null;
       example = "br0";
       description = ''
@@ -380,7 +381,7 @@ let
     };
 
     hostAddress6 = mkOption {
-      type = types.nullOr types.string;
+      type = types.nullOr types.str;
       default = null;
       example = "fc00::1";
       description = ''
@@ -402,7 +403,7 @@ let
     };
 
     localAddress6 = mkOption {
-      type = types.nullOr types.string;
+      type = types.nullOr types.str;
       default = null;
       example = "fc00::2";
       description = ''
@@ -419,6 +420,8 @@ let
     {
       extraVeths = {};
       additionalCapabilities = [];
+      ephemeral = false;
+      timeoutStartSec = "15s";
       allowedDevices = [];
       hostAddress = null;
       hostAddress6 = null;
@@ -445,7 +448,7 @@ in
       type = types.bool;
       default = !config.boot.isContainer;
       description = ''
-        Whether to enable support for nixos containers.
+        Whether to enable support for NixOS containers.
       '';
     };
 
@@ -465,20 +468,24 @@ in
                 merge = loc: defs: (import ../../lib/eval-config.nix {
                   inherit system;
                   modules =
-                    let extraConfig =
-                      { boot.isContainer = true;
-                        networking.hostName = mkDefault name;
-                        networking.useDHCP = false;
-                        assertions = [
-                          {
-                            assertion =  config.privateNetwork -> stringLength name < 12;
-                            message = ''
-                              Container name `${name}` is too long: When `privateNetwork` is enabled, container names can
-                              not be longer than 11 characters, because the container's interface name is derived from it.
-                              This might be fixed in the future. See https://github.com/NixOS/nixpkgs/issues/38509
-                            '';
-                          }
-                        ];
+                    let
+                      extraConfig = {
+                        _file = "module at ${__curPos.file}:${toString __curPos.line}";
+                        config = {
+                          boot.isContainer = true;
+                          networking.hostName = mkDefault name;
+                          networking.useDHCP = false;
+                          assertions = [
+                            {
+                              assertion =  config.privateNetwork -> stringLength name < 12;
+                              message = ''
+                                Container name `${name}` is too long: When `privateNetwork` is enabled, container names can
+                                not be longer than 11 characters, because the container's interface name is derived from it.
+                                This might be fixed in the future. See https://github.com/NixOS/nixpkgs/issues/38509
+                              '';
+                            }
+                          ];
+                        };
                       };
                     in [ extraConfig ] ++ (map (x: x.value) defs);
                   prefix = [ "containers" name ];
@@ -507,6 +514,26 @@ in
                 information.
               '';
             };
+
+            ephemeral = mkOption {
+              type = types.bool;
+              default = false;
+              description = ''
+                Runs container in ephemeral mode with the empty root filesystem at boot.
+                This way container will be bootstrapped from scratch on each boot
+                and will be cleaned up on shutdown leaving no traces behind.
+                Useful for completely stateless, reproducible containers.
+
+                Note that this option might require to do some adjustments to the container configuration,
+                e.g. you might want to set
+                <varname>systemd.network.networks.$interface.dhcpConfig.ClientIdentifier</varname> to "mac"
+                if you use <varname>macvlans</varname> option.
+                This way dhcp client identifier will be stable between the container restarts.
+
+                Note that the container journal will not be linked to the host if this option is enabled.
+              '';
+            };
+
             enableTun = mkOption {
               type = types.bool;
               default = false;
@@ -532,7 +559,7 @@ in
             };
 
             interfaces = mkOption {
-              type = types.listOf types.string;
+              type = types.listOf types.str;
               default = [];
               example = [ "eth1" "eth2" ];
               description = ''
@@ -567,6 +594,18 @@ in
               '';
             };
 
+		    timeoutStartSec = mkOption {
+		      type = types.str;
+		      default = "1min";
+		      description = ''
+		        Time for the container to start. In case of a timeout,
+		        the container processes get killed.
+		        See <citerefentry><refentrytitle>systemd.time</refentrytitle>
+		        <manvolnum>7</manvolnum></citerefentry>
+		        for more information about the format.
+		       '';
+		    };
+
             bindMounts = mkOption {
               type = with types; loaOf (submodule bindMountOpts);
               default = {};
@@ -655,12 +694,14 @@ in
     unit = {
       description = "Container '%i'";
 
-      unitConfig.RequiresMountsFor = [ "/var/lib/containers/%i" ];
+      unitConfig.RequiresMountsFor = "/var/lib/containers/%i";
 
       path = [ pkgs.iproute ];
 
-      environment.INSTANCE = "%i";
-      environment.root = "/var/lib/containers/%i";
+      environment = {
+        root = "/var/lib/containers/%i";
+        INSTANCE = "%i";
+      };
 
       preStart = preStartScript dummyConfig;
 
@@ -668,28 +709,21 @@ in
 
       postStart = postStartScript dummyConfig;
 
-      preStop =
-        ''
-          pid="$(cat /run/containers/$INSTANCE.pid)"
-          if [ -n "$pid" ]; then
-            kill -RTMIN+4 "$pid"
-          fi
-          rm -f "/run/containers/$INSTANCE.pid"
-        '';
+      preStop = "machinectl poweroff $INSTANCE";
 
       restartIfChanged = false;
 
       serviceConfig = serviceDirectives dummyConfig;
     };
   in {
-    systemd.targets."multi-user".wants = [ "machines.target" ];
+    systemd.targets.multi-user.wants = [ "machines.target" ];
 
     systemd.services = listToAttrs (filter (x: x.value != null) (
       # The generic container template used by imperative containers
       [{ name = "container@"; value = unit; }]
       # declarative containers
       ++ (mapAttrsToList (name: cfg: nameValuePair "container@${name}" (let
-          config = cfg // (
+          containerConfig = cfg // (
           if cfg.enableTun then
             {
               allowedDevices = cfg.allowedDevices
@@ -699,19 +733,24 @@ in
             }
           else {});
         in
-          unit // {
-            preStart = preStartScript config;
-            script = startScript config;
-            postStart = postStartScript config;
-            serviceConfig = serviceDirectives config;
+          recursiveUpdate unit {
+            preStart = preStartScript containerConfig;
+            script = startScript containerConfig;
+            postStart = postStartScript containerConfig;
+            serviceConfig = serviceDirectives containerConfig;
+            unitConfig.RequiresMountsFor = lib.optional (!containerConfig.ephemeral) "/var/lib/containers/%i";
+            environment.root = if containerConfig.ephemeral then "/run/containers/%i" else "/var/lib/containers/%i";
           } // (
-          if config.autoStart then
+          if containerConfig.autoStart then
             {
               wantedBy = [ "machines.target" ];
               wants = [ "network.target" ];
               after = [ "network.target" ];
-              restartTriggers = [ config.path ];
-              reloadIfChanged = true;
+              restartTriggers = [
+                containerConfig.path
+                config.environment.etc."containers/${name}.conf".source
+              ];
+              restartIfChanged = true;
             }
           else {})
       )) config.containers)
@@ -772,5 +811,12 @@ in
     '';
 
     environment.systemPackages = [ pkgs.nixos-container ];
+
+    boot.kernelModules = [
+      "bridge"
+      "macvlan"
+      "tap"
+      "tun"
+    ];
   });
 }
diff --git a/nixos/modules/virtualisation/cri-o.nix b/nixos/modules/virtualisation/cri-o.nix
new file mode 100644
index 000000000000..14a435f6c8bb
--- /dev/null
+++ b/nixos/modules/virtualisation/cri-o.nix
@@ -0,0 +1,106 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.virtualisation.cri-o;
+in
+{
+  options.virtualisation.cri-o = {
+    enable = mkEnableOption "Container Runtime Interface for OCI (CRI-O)";
+
+    storageDriver = mkOption {
+      type = types.enum ["btrfs" "overlay" "vfs"];
+      default = "overlay";
+      description = "Storage driver to be used";
+    };
+
+    logLevel = mkOption {
+      type = types.enum ["trace" "debug" "info" "warn" "error" "fatal"];
+      default = "info";
+      description = "Log level to be used";
+    };
+
+    pauseImage = mkOption {
+      type = types.str;
+      default = "k8s.gcr.io/pause:3.1";
+      description = "Pause image for pod sandboxes to be used";
+    };
+
+    pauseCommand = mkOption {
+      type = types.str;
+      default = "/pause";
+      description = "Pause command to be executed";
+    };
+
+    registries = mkOption {
+      type = types.listOf types.str;
+      default = [ "docker.io" "quay.io" ];
+      description = "Registries to be configured for unqualified image pull";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs;
+      [ cri-o cri-tools conmon cni-plugins iptables runc utillinux ];
+    environment.etc."crictl.yaml".text = ''
+      runtime-endpoint: unix:///var/run/crio/crio.sock
+    '';
+    environment.etc."crio/crio.conf".text = ''
+      [crio]
+      storage_driver = "${cfg.storageDriver}"
+
+      [crio.image]
+      pause_image = "${cfg.pauseImage}"
+      pause_command = "${cfg.pauseCommand}"
+      registries = [
+        ${concatMapStringsSep ", " (x: "\"" + x + "\"") cfg.registries}
+      ]
+
+      [crio.runtime]
+      conmon = "${pkgs.conmon}/bin/conmon"
+      log_level = "${cfg.logLevel}"
+      manage_network_ns_lifecycle = true
+    '';
+    environment.etc."containers/policy.json".text = ''
+      {"default": [{"type": "insecureAcceptAnything"}]}
+    '';
+    environment.etc."cni/net.d/20-cri-o-bridge.conf".text = ''
+      {
+        "cniVersion": "0.3.1",
+        "name": "crio-bridge",
+        "type": "bridge",
+        "bridge": "cni0",
+        "isGateway": true,
+        "ipMasq": true,
+        "ipam": {
+          "type": "host-local",
+          "subnet": "10.88.0.0/16",
+          "routes": [
+              { "dst": "0.0.0.0/0" }
+          ]
+        }
+      }
+    '';
+
+    systemd.services.crio = {
+      description = "Container Runtime Interface for OCI (CRI-O)";
+      documentation = [ "https://github.com/cri-o/cri-o" ];
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      path = [ pkgs.utillinux pkgs.runc pkgs.iptables ];
+      serviceConfig = {
+        Type = "notify";
+        ExecStart = "${pkgs.cri-o}/bin/crio";
+        ExecReload = "/bin/kill -s HUP $MAINPID";
+        TasksMax = "infinity";
+        LimitNOFILE = "1048576";
+        LimitNPROC = "1048576";
+        LimitCORE = "infinity";
+        OOMScoreAdjust = "-999";
+        TimeoutStartSec = "0";
+        Restart = "on-abnormal";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/digital-ocean-config.nix b/nixos/modules/virtualisation/digital-ocean-config.nix
new file mode 100644
index 000000000000..88cb0cd450e8
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-config.nix
@@ -0,0 +1,197 @@
+{ config, pkgs, lib, modulesPath, ... }:
+with lib;
+{
+  imports = [
+    (modulesPath + "/profiles/qemu-guest.nix")
+    (modulesPath + "/virtualisation/digital-ocean-init.nix")
+  ];
+  options.virtualisation.digitalOcean = with types; {
+    setRootPassword = mkOption {
+      type = bool;
+      default = false;
+      example = true;
+      description = "Whether to set the root password from the Digital Ocean metadata";
+    };
+    setSshKeys = mkOption {
+      type = bool;
+      default = true;
+      example = true;
+      description = "Whether to fetch ssh keys from Digital Ocean";
+    };
+    seedEntropy = mkOption {
+      type = bool;
+      default = true;
+      example = true;
+      description = "Whether to run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
+    };
+  };
+  config =
+    let
+      cfg = config.virtualisation.digitalOcean;
+      hostName = config.networking.hostName;
+      doMetadataFile = "/run/do-metadata/v1.json";
+    in mkMerge [{
+      fileSystems."/" = {
+        device = "/dev/disk/by-label/nixos";
+        autoResize = true;
+        fsType = "ext4";
+      };
+      boot = {
+        growPartition = true;
+        kernelParams = [ "console=ttyS0" "panic=1" "boot.panic_on_fail" ];
+        initrd.kernelModules = [ "virtio_scsi" ];
+        kernelModules = [ "virtio_pci" "virtio_net" ];
+        loader = {
+          grub.device = "/dev/vda";
+          timeout = 0;
+          grub.configurationLimit = 0;
+        };
+      };
+      services.openssh = {
+        enable = mkDefault true;
+        passwordAuthentication = mkDefault false;
+      };
+      services.do-agent.enable = mkDefault true;
+      networking = {
+        hostName = mkDefault ""; # use Digital Ocean metadata server
+      };
+
+      /* Check for and wait for the metadata server to become reachable.
+       * This serves as a dependency for all the other metadata services. */
+      systemd.services.digitalocean-metadata = {
+        path = [ pkgs.curl ];
+        description = "Get host metadata provided by Digitalocean";
+        script = ''
+          set -eu
+          DO_DELAY_ATTEMPTS=0
+          while ! curl -fsSL -o $RUNTIME_DIRECTORY/v1.json http://169.254.169.254/metadata/v1.json; do
+            DO_DELAY_ATTEMPTS=$((DO_DELAY_ATTEMPTS + 1))
+            if (( $DO_DELAY_ATTEMPTS >= $DO_DELAY_ATTEMPTS_MAX )); then
+              echo "giving up"
+              exit 1
+            fi
+
+            echo "metadata unavailable, trying again in 1s..."
+            sleep 1
+          done
+          chmod 600 $RUNTIME_DIRECTORY/v1.json
+          '';
+        environment = {
+          DO_DELAY_ATTEMPTS_MAX = "10";
+        };
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+          RuntimeDirectory = "do-metadata";
+          RuntimeDirectoryPreserve = "yes";
+        };
+        unitConfig = {
+          ConditionPathExists = "!${doMetadataFile}";
+          After = [ "network-pre.target" ] ++
+            optional config.networking.dhcpcd.enable "dhcpcd.service" ++
+            optional config.systemd.network.enable "systemd-networkd.service";
+        };
+      };
+
+      /* Fetch the root password from the digital ocean metadata.
+       * There is no specific route for this, so we use jq to get
+       * it from the One Big JSON metadata blob */
+      systemd.services.digitalocean-set-root-password = mkIf cfg.setRootPassword {
+        path = [ pkgs.shadow pkgs.jq ];
+        description = "Set root password provided by Digitalocean";
+        wantedBy = [ "multi-user.target" ];
+        script = ''
+          set -eo pipefail
+          ROOT_PASSWORD=$(jq -er '.auth_key' ${doMetadataFile})
+          echo "root:$ROOT_PASSWORD" | chpasswd
+          mkdir -p /etc/do-metadata/set-root-password
+          '';
+        unitConfig = {
+          ConditionPathExists = "!/etc/do-metadata/set-root-password";
+          Before = optional config.services.openssh.enable "sshd.service";
+          After = [ "digitalocean-metadata.service" ];
+          Requires = [ "digitalocean-metadata.service" ];
+        };
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+      /* Set the hostname from Digital Ocean, unless the user configured it in
+       * the NixOS configuration. The cached metadata file isn't used here
+       * because the hostname is a mutable part of the droplet. */
+      systemd.services.digitalocean-set-hostname = mkIf (hostName == "") {
+        path = [ pkgs.curl pkgs.nettools ];
+        description = "Set hostname provided by Digitalocean";
+        wantedBy = [ "network.target" ];
+        script = ''
+          set -e
+          DIGITALOCEAN_HOSTNAME=$(curl -fsSL http://169.254.169.254/metadata/v1/hostname)
+          hostname "$DIGITALOCEAN_HOSTNAME"
+          if [[ ! -e /etc/hostname || -w /etc/hostname ]]; then
+            printf "%s\n" "$DIGITALOCEAN_HOSTNAME" > /etc/hostname
+          fi
+        '';
+        unitConfig = {
+          Before = [ "network.target" ];
+          After = [ "digitalocean-metadata.service" ];
+          Wants = [ "digitalocean-metadata.service" ];
+        };
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+      /* Fetch the ssh keys for root from Digital Ocean */
+      systemd.services.digitalocean-ssh-keys = mkIf cfg.setSshKeys {
+        description = "Set root ssh keys provided by Digital Ocean";
+        wantedBy = [ "multi-user.target" ];
+        path = [ pkgs.jq ];
+        script = ''
+          set -e
+          mkdir -m 0700 -p /root/.ssh
+          jq -er '.public_keys[]' ${doMetadataFile} > /root/.ssh/authorized_keys
+          chmod 600 /root/.ssh/authorized_keys
+        '';
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = true;
+        };
+        unitConfig = {
+          ConditionPathExists = "!/root/.ssh/authorized_keys";
+          Before = optional config.services.openssh.enable "sshd.service";
+          After = [ "digitalocean-metadata.service" ];
+          Requires = [ "digitalocean-metadata.service" ];
+        };
+      };
+
+      /* Initialize the RNG by running the entropy-seed script from the
+       * Digital Ocean metadata
+       */
+      systemd.services.digitalocean-entropy-seed = mkIf cfg.seedEntropy {
+        description = "Run the kernel RNG entropy seeding script from the Digital Ocean vendor data";
+        wantedBy = [ "network.target" ];
+        path = [ pkgs.jq pkgs.mpack ];
+        script = ''
+          set -eo pipefail
+          TEMPDIR=$(mktemp -d)
+          jq -er '.vendor_data' ${doMetadataFile} | munpack -tC $TEMPDIR
+          ENTROPY_SEED=$(grep -rl "DigitalOcean Entropy Seed script" $TEMPDIR)
+          ${pkgs.runtimeShell} $ENTROPY_SEED
+          rm -rf $TEMPDIR
+          '';
+        unitConfig = {
+          Before = [ "network.target" ];
+          After = [ "digitalocean-metadata.service" ];
+          Requires = [ "digitalocean-metadata.service" ];
+        };
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+    }
+  ];
+  meta.maintainers = with maintainers; [ arianvp eamsden ];
+}
+
diff --git a/nixos/modules/virtualisation/digital-ocean-image.nix b/nixos/modules/virtualisation/digital-ocean-image.nix
new file mode 100644
index 000000000000..b582e235d435
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-image.nix
@@ -0,0 +1,69 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  cfg = config.virtualisation.digitalOceanImage;
+in
+{
+
+  imports = [ ./digital-ocean-config.nix ];
+
+  options = {
+    virtualisation.digitalOceanImage.diskSize = mkOption {
+      type = with types; int;
+      default = 4096;
+      description = ''
+        Size of disk image. Unit is MB.
+      '';
+    };
+
+    virtualisation.digitalOceanImage.configFile = mkOption {
+      type = with types; nullOr path;
+      default = null;
+      description = ''
+        A path to a configuration file which will be placed at
+        <literal>/etc/nixos/configuration.nix</literal> and be used when switching
+        to a new configuration. If set to <literal>null</literal>, a default
+        configuration is used that imports
+        <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+      '';
+    };
+
+    virtualisation.digitalOceanImage.compressionMethod = mkOption {
+      type = types.enum [ "gzip" "bzip2" ];
+      default = "gzip";
+      example = "bzip2";
+      description = ''
+        Disk image compression method. Choose bzip2 to generate smaller images that
+        take longer to generate but will consume less metered storage space on your
+        Digital Ocean account.
+      '';
+    };
+  };
+
+  #### implementation
+  config = {
+
+    system.build.digitalOceanImage = import ../../lib/make-disk-image.nix {
+      name = "digital-ocean-image";
+      format = "qcow2";
+      postVM = let
+        compress = {
+          "gzip" = "${pkgs.gzip}/bin/gzip";
+          "bzip2" = "${pkgs.bzip2}/bin/bzip2";
+        }.${cfg.compressionMethod};
+      in ''
+        ${compress} $diskImage
+      '';
+      configFile = if cfg.configFile == null
+        then config.virtualisation.digitalOcean.defaultConfigFile
+        else cfg.configFile;
+      inherit (cfg) diskSize;
+      inherit config lib pkgs;
+    };
+
+  };
+
+  meta.maintainers = with maintainers; [ arianvp eamsden ];
+
+}
diff --git a/nixos/modules/virtualisation/digital-ocean-init.nix b/nixos/modules/virtualisation/digital-ocean-init.nix
new file mode 100644
index 000000000000..02f4de009fa8
--- /dev/null
+++ b/nixos/modules/virtualisation/digital-ocean-init.nix
@@ -0,0 +1,95 @@
+{ config, pkgs, lib, ... }:
+with lib;
+let
+  cfg = config.virtualisation.digitalOcean;
+  defaultConfigFile = pkgs.writeText "digitalocean-configuration.nix" ''
+    { modulesPath, lib, ... }:
+    {
+      imports = lib.optional (builtins.pathExists ./do-userdata.nix) ./do-userdata.nix ++ [
+        (modulesPath + "/virtualisation/digital-ocean-config.nix")
+      ];
+    }
+  '';
+in {
+  options.virtualisation.digitalOcean.rebuildFromUserData = mkOption {
+    type = types.bool;
+    default = true;
+    example = true;
+    description = "Whether to reconfigure the system from Digital Ocean user data";
+  };
+  options.virtualisation.digitalOcean.defaultConfigFile = mkOption {
+    type = types.path;
+    default = defaultConfigFile;
+    defaultText = ''
+      The default configuration imports user-data if applicable and
+      <literal>(modulesPath + "/virtualisation/digital-ocean-config.nix")</literal>.
+    '';
+    description = ''
+      A path to a configuration file which will be placed at
+      <literal>/etc/nixos/configuration.nix</literal> and be used when switching to
+      a new configuration.
+    '';
+  };
+
+  config = {
+    systemd.services.digitalocean-init = mkIf cfg.rebuildFromUserData {
+      description = "Reconfigure the system from Digital Ocean userdata on startup";
+      wantedBy = [ "network-online.target" ];
+      unitConfig = {
+        ConditionPathExists = "!/etc/nixos/do-userdata.nix";
+        After = [ "digitalocean-metadata.service" "network-online.target" ];
+        Requires = [ "digitalocean-metadata.service" ];
+        X-StopOnRemoval = false;
+      };
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+      restartIfChanged = false;
+      path = [ pkgs.jq pkgs.gnused pkgs.gnugrep pkgs.systemd config.nix.package config.system.build.nixos-rebuild ];
+      environment = {
+        HOME = "/root";
+        NIX_PATH = concatStringsSep ":" [
+          "/nix/var/nix/profiles/per-user/root/channels/nixos"
+          "nixos-config=/etc/nixos/configuration.nix"
+          "/nix/var/nix/profiles/per-user/root/channels"
+        ];
+      };
+      script = ''
+        set -e
+        echo "attempting to fetch configuration from Digital Ocean user data..."
+        userData=$(mktemp)
+        if jq -er '.user_data' /run/do-metadata/v1.json > $userData; then
+          # If the user-data looks like it could be a nix expression,
+          # copy it over. Also, look for a magic three-hash comment and set
+          # that as the channel.
+          if nix-instantiate --parse $userData > /dev/null; then
+            channels="$(grep '^###' "$userData" | sed 's|###\s*||')"
+            printf "%s" "$channels" | while read channel; do
+              echo "writing channel: $channel"
+            done
+
+            if [[ -n "$channels" ]]; then
+              printf "%s" "$channels" > /root/.nix-channels
+              nix-channel --update
+            fi
+
+            echo "setting configuration from Digital Ocean user data"
+            cp "$userData" /etc/nixos/do-userdata.nix
+            if [[ ! -e /etc/nixos/configuration.nix ]]; then
+              install -m0644 ${cfg.defaultConfigFile} /etc/nixos/configuration.nix
+            fi
+          else
+            echo "user data does not appear to be a Nix expression; ignoring"
+            exit
+          fi
+
+          nixos-rebuild switch
+        else
+          echo "no user data is available"
+        fi
+        '';
+    };
+  };
+  meta.maintainers = with maintainers; [ arianvp eamsden ];
+}
diff --git a/nixos/modules/virtualisation/docker-containers.nix b/nixos/modules/virtualisation/docker-containers.nix
index c4e47bfa477c..3a2eb97d1bf1 100644
--- a/nixos/modules/virtualisation/docker-containers.nix
+++ b/nixos/modules/virtualisation/docker-containers.nix
@@ -5,16 +5,29 @@ let
   cfg = config.docker-containers;
 
   dockerContainer =
-    { name, config, ... }: {
+    { ... }: {
 
       options = {
 
         image = mkOption {
-          type = types.str;
+          type = with types; str;
           description = "Docker image to run.";
           example = "library/hello-world";
         };
 
+        imageFile = mkOption {
+          type = with types; nullOr package;
+          default = null;
+          description = ''
+            Path to an image file to load instead of pulling from a registry.
+            If defined, do not pull from registry.
+
+            You still need to set the <literal>image</literal> attribute, as it
+            will be used as the image name for docker to start a container.
+          '';
+          example = literalExample "pkgs.dockerTools.buildDockerImage {...};";
+        };
+
         cmd = mkOption {
           type =  with types; listOf str;
           default = [];
@@ -65,10 +78,9 @@ let
           default = [];
           description = ''
             Network ports to publish from the container to the outer host.
-            </para>
-            <para>
+
             Valid formats:
-            </para>
+
             <itemizedlist>
               <listitem>
                 <para>
@@ -91,21 +103,19 @@ let
                 </para>
               </listitem>
             </itemizedlist>
-            <para>
+
             Both <literal>hostPort</literal> and
             <literal>containerPort</literal> can be specified as a range of
             ports.  When specifying ranges for both, the number of container
             ports in the range must match the number of host ports in the
             range.  Example: <literal>1234-1236:1234-1236/tcp</literal>
-            </para>
-            <para>
+
             When specifying a range for <literal>hostPort</literal> only, the
             <literal>containerPort</literal> must <emphasis>not</emphasis> be a
             range.  In this case, the container port is published somewhere
             within the specified <literal>hostPort</literal> range.  Example:
             <literal>1234-1236:1234/tcp</literal>
-            </para>
-            <para>
+
             Refer to the
             <link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports">
             Docker engine documentation</link> for full details.
@@ -156,6 +166,24 @@ let
           example = "/var/lib/hello_world";
         };
 
+        dependsOn = mkOption {
+          type = with types; listOf str;
+          default = [];
+          description = ''
+            Define which other containers this one depends on. They will be added to both After and Requires for the unit.
+
+            Use the same name as the attribute under <literal>services.docker-containers</literal>.
+          '';
+          example = literalExample ''
+            services.docker-containers = {
+              node1 = {};
+              node2 = {
+                dependsOn = [ "node1" ];
+              }
+            }
+          '';
+        };
+
         extraDockerOptions = mkOption {
           type = with types; listOf str;
           default = [];
@@ -167,30 +195,38 @@ let
       };
     };
 
-  mkService = name: container: {
+  mkService = name: container: let
+    mkAfter = map (x: "docker-${x}.service") container.dependsOn;
+  in rec {
     wantedBy = [ "multi-user.target" ];
-    after = [ "docker.service" "docker.socket" ];
-    requires = [ "docker.service" "docker.socket" ];
+    after = [ "docker.service" "docker.socket" ] ++ mkAfter;
+    requires = after;
+
     serviceConfig = {
       ExecStart = concatStringsSep " \\\n  " ([
         "${pkgs.docker}/bin/docker run"
         "--rm"
-        "--name=%n"
+        "--name=${name}"
         "--log-driver=${container.log-driver}"
-      ] ++ optional (! isNull container.entrypoint)
+      ] ++ optional (container.entrypoint != null)
         "--entrypoint=${escapeShellArg container.entrypoint}"
         ++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment)
         ++ map (p: "-p ${escapeShellArg p}") container.ports
-        ++ optional (! isNull container.user) "-u ${escapeShellArg container.user}"
+        ++ optional (container.user != null) "-u ${escapeShellArg container.user}"
         ++ map (v: "-v ${escapeShellArg v}") container.volumes
-        ++ optional (! isNull container.workdir) "-w ${escapeShellArg container.workdir}"
+        ++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
         ++ map escapeShellArg container.extraDockerOptions
         ++ [container.image]
         ++ map escapeShellArg container.cmd
       );
-      ExecStartPre = "-${pkgs.docker}/bin/docker rm -f %n";
-      ExecStop = "${pkgs.docker}/bin/docker stop %n";
-      ExecStopPost = "-${pkgs.docker}/bin/docker rm -f %n";
+
+      ExecStartPre = ["-${pkgs.docker}/bin/docker rm -f ${name}"
+                      "-${pkgs.docker}/bin/docker image prune -f"] ++
+        (optional (container.imageFile != null)
+                ["${pkgs.docker}/bin/docker load -i ${container.imageFile}"]);
+
+      ExecStop = ''${pkgs.bash}/bin/sh -c "[ $SERVICE_RESULT = success ] || ${pkgs.docker}/bin/docker stop ${name}"'';
+      ExecStopPost = "-${pkgs.docker}/bin/docker rm -f ${name}";
 
       ### There is no generalized way of supporting `reload` for docker
       ### containers. Some containers may respond well to SIGHUP sent to their
diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix
index ba04dfd57942..7d196a46276a 100644
--- a/nixos/modules/virtualisation/docker.nix
+++ b/nixos/modules/virtualisation/docker.nix
@@ -46,8 +46,8 @@ in
         description =
           ''
             When enabled dockerd is started on boot. This is required for
-            container, which are created with the
-            <literal>--restart=always</literal> flag, to work. If this option is
+            containers which are created with the
+            <literal>--restart=always</literal> flag to work. If this option is
             disabled, docker might be started on demand by socket activation.
           '';
       };
diff --git a/nixos/modules/virtualisation/ec2-amis.nix b/nixos/modules/virtualisation/ec2-amis.nix
index f640bb21b133..3b4e55d39d7b 100644
--- a/nixos/modules/virtualisation/ec2-amis.nix
+++ b/nixos/modules/virtualisation/ec2-amis.nix
@@ -291,5 +291,21 @@ let self = {
   "19.03".sa-east-1.hvm-ebs = "ami-0c6a43c6e0ad1f4e2";
   "19.03".ap-south-1.hvm-ebs = "ami-0303deb1b5890f878";
 
-  latest = self."19.03";
+  # 19.09.981.205691b7cbe
+  "19.09".eu-west-1.hvm-ebs = "ami-0ebd3156e21e9642f";
+  "19.09".eu-west-2.hvm-ebs = "ami-02a2b5480a79084b7";
+  "19.09".eu-west-3.hvm-ebs = "ami-09aa175c7588734f7";
+  "19.09".eu-central-1.hvm-ebs = "ami-00a7fafd7e237a330";
+  "19.09".us-east-1.hvm-ebs = "ami-00a8eeaf232a74f84";
+  "19.09".us-east-2.hvm-ebs = "ami-093efd3a57a1e03a8";
+  "19.09".us-west-1.hvm-ebs = "ami-0913e9a2b677fac30";
+  "19.09".us-west-2.hvm-ebs = "ami-02d9a19f77b47882a";
+  "19.09".ca-central-1.hvm-ebs = "ami-0627dd3f7b3627a29";
+  "19.09".ap-southeast-1.hvm-ebs = "ami-083614e4d08f2164d";
+  "19.09".ap-southeast-2.hvm-ebs = "ami-0048c704185ded6dc";
+  "19.09".ap-northeast-1.hvm-ebs = "ami-0329e7fc2d7f60bd0";
+  "19.09".ap-northeast-2.hvm-ebs = "ami-03d4ae7d0b5fc364f";
+  "19.09".ap-south-1.hvm-ebs = "ami-0b599690b35aeef23";
+
+  latest = self."19.09";
 }; in self
diff --git a/nixos/modules/virtualisation/ec2-data.nix b/nixos/modules/virtualisation/ec2-data.nix
index db3dd9949c12..629125350182 100644
--- a/nixos/modules/virtualisation/ec2-data.nix
+++ b/nixos/modules/virtualisation/ec2-data.nix
@@ -7,6 +7,10 @@
 with lib;
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "ec2" "metadata" ] "")
+  ];
+
   config = {
 
     systemd.services.apply-ec2-data =
@@ -64,7 +68,7 @@ with lib;
         serviceConfig.RemainAfterExit = true;
       };
 
-    systemd.services."print-host-key" =
+    systemd.services.print-host-key =
       { description = "Print SSH Host Key";
         wantedBy = [ "multi-user.target" ];
         after = [ "sshd.service" ];
diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix
index 8c7331fe4d2b..327324f2921d 100644
--- a/nixos/modules/virtualisation/google-compute-config.nix
+++ b/nixos/modules/virtualisation/google-compute-config.nix
@@ -2,7 +2,6 @@
 with lib;
 let
   gce = pkgs.google-compute-engine;
-  cfg = config.virtualisation.googleComputeImage;
 in
 {
   imports = [
@@ -12,6 +11,7 @@ in
 
 
   fileSystems."/" = {
+    fsType = "ext4";
     device = "/dev/disk/by-label/nixos";
     autoResize = true;
   };
@@ -21,7 +21,7 @@ in
   boot.initrd.kernelModules = [ "virtio_scsi" ];
   boot.kernelModules = [ "virtio_pci" "virtio_net" ];
 
-  # Generate a GRUB menu.  Amazon's pv-grub uses this to boot our kernel/initrd.
+  # Generate a GRUB menu.
   boot.loader.grub.device = "/dev/sda";
   boot.loader.timeout = 0;
 
@@ -29,12 +29,16 @@ in
   # way to select them anyway.
   boot.loader.grub.configurationLimit = 0;
 
-  # Allow root logins only using the SSH key that the user specified
-  # at instance creation time.
+  # Allow root logins only using SSH keys
+  # and disable password authentication in general
   services.openssh.enable = true;
   services.openssh.permitRootLogin = "prohibit-password";
   services.openssh.passwordAuthentication = mkDefault false;
 
+  # enable OS Login. This also requires setting enable-oslogin=TRUE metadata on
+  # instance or project level
+  security.googleOsLogin.enable = true;
+
   # Use GCE udev rules for dynamic disk volumes
   services.udev.packages = [ gce ];
 
@@ -65,171 +69,80 @@ in
   # GC has 1460 MTU
   networking.interfaces.eth0.mtu = 1460;
 
-  security.googleOsLogin.enable = true;
-
-  systemd.services.google-clock-skew-daemon = {
-    description = "Google Compute Engine Clock Skew Daemon";
-    after = [
-      "network.target"
-      "google-instance-setup.service"
-      "google-network-setup.service"
-    ];
-    requires = ["network.target"];
-    wantedBy = ["multi-user.target"];
-    serviceConfig = {
-      Type = "simple";
-      ExecStart = "${gce}/bin/google_clock_skew_daemon --debug";
-    };
-  };
-
   systemd.services.google-instance-setup = {
     description = "Google Compute Engine Instance Setup";
-    after = ["local-fs.target" "network-online.target" "network.target" "rsyslog.service"];
-    before = ["sshd.service"];
-    wants = ["local-fs.target" "network-online.target" "network.target"];
-    wantedBy = [ "sshd.service" "multi-user.target" ];
-    path = with pkgs; [ ethtool openssh ];
+    after = [ "network-online.target" "network.target" "rsyslog.service" ];
+    before = [ "sshd.service" ];
+    path = with pkgs; [ coreutils ethtool openssh ];
     serviceConfig = {
-      ExecStart = "${gce}/bin/google_instance_setup --debug";
+      ExecStart = "${gce}/bin/google_instance_setup";
+      StandardOutput="journal+console";
       Type = "oneshot";
     };
+    wantedBy = [ "sshd.service" "multi-user.target" ];
   };
 
   systemd.services.google-network-daemon = {
     description = "Google Compute Engine Network Daemon";
-    after = ["local-fs.target" "network-online.target" "network.target" "rsyslog.service" "google-instance-setup.service"];
-    wants = ["local-fs.target" "network-online.target" "network.target"];
-    requires = ["network.target"];
-    partOf = ["network.target"];
-    wantedBy = [ "multi-user.target" ];
+    after = [ "network-online.target" "network.target" "google-instance-setup.service" ];
     path = with pkgs; [ iproute ];
     serviceConfig = {
-      ExecStart = "${gce}/bin/google_network_daemon --debug";
+      ExecStart = "${gce}/bin/google_network_daemon";
+      StandardOutput="journal+console";
+      Type="simple";
+    };
+    wantedBy = [ "multi-user.target" ];
+  };
+
+  systemd.services.google-clock-skew-daemon = {
+    description = "Google Compute Engine Clock Skew Daemon";
+    after = [ "network.target" "google-instance-setup.service" "google-network-daemon.service" ];
+    serviceConfig = {
+      ExecStart = "${gce}/bin/google_clock_skew_daemon";
+      StandardOutput="journal+console";
+      Type = "simple";
     };
+    wantedBy = ["multi-user.target"];
   };
 
+
   systemd.services.google-shutdown-scripts = {
     description = "Google Compute Engine Shutdown Scripts";
     after = [
-      "local-fs.target"
       "network-online.target"
       "network.target"
       "rsyslog.service"
-      "systemd-resolved.service"
       "google-instance-setup.service"
       "google-network-daemon.service"
     ];
-    wants = [ "local-fs.target" "network-online.target" "network.target"];
-    wantedBy = [ "multi-user.target" ];
     serviceConfig = {
       ExecStart = "${pkgs.coreutils}/bin/true";
-      ExecStop = "${gce}/bin/google_metadata_script_runner --debug --script-type shutdown";
-      Type = "oneshot";
+      ExecStop = "${gce}/bin/google_metadata_script_runner --script-type shutdown";
       RemainAfterExit = true;
-      TimeoutStopSec = "infinity";
+      StandardOutput="journal+console";
+      TimeoutStopSec = "0";
+      Type = "oneshot";
     };
+    wantedBy = [ "multi-user.target" ];
   };
 
   systemd.services.google-startup-scripts = {
     description = "Google Compute Engine Startup Scripts";
     after = [
-      "local-fs.target"
       "network-online.target"
       "network.target"
       "rsyslog.service"
       "google-instance-setup.service"
       "google-network-daemon.service"
     ];
-    wants = ["local-fs.target" "network-online.target" "network.target"];
-    wantedBy = [ "multi-user.target" ];
     serviceConfig = {
-      ExecStart = "${gce}/bin/google_metadata_script_runner --debug --script-type startup";
+      ExecStart = "${gce}/bin/google_metadata_script_runner --script-type startup";
       KillMode = "process";
+      StandardOutput = "journal+console";
       Type = "oneshot";
     };
+    wantedBy = [ "multi-user.target" ];
   };
 
-
-  # Settings taken from https://github.com/GoogleCloudPlatform/compute-image-packages/blob/master/google_config/sysctl/11-gce-network-security.conf
-  boot.kernel.sysctl = {
-    # Turn on SYN-flood protections.  Starting with 2.6.26, there is no loss
-    # of TCP functionality/features under normal conditions.  When flood
-    # protections kick in under high unanswered-SYN load, the system
-    # should remain more stable, with a trade off of some loss of TCP
-    # functionality/features (e.g. TCP Window scaling).
-    "net.ipv4.tcp_syncookies" = mkDefault "1";
-
-    # ignores source-routed packets
-    "net.ipv4.conf.all.accept_source_route" = mkDefault "0";
-
-    # ignores source-routed packets
-    "net.ipv4.conf.default.accept_source_route" = mkDefault "0";
-
-    # ignores ICMP redirects
-    "net.ipv4.conf.all.accept_redirects" = mkDefault "0";
-
-    # ignores ICMP redirects
-    "net.ipv4.conf.default.accept_redirects" = mkDefault "0";
-
-    # ignores ICMP redirects from non-GW hosts
-    "net.ipv4.conf.all.secure_redirects" = mkDefault "1";
-
-    # ignores ICMP redirects from non-GW hosts
-    "net.ipv4.conf.default.secure_redirects" = mkDefault "1";
-
-    # don't allow traffic between networks or act as a router
-    "net.ipv4.ip_forward" = mkDefault "0";
-
-    # don't allow traffic between networks or act as a router
-    "net.ipv4.conf.all.send_redirects" = mkDefault "0";
-
-    # don't allow traffic between networks or act as a router
-    "net.ipv4.conf.default.send_redirects" = mkDefault "0";
-
-    # reverse path filtering - IP spoofing protection
-    "net.ipv4.conf.all.rp_filter" = mkDefault "1";
-
-    # reverse path filtering - IP spoofing protection
-    "net.ipv4.conf.default.rp_filter" = mkDefault "1";
-
-    # ignores ICMP broadcasts to avoid participating in Smurf attacks
-    "net.ipv4.icmp_echo_ignore_broadcasts" = mkDefault "1";
-
-    # ignores bad ICMP errors
-    "net.ipv4.icmp_ignore_bogus_error_responses" = mkDefault "1";
-
-    # logs spoofed, source-routed, and redirect packets
-    "net.ipv4.conf.all.log_martians" = mkDefault "1";
-
-    # log spoofed, source-routed, and redirect packets
-    "net.ipv4.conf.default.log_martians" = mkDefault "1";
-
-    # implements RFC 1337 fix
-    "net.ipv4.tcp_rfc1337" = mkDefault "1";
-
-    # randomizes addresses of mmap base, heap, stack and VDSO page
-    "kernel.randomize_va_space" = mkDefault "2";
-
-    # Reboot the machine soon after a kernel panic.
-    "kernel.panic" = mkDefault "10";
-
-    ## Not part of the original config
-
-    # provides protection from ToCToU races
-    "fs.protected_hardlinks" = mkDefault "1";
-
-    # provides protection from ToCToU races
-    "fs.protected_symlinks" = mkDefault "1";
-
-    # makes locating kernel addresses more difficult
-    "kernel.kptr_restrict" = mkDefault "1";
-
-    # set ptrace protections
-    "kernel.yama.ptrace_scope" = mkOverride 500 "1";
-
-    # set perf only available to root
-    "kernel.perf_event_paranoid" = mkDefault "2";
-
-  };
-
+  environment.etc."sysctl.d/11-gce-network-security.conf".source = "${gce}/sysctl.d/11-gce-network-security.conf";
 }
diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix
index 0d2d25d30752..d172ae38fdcf 100644
--- a/nixos/modules/virtualisation/google-compute-image.nix
+++ b/nixos/modules/virtualisation/google-compute-image.nix
@@ -51,7 +51,7 @@ in
         popd
       '';
       format = "raw";
-      configFile = if isNull cfg.configFile then defaultConfigFile else cfg.configFile;
+      configFile = if cfg.configFile == null then defaultConfigFile else cfg.configFile;
       inherit (cfg) diskSize;
       inherit config lib pkgs;
     };
diff --git a/nixos/modules/virtualisation/kvmgt.nix b/nixos/modules/virtualisation/kvmgt.nix
index 132815a0ad63..36ef6d17df69 100644
--- a/nixos/modules/virtualisation/kvmgt.nix
+++ b/nixos/modules/virtualisation/kvmgt.nix
@@ -4,13 +4,16 @@ with lib;
 
 let
   cfg = config.virtualisation.kvmgt;
+
   kernelPackages = config.boot.kernelPackages;
+
   vgpuOptions = {
     uuid = mkOption {
-      type = types.string;
+      type = types.str;
       description = "UUID of VGPU device. You can generate one with <package>libossp_uuid</package>.";
     };
   };
+
 in {
   options = {
     virtualisation.kvmgt = {
@@ -20,7 +23,7 @@ in {
       '';
       # multi GPU support is under the question
       device = mkOption {
-        type = types.string;
+        type = types.str;
         default = "0000:00:02.0";
         description = "PCI ID of graphics card. You can figure it with <command>ls /sys/class/mdev_bus</command>.";
       };
@@ -32,7 +35,7 @@ in {
           and find info about device via <command>cat /sys/bus/pci/devices/*/mdev_supported_types/i915-GVTg_V5_4/description</command>
         '';
         example = {
-          "i915-GVTg_V5_8" = {
+          i915-GVTg_V5_8 = {
             uuid = "a297db4a-f4c2-11e6-90f6-d3b88d6c9525";
           };
         };
@@ -45,23 +48,32 @@ in {
       assertion = versionAtLeast kernelPackages.kernel.version "4.16";
       message = "KVMGT is not properly supported for kernels older than 4.16";
     };
-    boot.kernelParams = [ "i915.enable_gvt=1" ];
+
+    boot.kernelModules = [ "kvmgt" ];
+
+    boot.extraModprobeConfig = ''
+      options i915 enable_gvt=1
+    '';
+
+    systemd.paths = mapAttrs' (name: value:
+      nameValuePair "kvmgt-${name}" {
+        description = "KVMGT VGPU ${name} path";
+        wantedBy = [ "multi-user.target" ];
+        pathConfig = {
+          PathExists = "/sys/bus/pci/devices/${cfg.device}/mdev_supported_types/${name}/create";
+        };
+      }
+    ) cfg.vgpus;
+
     systemd.services = mapAttrs' (name: value:
       nameValuePair "kvmgt-${name}" {
         description = "KVMGT VGPU ${name}";
         serviceConfig = {
-          Type = "forking";
+          Type = "oneshot";
           RemainAfterExit = true;
-          Restart = "on-failure";
-          RestartSec = 5;
           ExecStart = "${pkgs.runtimeShell} -c 'echo ${value.uuid} > /sys/bus/pci/devices/${cfg.device}/mdev_supported_types/${name}/create'";
           ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/bus/pci/devices/${cfg.device}/${value.uuid}/remove'";
         };
-        unitConfig = {
-          StartLimitBurst = 5;
-          StartLimitIntervalSec = 30;
-        };
-        wantedBy = [ "multi-user.target" ];
       }
     ) cfg.vgpus;
   };
diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix
index f4d7af1664af..52d852894ce5 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -23,14 +23,21 @@ let
     ''}
     ${cfg.qemuVerbatimConfig}
   '';
+  dirName = "libvirt";
+  subDirs = list: [ dirName ] ++ map (e: "${dirName}/${e}") list;
 
 in {
 
+  imports = [
+    (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ]
+      "Set the option `virtualisation.libvirtd.qemuPackage' instead.")
+  ];
+
   ###### interface
 
-  options = {
+  options.virtualisation.libvirtd = {
 
-    virtualisation.libvirtd.enable = mkOption {
+    enable = mkOption {
       type = types.bool;
       default = false;
       description = ''
@@ -41,7 +48,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.qemuPackage = mkOption {
+    qemuPackage = mkOption {
       type = types.package;
       default = pkgs.qemu;
       description = ''
@@ -51,7 +58,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.extraConfig = mkOption {
+    extraConfig = mkOption {
       type = types.lines;
       default = "";
       description = ''
@@ -60,7 +67,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.qemuRunAsRoot = mkOption {
+    qemuRunAsRoot = mkOption {
       type = types.bool;
       default = true;
       description = ''
@@ -72,7 +79,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.qemuVerbatimConfig = mkOption {
+    qemuVerbatimConfig = mkOption {
       type = types.lines;
       default = ''
         namespaces = []
@@ -84,7 +91,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.qemuOvmf = mkOption {
+    qemuOvmf = mkOption {
       type = types.bool;
       default = true;
       description = ''
@@ -93,7 +100,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.extraOptions = mkOption {
+    extraOptions = mkOption {
       type = types.listOf types.str;
       default = [ ];
       example = [ "--verbose" ];
@@ -102,7 +109,19 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.onShutdown = mkOption {
+    onBoot = mkOption {
+      type = types.enum ["start" "ignore" ];
+      default = "start";
+      description = ''
+        Specifies the action to be done to / on the guests when the host boots.
+        The "start" option starts all guests that were running prior to shutdown
+        regardless of their autostart settings. The "ignore" option will not
+        start the formally running guest on boot. However, any guest marked as
+        autostart will still be automatically started by libvirtd.
+      '';
+    };
+
+    onShutdown = mkOption {
       type = types.enum ["shutdown" "suspend" ];
       default = "suspend";
       description = ''
@@ -113,6 +132,14 @@ in {
       '';
     };
 
+    allowedBridges = mkOption {
+      type = types.listOf types.str;
+      default = [ "virbr0" ];
+      description = ''
+        List of bridge devices that can be used by qemu:///session
+      '';
+    };
+
   };
 
 
@@ -120,7 +147,13 @@ in {
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = with pkgs; [ libvirt libressl.nc cfg.qemuPackage ];
+    environment = {
+      # this file is expected in /etc/qemu and not sysconfdir (/var/lib)
+      etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n" (e:
+        "allow ${e}") cfg.allowedBridges;
+      systemPackages = with pkgs; [ libvirt libressl.nc iptables cfg.qemuPackage ];
+      etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes";
+    };
 
     boot.kernelModules = [ "tun" ];
 
@@ -134,30 +167,15 @@ in {
       group = "qemu-libvirtd";
     };
 
-    systemd.packages = [ pkgs.libvirt ];
-
-    systemd.services.libvirtd = {
-      description = "Libvirt Virtual Machine Management Daemon";
-
-      wantedBy = [ "multi-user.target" ];
-      after = [ "systemd-udev-settle.service" ]
-              ++ optional vswitch.enable "vswitchd.service";
-
-      environment.LIBVIRTD_ARGS = ''--config "${configFile}" ${concatStringsSep " " cfg.extraOptions}'';
-
-      path = [ cfg.qemuPackage ] # libvirtd requires qemu-img to manage disk images
-             ++ optional vswitch.enable vswitch.package;
-
-      preStart = ''
-        mkdir -p /var/log/libvirt/qemu -m 755
-        rm -f /var/run/libvirtd.pid
-
-        mkdir -p /var/lib/libvirt
-        mkdir -p /var/lib/libvirt/dnsmasq
+    security.wrappers.qemu-bridge-helper = {
+      source = "/run/${dirName}/nix-helpers/qemu-bridge-helper";
+    };
 
-        chmod 755 /var/lib/libvirt
-        chmod 755 /var/lib/libvirt/dnsmasq
+    systemd.packages = [ pkgs.libvirt ];
 
+    systemd.services.libvirtd-config = {
+      description = "Libvirt Virtual Machine Management Daemon - configuration";
+      script = ''
         # Copy default libvirt network config .xml files to /var/lib
         # Files modified by the user will not be overwritten
         for i in $(cd ${pkgs.libvirt}/var/lib && echo \
@@ -169,22 +187,46 @@ in {
         done
 
         # Copy generated qemu config to libvirt directory
-        cp -f ${qemuConfigFile} /var/lib/libvirt/qemu.conf
+        cp -f ${qemuConfigFile} /var/lib/${dirName}/qemu.conf
 
         # stable (not GC'able as in /nix/store) paths for using in <emulator> section of xml configs
-        mkdir -p /run/libvirt/nix-emulators
         for emulator in ${pkgs.libvirt}/libexec/libvirt_lxc ${cfg.qemuPackage}/bin/qemu-kvm ${cfg.qemuPackage}/bin/qemu-system-*; do
-          ln -s --force "$emulator" /run/libvirt/nix-emulators/
+          ln -s --force "$emulator" /run/${dirName}/nix-emulators/
+        done
+
+        for helper in libexec/qemu-bridge-helper bin/qemu-pr-helper; do
+          ln -s --force ${cfg.qemuPackage}/$helper /run/${dirName}/nix-helpers/
         done
 
         ${optionalString cfg.qemuOvmf ''
-            mkdir -p /run/libvirt/nix-ovmf
-            ln -s --force ${pkgs.OVMF.fd}/FV/OVMF_CODE.fd /run/libvirt/nix-ovmf/
-            ln -s --force ${pkgs.OVMF.fd}/FV/OVMF_VARS.fd /run/libvirt/nix-ovmf/
+          ln -s --force ${pkgs.OVMF.fd}/FV/OVMF_CODE.fd /run/${dirName}/nix-ovmf/
+          ln -s --force ${pkgs.OVMF.fd}/FV/OVMF_VARS.fd /run/${dirName}/nix-ovmf/
         ''}
       '';
 
       serviceConfig = {
+        Type = "oneshot";
+        RuntimeDirectoryPreserve = "yes";
+        LogsDirectory = subDirs [ "qemu" ];
+        RuntimeDirectory = subDirs [ "nix-emulators" "nix-helpers" "nix-ovmf" ];
+        StateDirectory = subDirs [ "dnsmasq" ];
+      };
+    };
+
+    systemd.services.libvirtd = {
+      description = "Libvirt Virtual Machine Management Daemon";
+
+      wantedBy = [ "multi-user.target" ];
+      requires = [ "libvirtd-config.service" ];
+      after = [ "systemd-udev-settle.service" "libvirtd-config.service" ]
+              ++ optional vswitch.enable "vswitchd.service";
+
+      environment.LIBVIRTD_ARGS = ''--config "${configFile}" ${concatStringsSep " " cfg.extraOptions}'';
+
+      path = [ cfg.qemuPackage ] # libvirtd requires qemu-img to manage disk images
+             ++ optional vswitch.enable vswitch.package;
+
+      serviceConfig = {
         Type = "notify";
         KillMode = "process"; # when stopping, leave the VMs alone
         Restart = "no";
@@ -197,13 +239,14 @@ in {
       path = with pkgs; [ coreutils libvirt gawk ];
       restartIfChanged = false;
 
+      environment.ON_BOOT = "${cfg.onBoot}";
       environment.ON_SHUTDOWN = "${cfg.onShutdown}";
     };
 
     systemd.sockets.virtlogd = {
       description = "Virtual machine log manager socket";
       wantedBy = [ "sockets.target" ];
-      listenStreams = [ "/run/libvirt/virtlogd-sock" ];
+      listenStreams = [ "/run/${dirName}/virtlogd-sock" ];
     };
 
     systemd.services.virtlogd = {
@@ -215,7 +258,7 @@ in {
     systemd.sockets.virtlockd = {
       description = "Virtual machine lock manager socket";
       wantedBy = [ "sockets.target" ];
-      listenStreams = [ "/run/libvirt/virtlockd-sock" ];
+      listenStreams = [ "/run/${dirName}/virtlockd-sock" ];
     };
 
     systemd.services.virtlockd = {
diff --git a/nixos/modules/virtualisation/lxc.nix b/nixos/modules/virtualisation/lxc.nix
index 9b5adaf08249..f484d5ee59a8 100644
--- a/nixos/modules/virtualisation/lxc.nix
+++ b/nixos/modules/virtualisation/lxc.nix
@@ -58,7 +58,7 @@ in
           ''
             This is the config file for managing unprivileged user network
             administration access in LXC. See <citerefentry>
-            <refentrytitle>lxc-user-net</refentrytitle><manvolnum>5</manvolnum>
+            <refentrytitle>lxc-usernet</refentrytitle><manvolnum>5</manvolnum>
             </citerefentry>.
           '';
       };
diff --git a/nixos/modules/virtualisation/lxd.nix b/nixos/modules/virtualisation/lxd.nix
index 505c11abd208..b4934a86cf56 100644
--- a/nixos/modules/virtualisation/lxd.nix
+++ b/nixos/modules/virtualisation/lxd.nix
@@ -35,6 +35,18 @@ in
           with nixos.
         '';
       };
+      recommendedSysctlSettings = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          enables various settings to avoid common pitfalls when
+          running containers requiring many file operations.
+          Fixes errors like "Too many open files" or
+          "neighbour: ndisc_cache: neighbor table overflow!".
+          See https://lxd.readthedocs.io/en/latest/production-setup/
+          for details.
+        '';
+      };
     };
   };
 
@@ -69,8 +81,11 @@ in
         ExecStart = "@${pkgs.lxd.bin}/bin/lxd lxd --group lxd";
         Type = "simple";
         KillMode = "process"; # when stopping, leave the containers alone
+        LimitMEMLOCK = "infinity";
+        LimitNOFILE = "1048576";
+        LimitNPROC = "infinity";
+        TasksMax = "infinity";
       };
-
     };
 
     users.groups.lxd.gid = config.ids.gids.lxd;
@@ -79,5 +94,16 @@ in
       subUidRanges = [ { startUid = 1000000; count = 65536; } ];
       subGidRanges = [ { startGid = 1000000; count = 65536; } ];
     };
+
+    boot.kernel.sysctl = mkIf cfg.recommendedSysctlSettings {
+      "fs.inotify.max_queued_events" = 1048576;
+      "fs.inotify.max_user_instances" = 1048576;
+      "fs.inotify.max_user_watches" = 1048576;
+      "vm.max_map_count" = 262144;
+      "kernel.dmesg_restrict" = 1;
+      "net.ipv4.neigh.default.gc_thresh3" = 8192;
+      "net.ipv6.neigh.default.gc_thresh3" = 8192;
+      "kernel.keys.maxkeys" = 2000;
+    };
   };
 }
diff --git a/nixos/modules/virtualisation/openvswitch.nix b/nixos/modules/virtualisation/openvswitch.nix
index 47e07e7432cb..6b8ad83661fe 100644
--- a/nixos/modules/virtualisation/openvswitch.nix
+++ b/nixos/modules/virtualisation/openvswitch.nix
@@ -42,6 +42,9 @@ in {
       default = false;
       description = ''
         Whether to start racoon service for openvswitch.
+        Supported only if openvswitch version is less than 2.6.0.
+        Use <literal>virtualisation.vswitch.package = pkgs.openvswitch-lts</literal>
+        for a version that supports ipsec over GRE.
       '';
     };
   };
@@ -54,7 +57,7 @@ in {
     # The path to the an initialized version of the database
     db = pkgs.stdenv.mkDerivation {
       name = "vswitch.db";
-      unpackPhase = "true";
+      dontUnpack = true;
       buildPhase = "true";
       buildInputs = with pkgs; [
         cfg.package
@@ -89,6 +92,13 @@ in {
             "${cfg.package}/share/openvswitch/vswitch.ovsschema"
         fi
         chmod -R +w /var/db/openvswitch
+        if ${cfg.package}/bin/ovsdb-tool needs-conversion /var/db/openvswitch/conf.db | grep -q "yes"
+        then
+          echo "Performing database upgrade"
+          ${cfg.package}/bin/ovsdb-tool convert /var/db/openvswitch/conf.db
+        else
+          echo "Database already up to date"
+        fi
         '';
       serviceConfig = {
         ExecStart =
@@ -133,7 +143,7 @@ in {
     };
 
   }
-  (mkIf cfg.ipsec {
+  (mkIf (cfg.ipsec && (versionOlder cfg.package.version "2.6.0")) {
     services.racoon.enable = true;
     services.racoon.configPath = "${runDir}/ipsec/etc/racoon/racoon.conf";
 
@@ -172,5 +182,4 @@ in {
       '';
     };
   })]));
-
 }
diff --git a/nixos/modules/virtualisation/parallels-guest.nix b/nixos/modules/virtualisation/parallels-guest.nix
index 4e0f2cae299e..828419fb4b9d 100644
--- a/nixos/modules/virtualisation/parallels-guest.nix
+++ b/nixos/modules/virtualisation/parallels-guest.nix
@@ -47,7 +47,7 @@ in
   config = mkIf config.hardware.parallels.enable {
     services.xserver = {
       drivers = singleton
-        { name = "prlvideo"; modules = [ prl-tools ]; libPath = [ prl-tools ]; };
+        { name = "prlvideo"; modules = [ prl-tools ]; };
 
       screenSection = ''
         Option "NoMTRR"
@@ -65,6 +65,7 @@ in
 
     hardware.opengl.package = prl-tools;
     hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.prl-tools.override { libsOnly = true; kernel = null; };
+    hardware.opengl.setLdLibraryPath = true;
 
     services.udev.packages = [ prl-tools ];
 
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index ed3431554be4..31d332e9f07a 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -23,24 +23,56 @@ let
 
   cfg = config.virtualisation;
 
-  qemuGraphics = lib.optionalString (!cfg.graphics) "-nographic";
-
   consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles;
 
-  # XXX: This is very ugly and in the future we really should use attribute
-  # sets to build ALL of the QEMU flags instead of this mixed mess of Nix
-  # expressions and shell script stuff.
-  mkDiskIfaceDriveFlag = idx: driveArgs: let
-    inherit (cfg.qemu) diskInterface;
-    # The drive identifier created by incrementing the index by one using the
-    # shell.
-    drvId = "drive$((${idx} + 1))";
-    # NOTE: DO NOT shell escape, because this may contain shell variables.
-    commonArgs = "index=${idx},id=${drvId},${driveArgs}";
-    isSCSI = diskInterface == "scsi";
-    devArgs = "${diskInterface}-hd,drive=${drvId}";
-    args = "-drive ${commonArgs},if=none -device lsi53c895a -device ${devArgs}";
-  in if isSCSI then args else "-drive ${commonArgs},if=${diskInterface}";
+  driveOpts = { ... }: {
+
+    options = {
+
+      file = mkOption {
+        type = types.str;
+        description = "The file image used for this drive.";
+      };
+
+      driveExtraOpts = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        description = "Extra options passed to drive flag.";
+      };
+
+      deviceExtraOpts = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        description = "Extra options passed to device flag.";
+      };
+
+    };
+
+  };
+
+  driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }:
+    let
+      drvId = "drive${toString idx}";
+      mkKeyValue = generators.mkKeyValueDefault {} "=";
+      mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts);
+      driveOpts = mkOpts (driveExtraOpts // {
+        index = idx;
+        id = drvId;
+        "if" = "none";
+        inherit file;
+      });
+      deviceOpts = mkOpts (deviceExtraOpts // {
+        drive = drvId;
+      });
+      device =
+        if cfg.qemu.diskInterface == "scsi" then
+          "-device lsi53c895a -device scsi-hd,${deviceOpts}"
+        else
+          "-device virtio-blk-pci,${deviceOpts}";
+    in
+      "-drive ${driveOpts} ${device}";
+
+  drivesCmdLine = drives: concatStringsSep " " (imap1 driveCmdline drives);
 
   # Shell script to start the VM.
   startVM =
@@ -77,13 +109,11 @@ let
       ''}
 
       cd $TMPDIR
-      idx=2
-      extraDisks=""
+      idx=0
       ${flip concatMapStrings cfg.emptyDiskImages (size: ''
         if ! test -e "empty$idx.qcow2"; then
             ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M"
         fi
-        extraDisks="$extraDisks ${mkDiskIfaceDriveFlag "$idx" "file=$(pwd)/empty$idx.qcow2,werror=report"}"
         idx=$((idx + 1))
       '')}
 
@@ -97,21 +127,7 @@ let
           -virtfs local,path=/nix/store,security_model=none,mount_tag=store \
           -virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \
           -virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \
-          ${if cfg.useBootLoader then ''
-            ${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \
-            ${mkDiskIfaceDriveFlag "1" "file=$TMPDIR/disk.img,media=disk"} \
-            ${if cfg.useEFIBoot then ''
-              -pflash $TMPDIR/bios.bin \
-            '' else ''
-            ''}
-          '' else ''
-            ${mkDiskIfaceDriveFlag "0" "file=$NIX_DISK_IMAGE,cache=writeback,werror=report"} \
-            -kernel ${config.system.build.toplevel}/kernel \
-            -initrd ${config.system.build.toplevel}/initrd \
-            -append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS" \
-          ''} \
-          $extraDisks \
-          ${qemuGraphics} \
+          ${drivesCmdLine config.virtualisation.qemu.drives} \
           ${toString config.virtualisation.qemu.options} \
           $QEMU_OPTS \
           "$@"
@@ -367,6 +383,12 @@ in
           '';
         };
 
+      drives =
+        mkOption {
+          type = types.listOf (types.submodule driveOpts);
+          description = "Drives passed to qemu.";
+        };
+
       diskInterface =
         mkOption {
           default = "virtio";
@@ -476,8 +498,49 @@ in
 
     # FIXME: Consolidate this one day.
     virtualisation.qemu.options = mkMerge [
-      (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ "-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0" ])
-      (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [ "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" ])
+      (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [
+        "-vga std" "-usb" "-device usb-tablet,bus=usb-bus.0"
+      ])
+      (mkIf (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) [
+        "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet"
+      ])
+      (mkIf (!cfg.useBootLoader) [
+        "-kernel ${config.system.build.toplevel}/kernel"
+        "-initrd ${config.system.build.toplevel}/initrd"
+        ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"''
+      ])
+      (mkIf cfg.useEFIBoot [
+        "-pflash $TMPDIR/bios.bin"
+      ])
+      (mkIf (!cfg.graphics) [
+        "-nographic"
+      ])
+    ];
+
+    virtualisation.qemu.drives = mkMerge [
+      (mkIf cfg.useBootLoader [
+        {
+          file = "$NIX_DISK_IMAGE";
+          driveExtraOpts.cache = "writeback";
+          driveExtraOpts.werror = "report";
+        }
+        {
+          file = "$TMPDIR/disk.img";
+          driveExtraOpts.media = "disk";
+          deviceExtraOpts.bootindex = "1";
+        }
+      ])
+      (mkIf (!cfg.useBootLoader) [
+        {
+          file = "$NIX_DISK_IMAGE";
+          driveExtraOpts.cache = "writeback";
+          driveExtraOpts.werror = "report";
+        }
+      ])
+      (imap0 (idx: _: {
+        file = "$(pwd)/empty${toString idx}.qcow2";
+        driveExtraOpts.werror = "report";
+      }) cfg.emptyDiskImages)
     ];
 
     # Mount the host filesystem via 9P, and bind-mount the Nix store
@@ -557,7 +620,7 @@ in
 
     # Wireless won't work in the VM.
     networking.wireless.enable = mkVMOverride false;
-    networking.connman.enable = mkVMOverride false;
+    services.connman.enable = mkVMOverride false;
 
     # Speed up booting by not waiting for ARP.
     networking.dhcpcd.extraConfig = "noarp";
diff --git a/nixos/modules/virtualisation/railcar.nix b/nixos/modules/virtualisation/railcar.nix
new file mode 100644
index 000000000000..12da1c75fc38
--- /dev/null
+++ b/nixos/modules/virtualisation/railcar.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.railcar;
+  generateUnit = name: containerConfig:
+    let
+      container = pkgs.ociTools.buildContainer {
+        args = [
+          (pkgs.writeShellScript "run.sh" containerConfig.cmd).outPath
+        ];
+      };
+    in
+      nameValuePair "railcar-${name}" {
+        enable = true;
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+            ExecStart = ''
+              ${cfg.package}/bin/railcar -r ${cfg.stateDir} run ${name} -b ${container}
+            '';
+            Type = containerConfig.runType;
+          };
+      };
+  mount = with types; (submodule {
+    options = {
+      type = mkOption {
+        type = str;
+        default = "none";
+        description = ''
+          The type of the filesystem to be mounted.
+          Linux: filesystem types supported by the kernel as listed in 
+          `/proc/filesystems` (e.g., "minix", "ext2", "ext3", "jfs", "xfs", 
+          "reiserfs", "msdos", "proc", "nfs", "iso9660"). For bind mounts 
+          (when options include either bind or rbind), the type is a dummy,
+          often "none" (not listed in /proc/filesystems).
+        '';
+      };
+      source = mkOption {
+        type = str;
+        description = "Source for the in-container mount";
+      };
+      options = mkOption {
+        type = loaOf (str);
+        default = [ "bind" ];
+        description = ''
+          Mount options of the filesystem to be used.
+        
+          Support optoions are listed in the mount(8) man page. Note that 
+          both filesystem-independent and filesystem-specific options 
+          are listed.
+        '';
+      };
+    };
+  });
+in
+{
+  options.services.railcar = {
+    enable = mkEnableOption "railcar";
+
+    containers = mkOption {
+      default = {};
+      description = "Declarative container configuration";
+      type = with types; loaOf (submodule ({ name, config, ... }: {
+        options = {
+          cmd = mkOption {
+            type = types.lines;
+            description = "Command or script to run inside the container";
+          };
+
+          mounts = mkOption {
+            type = with types; attrsOf mount;
+            default = {};
+            description = ''
+              A set of mounts inside the container.
+
+              The defaults have been chosen for simple bindmounts, meaning
+              that you only need to provide the "source" parameter.
+            '';
+            example = ''
+              { "/data" = { source = "/var/lib/data"; }; }
+            '';
+          };
+
+          runType = mkOption {
+            type = types.str;
+            default = "oneshot";
+            description = "The systemd service run type";
+          };
+
+          os = mkOption {
+            type = types.str;
+            default = "linux";
+            description = "OS type of the container";
+          };
+
+          arch = mkOption {
+            type = types.str;
+            default = "x86_64";
+            description = "Computer architecture type of the container";
+          };
+        };
+      }));
+    };
+
+    stateDir = mkOption {
+      type = types.path;
+      default = ''/var/railcar'';
+      description = "Railcar persistent state directory";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.railcar;
+      description = "Railcar package to use";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services = flip mapAttrs' cfg.containers (name: containerConfig:
+      generateUnit name containerConfig
+    );
+  };
+}
+
diff --git a/nixos/modules/virtualisation/virtualbox-host.nix b/nixos/modules/virtualisation/virtualbox-host.nix
index 6f7370181740..ddb0a7bda4f3 100644
--- a/nixos/modules/virtualisation/virtualbox-host.nix
+++ b/nixos/modules/virtualisation/virtualbox-host.nix
@@ -104,7 +104,7 @@ in
       "VBoxNetNAT"
       "VBoxSDL"
       "VBoxVolInfo"
-      "VirtualBox"
+      "VirtualBoxVM"
     ]));
 
     users.groups.vboxusers.gid = config.ids.gids.vboxusers;
@@ -122,7 +122,7 @@ in
 
     # Since we lack the right setuid/setcap binaries, set up a host-only network by default.
   } (mkIf cfg.addNetworkInterface {
-    systemd.services."vboxnet0" =
+    systemd.services.vboxnet0 =
       { description = "VirtualBox vboxnet0 Interface";
         requires = [ "dev-vboxnetctl.device" ];
         after = [ "dev-vboxnetctl.device" ];
@@ -149,5 +149,12 @@ in
     # Make sure NetworkManager won't assume this interface being up
     # means we have internet access.
     networking.networkmanager.unmanaged = ["vboxnet0"];
-  })]);
+  }) (mkIf config.networking.useNetworkd {
+    systemd.network.networks."40-vboxnet0".extraConfig = ''
+      [Link]
+      RequiredForOnline=no
+    '';
+  })
+
+]);
 }
diff --git a/nixos/modules/virtualisation/vmware-guest.nix b/nixos/modules/virtualisation/vmware-guest.nix
index d18778f81588..962a9059ea47 100644
--- a/nixos/modules/virtualisation/vmware-guest.nix
+++ b/nixos/modules/virtualisation/vmware-guest.nix
@@ -8,6 +8,10 @@ let
   xf86inputvmmouse = pkgs.xorg.xf86inputvmmouse;
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "services" "vmwareGuest" ] [ "virtualisation" "vmware" "guest" ])
+  ];
+
   options.virtualisation.vmware.guest = {
     enable = mkEnableOption "VMWare Guest Support";
     headless = mkOption {
@@ -33,7 +37,7 @@ in
         serviceConfig.ExecStart = "${open-vm-tools}/bin/vmtoolsd";
       };
 
-    environment.etc."vmware-tools".source = "${open-vm-tools}/etc/vmware-tools/*";
+    environment.etc.vmware-tools.source = "${open-vm-tools}/etc/vmware-tools/*";
 
     services.xserver = mkIf (!cfg.headless) {
       videoDrivers = mkOverride 50 [ "vmware" ];
diff --git a/nixos/modules/virtualisation/xen-dom0.nix b/nixos/modules/virtualisation/xen-dom0.nix
index 70e575b6c0d2..7f0af9901b9b 100644
--- a/nixos/modules/virtualisation/xen-dom0.nix
+++ b/nixos/modules/virtualisation/xen-dom0.nix
@@ -9,6 +9,11 @@ let
 in
 
 {
+  imports = [
+    (mkRemovedOptionModule [ "virtualisation" "xen" "qemu" ] "You don't need this option anymore, it will work without it.")
+    (mkRenamedOptionModule [ "virtualisation" "xen" "qemu-package" ] [ "virtualisation" "xen" "package-qemu" ])
+  ];
+
   ###### interface
 
   options = {
@@ -119,7 +124,7 @@ in
 
     virtualisation.xen.domains = {
         extraConfig = mkOption {
-          type = types.string;
+          type = types.lines;
           default = "";
           description =
             ''
@@ -228,26 +233,19 @@ in
 
 
     environment.etc =
-      [ { source = "${cfg.package}/etc/xen/xl.conf";
-          target = "xen/xl.conf";
-        }
-        { source = "${cfg.package}/etc/xen/scripts";
-          target = "xen/scripts";
-        }
-        { text = ''
-            source ${cfg.package}/etc/default/xendomains
-
-            ${cfg.domains.extraConfig}
-          '';
-          target = "default/xendomains";
-        }
-      ]
-      ++ lib.optionals (builtins.compareVersions cfg.package.version "4.10" >= 0) [
+      {
+        "xen/xl.conf".source = "${cfg.package}/etc/xen/xl.conf";
+        "xen/scripts".source = "${cfg.package}/etc/xen/scripts";
+        "default/xendomains".text = ''
+          source ${cfg.package}/etc/default/xendomains
+
+          ${cfg.domains.extraConfig}
+        '';
+      }
+      // optionalAttrs (builtins.compareVersions cfg.package.version "4.10" >= 0) {
         # in V 4.10 oxenstored requires /etc/xen/oxenstored.conf to start
-        { source = "${cfg.package}/etc/xen/oxenstored.conf";
-          target = "xen/oxenstored.conf";
-        }
-      ];
+        "xen/oxenstored.conf".source = "${cfg.package}/etc/xen/oxenstored.conf";
+      };
 
     # Xen provides udev rules.
     services.udev.packages = [ cfg.package ];