about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/administration/cleaning-store.xml10
-rw-r--r--nixos/doc/manual/administration/container-networking.xml4
-rw-r--r--nixos/doc/manual/administration/control-groups.xml2
-rw-r--r--nixos/doc/manual/administration/logging.xml6
-rw-r--r--nixos/doc/manual/administration/rollback.xml2
-rw-r--r--nixos/doc/manual/administration/service-mgmt.xml4
-rw-r--r--nixos/doc/manual/administration/store-corruption.xml4
-rw-r--r--nixos/doc/manual/administration/user-sessions.xml4
-rw-r--r--nixos/doc/manual/configuration/ad-hoc-packages.xml10
-rw-r--r--nixos/doc/manual/configuration/adding-custom-packages.xml8
-rw-r--r--nixos/doc/manual/configuration/declarative-packages.xml2
-rw-r--r--nixos/doc/manual/configuration/ipv6-config.xml2
-rw-r--r--nixos/doc/manual/configuration/matrix.xml188
-rw-r--r--nixos/doc/manual/configuration/modularity.xml28
-rw-r--r--nixos/doc/manual/configuration/profiles.xml2
-rw-r--r--nixos/doc/manual/configuration/user-mgmt.xml2
-rw-r--r--nixos/doc/manual/configuration/wireless.xml6
-rw-r--r--nixos/doc/manual/configuration/x-windows.xml134
-rw-r--r--nixos/doc/manual/configuration/xfce.xml12
-rw-r--r--nixos/doc/manual/development/building-nixos.xml8
-rw-r--r--nixos/doc/manual/development/building-parts.xml20
-rw-r--r--nixos/doc/manual/development/running-nixos-tests-interactively.xml18
-rw-r--r--nixos/doc/manual/development/running-nixos-tests.xml8
-rw-r--r--nixos/doc/manual/development/sources.xml24
-rw-r--r--nixos/doc/manual/development/testing-installer.xml12
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.xml14
-rw-r--r--nixos/doc/manual/installation/changing-config.xml23
-rw-r--r--nixos/doc/manual/installation/installing-from-other-distro.xml45
-rw-r--r--nixos/doc/manual/installation/installing-usb.xml10
-rw-r--r--nixos/doc/manual/installation/installing.xml88
-rw-r--r--nixos/doc/manual/man-nixos-generate-config.xml10
-rw-r--r--nixos/doc/manual/man-nixos-install.xml38
-rw-r--r--nixos/doc/manual/man-nixos-option.xml12
-rw-r--r--nixos/doc/manual/man-nixos-rebuild.xml40
-rw-r--r--nixos/doc/manual/man-pages.xml2
-rw-r--r--nixos/doc/manual/release-notes/rl-1509.xml2
-rw-r--r--nixos/doc/manual/release-notes/rl-1703.xml16
-rw-r--r--nixos/doc/manual/release-notes/rl-1909.xml259
-rw-r--r--nixos/lib/build-vms.nix2
-rw-r--r--nixos/lib/make-ext4-fs.nix25
-rw-r--r--nixos/lib/test-driver/Machine.pm12
-rw-r--r--nixos/lib/testing.nix2
-rw-r--r--nixos/lib/testing/jquery.nix2
-rw-r--r--nixos/modules/config/malloc.nix12
-rw-r--r--nixos/modules/config/networking.nix103
-rw-r--r--nixos/modules/config/no-x-libs.nix2
-rw-r--r--nixos/modules/config/resolvconf.nix149
-rw-r--r--nixos/modules/config/xdg/portal.nix58
-rw-r--r--nixos/modules/hardware/ksm.nix22
-rw-r--r--nixos/modules/hardware/opengl.nix24
-rw-r--r--nixos/modules/hardware/video/amdgpu-pro.nix3
-rw-r--r--nixos/modules/hardware/video/ati.nix3
-rw-r--r--nixos/modules/hardware/video/nvidia.nix3
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-aarch64.nix20
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix15
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix15
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image.nix94
-rw-r--r--nixos/modules/installer/netboot/netboot.nix2
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl118
-rw-r--r--nixos/modules/installer/tools/tools.nix105
-rw-r--r--nixos/modules/misc/documentation.nix8
-rw-r--r--nixos/modules/misc/ids.nix6
-rw-r--r--nixos/modules/misc/nixops-autoluks.nix43
-rw-r--r--nixos/modules/misc/nixpkgs.nix1
-rw-r--r--nixos/modules/misc/version.nix3
-rw-r--r--nixos/modules/module-list.nix20
-rw-r--r--nixos/modules/profiles/hardened.nix40
-rw-r--r--nixos/modules/programs/captive-browser.nix122
-rw-r--r--nixos/modules/programs/shell.nix4
-rw-r--r--nixos/modules/programs/ssh.nix11
-rw-r--r--nixos/modules/programs/tsm-client.nix287
-rw-r--r--nixos/modules/programs/xonsh.nix5
-rw-r--r--nixos/modules/rename.nix17
-rw-r--r--nixos/modules/security/misc.nix24
-rw-r--r--nixos/modules/services/audio/mopidy.nix10
-rw-r--r--nixos/modules/services/audio/roon-server.nix49
-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/backup/duplicati.nix16
-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/cluster/kubernetes/default.nix7
-rw-r--r--nixos/modules/services/cluster/kubernetes/kubelet.nix7
-rw-r--r--nixos/modules/services/cluster/kubernetes/pki.nix1
-rw-r--r--nixos/modules/services/computing/boinc/client.nix9
-rw-r--r--nixos/modules/services/databases/cassandra.nix210
-rw-r--r--nixos/modules/services/databases/firebird.nix14
-rw-r--r--nixos/modules/services/databases/foundationdb.nix22
-rw-r--r--nixos/modules/services/databases/foundationdb.xml38
-rw-r--r--nixos/modules/services/databases/hbase.nix17
-rw-r--r--nixos/modules/services/databases/mysql.nix207
-rw-r--r--nixos/modules/services/databases/openldap.nix4
-rw-r--r--nixos/modules/services/databases/postgresql.nix41
-rw-r--r--nixos/modules/services/databases/postgresql.xml76
-rw-r--r--nixos/modules/services/desktops/deepin/deepin.nix4
-rw-r--r--nixos/modules/services/desktops/flatpak.nix28
-rw-r--r--nixos/modules/services/desktops/flatpak.xml24
-rw-r--r--nixos/modules/services/desktops/tumbler.nix2
-rw-r--r--nixos/modules/services/development/bloop.nix23
-rw-r--r--nixos/modules/services/editors/emacs.xml13
-rw-r--r--nixos/modules/services/hardware/80-net-setup-link.rules13
-rw-r--r--nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix4
-rw-r--r--nixos/modules/services/hardware/throttled.nix11
-rw-r--r--nixos/modules/services/hardware/triggerhappy.nix2
-rw-r--r--nixos/modules/services/hardware/udev.nix8
-rw-r--r--nixos/modules/services/logging/graylog.nix17
-rw-r--r--nixos/modules/services/logging/heartbeat.nix6
-rw-r--r--nixos/modules/services/mail/dspam.nix13
-rw-r--r--nixos/modules/services/mail/opendkim.nix7
-rw-r--r--nixos/modules/services/mail/postfix.nix19
-rw-r--r--nixos/modules/services/mail/rspamd.nix1
-rw-r--r--nixos/modules/services/misc/apache-kafka.nix9
-rw-r--r--nixos/modules/services/misc/couchpotato.nix7
-rw-r--r--nixos/modules/services/misc/gitea.nix125
-rw-r--r--nixos/modules/services/misc/gitlab.nix94
-rw-r--r--nixos/modules/services/misc/gitlab.xml12
-rw-r--r--nixos/modules/services/misc/gitolite.nix29
-rw-r--r--nixos/modules/services/misc/gollum.nix15
-rw-r--r--nixos/modules/services/misc/greenclip.nix31
-rw-r--r--nixos/modules/services/misc/jackett.nix9
-rw-r--r--nixos/modules/services/misc/lidarr.nix57
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix7
-rw-r--r--nixos/modules/services/misc/octoprint.nix7
-rw-r--r--nixos/modules/services/misc/phd.nix52
-rw-r--r--nixos/modules/services/misc/redmine.nix75
-rw-r--r--nixos/modules/services/misc/taskserver/default.nix2
-rw-r--r--nixos/modules/services/misc/taskserver/doc.xml2
-rw-r--r--nixos/modules/services/misc/tiddlywiki.nix52
-rw-r--r--nixos/modules/services/misc/zoneminder.nix25
-rw-r--r--nixos/modules/services/monitoring/alerta.nix11
-rw-r--r--nixos/modules/services/monitoring/grafana-reporter.nix2
-rw-r--r--nixos/modules/services/monitoring/grafana.nix2
-rw-r--r--nixos/modules/services/monitoring/kapacitor.nix11
-rw-r--r--nixos/modules/services/monitoring/loki.nix112
-rw-r--r--nixos/modules/services/monitoring/nagios.nix23
-rw-r--r--nixos/modules/services/monitoring/netdata.nix27
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix90
-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.nix3
-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.nix3
-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/nginx.nix27
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/node.nix6
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/postfix.nix3
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/postgres.nix47
-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.nix37
-rw-r--r--nixos/modules/services/monitoring/riemann-dash.nix8
-rw-r--r--nixos/modules/services/monitoring/riemann-tools.nix1
-rw-r--r--nixos/modules/services/monitoring/scollector.nix1
-rw-r--r--nixos/modules/services/monitoring/thanos.nix801
-rw-r--r--nixos/modules/services/monitoring/zabbix-agent.nix163
-rw-r--r--nixos/modules/services/monitoring/zabbix-proxy.nix298
-rw-r--r--nixos/modules/services/monitoring/zabbix-server.nix341
-rw-r--r--nixos/modules/services/network-filesystems/kbfs.nix1
-rw-r--r--nixos/modules/services/network-filesystems/samba.nix8
-rw-r--r--nixos/modules/services/networking/aria2.nix14
-rw-r--r--nixos/modules/services/networking/autossh.nix1
-rw-r--r--nixos/modules/services/networking/avahi-daemon.nix344
-rw-r--r--nixos/modules/services/networking/bind.nix8
-rw-r--r--nixos/modules/services/networking/bitcoind.nix2
-rw-r--r--nixos/modules/services/networking/charybdis.nix9
-rw-r--r--nixos/modules/services/networking/ddclient.nix2
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix2
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy.xml8
-rw-r--r--nixos/modules/services/networking/dnsmasq.nix11
-rw-r--r--nixos/modules/services/networking/hostapd.nix14
-rw-r--r--nixos/modules/services/networking/iperf3.nix10
-rw-r--r--nixos/modules/services/networking/keybase.nix1
-rw-r--r--nixos/modules/services/networking/kresd.nix7
-rw-r--r--nixos/modules/services/networking/minidlna.nix8
-rw-r--r--nixos/modules/services/networking/networkmanager.nix159
-rw-r--r--nixos/modules/services/networking/quassel.nix10
-rw-r--r--nixos/modules/services/networking/rdnssd.nix5
-rw-r--r--nixos/modules/services/networking/smokeping.nix22
-rw-r--r--nixos/modules/services/networking/squid.nix3
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix10
-rw-r--r--nixos/modules/services/networking/syncthing.nix22
-rw-r--r--nixos/modules/services/networking/teamspeak3.nix10
-rw-r--r--nixos/modules/services/networking/thelounge.nix75
-rw-r--r--nixos/modules/services/networking/unbound.nix2
-rw-r--r--nixos/modules/services/networking/unifi.nix11
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix1
-rw-r--r--nixos/modules/services/networking/zeronet.nix13
-rw-r--r--nixos/modules/services/security/sshguard.nix2
-rw-r--r--nixos/modules/services/security/tor.nix2
-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.nix31
-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.nix91
-rw-r--r--nixos/modules/services/web-apps/atlassian/confluence.nix18
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix17
-rw-r--r--nixos/modules/services/web-apps/atlassian/jira.nix18
-rw-r--r--nixos/modules/services/web-apps/frab.nix10
-rw-r--r--nixos/modules/services/web-apps/limesurvey.nix20
-rw-r--r--nixos/modules/services/web-apps/matomo-doc.xml22
-rw-r--r--nixos/modules/services/web-apps/mediawiki.nix473
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix35
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml64
-rw-r--r--nixos/modules/services/web-apps/restya-board.nix2
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix63
-rw-r--r--nixos/modules/services/web-apps/wordpress.nix371
-rw-r--r--nixos/modules/services/web-apps/zabbix.nix225
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix11
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/foswiki.nix78
-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.nix8
-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/lighttpd/cgit.nix4
-rw-r--r--nixos/modules/services/web-servers/lighttpd/collectd.nix2
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix11
-rw-r--r--nixos/modules/services/x11/compton.nix136
-rw-r--r--nixos/modules/services/x11/desktop-managers/default.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/enlightenment.nix2
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix5
-rw-r--r--nixos/modules/services/x11/desktop-managers/pantheon.nix8
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix24
-rw-r--r--nixos/modules/services/x11/desktop-managers/surf-display.nix127
-rw-r--r--nixos/modules/services/x11/desktop-managers/xterm.nix4
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix46
-rw-r--r--nixos/modules/services/x11/extra-layouts.nix165
-rw-r--r--nixos/modules/services/x11/gdk-pixbuf.nix8
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix2
-rw-r--r--nixos/modules/services/x11/xserver.nix14
-rw-r--r--nixos/modules/system/boot/kernel.nix2
-rw-r--r--nixos/modules/system/boot/kernel_config.nix1
-rw-r--r--nixos/modules/system/boot/loader/grub/install-grub.pl23
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py4
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix15
-rw-r--r--nixos/modules/system/boot/luksroot.nix1
-rw-r--r--nixos/modules/system/boot/networkd.nix2
-rw-r--r--nixos/modules/system/boot/resolved.nix43
-rw-r--r--nixos/modules/system/boot/stage-2.nix9
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix2
-rw-r--r--nixos/modules/system/boot/systemd.nix5
-rw-r--r--nixos/modules/system/boot/timesyncd.nix9
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix32
-rw-r--r--nixos/modules/tasks/filesystems/xfs.nix1
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix46
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix2
-rw-r--r--nixos/modules/tasks/network-interfaces-systemd.nix11
-rw-r--r--nixos/modules/tasks/network-interfaces.nix23
-rw-r--r--nixos/modules/testing/test-instrumentation.nix3
-rw-r--r--nixos/modules/virtualisation/anbox.nix7
-rw-r--r--nixos/modules/virtualisation/cloudstack-config.nix2
-rw-r--r--nixos/modules/virtualisation/containers.nix34
-rw-r--r--nixos/modules/virtualisation/docker-containers.nix2
-rw-r--r--nixos/modules/virtualisation/google-compute-config.nix1
-rw-r--r--nixos/modules/virtualisation/kvmgt.nix30
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix104
-rw-r--r--nixos/modules/virtualisation/openvswitch.nix2
-rw-r--r--nixos/modules/virtualisation/parallels-guest.nix3
-rw-r--r--nixos/release-small.nix2
-rw-r--r--nixos/tests/all-tests.nix11
-rw-r--r--nixos/tests/avahi.nix7
-rw-r--r--nixos/tests/boot.nix107
-rw-r--r--nixos/tests/cassandra.nix122
-rw-r--r--nixos/tests/containers-imperative.nix21
-rw-r--r--nixos/tests/deluge.nix6
-rw-r--r--nixos/tests/etcd-cluster.nix2
-rw-r--r--nixos/tests/flatpak-builder.nix20
-rw-r--r--nixos/tests/fluentd.nix46
-rw-r--r--nixos/tests/gdk-pixbuf.nix4
-rw-r--r--nixos/tests/gitea.nix16
-rw-r--r--nixos/tests/grafana.nix90
-rw-r--r--nixos/tests/graylog.nix111
-rw-r--r--nixos/tests/hydra/default.nix2
-rw-r--r--nixos/tests/initdb.nix26
-rw-r--r--nixos/tests/installer.nix132
-rw-r--r--nixos/tests/ipv6.nix11
-rw-r--r--nixos/tests/loki.nix37
-rw-r--r--nixos/tests/mediawiki.nix19
-rw-r--r--nixos/tests/mysql.nix8
-rw-r--r--nixos/tests/networking.nix22
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix15
-rw-r--r--nixos/tests/nixos-generate-config.nix24
-rw-r--r--nixos/tests/ostree.nix2
-rw-r--r--nixos/tests/phabricator.nix77
-rw-r--r--nixos/tests/prometheus-2.nix208
-rw-r--r--nixos/tests/prometheus-exporters.nix78
-rw-r--r--nixos/tests/radicale.nix2
-rw-r--r--nixos/tests/redmine.nix24
-rw-r--r--nixos/tests/systemd-timesyncd.nix52
-rw-r--r--nixos/tests/tiddlywiki.nix67
-rw-r--r--nixos/tests/tomcat.nix30
-rw-r--r--nixos/tests/trac.nix74
-rw-r--r--nixos/tests/wordpress.nix65
308 files changed, 8653 insertions, 3949 deletions
diff --git a/nixos/doc/manual/administration/cleaning-store.xml b/nixos/doc/manual/administration/cleaning-store.xml
index f078b8c3ba37..526803e429ba 100644
--- a/nixos/doc/manual/administration/cleaning-store.xml
+++ b/nixos/doc/manual/administration/cleaning-store.xml
@@ -11,12 +11,12 @@
   Nix’s <emphasis>garbage collector</emphasis> to remove old, unreferenced
   packages. This is easy:
 <screen>
-$ nix-collect-garbage
+<prompt>$ </prompt>nix-collect-garbage
 </screen>
   Alternatively, you can use a systemd unit that does the same in the
   background:
 <screen>
-# systemctl start nix-gc.service
+<prompt># </prompt>systemctl start nix-gc.service
 </screen>
   You can tell NixOS in <filename>configuration.nix</filename> to run this unit
   automatically at certain points in time, for instance, every night at 03:15:
@@ -31,11 +31,11 @@ $ nix-collect-garbage
   configurations. The following command deletes old roots, removing the ability
   to roll back to them:
 <screen>
-$ nix-collect-garbage -d
+<prompt>$ </prompt>nix-collect-garbage -d
 </screen>
   You can also do this for specific profiles, e.g.
 <screen>
-$ nix-env -p /nix/var/nix/profiles/per-user/eelco/profile --delete-generations old
+<prompt>$ </prompt>nix-env -p /nix/var/nix/profiles/per-user/eelco/profile --delete-generations old
 </screen>
   Note that NixOS system configurations are stored in the profile
   <filename>/nix/var/nix/profiles/system</filename>.
@@ -45,7 +45,7 @@ $ nix-env -p /nix/var/nix/profiles/per-user/eelco/profile --delete-generations o
   Nix store) is to run Nix’s store optimiser, which seeks out identical files
   in the store and replaces them with hard links to a single copy.
 <screen>
-$ nix-store --optimise
+<prompt>$ </prompt>nix-store --optimise
 </screen>
   Since this command needs to read the entire Nix store, it can take quite a
   while to finish.
diff --git a/nixos/doc/manual/administration/container-networking.xml b/nixos/doc/manual/administration/container-networking.xml
index 2ee8bfdd50f1..42486f01fe8c 100644
--- a/nixos/doc/manual/administration/container-networking.xml
+++ b/nixos/doc/manual/administration/container-networking.xml
@@ -11,10 +11,10 @@
   <literal>10.233.0.0/16</literal>. You can get the container’s IPv4 address
   as follows:
 <screen>
-# nixos-container show-ip foo
+<prompt># </prompt>nixos-container show-ip foo
 10.233.4.2
 
-$ ping -c1 10.233.4.2
+<prompt>$ </prompt>ping -c1 10.233.4.2
 64 bytes from 10.233.4.2: icmp_seq=1 ttl=64 time=0.106 ms
 </screen>
  </para>
diff --git a/nixos/doc/manual/administration/control-groups.xml b/nixos/doc/manual/administration/control-groups.xml
index bb8b7f83d9e0..16d03cc0d1ab 100644
--- a/nixos/doc/manual/administration/control-groups.xml
+++ b/nixos/doc/manual/administration/control-groups.xml
@@ -16,7 +16,7 @@
   <literal>systemd</literal> hierarchy, which is what systemd uses to keep
   track of the processes belonging to each service or user session:
 <screen>
-$ systemd-cgls
+<prompt>$ </prompt>systemd-cgls
 ├─user
 │ └─eelco
 │   └─c1
diff --git a/nixos/doc/manual/administration/logging.xml b/nixos/doc/manual/administration/logging.xml
index a41936b373d6..da4877fcdf08 100644
--- a/nixos/doc/manual/administration/logging.xml
+++ b/nixos/doc/manual/administration/logging.xml
@@ -11,14 +11,14 @@
   The command <literal>journalctl</literal> allows you to see the contents of
   the journal. For example,
 <screen>
-$ journalctl -b
+<prompt>$ </prompt>journalctl -b
 </screen>
   shows all journal entries since the last reboot. (The output of
   <command>journalctl</command> is piped into <command>less</command> by
   default.) You can use various options and match operators to restrict output
   to messages of interest. For instance, to get all messages from PostgreSQL:
 <screen>
-$ journalctl -u postgresql.service
+<prompt>$ </prompt>journalctl -u postgresql.service
 -- Logs begin at Mon, 2013-01-07 13:28:01 CET, end at Tue, 2013-01-08 01:09:57 CET. --
 ...
 Jan 07 15:44:14 hagbard postgres[2681]: [2-1] LOG:  database system is shut down
@@ -29,7 +29,7 @@ Jan 07 15:45:13 hagbard postgres[2500]: [1-1] LOG:  database system is ready to
   Or to get all messages since the last reboot that have at least a
   “critical” severity level:
 <screen>
-$ journalctl -b -p crit
+<prompt>$ </prompt>journalctl -b -p crit
 Dec 17 21:08:06 mandark sudo[3673]: pam_unix(sudo:auth): auth could not identify password for [alice]
 Dec 29 01:30:22 mandark kernel[6131]: [1053513.909444] CPU6: Core temperature above threshold, cpu clock throttled (total events = 1)
 </screen>
diff --git a/nixos/doc/manual/administration/rollback.xml b/nixos/doc/manual/administration/rollback.xml
index 07c6acaa469c..fb87810ba461 100644
--- a/nixos/doc/manual/administration/rollback.xml
+++ b/nixos/doc/manual/administration/rollback.xml
@@ -33,7 +33,7 @@
   where <replaceable>N</replaceable> is the number of the NixOS system
   configuration. To get a list of the available configurations, do:
 <screen>
-$ ls -l /nix/var/nix/profiles/system-*-link
+<prompt>$ </prompt>ls -l /nix/var/nix/profiles/system-*-link
 <replaceable>...</replaceable>
 lrwxrwxrwx 1 root root 78 Aug 12 13:54 /nix/var/nix/profiles/system-268-link -> /nix/store/202b...-nixos-13.07pre4932_5a676e4-4be1055
 </screen>
diff --git a/nixos/doc/manual/administration/service-mgmt.xml b/nixos/doc/manual/administration/service-mgmt.xml
index 0c2085c81559..1b9c745eb59f 100644
--- a/nixos/doc/manual/administration/service-mgmt.xml
+++ b/nixos/doc/manual/administration/service-mgmt.xml
@@ -21,7 +21,7 @@
   <command>systemd</command>. Without any arguments, it shows the status of
   active units:
 <screen>
-$ systemctl
+<prompt>$ </prompt>systemctl
 -.mount          loaded active mounted   /
 swapfile.swap    loaded active active    /swapfile
 sshd.service     loaded active running   SSH Daemon
@@ -33,7 +33,7 @@ graphical.target loaded active active    Graphical Interface
   You can ask for detailed status information about a unit, for instance, the
   PostgreSQL database service:
 <screen>
-$ systemctl status postgresql.service
+<prompt>$ </prompt>systemctl status postgresql.service
 postgresql.service - PostgreSQL Server
           Loaded: loaded (/nix/store/pn3q73mvh75gsrl8w7fdlfk3fq5qm5mw-unit/postgresql.service)
           Active: active (running) since Mon, 2013-01-07 15:55:57 CET; 9h ago
diff --git a/nixos/doc/manual/administration/store-corruption.xml b/nixos/doc/manual/administration/store-corruption.xml
index a4ca3b651e20..b9d11152d5e1 100644
--- a/nixos/doc/manual/administration/store-corruption.xml
+++ b/nixos/doc/manual/administration/store-corruption.xml
@@ -18,7 +18,7 @@
   If the corruption is in a path in the closure of the NixOS system
   configuration, you can fix it by doing
 <screen>
-# nixos-rebuild switch --repair
+<prompt># </prompt>nixos-rebuild switch --repair
 </screen>
   This will cause Nix to check every path in the closure, and if its
   cryptographic hash differs from the hash recorded in Nix’s database, the
@@ -28,7 +28,7 @@
  <para>
   You can also scan the entire Nix store for corrupt paths:
 <screen>
-# nix-store --verify --check-contents --repair
+<prompt># </prompt>nix-store --verify --check-contents --repair
 </screen>
   Any corrupt paths will be redownloaded if they’re available in a binary
   cache; otherwise, they cannot be repaired.
diff --git a/nixos/doc/manual/administration/user-sessions.xml b/nixos/doc/manual/administration/user-sessions.xml
index 1d95cfb22b69..80daf6bdbff0 100644
--- a/nixos/doc/manual/administration/user-sessions.xml
+++ b/nixos/doc/manual/administration/user-sessions.xml
@@ -10,7 +10,7 @@
   allows querying and manipulating user sessions. For instance, to list all
   user sessions:
 <screen>
-$ loginctl
+<prompt>$ </prompt>loginctl
    SESSION        UID USER             SEAT
         c1        500 eelco            seat0
         c3          0 root             seat0
@@ -21,7 +21,7 @@ $ loginctl
   devices attached to the system; usually, there is only one seat.) To get
   information about a session:
 <screen>
-$ loginctl session-status c3
+<prompt>$ </prompt>loginctl session-status c3
 c3 - root (0)
            Since: Tue, 2013-01-08 01:17:56 CET; 4min 42s ago
           Leader: 2536 (login)
diff --git a/nixos/doc/manual/configuration/ad-hoc-packages.xml b/nixos/doc/manual/configuration/ad-hoc-packages.xml
index 19159d8db5b6..c7e882d846fa 100644
--- a/nixos/doc/manual/configuration/ad-hoc-packages.xml
+++ b/nixos/doc/manual/configuration/ad-hoc-packages.xml
@@ -9,7 +9,7 @@
   With the command <command>nix-env</command>, you can install and uninstall
   packages from the command line. For instance, to install Mozilla Thunderbird:
 <screen>
-$ nix-env -iA nixos.thunderbird</screen>
+<prompt>$ </prompt>nix-env -iA nixos.thunderbird</screen>
   If you invoke this as root, the package is installed in the Nix profile
   <filename>/nix/var/nix/profiles/default</filename> and visible to all users
   of the system; otherwise, the package ends up in
@@ -25,7 +25,7 @@ $ nix-env -iA nixos.thunderbird</screen>
   Packages come from the NixOS channel. You typically upgrade a package by
   updating to the latest version of the NixOS channel:
 <screen>
-$ nix-channel --update nixos
+<prompt>$ </prompt>nix-channel --update nixos
 </screen>
   and then running <literal>nix-env -i</literal> again. Other packages in the
   profile are <emphasis>not</emphasis> affected; this is the crucial difference
@@ -34,21 +34,21 @@ $ nix-channel --update nixos
   their current versions in the NixOS channel. You can however upgrade all
   packages for which there is a newer version by doing:
 <screen>
-$ nix-env -u '*'
+<prompt>$ </prompt>nix-env -u '*'
 </screen>
  </para>
 
  <para>
   A package can be uninstalled using the <option>-e</option> flag:
 <screen>
-$ nix-env -e thunderbird
+<prompt>$ </prompt>nix-env -e thunderbird
 </screen>
  </para>
 
  <para>
   Finally, you can roll back an undesirable <command>nix-env</command> action:
 <screen>
-$ nix-env --rollback
+<prompt>$ </prompt>nix-env --rollback
 </screen>
  </para>
 
diff --git a/nixos/doc/manual/configuration/adding-custom-packages.xml b/nixos/doc/manual/configuration/adding-custom-packages.xml
index cdcfa10b8200..182641055e4d 100644
--- a/nixos/doc/manual/configuration/adding-custom-packages.xml
+++ b/nixos/doc/manual/configuration/adding-custom-packages.xml
@@ -14,8 +14,8 @@
 xlink:href="http://nixos.org/nixpkgs/manual">Nixpkgs
   manual</link>. In short, you clone Nixpkgs:
 <screen>
-$ git clone https://github.com/NixOS/nixpkgs
-$ cd nixpkgs
+<prompt>$ </prompt>git clone https://github.com/NixOS/nixpkgs
+<prompt>$ </prompt>cd nixpkgs
 </screen>
   Then you write and test the package as described in the Nixpkgs manual.
   Finally, you add it to <literal>environment.systemPackages</literal>, e.g.
@@ -65,8 +65,8 @@ stdenv.mkDerivation rec {
 </programlisting>
   This allows testing the package easily:
 <screen>
-$ nix-build my-hello.nix
-$ ./result/bin/hello
+<prompt>$ </prompt>nix-build my-hello.nix
+<prompt>$ </prompt>./result/bin/hello
 Hello, world!
 </screen>
  </para>
diff --git a/nixos/doc/manual/configuration/declarative-packages.xml b/nixos/doc/manual/configuration/declarative-packages.xml
index c9acbefea60e..5fb3bcb9f8f5 100644
--- a/nixos/doc/manual/configuration/declarative-packages.xml
+++ b/nixos/doc/manual/configuration/declarative-packages.xml
@@ -22,7 +22,7 @@
  <para>
   You can get a list of the available packages as follows:
 <screen>
-$ nix-env -qaP '*' --description
+<prompt>$ </prompt>nix-env -qaP '*' --description
 nixos.firefox   firefox-23.0   Mozilla Firefox - the browser, reloaded
 <replaceable>...</replaceable>
 </screen>
diff --git a/nixos/doc/manual/configuration/ipv6-config.xml b/nixos/doc/manual/configuration/ipv6-config.xml
index e9ab7cce4eb2..675a5d9a260d 100644
--- a/nixos/doc/manual/configuration/ipv6-config.xml
+++ b/nixos/doc/manual/configuration/ipv6-config.xml
@@ -39,7 +39,7 @@
 <xref linkend="opt-networking.defaultGateway6"/> = {
   address = "fe00::1";
   interface = "enp0s3";
-}
+};
 </programlisting>
  </para>
 
diff --git a/nixos/doc/manual/configuration/matrix.xml b/nixos/doc/manual/configuration/matrix.xml
index 66965460a15d..ef8d5cbda889 100644
--- a/nixos/doc/manual/configuration/matrix.xml
+++ b/nixos/doc/manual/configuration/matrix.xml
@@ -33,91 +33,91 @@
    <link xlink:href="https://github.com/matrix-org/synapse#synapse-installation">
    installation instructions of Synapse </link>.
 <programlisting>
+let
+  fqdn =
     let
-      fqdn =
-        let
-          join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
-        in join config.networking.hostName config.networking.domain;
-    in {
-      networking = {
-        hostName = "myhostname";
-        domain = "example.org";
-      };
-      networking.firewall.allowedTCPPorts = [ 80 443 ];
+      join = hostName: domain: hostName + optionalString (domain != null) ".${domain}";
+    in join config.networking.hostName config.networking.domain;
+in {
+  networking = {
+    hostName = "myhostname";
+    domain = "example.org";
+  };
+  networking.firewall.allowedTCPPorts = [ 80 443 ];
 
-      services.nginx = {
-        enable = true;
-        # only recommendedProxySettings and recommendedGzipSettings are strictly required,
-        # but the rest make sense as well
-        recommendedTlsSettings = true;
-        recommendedOptimisation = true;
-        recommendedGzipSettings = true;
-        recommendedProxySettings = true;
+  services.nginx = {
+    enable = true;
+    # only recommendedProxySettings and recommendedGzipSettings are strictly required,
+    # but the rest make sense as well
+    recommendedTlsSettings = true;
+    recommendedOptimisation = true;
+    recommendedGzipSettings = true;
+    recommendedProxySettings = true;
 
-        virtualHosts = {
-          # This host section can be placed on a different host than the rest,
-          # i.e. to delegate from the host being accessible as ${config.networking.domain}
-          # to another host actually running the Matrix homeserver.
-          "${config.networking.domain}" = {
-            locations."= /.well-known/matrix/server".extraConfig =
-              let
-                # use 443 instead of the default 8448 port to unite
-                # the client-server and server-server port for simplicity
-                server = { "m.server" = "${fqdn}:443"; };
-              in ''
-                add_header Content-Type application/json;
-                return 200 '${builtins.toJSON server}';
-              '';
-            locations."= /.well-known/matrix/client".extraConfig =
-              let
-                client = {
-                  "m.homeserver" =  { "base_url" = "https://${fqdn}"; };
-                  "m.identity_server" =  { "base_url" = "https://vector.im"; };
-                };
-              # ACAO required to allow riot-web on any URL to request this json file
-              in ''
-                add_header Content-Type application/json;
-                add_header Access-Control-Allow-Origin *;
-                return 200 '${builtins.toJSON client}';
-              '';
-          };
+    virtualHosts = {
+      # This host section can be placed on a different host than the rest,
+      # i.e. to delegate from the host being accessible as ${config.networking.domain}
+      # to another host actually running the Matrix homeserver.
+      "${config.networking.domain}" = {
+        locations."= /.well-known/matrix/server".extraConfig =
+          let
+            # use 443 instead of the default 8448 port to unite
+            # the client-server and server-server port for simplicity
+            server = { "m.server" = "${fqdn}:443"; };
+          in ''
+            add_header Content-Type application/json;
+            return 200 '${builtins.toJSON server}';
+          '';
+        locations."= /.well-known/matrix/client".extraConfig =
+          let
+            client = {
+              "m.homeserver" =  { "base_url" = "https://${fqdn}"; };
+              "m.identity_server" =  { "base_url" = "https://vector.im"; };
+            };
+          # ACAO required to allow riot-web on any URL to request this json file
+          in ''
+            add_header Content-Type application/json;
+            add_header Access-Control-Allow-Origin *;
+            return 200 '${builtins.toJSON client}';
+          '';
+      };
 
-          # Reverse proxy for Matrix client-server and server-server communication
-          ${fqdn} = {
-            enableACME = true;
-            forceSSL = true;
+      # Reverse proxy for Matrix client-server and server-server communication
+      ${fqdn} = {
+        enableACME = true;
+        forceSSL = true;
 
-            # Or do a redirect instead of the 404, or whatever is appropriate for you.
-            # But do not put a Matrix Web client here! See the Riot Web section below.
-            locations."/".extraConfig = ''
-              return 404;
-            '';
+        # Or do a redirect instead of the 404, or whatever is appropriate for you.
+        # But do not put a Matrix Web client here! See the Riot Web section below.
+        locations."/".extraConfig = ''
+          return 404;
+        '';
 
-            # forward all Matrix API calls to the synapse Matrix homeserver
-            locations."/_matrix" = {
-              proxyPass = "http://[::1]:8008";
-            };
-          };
+        # forward all Matrix API calls to the synapse Matrix homeserver
+        locations."/_matrix" = {
+          proxyPass = "http://[::1]:8008"; # without a trailing /
         };
       };
-      services.matrix-synapse = {
-        enable = true;
-        server_name = config.networking.domain;
-        listeners = [
-          {
-            port = 8008;
-            bind_address = "::1";
-            type = "http";
-            tls = false;
-            x_forwarded = true;
-            resources = [
-              { names = [ "client" "federation" ]; compress = false; }
-            ];
-          }
-        ];
-      };
     };
-   </programlisting>
+  };
+  services.matrix-synapse = {
+    enable = true;
+    server_name = config.networking.domain;
+    listeners = [
+      {
+        port = 8008;
+        bind_address = "::1";
+        type = "http";
+        tls = false;
+        x_forwarded = true;
+        resources = [
+          { names = [ "client" "federation" ]; compress = false; }
+        ];
+      }
+    ];
+  };
+};
+</programlisting>
   </para>
 
   <para>
@@ -141,15 +141,15 @@
    <option>services.matrix-synapse.registration_shared_secret</option>. To
    create a new user or admin, run the following after you have set the secret
    and have rebuilt NixOS:
-<programlisting>
-    $ nix run nixpkgs.matrix-synapse
-    $ register_new_matrix_user -k &lt;your-registration-shared-secret&gt; http://localhost:8008
-    New user localpart: &lt;your-username&gt;
-    Password:
-    Confirm password:
-    Make admin [no]:
-    Success!
-   </programlisting>
+<screen>
+<prompt>$ </prompt>nix run nixpkgs.matrix-synapse
+<prompt>$ </prompt>register_new_matrix_user -k <replaceable>your-registration-shared-secret</replaceable> http://localhost:8008
+<prompt>New user localpart: </prompt><replaceable>your-username</replaceable>
+<prompt>Password:</prompt>
+<prompt>Confirm password:</prompt>
+<prompt>Make admin [no]:</prompt>
+Success!
+</screen>
    In the example, this would create a user with the Matrix Identifier
    <literal>@your-username:example.org</literal>. Note that the registration
    secret ends up in the nix store and therefore is world-readable by any user
@@ -177,16 +177,16 @@
    Matrix Now!</link> for a list of existing clients and their supported
    featureset.
 <programlisting>
-     services.nginx.virtualHosts."riot.${fqdn}" = {
-       enableACME = true;
-       forceSSL = true;
-       serverAliases = [
-        "riot.${config.networking.domain}"
-       ];
+services.nginx.virtualHosts."riot.${fqdn}" = {
+  enableACME = true;
+  forceSSL = true;
+  serverAliases = [
+    "riot.${config.networking.domain}"
+  ];
 
-       root = pkgs.riot-web;
-     };
-   </programlisting>
+  root = pkgs.riot-web;
+};
+</programlisting>
   </para>
 
   <para>
diff --git a/nixos/doc/manual/configuration/modularity.xml b/nixos/doc/manual/configuration/modularity.xml
index 724abd31ca4e..7ad0ae80a48a 100644
--- a/nixos/doc/manual/configuration/modularity.xml
+++ b/nixos/doc/manual/configuration/modularity.xml
@@ -106,21 +106,21 @@ The unique option `services.httpd.adminAddr' is defined multiple times, in `/etc
   configuration option is. The command <option>nixos-option</option> allows you
   to find out:
 <screen>
-$ nixos-option <xref linkend="opt-services.xserver.enable"/>
+<prompt>$ </prompt>nixos-option <xref linkend="opt-services.xserver.enable"/>
 true
 
-$ nixos-option <xref linkend="opt-boot.kernelModules"/>
+<prompt>$ </prompt>nixos-option <xref linkend="opt-boot.kernelModules"/>
 [ "tun" "ipv6" "loop" <replaceable>...</replaceable> ]
 </screen>
   Interactive exploration of the configuration is possible using <command>nix
   repl</command>, a read-eval-print loop for Nix expressions. A typical use:
 <screen>
-$ nix repl '&lt;nixpkgs/nixos>'
+<prompt>$ </prompt>nix repl '&lt;nixpkgs/nixos>'
 
-nix-repl> config.<xref linkend="opt-networking.hostName"/>
+<prompt>nix-repl> </prompt>config.<xref linkend="opt-networking.hostName"/>
 "mandark"
 
-nix-repl> map (x: x.hostName) config.<xref linkend="opt-services.httpd.virtualHosts"/>
+<prompt>nix-repl> </prompt>map (x: x.hostName) config.<xref linkend="opt-services.httpd.virtualHosts"/>
 [ "example.org" "example.gov" ]
 </screen>
  </para>
@@ -129,17 +129,17 @@ nix-repl> map (x: x.hostName) config.<xref linkend="opt-services.httpd.virtualHo
   While abstracting your configuration, you may find it useful to generate
   modules using code, instead of writing files. The example below would have
   the same effect as importing a file which sets those options.
-<screen>
-     { config, pkgs, ... }:
+<programlisting>
+{ config, pkgs, ... }:
 
-     let netConfig = { hostName }: {
-       networking.hostName = hostName;
-       networking.useDHCP = false;
-    };
+let netConfig = { hostName }: {
+  networking.hostName = hostName;
+  networking.useDHCP = false;
+};
 
-    in
+in
 
-    { imports = [ (netConfig "nixos.localdomain") ]; }
-  </screen>
+{ imports = [ (netConfig "nixos.localdomain") ]; }
+</programlisting>
  </para>
 </section>
diff --git a/nixos/doc/manual/configuration/profiles.xml b/nixos/doc/manual/configuration/profiles.xml
index c0a8f55785fc..9d08f7f7bed2 100644
--- a/nixos/doc/manual/configuration/profiles.xml
+++ b/nixos/doc/manual/configuration/profiles.xml
@@ -16,7 +16,7 @@
   imports = [
    &lt;nixpkgs/nixos/modules/profiles/profile-name.nix&gt;
   ];
- </programlisting>
+</programlisting>
  <para>
   Even if some of these profiles seem only useful in the context of install
   media, many are actually intended to be used in real installs.
diff --git a/nixos/doc/manual/configuration/user-mgmt.xml b/nixos/doc/manual/configuration/user-mgmt.xml
index 66c1c6eb3a11..4b1710f3a2b1 100644
--- a/nixos/doc/manual/configuration/user-mgmt.xml
+++ b/nixos/doc/manual/configuration/user-mgmt.xml
@@ -44,7 +44,7 @@
   A user ID (uid) is assigned automatically. You can also specify a uid
   manually by adding
 <programlisting>
-    uid = 1000;
+uid = 1000;
 </programlisting>
   to the user specification.
  </para>
diff --git a/nixos/doc/manual/configuration/wireless.xml b/nixos/doc/manual/configuration/wireless.xml
index 7c7b3b4a65a5..9c0e3a8d7aa4 100644
--- a/nixos/doc/manual/configuration/wireless.xml
+++ b/nixos/doc/manual/configuration/wireless.xml
@@ -37,7 +37,7 @@
   If you are using WPA2 you can generate pskRaw key using
   <command>wpa_passphrase</command>:
 <screen>
-$ wpa_passphrase ESSID PSK
+<prompt>$ </prompt>wpa_passphrase ESSID PSK
 network={
         ssid="echelon"
         #psk="abcdefgh"
@@ -54,10 +54,10 @@ network={
   or you can use it to directly generate the
   <literal>wpa_supplicant.conf</literal>:
 <screen>
-# wpa_passphrase ESSID PSK > /etc/wpa_supplicant.conf</screen>
+<prompt># </prompt>wpa_passphrase ESSID PSK > /etc/wpa_supplicant.conf</screen>
   After you have edited the <literal>wpa_supplicant.conf</literal>, you need to
   restart the wpa_supplicant service.
 <screen>
-# systemctl restart wpa_supplicant.service</screen>
+<prompt># </prompt>systemctl restart wpa_supplicant.service</screen>
  </para>
 </section>
diff --git a/nixos/doc/manual/configuration/x-windows.xml b/nixos/doc/manual/configuration/x-windows.xml
index 798d1fbdfd85..7cdc5196e0d2 100644
--- a/nixos/doc/manual/configuration/x-windows.xml
+++ b/nixos/doc/manual/configuration/x-windows.xml
@@ -125,10 +125,12 @@
    You will need to reboot after enabling this driver to prevent a clash with
    other kernel modules.
   </para>
+  <note>
   <para>
-   Note: for recent AMD GPUs you most likely want to keep either the defaults
+   For recent AMD GPUs you most likely want to keep either the defaults
    or <literal>"amdgpu"</literal> (both free).
   </para>
+  </note>
  </simplesect>
  <simplesect xml:id="sec-x11-touchpads">
   <title>Touchpads</title>
@@ -157,4 +159,134 @@
    versions.
   </para>
  </simplesect>
+ <simplesect xml:id="custom-xkb-layouts">
+  <title>Custom XKB layouts</title>
+  <para>
+   It is possible to install custom
+   <link xlink:href="https://en.wikipedia.org/wiki/X_keyboard_extension">
+    XKB
+   </link>
+   keyboard layouts using the option
+   <option>
+    <link linkend="opt-services.xserver.extraLayouts">
+     services.xserver.extraLayouts
+    </link>
+   </option>.
+   As a first example, we are going to create a layout based on the basic US
+   layout, with an additional layer to type some greek symbols by pressing the
+   right-alt key.
+  </para>
+  <para>
+   To do this we are going to create a <literal>us-greek</literal> file
+   with a <literal>xkb_symbols</literal> section.
+  </para>
+<programlisting>
+xkb_symbols &quot;us-greek&quot;
+{
+  include &quot;us(basic)&quot;            // includes the base US keys
+  include &quot;level3(ralt_switch)&quot;  // configures right alt as a third level switch
+
+  key &lt;LatA&gt; { [ a, A, Greek_alpha ] };
+  key &lt;LatB&gt; { [ b, B, Greek_beta  ] };
+  key &lt;LatG&gt; { [ g, G, Greek_gamma ] };
+  key &lt;LatD&gt; { [ d, D, Greek_delta ] };
+  key &lt;LatZ&gt; { [ z, Z, Greek_zeta  ] };
+};
+</programlisting>
+  <para>
+   To install the layout, the filepath, a description and the list of
+   languages must be given:
+  </para>
+<programlisting>
+<xref linkend="opt-services.xserver.extraLayouts"/>.us-greek = {
+  description = "US layout with alt-gr greek";
+  languages   = [ "eng" ];
+  symbolsFile = /path/to/us-greek;
+}
+</programlisting>
+  <note>
+  <para>
+   The name should match the one given to the
+   <literal>xkb_symbols</literal> block.
+  </para>
+  </note>
+  <para>
+   The layout should now be installed and ready to use: try it by
+   running <literal>setxkbmap us-greek</literal> and type
+   <literal>&lt;alt&gt;+a</literal>. To change the default the usual
+   <option>
+    <link linkend="opt-services.xserver.layout">
+     services.xserver.layout
+    </link>
+   </option>
+   option can still be used.
+  </para>
+  <para>
+   A layout can have several other components besides
+   <literal>xkb_symbols</literal>, for example we will define new
+   keycodes for some multimedia key and bind these to some symbol.
+  </para>
+  <para>
+   Use the <emphasis>xev</emphasis> utility from
+   <literal>pkgs.xorg.xev</literal> to find the codes of the keys of
+   interest, then create a <literal>media-key</literal> file to hold
+   the keycodes definitions
+  </para>
+<programlisting>
+xkb_keycodes &quot;media&quot;
+{
+ &lt;volUp&gt;   = 123;
+ &lt;volDown&gt; = 456;
+}
+</programlisting>
+  <para>
+    Now use the newly define keycodes in <literal>media-sym</literal>:
+  </para>
+<programlisting>
+xkb_symbols &quot;media&quot;
+{
+ key.type = &quot;ONE_LEVEL&quot;;
+ key &lt;volUp&gt;   { [ XF86AudioLowerVolume ] };
+ key &lt;volDown&gt; { [ XF86AudioRaiseVolume ] };
+}
+</programlisting>
+  <para>
+    As before, to install the layout do
+  </para>
+<programlisting>
+<xref linkend="opt-services.xserver.extraLayouts"/>.media = {
+  description  = "Multimedia keys remapping";
+  languages    = [ "eng" ];
+  symbolsFile  = /path/to/media-key;
+  keycodesFile = /path/to/media-sym;
+};
+</programlisting>
+  <note>
+  <para>
+   The function <literal>pkgs.writeText &lt;filename&gt; &lt;content&gt;
+   </literal> can be useful if you prefer to keep the layout definitions
+   inside the NixOS configuration.
+  </para>
+  </note>
+  <para>
+    Unfortunately, the Xorg server does not (currently) support setting a
+    keymap directly but relies instead on XKB rules to select the matching
+    components (keycodes, types, ...) of a layout. This means that components
+    other than symbols won't be loaded by default. As a workaround, you
+    can set the keymap using <literal>setxkbmap</literal> at the start of the
+    session with:
+  </para>
+<programlisting>
+<xref linkend="opt-services.xserver.displayManager.sessionCommands"/> = "setxkbmap -keycodes media";
+</programlisting>
+  <para>
+   To learn how to write layouts take a look at the XKB
+  <link xlink:href="https://www.x.org/releases/current/doc/xorg-docs/input/XKB-Enhancing.html#Defining_New_Layouts">
+   documentation
+  </link>. More example layouts can also be found
+  <link xlink:href="https://wiki.archlinux.org/index.php/X_KeyBoard_extension#Basic_examples">
+   here
+  </link>.
+  </para>
+</simplesect>
 </chapter>
diff --git a/nixos/doc/manual/configuration/xfce.xml b/nixos/doc/manual/configuration/xfce.xml
index 77d5d9632792..6ac99c6b2bee 100644
--- a/nixos/doc/manual/configuration/xfce.xml
+++ b/nixos/doc/manual/configuration/xfce.xml
@@ -11,7 +11,7 @@
   <link linkend="opt-services.xserver.desktopManager.xfce.enable">xfce.enable</link> = true;
   <link linkend="opt-services.xserver.desktopManager.default">default</link> = "xfce";
 };
-        </programlisting>
+</programlisting>
  </para>
  <para>
   Optionally, <emphasis>compton</emphasis> can be enabled for nice graphical
@@ -24,7 +24,7 @@
   <link linkend="opt-services.compton.shadow">shadow</link>          = true;
   <link linkend="opt-services.compton.fadeDelta">fadeDelta</link>       = 4;
 };
-        </programlisting>
+</programlisting>
  </para>
  <para>
   Some Xfce programs are not installed automatically. To install them manually
@@ -37,7 +37,7 @@
    To enable <emphasis>Thunar</emphasis> volume support, put
 <programlisting>
 <xref linkend="opt-services.xserver.desktopManager.xfce.enable"/> = true;
-            </programlisting>
+</programlisting>
    into your <emphasis>configuration.nix</emphasis>.
   </para>
  </simplesect>
@@ -58,14 +58,14 @@
    on start (look at <command>journalctl --user -b</command>).
 <programlisting>
 Thunar:2410): GVFS-RemoteVolumeMonitor-WARNING **: remote volume monitor with dbus name org.gtk.Private.UDisks2VolumeMonitor is not supported
-            </programlisting>
+</programlisting>
    This is caused by some needed GNOME services not running. This is all fixed
    by enabling "Launch GNOME services on startup" in the Advanced tab of the
    Session and Startup settings panel. Alternatively, you can run this command
    to do the same thing.
 <programlisting>
-$ xfconf-query -c xfce4-session -p /compat/LaunchGNOME -s true
-            </programlisting>
+<prompt>$ </prompt>xfconf-query -c xfce4-session -p /compat/LaunchGNOME -s true
+</programlisting>
    A log-out and re-log will be needed for this to take effect.
   </para>
  </simplesect>
diff --git a/nixos/doc/manual/development/building-nixos.xml b/nixos/doc/manual/development/building-nixos.xml
index 23d9ddf88a77..56a596baed00 100644
--- a/nixos/doc/manual/development/building-nixos.xml
+++ b/nixos/doc/manual/development/building-nixos.xml
@@ -14,14 +14,14 @@
   Default CD/DVD configurations are available inside
   <filename>nixos/modules/installer/cd-dvd</filename>.
 <screen>
-$ git clone https://github.com/NixOS/nixpkgs.git
-$ cd nixpkgs/nixos
-$ nix-build -A config.system.build.isoImage -I nixos-config=modules/installer/cd-dvd/installation-cd-minimal.nix default.nix</screen>
+<prompt>$ </prompt>git clone https://github.com/NixOS/nixpkgs.git
+<prompt>$ </prompt>cd nixpkgs/nixos
+<prompt>$ </prompt>nix-build -A config.system.build.isoImage -I nixos-config=modules/installer/cd-dvd/installation-cd-minimal.nix default.nix</screen>
  </para>
  <para>
   Before burning your CD/DVD, you can check the content of the image by
   mounting anywhere like suggested by the following command:
 <screen>
-# mount -o loop -t iso9660 ./result/iso/cd.iso /mnt/iso</screen>
+<prompt># </prompt>mount -o loop -t iso9660 ./result/iso/cd.iso /mnt/iso</screen>
  </para>
 </chapter>
diff --git a/nixos/doc/manual/development/building-parts.xml b/nixos/doc/manual/development/building-parts.xml
index b4791b72970f..88369fb891b3 100644
--- a/nixos/doc/manual/development/building-parts.xml
+++ b/nixos/doc/manual/development/building-parts.xml
@@ -8,8 +8,8 @@
   With the command <command>nix-build</command>, you can build specific parts
   of your NixOS configuration. This is done as follows:
 <screen>
-$ cd <replaceable>/path/to/nixpkgs/nixos</replaceable>
-$ nix-build -A config.<replaceable>option</replaceable></screen>
+<prompt>$ </prompt>cd <replaceable>/path/to/nixpkgs/nixos</replaceable>
+<prompt>$ </prompt>nix-build -A config.<replaceable>option</replaceable></screen>
   where <replaceable>option</replaceable> is a NixOS option with type
   “derivation” (i.e. something that can be built). Attributes of interest
   include:
@@ -28,7 +28,7 @@ $ nix-build -A config.<replaceable>option</replaceable></screen>
      <para>
       A shortcut to build this is:
 <screen>
-$ nix-build -A system</screen>
+<prompt>$ </prompt>nix-build -A system</screen>
      </para>
     </listitem>
    </varlistentry>
@@ -66,9 +66,9 @@ $ nix-build -A system</screen>
       test whether the kernel and the initial ramdisk boot correctly, by using
       QEMU’s <option>-kernel</option> and <option>-initrd</option> options:
 <screen>
-$ nix-build -A config.system.build.initialRamdisk -o initrd
-$ nix-build -A config.system.build.kernel -o kernel
-$ qemu-system-x86_64 -kernel ./kernel/bzImage -initrd ./initrd/initrd -hda /dev/null
+<prompt>$ </prompt>nix-build -A config.system.build.initialRamdisk -o initrd
+<prompt>$ </prompt>nix-build -A config.system.build.kernel -o kernel
+<prompt>$ </prompt>qemu-system-x86_64 -kernel ./kernel/bzImage -initrd ./initrd/initrd -hda /dev/null
 </screen>
      </para>
     </listitem>
@@ -99,15 +99,15 @@ $ qemu-system-x86_64 -kernel ./kernel/bzImage -initrd ./initrd/initrd -hda /dev/
       contain dots (e.g. <literal>httpd.service</literal>), you need to put
       them between quotes, like this:
 <screen>
-$ nix-build -A 'config.systemd.units."httpd.service".unit'
+<prompt>$ </prompt>nix-build -A 'config.systemd.units."httpd.service".unit'
 </screen>
       You can also test individual units, without rebuilding the whole system,
       by putting them in <filename>/run/systemd/system</filename>:
 <screen>
-$ cp $(nix-build -A 'config.systemd.units."httpd.service".unit')/httpd.service \
+<prompt>$ </prompt>cp $(nix-build -A 'config.systemd.units."httpd.service".unit')/httpd.service \
     /run/systemd/system/tmp-httpd.service
-# systemctl daemon-reload
-# systemctl start tmp-httpd.service
+<prompt># </prompt>systemctl daemon-reload
+<prompt># </prompt>systemctl start tmp-httpd.service
 </screen>
       Note that the unit must not have the same name as any unit in
       <filename>/etc/systemd/system</filename> since those take precedence over
diff --git a/nixos/doc/manual/development/running-nixos-tests-interactively.xml b/nixos/doc/manual/development/running-nixos-tests-interactively.xml
index c15ad448317f..e390d62fde2f 100644
--- a/nixos/doc/manual/development/running-nixos-tests-interactively.xml
+++ b/nixos/doc/manual/development/running-nixos-tests-interactively.xml
@@ -9,17 +9,17 @@
   The test itself can be run interactively. This is particularly useful when
   developing or debugging a test:
 <screen>
-$ nix-build nixos/tests/login.nix -A driver
-$ ./result/bin/nixos-test-driver
+<prompt>$ </prompt>nix-build nixos/tests/login.nix -A driver
+<prompt>$ </prompt>./result/bin/nixos-test-driver
 starting VDE switch for network 1
-&gt;
+<prompt>&gt;</prompt>
 </screen>
   You can then take any Perl statement, e.g.
 <screen>
-&gt; startAll
-&gt; testScript
-&gt; $machine->succeed("touch /tmp/foo")
-&gt; print($machine->succeed("pwd")) # Show stdout of command
+<prompt>&gt;</prompt> startAll
+<prompt>&gt;</prompt> testScript
+<prompt>&gt;</prompt> $machine->succeed("touch /tmp/foo")
+<prompt>&gt;</prompt> print($machine->succeed("pwd")) # Show stdout of command
 </screen>
   The function <command>testScript</command> executes the entire test script
   and drops you back into the test driver command line upon its completion.
@@ -30,8 +30,8 @@ starting VDE switch for network 1
  <para>
   To just start and experiment with the VMs, run:
 <screen>
-$ nix-build nixos/tests/login.nix -A driver
-$ ./result/bin/nixos-run-vms
+<prompt>$ </prompt>nix-build nixos/tests/login.nix -A driver
+<prompt>$ </prompt>./result/bin/nixos-run-vms
 </screen>
   The script <command>nixos-run-vms</command> starts the virtual machines
   defined by test.
diff --git a/nixos/doc/manual/development/running-nixos-tests.xml b/nixos/doc/manual/development/running-nixos-tests.xml
index eadbe1ea4f26..13ae1ed93699 100644
--- a/nixos/doc/manual/development/running-nixos-tests.xml
+++ b/nixos/doc/manual/development/running-nixos-tests.xml
@@ -12,12 +12,12 @@
 xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/login.nix">login.nix</filename>,
   you just do:
 <screen>
-$ nix-build '&lt;nixpkgs/nixos/tests/login.nix>'
+<prompt>$ </prompt>nix-build '&lt;nixpkgs/nixos/tests/login.nix>'
 </screen>
   or, if you don’t want to rely on <envar>NIX_PATH</envar>:
 <screen>
-$ cd /my/nixpkgs/nixos/tests
-$ nix-build login.nix
+<prompt>$ </prompt>cd /my/nixpkgs/nixos/tests
+<prompt>$ </prompt>nix-build login.nix

 running the VM test script
 machine: QEMU running (pid 8841)
@@ -30,7 +30,7 @@ machine: QEMU running (pid 8841)
   fast, as no disk image needs to be created. Afterwards, you can view a
   pretty-printed log of the test:
 <screen>
-$ firefox result/log.html
+<prompt>$ </prompt>firefox result/log.html
 </screen>
  </para>
 </section>
diff --git a/nixos/doc/manual/development/sources.xml b/nixos/doc/manual/development/sources.xml
index eec9b56b1c07..3c30c782746d 100644
--- a/nixos/doc/manual/development/sources.xml
+++ b/nixos/doc/manual/development/sources.xml
@@ -11,10 +11,10 @@
   modify NixOS, however, you should check out the latest sources from Git. This
   is as follows:
 <screen>
-$ git clone https://github.com/NixOS/nixpkgs
-$ cd nixpkgs
-$ git remote add channels https://github.com/NixOS/nixpkgs-channels
-$ git remote update channels
+<prompt>$ </prompt>git clone https://github.com/NixOS/nixpkgs
+<prompt>$ </prompt>cd nixpkgs
+<prompt>$ </prompt>git remote add channels https://github.com/NixOS/nixpkgs-channels
+<prompt>$ </prompt>git remote update channels
 </screen>
   This will check out the latest Nixpkgs sources to
   <filename>./nixpkgs</filename> the NixOS sources to
@@ -32,23 +32,23 @@ $ git remote update channels
   not have caught up yet and you’ll have to rebuild everything from source.
   So you may want to create a local branch based on your current NixOS version:
 <screen>
-$ nixos-version
+<prompt>$ </prompt>nixos-version
 17.09pre104379.6e0b727 (Hummingbird)
 
-$ git checkout -b local 6e0b727
+<prompt>$ </prompt>git checkout -b local 6e0b727
 </screen>
   Or, to base your local branch on the latest version available in a NixOS
   channel:
 <screen>
-$ git remote update channels
-$ git checkout -b local channels/nixos-17.03
+<prompt>$ </prompt>git remote update channels
+<prompt>$ </prompt>git checkout -b local channels/nixos-17.03
 </screen>
   (Replace <literal>nixos-17.03</literal> with the name of the channel you want
   to use.) You can use <command>git merge</command> or <command>git
   rebase</command> to keep your local branch in sync with the channel, e.g.
 <screen>
-$ git remote update channels
-$ git merge channels/nixos-17.03
+<prompt>$ </prompt>git remote update channels
+<prompt>$ </prompt>git merge channels/nixos-17.03
 </screen>
   You can use <command>git cherry-pick</command> to copy commits from your
   local branch to the upstream branch.
@@ -58,7 +58,7 @@ $ git merge channels/nixos-17.03
   tell <command>nixos-rebuild</command> about them using the
   <option>-I</option> flag:
 <screen>
-# nixos-rebuild switch -I nixpkgs=<replaceable>/my/sources</replaceable>/nixpkgs
+<prompt># </prompt>nixos-rebuild switch -I nixpkgs=<replaceable>/my/sources</replaceable>/nixpkgs
 </screen>
  </para>
  <para>
@@ -67,7 +67,7 @@ $ git merge channels/nixos-17.03
   <replaceable>/my/sources</replaceable>/nixpkgs</command>, or change the
   default by adding a symlink in <filename>~/.nix-defexpr</filename>:
 <screen>
-$ ln -s <replaceable>/my/sources</replaceable>/nixpkgs ~/.nix-defexpr/nixpkgs
+<prompt>$ </prompt>ln -s <replaceable>/my/sources</replaceable>/nixpkgs ~/.nix-defexpr/nixpkgs
 </screen>
   You may want to delete the symlink
   <filename>~/.nix-defexpr/channels_root</filename> to prevent root’s NixOS
diff --git a/nixos/doc/manual/development/testing-installer.xml b/nixos/doc/manual/development/testing-installer.xml
index 63f5f3de7f4d..902f995fbc1b 100644
--- a/nixos/doc/manual/development/testing-installer.xml
+++ b/nixos/doc/manual/development/testing-installer.xml
@@ -8,15 +8,15 @@
   Building, burning, and booting from an installation CD is rather tedious, so
   here is a quick way to see if the installer works properly:
 <screen>
-# mount -t tmpfs none /mnt
-# nixos-generate-config --root /mnt
-$ nix-build '&lt;nixpkgs/nixos>' -A config.system.build.nixos-install
-# ./result/bin/nixos-install</screen>
+<prompt># </prompt>mount -t tmpfs none /mnt
+<prompt># </prompt>nixos-generate-config --root /mnt
+<prompt>$ </prompt>nix-build '&lt;nixpkgs/nixos>' -A config.system.build.nixos-install
+<prompt># </prompt>./result/bin/nixos-install</screen>
   To start a login shell in the new NixOS installation in
   <filename>/mnt</filename>:
 <screen>
-$ nix-build '&lt;nixpkgs/nixos>' -A config.system.build.nixos-enter
-# ./result/bin/nixos-enter
+<prompt>$ </prompt>nix-build '&lt;nixpkgs/nixos>' -A config.system.build.nixos-enter
+<prompt># </prompt>./result/bin/nixos-enter
 </screen>
  </para>
 </chapter>
diff --git a/nixos/doc/manual/development/writing-nixos-tests.xml b/nixos/doc/manual/development/writing-nixos-tests.xml
index 4a2615c9407b..6be2d0a4d231 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.xml
+++ b/nixos/doc/manual/development/writing-nixos-tests.xml
@@ -397,9 +397,9 @@ startAll;
      </para>
      <para>
 <programlisting>
-        $machine->systemctl("list-jobs --no-pager"); // runs `systemctl list-jobs --no-pager`
-        $machine->systemctl("list-jobs --no-pager", "any-user"); // spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager`
-      </programlisting>
+$machine->systemctl("list-jobs --no-pager"); // runs `systemctl list-jobs --no-pager`
+$machine->systemctl("list-jobs --no-pager", "any-user"); // spawns a shell for `any-user` and runs `systemctl --user list-jobs --no-pager`
+</programlisting>
      </para>
     </listitem>
    </varlistentry>
@@ -410,10 +410,10 @@ startAll;
   To test user units declared by <literal>systemd.user.services</literal> the
   optional <literal>$user</literal> argument can be used:
 <programlisting>
-    $machine->start;
-    $machine->waitForX;
-    $machine->waitForUnit("xautolock.service", "x-session-user");
-  </programlisting>
+$machine->start;
+$machine->waitForX;
+$machine->waitForUnit("xautolock.service", "x-session-user");
+</programlisting>
   This applies to <literal>systemctl</literal>, <literal>getUnitInfo</literal>,
   <literal>waitForUnit</literal>, <literal>startJob</literal> and
   <literal>stopJob</literal>.
diff --git a/nixos/doc/manual/installation/changing-config.xml b/nixos/doc/manual/installation/changing-config.xml
index 1a116ec0b655..b77d71389a9d 100644
--- a/nixos/doc/manual/installation/changing-config.xml
+++ b/nixos/doc/manual/installation/changing-config.xml
@@ -9,7 +9,8 @@
   <link linkend="ch-configuration">changed something</link> in that file, you
   should do
 <screen>
-# nixos-rebuild switch</screen>
+<prompt># </prompt>nixos-rebuild switch
+</screen>
   to build the new configuration, make it the default configuration for
   booting, and try to realise the configuration in the running system (e.g., by
   restarting system services).
@@ -23,7 +24,8 @@
  <para>
   You can also do
 <screen>
-# nixos-rebuild test</screen>
+<prompt># </prompt>nixos-rebuild test
+</screen>
   to build the configuration and switch the running system to it, but without
   making it the boot default. So if (say) the configuration locks up your
   machine, you can just reboot to get back to a working configuration.
@@ -31,7 +33,8 @@
  <para>
   There is also
 <screen>
-# nixos-rebuild boot</screen>
+<prompt># </prompt>nixos-rebuild boot
+</screen>
   to build the configuration and make it the boot default, but not switch to it
   now (so it will only take effect after the next reboot).
  </para>
@@ -39,7 +42,8 @@
   You can make your configuration show up in a different submenu of the GRUB 2
   boot screen by giving it a different <emphasis>profile name</emphasis>, e.g.
 <screen>
-# nixos-rebuild switch -p test </screen>
+<prompt># </prompt>nixos-rebuild switch -p test
+</screen>
   which causes the new configuration (and previous ones created using
   <literal>-p test</literal>) to show up in the GRUB submenu “NixOS - Profile
   'test'”. This can be useful to separate test configurations from
@@ -48,7 +52,8 @@
  <para>
   Finally, you can do
 <screen>
-$ nixos-rebuild build</screen>
+<prompt>$ </prompt>nixos-rebuild build
+</screen>
   to build the configuration but nothing more. This is useful to see whether
   everything compiles cleanly.
  </para>
@@ -58,8 +63,8 @@ $ nixos-rebuild build</screen>
   <emphasis>virtual machine</emphasis> that contains the desired configuration.
   Just do
 <screen>
-$ nixos-rebuild build-vm
-$ ./result/bin/run-*-vm
+<prompt>$ </prompt>nixos-rebuild build-vm
+<prompt>$ </prompt>./result/bin/run-*-vm
 </screen>
   The VM does not have any data from your host system, so your existing user
   accounts and home directories will not be available unless you have set
@@ -74,12 +79,12 @@ $ ./result/bin/run-*-vm
   guest. For instance, the following will forward host port 2222 to guest port
   22 (SSH):
 <screen>
-$ QEMU_NET_OPTS="hostfwd=tcp::2222-:22" ./result/bin/run-*-vm
+<prompt>$ </prompt>QEMU_NET_OPTS="hostfwd=tcp::2222-:22" ./result/bin/run-*-vm
 </screen>
   allowing you to log in via SSH (assuming you have set the appropriate
   passwords or SSH authorized keys):
 <screen>
-$ ssh -p 2222 localhost
+<prompt>$ </prompt>ssh -p 2222 localhost
 </screen>
  </para>
 </chapter>
diff --git a/nixos/doc/manual/installation/installing-from-other-distro.xml b/nixos/doc/manual/installation/installing-from-other-distro.xml
index d1e49a2a1597..8ed45899fd7f 100644
--- a/nixos/doc/manual/installation/installing-from-other-distro.xml
+++ b/nixos/doc/manual/installation/installing-from-other-distro.xml
@@ -47,8 +47,8 @@
     Short version:
    </para>
 <screen>
-$ curl https://nixos.org/nix/install | sh
-$ . $HOME/.nix-profile/etc/profile.d/nix.sh # …or open a fresh shell</screen>
+<prompt>$ </prompt>curl https://nixos.org/nix/install | sh
+<prompt>$ </prompt>. $HOME/.nix-profile/etc/profile.d/nix.sh # …or open a fresh shell</screen>
    <para>
     More details in the
     <link
@@ -65,14 +65,14 @@ $ . $HOME/.nix-profile/etc/profile.d/nix.sh # …or open a fresh shell</screen>
     the <literal>nixpkgs</literal> channel by default.
    </para>
 <screen>
-$ nix-channel --list
+<prompt>$ </prompt>nix-channel --list
 nixpkgs https://nixos.org/channels/nixpkgs-unstable</screen>
    <para>
     As that channel gets released without running the NixOS tests, it will be
     safer to use the <literal>nixos-*</literal> channels instead:
    </para>
 <screen>
-$ nix-channel --add https://nixos.org/channels/nixos-<replaceable>version</replaceable> nixpkgs</screen>
+<prompt>$ </prompt>nix-channel --add https://nixos.org/channels/nixos-<replaceable>version</replaceable> nixpkgs</screen>
    <para>
     You may want to throw in a <literal>nix-channel --update</literal> for good
     measure.
@@ -89,7 +89,7 @@ $ nix-channel --add https://nixos.org/channels/nixos-<replaceable>version</repla
     NixOS partition. They are installed by default on NixOS, but you don't have
     NixOS yet..
    </para>
-<screen>$ nix-env -iE "_: with import &lt;nixpkgs/nixos&gt; { configuration = {}; }; with config.system.build; [ nixos-generate-config nixos-install nixos-enter manual.manpages ]"</screen>
+<screen><prompt>$ </prompt>nix-env -iE "_: with import &lt;nixpkgs/nixos&gt; { configuration = {}; }; with config.system.build; [ nixos-generate-config nixos-install nixos-enter manual.manpages ]"</screen>
   </listitem>
   <listitem>
    <note>
@@ -116,7 +116,7 @@ $ nix-channel --add https://nixos.org/channels/nixos-<replaceable>version</repla
    <para>
     Generate your NixOS configuration:
    </para>
-<screen>$ sudo `which nixos-generate-config` --root /mnt</screen>
+<screen><prompt>$ </prompt>sudo `which nixos-generate-config` --root /mnt</screen>
    <para>
     You'll probably want to edit the configuration files. Refer to the
     <literal>nixos-generate-config</literal> step in
@@ -148,8 +148,8 @@ $ nix-channel --add https://nixos.org/channels/nixos-<replaceable>version</repla
     distribution:
    </para>
 <screen>
-$ sudo groupadd -g 30000 nixbld
-$ sudo useradd -u 30000 -g nixbld -G nixbld nixbld</screen>
+<prompt>$ </prompt>sudo groupadd -g 30000 nixbld
+<prompt>$ </prompt>sudo useradd -u 30000 -g nixbld -G nixbld nixbld</screen>
   </listitem>
   <listitem>
    <para>
@@ -161,7 +161,7 @@ $ sudo useradd -u 30000 -g nixbld -G nixbld nixbld</screen>
      existing systems without the help of a rescue USB drive or similar.
     </para>
    </warning>
-<screen>$ sudo PATH="$PATH" NIX_PATH="$NIX_PATH" `which nixos-install` --root /mnt</screen>
+<screen><prompt>$ </prompt>sudo PATH="$PATH" NIX_PATH="$NIX_PATH" `which nixos-install` --root /mnt</screen>
    <para>
     Again, please refer to the <literal>nixos-install</literal> step in
     <xref linkend="sec-installation" /> for more information.
@@ -175,8 +175,8 @@ $ sudo useradd -u 30000 -g nixbld -G nixbld nixbld</screen>
     Optionally, you may want to clean up your non-NixOS distribution:
    </para>
 <screen>
-$ sudo userdel nixbld
-$ sudo groupdel nixbld</screen>
+<prompt>$ </prompt>sudo userdel nixbld
+<prompt>$ </prompt>sudo groupdel nixbld</screen>
    <para>
     If you do not wish to keep the Nix package manager installed either, run
     something like <literal>sudo rm -rv ~/.nix-* /nix</literal> and remove the
@@ -193,7 +193,7 @@ $ sudo groupdel nixbld</screen>
    <para>
     Generate your NixOS configuration:
    </para>
-<screen>$ sudo `which nixos-generate-config` --root /</screen>
+<screen><prompt>$ </prompt>sudo `which nixos-generate-config` --root /</screen>
    <para>
     Note that this will place the generated configuration files in
     <literal>/etc/nixos</literal>. You'll probably want to edit the
@@ -212,21 +212,21 @@ $ sudo groupdel nixbld</screen>
    </para>
 <programlisting>
 <link linkend="opt-users.users._name__.initialHashedPassword">users.users.root.initialHashedPassword</link> = "";
-              </programlisting>
+</programlisting>
   </listitem>
   <listitem>
    <para>
     Build the NixOS closure and install it in the <literal>system</literal>
     profile:
    </para>
-<screen>$ nix-env -p /nix/var/nix/profiles/system -f '&lt;nixpkgs/nixos&gt;' -I nixos-config=/etc/nixos/configuration.nix -iA system</screen>
+<screen><prompt>$ </prompt>nix-env -p /nix/var/nix/profiles/system -f '&lt;nixpkgs/nixos&gt;' -I nixos-config=/etc/nixos/configuration.nix -iA system</screen>
   </listitem>
   <listitem>
    <para>
     Change ownership of the <literal>/nix</literal> tree to root (since your
     Nix install was probably single user):
    </para>
-<screen>$ sudo chown -R 0.0 /nix</screen>
+<screen><prompt>$ </prompt>sudo chown -R 0.0 /nix</screen>
   </listitem>
   <listitem>
    <para>
@@ -284,16 +284,16 @@ $ sudo groupdel nixbld</screen>
     Let's create the files:
    </para>
 <screen>
-$ sudo touch /etc/NIXOS
-$ sudo touch /etc/NIXOS_LUSTRATE
-            </screen>
+<prompt>$ </prompt>sudo touch /etc/NIXOS
+<prompt>$ </prompt>sudo touch /etc/NIXOS_LUSTRATE
+</screen>
    <para>
     Let's also make sure the NixOS configuration files are kept once we reboot
     on NixOS:
    </para>
 <screen>
-$ echo etc/nixos | sudo tee -a /etc/NIXOS_LUSTRATE
-            </screen>
+<prompt>$ </prompt>echo etc/nixos | sudo tee -a /etc/NIXOS_LUSTRATE
+</screen>
   </listitem>
   <listitem>
    <para>
@@ -312,8 +312,9 @@ $ echo etc/nixos | sudo tee -a /etc/NIXOS_LUSTRATE
     </para>
    </warning>
 <screen>
-$ sudo mv -v /boot /boot.bak &amp;&amp;
-    sudo /nix/var/nix/profiles/system/bin/switch-to-configuration boot</screen>
+<prompt>$ </prompt>sudo mv -v /boot /boot.bak &amp;&amp;
+sudo /nix/var/nix/profiles/system/bin/switch-to-configuration boot
+</screen>
    <para>
     Cross your fingers, reboot, hopefully you should get a NixOS prompt!
    </para>
diff --git a/nixos/doc/manual/installation/installing-usb.xml b/nixos/doc/manual/installation/installing-usb.xml
index c0372e8ebd9b..83598635acca 100644
--- a/nixos/doc/manual/installation/installing-usb.xml
+++ b/nixos/doc/manual/installation/installing-usb.xml
@@ -15,16 +15,16 @@
   <note>
    <title>On macOS</title>
    <para>
-<programlisting>
-$ diskutil list
+<screen>
+<prompt>$ </prompt>diskutil list
 [..]
 /dev/diskN (external, physical):
    #:                       TYPE NAME                    SIZE       IDENTIFIER
 [..]
-$ diskutil unmountDisk diskN
+<prompt>$ </prompt>diskutil unmountDisk diskN
 Unmount of all volumes on diskN was successful
-$ sudo dd if=nix.iso of=/dev/rdiskN
-</programlisting>
+<prompt>$ </prompt>sudo dd if=nix.iso of=/dev/rdiskN
+</screen>
     Using the 'raw' <command>rdiskN</command> device instead of
     <command>diskN</command> completes in minutes instead of hours. After
     <command>dd</command> completes, a GUI dialog "The disk you inserted was
diff --git a/nixos/doc/manual/installation/installing.xml b/nixos/doc/manual/installation/installing.xml
index 9687c21a01e6..742376378dea 100644
--- a/nixos/doc/manual/installation/installing.xml
+++ b/nixos/doc/manual/installation/installing.xml
@@ -110,7 +110,7 @@
      <listitem>
       <para>
        Create a <emphasis>GPT</emphasis> partition table.
-<screen language="commands"># parted /dev/sda -- mklabel gpt</screen>
+<screen language="commands"><prompt># </prompt>parted /dev/sda -- mklabel gpt</screen>
       </para>
      </listitem>
      <listitem>
@@ -118,14 +118,14 @@
        Add the <emphasis>root</emphasis> partition. This will fill the disk
        except for the end part, where the swap will live, and the space left in
        front (512MiB) which will be used by the boot partition.
-<screen language="commands"># parted /dev/sda -- mkpart primary 512MiB -8GiB</screen>
+<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart primary 512MiB -8GiB</screen>
       </para>
      </listitem>
      <listitem>
       <para>
        Next, add a <emphasis>swap</emphasis> partition. The size required will
        vary according to needs, here a 8GiB one is created.
-<screen language="commands"># parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
+<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
        <note>
         <para>
          The swap partition size rules are no different than for other Linux
@@ -140,8 +140,8 @@
        the ESP (EFI system partition) as its <emphasis>/boot</emphasis>
        partition. It uses the initially reserved 512MiB at the start of the
        disk.
-<screen language="commands"># parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
-# parted /dev/sda -- set 3 boot on</screen>
+<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+<prompt># </prompt>parted /dev/sda -- set 3 boot on</screen>
       </para>
      </listitem>
     </orderedlist>
@@ -172,21 +172,21 @@
      <listitem>
       <para>
        Create a <emphasis>MBR</emphasis> partition table.
-<screen language="commands"># parted /dev/sda -- mklabel msdos</screen>
+<screen language="commands"><prompt># </prompt>parted /dev/sda -- mklabel msdos</screen>
       </para>
      </listitem>
      <listitem>
       <para>
        Add the <emphasis>root</emphasis> partition. This will fill the the disk
        except for the end part, where the swap will live.
-<screen language="commands"># parted /dev/sda -- mkpart primary 1MiB -8GiB</screen>
+<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart primary 1MiB -8GiB</screen>
       </para>
      </listitem>
      <listitem>
       <para>
        Finally, add a <emphasis>swap</emphasis> partition. The size required
        will vary according to needs, here a 8GiB one is created.
-<screen language="commands"># parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
+<screen language="commands"><prompt># </prompt>parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
        <note>
         <para>
          The swap partition size rules are no different than for other Linux
@@ -218,7 +218,7 @@
        since this makes the file system configuration independent from device
        changes. For example:
 <screen>
-# mkfs.ext4 -L nixos /dev/sda1</screen>
+<prompt># </prompt>mkfs.ext4 -L nixos /dev/sda1</screen>
       </para>
      </listitem>
      <listitem>
@@ -227,7 +227,7 @@
        recommended to assign a label to the swap partition: <option>-L
        <replaceable>label</replaceable></option>. For example:
 <screen>
-# mkswap -L swap /dev/sda2</screen>
+<prompt># </prompt>mkswap -L swap /dev/sda2</screen>
       </para>
      </listitem>
      <listitem>
@@ -242,7 +242,7 @@
           it’s recommended to assign a label to the boot partition:
           <option>-n <replaceable>label</replaceable></option>. For example:
 <screen>
-# mkfs.fat -F 32 -n boot /dev/sda3</screen>
+<prompt># </prompt>mkfs.fat -F 32 -n boot /dev/sda3</screen>
          </para>
         </listitem>
        </varlistentry>
@@ -273,7 +273,7 @@
      Mount the target file system on which NixOS should be installed on
      <filename>/mnt</filename>, e.g.
 <screen>
-# mount /dev/disk/by-label/nixos /mnt
+<prompt># </prompt>mount /dev/disk/by-label/nixos /mnt
 </screen>
     </para>
    </listitem>
@@ -287,8 +287,8 @@
        <para>
         Mount the boot file system on <filename>/mnt/boot</filename>, e.g.
 <screen>
-# mkdir -p /mnt/boot
-# mount /dev/disk/by-label/boot /mnt/boot
+<prompt># </prompt>mkdir -p /mnt/boot
+<prompt># </prompt>mount /dev/disk/by-label/boot /mnt/boot
 </screen>
        </para>
       </listitem>
@@ -303,7 +303,7 @@
      the build actions that it may spawn) may need quite a bit of RAM,
      depending on your configuration.
 <screen>
-# swapon /dev/sda2</screen>
+<prompt># </prompt>swapon /dev/sda2</screen>
     </para>
    </listitem>
    <listitem>
@@ -325,11 +325,11 @@
      The command <command>nixos-generate-config</command> can generate an
      initial configuration file for you:
 <screen>
-# nixos-generate-config --root /mnt</screen>
+<prompt># </prompt>nixos-generate-config --root /mnt</screen>
      You should then edit <filename>/mnt/etc/nixos/configuration.nix</filename>
      to suit your needs:
 <screen>
-# nano /mnt/etc/nixos/configuration.nix
+<prompt># </prompt>nano /mnt/etc/nixos/configuration.nix
 </screen>
      If you’re using the graphical ISO image, other editors may be available
      (such as <command>vim</command>). If you have network access, you can also
@@ -412,7 +412,7 @@
     <para>
      Do the installation:
 <screen>
-# nixos-install</screen>
+<prompt># </prompt>nixos-install</screen>
      Cross fingers. If this fails due to a temporary problem (such as a network
      issue while downloading binaries from the NixOS binary cache), you can
      just re-run <command>nixos-install</command>. Otherwise, fix your
@@ -439,7 +439,7 @@ Retype new UNIX password: ***</screen>
     <para>
      If everything went well:
 <screen>
-# reboot</screen>
+<prompt># </prompt>reboot</screen>
     </para>
    </listitem>
    <listitem>
@@ -460,16 +460,16 @@ Retype new UNIX password: ***</screen>
      You’ll probably want to create some user accounts as well, which can be
      done with <command>useradd</command>:
 <screen>
-$ useradd -c 'Eelco Dolstra' -m eelco
-$ passwd eelco</screen>
+<prompt>$ </prompt>useradd -c 'Eelco Dolstra' -m eelco
+<prompt>$ </prompt>passwd eelco</screen>
     </para>
     <para>
      You may also want to install some software. For instance,
 <screen>
-$ nix-env -qa \*</screen>
+<prompt>$ </prompt>nix-env -qa \*</screen>
      shows what packages are available, and
 <screen>
-$ nix-env -i w3m</screen>
+<prompt>$ </prompt>nix-env -i w3m</screen>
      install the <literal>w3m</literal> browser.
     </para>
    </listitem>
@@ -489,19 +489,19 @@ $ nix-env -i w3m</screen>
   <example xml:id="ex-partition-scheme-MBR">
    <title>Example partition schemes for NixOS on <filename>/dev/sda</filename> (MBR)</title>
 <screen language="commands">
-# parted /dev/sda -- mklabel msdos
-# parted /dev/sda -- mkpart primary 1MiB -8GiB
-# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
+<prompt># </prompt>parted /dev/sda -- mklabel msdos
+<prompt># </prompt>parted /dev/sda -- mkpart primary 1MiB -8GiB
+<prompt># </prompt>parted /dev/sda -- mkpart primary linux-swap -8GiB 100%</screen>
   </example>
 
   <example xml:id="ex-partition-scheme-UEFI">
    <title>Example partition schemes for NixOS on <filename>/dev/sda</filename> (UEFI)</title>
 <screen language="commands">
-# parted /dev/sda -- mklabel gpt
-# parted /dev/sda -- mkpart primary 512MiB -8GiB
-# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
-# parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
-# parted /dev/sda -- set 3 boot on</screen>
+<prompt># </prompt>parted /dev/sda -- mklabel gpt
+<prompt># </prompt>parted /dev/sda -- mkpart primary 512MiB -8GiB
+<prompt># </prompt>parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
+<prompt># </prompt>parted /dev/sda -- mkpart ESP fat32 1MiB 512MiB
+<prompt># </prompt>parted /dev/sda -- set 3 boot on</screen>
   </example>
 
   <example xml:id="ex-install-sequence">
@@ -509,23 +509,23 @@ $ nix-env -i w3m</screen>
    <para>
     With a partitioned disk.
 <screen language="commands">
-# mkfs.ext4 -L nixos /dev/sda1
-# mkswap -L swap /dev/sda2
-# swapon /dev/sda2
-# mkfs.fat -F 32 -n boot /dev/sda3        # <lineannotation>(for UEFI systems only)</lineannotation>
-# mount /dev/disk/by-label/nixos /mnt
-# mkdir -p /mnt/boot                      # <lineannotation>(for UEFI systems only)</lineannotation>
-# mount /dev/disk/by-label/boot /mnt/boot # <lineannotation>(for UEFI systems only)</lineannotation>
-# nixos-generate-config --root /mnt
-# nano /mnt/etc/nixos/configuration.nix
-# nixos-install
-# reboot</screen>
+<prompt># </prompt>mkfs.ext4 -L nixos /dev/sda1
+<prompt># </prompt>mkswap -L swap /dev/sda2
+<prompt># </prompt>swapon /dev/sda2
+<prompt># </prompt>mkfs.fat -F 32 -n boot /dev/sda3        # <lineannotation>(for UEFI systems only)</lineannotation>
+<prompt># </prompt>mount /dev/disk/by-label/nixos /mnt
+<prompt># </prompt>mkdir -p /mnt/boot                      # <lineannotation>(for UEFI systems only)</lineannotation>
+<prompt># </prompt>mount /dev/disk/by-label/boot /mnt/boot # <lineannotation>(for UEFI systems only)</lineannotation>
+<prompt># </prompt>nixos-generate-config --root /mnt
+<prompt># </prompt>nano /mnt/etc/nixos/configuration.nix
+<prompt># </prompt>nixos-install
+<prompt># </prompt>reboot</screen>
    </para>
   </example>
 
   <example xml:id='ex-config'>
    <title>NixOS Configuration</title>
-<screen>
+<programlisting>
 { config, pkgs, ... }: {
   imports = [
     # Include the results of the hardware scan.
@@ -543,7 +543,7 @@ $ nix-env -i w3m</screen>
   # Enable the OpenSSH server.
   services.sshd.enable = true;
 }
-  </screen>
+</programlisting>
   </example>
  </section>
  <section xml:id="sec-installation-additional-notes">
diff --git a/nixos/doc/manual/man-nixos-generate-config.xml b/nixos/doc/manual/man-nixos-generate-config.xml
index 160ada9fff31..61531a8f01ca 100644
--- a/nixos/doc/manual/man-nixos-generate-config.xml
+++ b/nixos/doc/manual/man-nixos-generate-config.xml
@@ -13,18 +13,18 @@
  </refnamediv>
  <refsynopsisdiv>
   <cmdsynopsis>
-   <command>nixos-generate-config</command> 
+   <command>nixos-generate-config</command>
    <arg>
     <option>--force</option>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--root</option>
     </arg>
      <replaceable>root</replaceable>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--dir</option>
@@ -154,7 +154,7 @@
    file systems on <filename>/mnt</filename> and
    <filename>/mnt/boot</filename>, you would run:
 <screen>
-$ nixos-generate-config --root /mnt
+<prompt>$ </prompt>nixos-generate-config --root /mnt
 </screen>
    The resulting file
    <filename>/mnt/etc/nixos/hardware-configuration.nix</filename> might look
@@ -204,7 +204,7 @@ $ nixos-generate-config --root /mnt
   <para>
    After installation, if your hardware configuration changes, you can run:
 <screen>
-$ nixos-generate-config
+<prompt>$ </prompt>nixos-generate-config
 </screen>
    to update <filename>/etc/nixos/hardware-configuration.nix</filename>. Your
    <filename>/etc/nixos/configuration.nix</filename> will
diff --git a/nixos/doc/manual/man-nixos-install.xml b/nixos/doc/manual/man-nixos-install.xml
index 25f4f40613ac..4fb94ee7494c 100644
--- a/nixos/doc/manual/man-nixos-install.xml
+++ b/nixos/doc/manual/man-nixos-install.xml
@@ -13,72 +13,72 @@
  </refnamediv>
  <refsynopsisdiv>
   <cmdsynopsis>
-   <command>nixos-install</command> 
+   <command>nixos-install</command>
    <arg>
     <arg choice='plain'>
      <option>-I</option>
     </arg>
      <replaceable>path</replaceable>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--root</option>
     </arg>
      <replaceable>root</replaceable>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--system</option>
     </arg>
      <replaceable>path</replaceable>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--no-channel-copy</option>
     </arg>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--no-root-passwd</option>
     </arg>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--no-bootloader</option>
     </arg>
    </arg>
-    
+
    <arg>
-    <group choice='req'> 
+    <group choice='req'>
     <arg choice='plain'>
      <option>--max-jobs</option>
     </arg>
-     
+
     <arg choice='plain'>
      <option>-j</option>
     </arg>
      </group> <replaceable>number</replaceable>
    </arg>
-    
+
    <arg>
     <option>--cores</option> <replaceable>number</replaceable>
    </arg>
-    
+
    <arg>
     <option>--option</option> <replaceable>name</replaceable> <replaceable>value</replaceable>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--show-trace</option>
     </arg>
    </arg>
-    
+
    <arg>
     <arg choice='plain'>
      <option>--help</option>
@@ -255,12 +255,12 @@
    on an <literal>ext4</literal> file system created in
    <filename>/dev/sda1</filename>:
 <screen>
-$ mkfs.ext4 /dev/sda1
-$ mount /dev/sda1 /mnt
-$ nixos-generate-config --root /mnt
-$ # edit /mnt/etc/nixos/configuration.nix
-$ nixos-install
-$ reboot
+<prompt>$ </prompt>mkfs.ext4 /dev/sda1
+<prompt>$ </prompt>mount /dev/sda1 /mnt
+<prompt>$ </prompt>nixos-generate-config --root /mnt
+<prompt>$ </prompt># edit /mnt/etc/nixos/configuration.nix
+<prompt>$ </prompt>nixos-install
+<prompt>$ </prompt>reboot
 </screen>
   </para>
  </refsection>
diff --git a/nixos/doc/manual/man-nixos-option.xml b/nixos/doc/manual/man-nixos-option.xml
index d436cce742a2..3e316e10d4eb 100644
--- a/nixos/doc/manual/man-nixos-option.xml
+++ b/nixos/doc/manual/man-nixos-option.xml
@@ -13,19 +13,19 @@
  </refnamediv>
  <refsynopsisdiv>
   <cmdsynopsis>
-   <command>nixos-option</command> 
+   <command>nixos-option</command>
    <arg>
     <option>-I</option> <replaceable>path</replaceable>
    </arg>
-    
+
    <arg>
     <option>--verbose</option>
    </arg>
-    
+
    <arg>
     <option>--xml</option>
    </arg>
-    
+
    <arg choice="plain">
     <replaceable>option.name</replaceable>
    </arg>
@@ -103,13 +103,13 @@
   <title>Examples</title>
   <para>
    Investigate option values:
-<screen>$ nixos-option boot.loader
+<screen><prompt>$ </prompt>nixos-option boot.loader
 This attribute set contains:
 generationsDir
 grub
 initScript
 
-$ nixos-option boot.loader.grub.enable
+<prompt>$ </prompt>nixos-option boot.loader.grub.enable
 Value:
 true
 
diff --git a/nixos/doc/manual/man-nixos-rebuild.xml b/nixos/doc/manual/man-nixos-rebuild.xml
index 0b0c0b8f6ea2..9cec83f1e28b 100644
--- a/nixos/doc/manual/man-nixos-rebuild.xml
+++ b/nixos/doc/manual/man-nixos-rebuild.xml
@@ -13,39 +13,39 @@
  </refnamediv>
  <refsynopsisdiv>
   <cmdsynopsis>
-   <command>nixos-rebuild</command><group choice='req'> 
+   <command>nixos-rebuild</command><group choice='req'>
    <arg choice='plain'>
     <option>switch</option>
    </arg>
-    
+
    <arg choice='plain'>
     <option>boot</option>
    </arg>
-    
+
    <arg choice='plain'>
     <option>test</option>
    </arg>
-    
+
    <arg choice='plain'>
     <option>build</option>
    </arg>
-    
+
    <arg choice='plain'>
     <option>dry-build</option>
    </arg>
-    
+
    <arg choice='plain'>
     <option>dry-activate</option>
    </arg>
-    
+
    <arg choice='plain'>
     <option>edit</option>
    </arg>
-    
+
    <arg choice='plain'>
     <option>build-vm</option>
    </arg>
-    
+
    <arg choice='plain'>
     <option>build-vm-with-bootloader</option>
    </arg>
@@ -54,33 +54,33 @@
    <arg>
     <option>--upgrade</option>
    </arg>
-    
+
    <arg>
     <option>--install-bootloader</option>
    </arg>
-    
+
    <arg>
     <option>--no-build-nix</option>
    </arg>
-    
+
    <arg>
     <option>--fast</option>
    </arg>
-    
+
    <arg>
     <option>--rollback</option>
    </arg>
-    
+
    <arg>
     <option>--builders</option> <replaceable>builder-spec</replaceable>
    </arg>
    <sbr />
    <arg>
-    <group choice='req'> 
+    <group choice='req'>
     <arg choice='plain'>
      <option>--profile-name</option>
     </arg>
-     
+
     <arg choice='plain'>
      <option>-p</option>
     </arg>
@@ -160,7 +160,7 @@
        the current directory, which points to the output of the top-level
        “system” derivation. This is essentially the same as doing
 <screen>
-$ nix-build /path/to/nixpkgs/nixos -A system
+<prompt>$ </prompt>nix-build /path/to/nixpkgs/nixos -A system
 </screen>
        Note that you do not need to be <literal>root</literal> to run
        <command>nixos-rebuild build</command>.
@@ -215,8 +215,8 @@ $ nix-build /path/to/nixpkgs/nixos -A system
        at the script that starts the VM. Thus, to test a NixOS configuration in
        a virtual machine, you should do the following:
 <screen>
-$ nixos-rebuild build-vm
-$ ./result/bin/run-*-vm
+<prompt>$ </prompt>nixos-rebuild build-vm
+<prompt>$ </prompt>./result/bin/run-*-vm
 </screen>
       </para>
       <para>
@@ -375,7 +375,7 @@ $ ./result/bin/run-*-vm
       <filename>test.nix</filename> without affecting the default system
       profile, you would do:
 <screen>
-$ nixos-rebuild switch -p test -I nixos-config=./test.nix
+<prompt>$ </prompt>nixos-rebuild switch -p test -I nixos-config=./test.nix
 </screen>
       The new configuration will appear in the GRUB 2 submenu “NixOS -
       Profile 'test'”.
diff --git a/nixos/doc/manual/man-pages.xml b/nixos/doc/manual/man-pages.xml
index 0390dda6468f..f5a1dd2d69f4 100644
--- a/nixos/doc/manual/man-pages.xml
+++ b/nixos/doc/manual/man-pages.xml
@@ -6,7 +6,7 @@
   <author><personname><firstname>Eelco</firstname><surname>Dolstra</surname></personname>
    <contrib>Author</contrib>
   </author>
-  <copyright><year>2007-2018</year><holder>Eelco Dolstra</holder>
+  <copyright><year>2007-2019</year><holder>Eelco Dolstra</holder>
   </copyright>
  </info>
  <xi:include href="man-configuration.xml" />
diff --git a/nixos/doc/manual/release-notes/rl-1509.xml b/nixos/doc/manual/release-notes/rl-1509.xml
index e500c9d63422..5c4d99701785 100644
--- a/nixos/doc/manual/release-notes/rl-1509.xml
+++ b/nixos/doc/manual/release-notes/rl-1509.xml
@@ -627,7 +627,7 @@ nix-env -f &quot;&lt;nixpkgs&gt;&quot; -iA haskellPackages.pandoc
      In case of an infinite loop, use the <command>--show-trace</command>
      command line argument and read the line just above the error message.
 <screen>
-$ nixos-rebuild build --show-trace
+<prompt>$ </prompt>nixos-rebuild build --show-trace

 while evaluating the module argument `pkgs' in "/etc/nixos/my-module.nix":
 infinite recursion encountered
diff --git a/nixos/doc/manual/release-notes/rl-1703.xml b/nixos/doc/manual/release-notes/rl-1703.xml
index 6ca79e2bc00d..86f4a1ccfb78 100644
--- a/nixos/doc/manual/release-notes/rl-1703.xml
+++ b/nixos/doc/manual/release-notes/rl-1703.xml
@@ -626,17 +626,17 @@
     xlink:href="https://nixos.org/nixpkgs/manual/#sec-overlays-install">
      overlays</link>. For example, the following code:
 <programlisting>
-  let
-    pkgs = import &lt;nixpkgs&gt; {};
-  in
-    pkgs.overridePackages (self: super: ...)
+let
+  pkgs = import &lt;nixpkgs&gt; {};
+in
+  pkgs.overridePackages (self: super: ...)
 </programlisting>
      should be replaced by:
 <programlisting>
-  let
-    pkgs = import &lt;nixpkgs&gt; {};
-  in
-    import pkgs.path { overlays = [(self: super: ...)]; }
+let
+  pkgs = import &lt;nixpkgs&gt; {};
+in
+  import pkgs.path { overlays = [(self: super: ...)]; }
 </programlisting>
     </para>
    </listitem>
diff --git a/nixos/doc/manual/release-notes/rl-1909.xml b/nixos/doc/manual/release-notes/rl-1909.xml
index 726ac5b3abac..8e432932d29a 100644
--- a/nixos/doc/manual/release-notes/rl-1909.xml
+++ b/nixos/doc/manual/release-notes/rl-1909.xml
@@ -28,6 +28,11 @@
      PHP now defaults to PHP 7.3, updated from 7.2.
     </para>
    </listitem>
+   <listitem>
+    <para>
+     PHP 7.1 is no longer supported due to upstream not supporting this version for the entire lifecycle of the 19.09 release.
+    </para>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -83,45 +88,58 @@
    </listitem>
    <listitem>
     <para>
-      The options <option>services.prometheus.alertmanager.user</option> and
-      <option>services.prometheus.alertmanager.group</option> have been removed
-      because the alertmanager service is now using systemd's <link
-      xlink:href="http://0pointer.net/blog/dynamic-users-with-systemd.html">
-      DynamicUser mechanism</link> which obviates these options.
+     The options <option>services.prometheus.alertmanager.user</option> and
+     <option>services.prometheus.alertmanager.group</option> have been removed
+     because the alertmanager service is now using systemd's <link
+     xlink:href="http://0pointer.net/blog/dynamic-users-with-systemd.html">
+     DynamicUser mechanism</link> which obviates these options.
     </para>
    </listitem>
    <listitem>
     <para>
-      The NetworkManager systemd unit was renamed back from network-manager.service to
-      NetworkManager.service for better compatibility with other applications expecting this name.
-      The same applies to ModemManager where modem-manager.service is now called ModemManager.service again.
+     The NetworkManager systemd unit was renamed back from network-manager.service to
+     NetworkManager.service for better compatibility with other applications expecting this name.
+     The same applies to ModemManager where modem-manager.service is now called ModemManager.service again.
     </para>
    </listitem>
    <listitem>
     <para>
-      The <option>services.nzbget.configFile</option> and <option>services.nzbget.openFirewall</option>
-      options were removed as they are managed internally by the nzbget. The
-      <option>services.nzbget.dataDir</option> option hadn't actually been used by
-      the module for some time and so was removed as cleanup.
+     The <option>services.nzbget.configFile</option> and <option>services.nzbget.openFirewall</option>
+     options were removed as they are managed internally by the nzbget. The
+     <option>services.nzbget.dataDir</option> option hadn't actually been used by
+     the module for some time and so was removed as cleanup.
     </para>
    </listitem>
    <listitem>
     <para>
-      The <option>services.mysql.pidDir</option> option was removed, as it was only used by the wordpress
-      apache-httpd service to wait for mysql to have started up.
-      This can be accomplished by either describing a dependency on mysql.service (preferred)
-      or waiting for the (hardcoded) <filename>/run/mysqld/mysql.sock</filename> file to appear.
+     The <option>services.mysql.pidDir</option> option was removed, as it was only used by the wordpress
+     apache-httpd service to wait for mysql to have started up.
+     This can be accomplished by either describing a dependency on mysql.service (preferred)
+     or waiting for the (hardcoded) <filename>/run/mysqld/mysql.sock</filename> file to appear.
     </para>
    </listitem>
    <listitem>
     <para>
-      The <option>services.emby.enable</option> module has been removed, see
-      <option>services.jellyfin.enable</option> instead for a free software fork of Emby.
+     The <option>services.emby.enable</option> module has been removed, see
+     <option>services.jellyfin.enable</option> instead for a free software fork of Emby.
 
-      See the Jellyfin documentation:
-      <link xlink:href="https://jellyfin.readthedocs.io/en/latest/administrator-docs/migrate-from-emby/">
-        Migrating from Emby to Jellyfin
-      </link>
+     See the Jellyfin documentation:
+     <link xlink:href="https://jellyfin.readthedocs.io/en/latest/administrator-docs/migrate-from-emby/">
+       Migrating from Emby to Jellyfin
+     </link>
+    </para>
+   </listitem>
+   <listitem>
+     <para>
+     IPv6 Privacy Extensions are now enabled by default for undeclared
+     interfaces. The previous behaviour was quite misleading — even though
+     the default value for
+     <option>networking.interfaces.*.preferTempAddress</option> was
+     <literal>true</literal>, undeclared interfaces would not prefer temporary
+     addresses. Now, interfaces not mentioned in the config will prefer
+     temporary addresses. EUI64 addresses can still be set as preferred by
+     explicitly setting the option to <literal>false</literal> for the
+     interface in question.
     </para>
    </listitem>
    <listitem>
@@ -138,17 +156,83 @@
    </listitem>
    <listitem>
     <para>
-      The limesurvey apache subservice was replaced with a full NixOS module.
-      One can configure it using the <option>services.limesurvey.enable</option>
-      and <option>services.limesurvey.virtualHost</option> options.
+     The httpd service no longer attempts to start the postgresql service. If you have come to depend
+     on this behaviour then you can preserve the behavior with the following configuration:
+     <literal>systemd.services.httpd.after = [ "postgresql.service" ];</literal>
+    </para>
+    <para>
+     The option <option>services.httpd.extraSubservices</option> has been
+     marked as deprecated. You may still use this feature, but it will be
+     removed in a future release of NixOS. You are encouraged to convert any
+     httpd subservices you may have written to a full NixOS module.
+    </para>
+    <para>
+     Most of the httpd subservices packaged with NixOS have been replaced with
+     full NixOS modules including LimeSurvey, WordPress, and Zabbix. These
+     modules can be enabled using the <option>services.limesurvey.enable</option>,
+     <option>services.mediawiki.enable</option>, <option>services.wordpress.enable</option>,
+     and <option>services.zabbixWeb.enable</option> options.
     </para>
    </listitem>
    <listitem>
     <para>
-      The setopt declarations will be evaluated at the end of <literal>/etc/zshrc</literal>, so any code in <xref linkend="opt-programs.zsh.interactiveShellInit" />,
-      <xref linkend="opt-programs.zsh.loginShellInit" /> and <xref linkend="opt-programs.zsh.promptInit" /> may break if it relies on those options being set.
+     The option <option>systemd.network.networks.&lt;name&gt;.routes.*.routeConfig.GatewayOnlink</option>
+     was renamed to <option>systemd.network.networks.&lt;name&gt;.routes.*.routeConfig.GatewayOnLink</option>
+     (capital <literal>L</literal>). This follows
+     <link xlink:href="https://github.com/systemd/systemd/commit/9cb8c5593443d24c19e40bfd4fc06d672f8c554c">
+      upstreams renaming
+     </link> of the setting.
     </para>
    </listitem>
+   <listitem>
+    <para>
+     As of this release the NixOps feature <literal>autoLuks</literal> is deprecated. It no longer works
+     with our systemd version without manual intervention.
+    </para>
+    <para>
+     Whenever the usage of the module is detected the evaluation will fail with a message
+     explaining why and how to deal with the situation.
+    </para>
+    <para>
+     A new knob named <literal>nixops.enableDeprecatedAutoLuks</literal>
+     has been introduced to disable the eval failure and to acknowledge the notice was received and read.
+     If you plan on using the feature please note that it might break with subsequent updates.
+    </para>
+    <para>
+     Make sure you set the <literal>_netdev</literal> option for each of the file systems referring to block
+     devices provided by the autoLuks module. Not doing this might render the system in a
+     state where it doesn't boot anymore.
+    </para>
+    <para>
+     If you are actively using the <literal>autoLuks</literal> module please let us know in
+     <link xlink:href="https://github.com/NixOS/nixpkgs/issues/62211">issue #62211</link>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The setopt declarations will be evaluated at the end of <literal>/etc/zshrc</literal>, so any code in <xref linkend="opt-programs.zsh.interactiveShellInit" />,
+     <xref linkend="opt-programs.zsh.loginShellInit" /> and <xref linkend="opt-programs.zsh.promptInit" /> may break if it relies on those options being set.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      The <literal>prometheus-nginx-exporter</literal> package now uses the offical exporter provided by NGINX Inc.
+      Its metrics are differently structured and are incompatible to the old ones. For information about the metrics,
+      have a look at the <link xlink:href="https://github.com/nginxinc/nginx-prometheus-exporter">official repo</link>.
+    </para>
+   </listitem>
+   <listitem>
+     <para>
+       Nodejs 8 is scheduled EOL under the lifetime of 19.09 and has been dropped.
+     </para>
+   </listitem>
+   <listitem>
+     <para>
+       By default, prometheus exporters are now run with <literal>DynamicUser</literal> enabled.
+       Exporters that need a real user, now run under a seperate user and group which follow the pattern <literal>&lt;exporter-name&gt;-exporter</literal>, instead of the previous default <literal>nobody</literal> and <literal>nogroup</literal>.
+       Only some exporters are affected by the latter, namely the exporters <literal>dovecot</literal>, <literal>node</literal>, <literal>postfix</literal> and <literal>varnish</literal>.
+     </para>
+   </listitem>
   </itemizedlist>
  </section>
 
@@ -203,44 +287,111 @@
    </listitem>
    <listitem>
     <para>
-      The <literal>hunspellDicts.fr-any</literal> dictionary now ships with <literal>fr_FR.{aff,dic}</literal>
-      which is linked to <literal>fr-toutesvariantes.{aff,dic}</literal>.
+     The <literal>hunspellDicts.fr-any</literal> dictionary now ships with <literal>fr_FR.{aff,dic}</literal>
+     which is linked to <literal>fr-toutesvariantes.{aff,dic}</literal>.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      The <literal>mysql</literal> service now runs as <literal>mysql</literal>
-      user. Previously, systemd did execute it as root, and mysql dropped privileges
-      itself.
-      This includes <literal>ExecStartPre=</literal> and
-      <literal>ExecStartPost=</literal> phases.
-      To accomplish that, runtime and data directory setup was delegated to
-      RuntimeDirectory and tmpfiles.
+     The <literal>mysql</literal> service now runs as <literal>mysql</literal>
+     user. Previously, systemd did execute it as root, and mysql dropped privileges
+     itself.
+     This includes <literal>ExecStartPre=</literal> and
+     <literal>ExecStartPost=</literal> phases.
+     To accomplish that, runtime and data directory setup was delegated to
+     RuntimeDirectory and tmpfiles.
     </para>
    </listitem>
-  <listitem>
+   <listitem>
     <para>
-      Since version 0.1.19, <literal>cargo-vendor</literal> honors package
-      includes that are specified in the <filename>Cargo.toml</filename>
-      file of Rust crates. <literal>rustPlatform.buildRustPackage</literal> uses
-      <literal>cargo-vendor</literal> to collect and build dependent crates.
-      Since this change in <literal>cargo-vendor</literal> changes the set of
-      vendored files for most Rust packages, the hash that use used to verify
-      the dependencies, <literal>cargoSha256</literal>, also changes.
+     With the upgrade to systemd version 242 the <literal>systemd-timesyncd</literal>
+     service is no longer using <literal>DynamicUser=yes</literal>. In order for the
+     upgrade to work we rely on an activation script to move the state from the old
+     to the new directory. The older directory (prior <literal>19.09</literal>) was
+     <literal>/var/lib/private/systemd/timesync</literal>.
     </para>
-
     <para>
-      The <literal>cargoSha256</literal> hashes of all in-tree derivations that
-      use <literal>buildRustPackage</literal> have been updated to reflect this
-      change. However, third-party derivations that use
-      <literal>buildRustPackage</literal> may have to be updated as well.
+     As long as the <literal>system.config.stateVersion</literal> is below
+     <literal>19.09</literal> the state folder will migrated to its proper location
+     (<literal>/var/lib/systemd/timesync</literal>), if required.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The package <literal>avahi</literal> is now built to look up service
+     definitions from <literal>/etc/avahi/services</literal> instead of its
+     output directory in the nix store. Accordingly the module
+     <option>avahi</option> now supports custom service definitions via
+     <option>services.avahi.extraServiceFiles</option>, which are then placed
+     in the aforementioned directory. See <citerefentry>
+     <refentrytitle>avahi.service</refentrytitle><manvolnum>5</manvolnum>
+     </citerefentry> for more information on custom service definitions.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Since version 0.1.19, <literal>cargo-vendor</literal> honors package
+     includes that are specified in the <filename>Cargo.toml</filename>
+     file of Rust crates. <literal>rustPlatform.buildRustPackage</literal> uses
+     <literal>cargo-vendor</literal> to collect and build dependent crates.
+     Since this change in <literal>cargo-vendor</literal> changes the set of
+     vendored files for most Rust packages, the hash that use used to verify
+     the dependencies, <literal>cargoSha256</literal>, also changes.
+    </para>
+    <para>
+     The <literal>cargoSha256</literal> hashes of all in-tree derivations that
+     use <literal>buildRustPackage</literal> have been updated to reflect this
+     change. However, third-party derivations that use
+     <literal>buildRustPackage</literal> may have to be updated as well.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>consul</literal> package was upgraded past version <literal>1.5</literal>,
+     so its deprecated legacy UI is no longer available.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The default resample-method for PulseAudio has been changed from the upstream default <literal>speex-float-1</literal>
+     to <literal>speex-float-5</literal>. Be aware that low-powered ARM-based and MIPS-based boards will struggle with this
+     so you'll need to set <option>hardware.pulseaudio.daemon.config.resample-method</option> back to <literal>speex-float-1</literal>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>phabricator</literal> package and associated <literal>httpd.extraSubservice</literal>, as well as the
+     <literal>phd</literal> service have been removed from nixpkgs due to lack of maintainer.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>mercurial</literal> <literal>httpd.extraSubservice</literal> has been removed from nixpkgs due to lack of maintainer.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>trac</literal> <literal>httpd.extraSubservice</literal> has been removed from nixpkgs because it was unmaintained.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>foswiki</literal> package and associated <literal>httpd.extraSubservice</literal> have been removed
+     from nixpkgs due to lack of maintainer.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>tomcat-connector</literal> <literal>httpd.extraSubservice</literal> has been removed from nixpkgs.
     </para>
    </listitem>
    <listitem>
     <para>
-      The default resample-method for PulseAudio has been changed from the upstream default <literal>speex-float-1</literal>
-      to <literal>speex-float-5</literal>. Be aware that low-powered ARM-based and MIPS-based boards will struggle with this
-      so you'll need to set <option>hardware.pulseaudio.daemon.config.resample-method</option> back to <literal>speex-float-1</literal>.
+     It's now possible to change configuration in
+     <link linkend="opt-services.nextcloud.enable">services.nextcloud</link> after the initial deploy
+     since all config parameters are persisted in an additional config file generated by the module.
+     Previously core configuration like database parameters were set using their imperative
+     installer after creating <literal>/var/lib/nextcloud</literal>.
     </para>
    </listitem>
   </itemizedlist>
diff --git a/nixos/lib/build-vms.nix b/nixos/lib/build-vms.nix
index 6c92aa1ffa2f..b622a4d59e89 100644
--- a/nixos/lib/build-vms.nix
+++ b/nixos/lib/build-vms.nix
@@ -67,7 +67,7 @@ rec {
             in
             { key = "ip-address";
               config =
-                { networking.hostName = m.fst;
+                { networking.hostName = mkDefault m.fst;
 
                   networking.interfaces = listToAttrs interfaces;
 
diff --git a/nixos/lib/make-ext4-fs.nix b/nixos/lib/make-ext4-fs.nix
index 47c6374c81ad..932adcd97967 100644
--- a/nixos/lib/make-ext4-fs.nix
+++ b/nixos/lib/make-ext4-fs.nix
@@ -1,9 +1,14 @@
 # Builds an ext4 image containing a populated /nix/store with the closure
-# of store paths passed in the storePaths parameter. The generated image
-# is sized to only fit its contents, with the expectation that a script
-# resizes the filesystem at boot time.
+# of store paths passed in the storePaths parameter, in addition to the
+# contents of a directory that can be populated with commands. The
+# generated image is sized to only fit its contents, with the expectation
+# that a script resizes the filesystem at boot time.
 { pkgs
+# List of derivations to be included
 , storePaths
+# Shell commands to populate the ./files directory.
+# All files in that directory are copied to the root of the FS.
+, populateImageCommands ? ""
 , volumeLabel
 , uuid ? "44444444-4444-4444-8888-888888888888"
 , e2fsprogs
@@ -23,13 +28,17 @@ pkgs.stdenv.mkDerivation {
 
   buildCommand =
     ''
+      (
+      mkdir -p ./files
+      ${populateImageCommands}
+      )
       # Add the closures of the top-level store objects.
       storePaths=$(cat ${sdClosureInfo}/store-paths)
 
       # Make a crude approximation of the size of the target image.
       # If the script starts failing, increase the fudge factors here.
-      numInodes=$(find $storePaths | wc -l)
-      numDataBlocks=$(du -c -B 4096 --apparent-size $storePaths | awk '$2 == "total" { print int($1 * 1.03) }')
+      numInodes=$(find $storePaths ./files | wc -l)
+      numDataBlocks=$(du -s -c -B 4096 --apparent-size $storePaths ./files | tail -1 | awk '{ print int($1 * 1.03) }')
       bytes=$((2 * 4096 * $numInodes + 4096 * $numDataBlocks))
       echo "Creating an EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks)"
 
@@ -47,6 +56,12 @@ pkgs.stdenv.mkDerivation {
       echo "copying store paths to image..."
       cptofs -t ext4 -i $out $storePaths /nix/store/
 
+      (
+      echo "copying files to image..."
+      cd ./files
+      cptofs -t ext4 -i $out ./* /
+      )
+
       # I have ended up with corrupted images sometimes, I suspect that happens when the build machine's disk gets full during the build.
       if ! fsck.ext4 -n -f $out; then
         echo "--- Fsck failed for EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks) ---"
diff --git a/nixos/lib/test-driver/Machine.pm b/nixos/lib/test-driver/Machine.pm
index 006da889671e..4d3d63cd2dbf 100644
--- a/nixos/lib/test-driver/Machine.pm
+++ b/nixos/lib/test-driver/Machine.pm
@@ -31,9 +31,17 @@ sub new {
 
     if (!$startCommand) {
         # !!! merge with qemu-vm.nix.
+        my $netBackend = "-netdev user,id=net0";
+        my $netFrontend = "-device virtio-net-pci,netdev=net0";
+
+        $netBackend .= "," . $args->{netBackendArgs}
+            if defined $args->{netBackendArgs};
+
+        $netFrontend .= "," . $args->{netFrontendArgs}
+            if defined $args->{netFrontendArgs};
+
         $startCommand =
-            "qemu-kvm -m 384 " .
-            "-net nic,model=virtio \$QEMU_OPTS ";
+            "qemu-kvm -m 384 $netBackend $netFrontend \$QEMU_OPTS ";
 
         if (defined $args->{hda}) {
             if ($args->{hdaInterface} eq "scsi") {
diff --git a/nixos/lib/testing.nix b/nixos/lib/testing.nix
index a13e76a69560..767068771036 100644
--- a/nixos/lib/testing.nix
+++ b/nixos/lib/testing.nix
@@ -24,7 +24,7 @@ in rec {
 
     buildInputs = [ makeWrapper perl ];
 
-    unpackPhase = "true";
+    dontUnpack = true;
 
     preferLocalBuild = true;
 
diff --git a/nixos/lib/testing/jquery.nix b/nixos/lib/testing/jquery.nix
index 103721cadc38..e272f66a5765 100644
--- a/nixos/lib/testing/jquery.nix
+++ b/nixos/lib/testing/jquery.nix
@@ -16,7 +16,7 @@ stdenv.mkDerivation rec {
       sha256 = "1v956yf5spw0156rni5z77hzqwmby7ajwdcd6mkhb6zvl36awr90";
     };
 
-  unpackPhase = "true";
+  dontUnpack = true;
 
   installPhase =
     ''
diff --git a/nixos/modules/config/malloc.nix b/nixos/modules/config/malloc.nix
index dfa86a44a326..65130454735c 100644
--- a/nixos/modules/config/malloc.nix
+++ b/nixos/modules/config/malloc.nix
@@ -79,19 +79,13 @@ in
         and/or service failure.
         </para>
         </warning>
-
-        <note>
-        <para>
-        Changing this option does not affect the current session.
-        </para>
-        </note>
       '';
     };
   };
 
   config = mkIf (cfg.provider != "libc") {
-    environment.variables.LD_PRELOAD = providerLibPath;
-    systemd.extraConfig = "DefaultEnvironment=\"LD_PRELOAD=${providerLibPath}\"";
-    systemd.user.extraConfig = "DefaultEnvironment=\"LD_PRELOAD=${providerLibPath}\"";
+    environment.etc."ld-nix.so.preload".text = ''
+      ${providerLibPath}
+    '';
   };
 }
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 25253cf02e1d..4b9086022ed5 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";
@@ -64,48 +54,6 @@ in
       '';
     };
 
-    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"
@@ -233,42 +181,13 @@ in
           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";
       } // optionalAttrs (pkgs.stdenv.hostPlatform.libc == "glibc") {
         # /etc/rpc: RPC program numbers.
         "rpc".source = pkgs.glibc.out + "/etc/rpc";
@@ -295,26 +214,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..74cf74d74181 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -34,7 +34,7 @@ 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; };
+      pinentry = super.pinentry.override { gtk2 = null; gcr = null; qt4 = null; qt5 = null; };
       gobject-introspection = super.gobject-introspection.override { x11Support = false; };
     }));
   };
diff --git a/nixos/modules/config/resolvconf.nix b/nixos/modules/config/resolvconf.nix
new file mode 100644
index 000000000000..406c6a7ac329
--- /dev/null
+++ b/nixos/modules/config/resolvconf.nix
@@ -0,0 +1,149 @@
+# /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
+
+{
+
+  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/xdg/portal.nix b/nixos/modules/config/xdg/portal.nix
new file mode 100644
index 000000000000..bdbbfda2bb42
--- /dev/null
+++ b/nixos/modules/config/xdg/portal.nix
@@ -0,0 +1,58 @@
+{ config, pkgs ,lib ,... }:
+
+with lib;
+
+{
+  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/hardware/ksm.nix b/nixos/modules/hardware/ksm.nix
index d6ac69b5d65e..99d46c25236e 100644
--- a/nixos/modules/hardware/ksm.nix
+++ b/nixos/modules/hardware/ksm.nix
@@ -1,9 +1,24 @@
 { 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 {
+  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 +26,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/opengl.nix b/nixos/modules/hardware/opengl.nix
index 6b7b8069fd44..57cac56bd8ab 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)
       ];
   };
@@ -118,6 +118,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 +158,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/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..f867bba80630 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 ];
 
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index 9f2360f41c6e..da3c8ee5a9fa 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;
 
@@ -138,7 +138,6 @@ in
     services.xserver.drivers = singleton {
       name = "nvidia";
       modules = [ nvidia_x11.bin ];
-      libPath = [ nvidia_x11 ];
       deviceSection = optionalString optimusCfg.enable
         ''
           BusID "${optimusCfg.nvidiaBusId}"
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..f65239a5bc0a 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
@@ -1,7 +1,7 @@
 # This module contains the basic configuration for building a graphical NixOS
 # installation CD.
 
-{ config, lib, pkgs, ... }:
+{ lib, pkgs, ... }:
 
 with lib;
 
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..0b813bbf37b4 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;
 
diff --git a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
index 5f7194e92a36..a9241870fa71 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,13 @@ 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
+    '';
   };
 }
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..dab092415316 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,14 @@ 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
+    '';
   };
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
index 96e06670694e..8c9090471dcd 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
@@ -22,7 +22,7 @@ in
   boot.kernelPackages = pkgs.linuxPackages_rpi;
 
   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,14 @@ 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
+    '';
   };
 }
diff --git a/nixos/modules/installer/cd-dvd/sd-image.nix b/nixos/modules/installer/cd-dvd/sd-image.nix
index 69746a8e9799..0c407b199367 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,18 @@ with lib;
 let
   rootfsImage = pkgs.callPackage ../../../lib/make-ext4-fs.nix ({
     inherit (config.sdImage) storePaths;
+    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,12 +53,12 @@ in
       '';
     };
 
-    bootPartitionID = mkOption {
+    firmwarePartitionID = mkOption {
       type = types.string;
       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.
       '';
     };
 
@@ -61,29 +71,44 @@ in
       '';
     };
 
-    bootSize = mkOption {
+    firmwareSize = mkOption {
       type = types.int;
-      default = 120;
+      # As of 2019-05-31 the Raspberry pi firmware + u-bot takes ~13MiB
+      default = 20;
+      description = ''
+        Size of the /boot/firmware partition, in megabytes.
+      '';
+    };
+
+    populateFirmwareCommands = mkOption {
+      example = literalExample "'' cp \${pkgs.myBootLoader}/u-boot.bin firmware/ ''";
       description = ''
-        Size of the /boot partition, in megabytes.
+        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.
       '';
     };
   };
 
   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";
@@ -105,39 +130,44 @@ in
         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
+        # 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 ${rootfsImage} | awk '{ print $1 }')
-        bootSizeBlocks=$((${toString config.sdImage.bootSize} * 1024 * 1024 / 512))
-        imageSize=$((rootSizeBlocks * 512 + bootSizeBlocks * 512 + 20 * 1024 * 1024))
+        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
 
-        # 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
       '';
     }) {};
 
diff --git a/nixos/modules/installer/netboot/netboot.nix b/nixos/modules/installer/netboot/netboot.nix
index cdfba5ab9e31..f9b8d95c684d 100644
--- a/nixos/modules/installer/netboot/netboot.nix
+++ b/nixos/modules/installer/netboot/netboot.nix
@@ -84,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/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index b7e5b99a9e01..cfdbdaabf5c5 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,19 @@ 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") {
+            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 +441,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\" =
@@ -512,6 +535,7 @@ sub multiLineList {
 }
 
 my $initrdAvailableKernelModules = toNixStringList(uniq @initrdAvailableKernelModules);
+my $initrdKernelModules = toNixStringList(uniq @initrdKernelModules);
 my $kernelModules = toNixStringList(uniq @kernelModules);
 my $modulePackages = toNixList(uniq @modulePackages);
 
@@ -531,6 +555,7 @@ my $hwConfig = <<EOF;
   imports =${\multiLineList("    ", @imports)};
 
   boot.initrd.availableKernelModules = [$initrdAvailableKernelModules ];
+  boot.initrd.kernelModules = [$initrdKernelModules ];
   boot.kernelModules = [$kernelModules ];
   boot.extraModulePackages = [$modulePackages ];
 $fsAndSwap
@@ -582,90 +607,7 @@ 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?
-
-}
+@configuration@
 EOF
     } else {
         print STDERR "warning: not overwriting existing $fn\n";
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index 59eb4a63af40..05add59117d1 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -38,7 +38,7 @@ let
     src = ./nixos-generate-config.pl;
     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 {
@@ -61,8 +61,111 @@ 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.
+
+        # 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 = "${config.system.nixos.release}"; # Did you read the comment?
+
+      }
+    '';
+
     environment.systemPackages =
       [ nixos-build-vms
         nixos-install
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index 834ac0de9121..deecb005270f 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -49,11 +49,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}
@@ -187,8 +183,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 f1118f472e44..1047df95cdf5 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -44,7 +44,7 @@
       vsftpd = 7;
       ftp = 8;
       bitlbee = 9;
-      avahi = 10;
+      #avahi = 10; # removed 2019-05-22
       nagios = 11;
       atd = 12;
       postfix = 13;
@@ -358,7 +358,7 @@
       vsftpd = 7;
       ftp = 8;
       bitlbee = 9;
-      avahi = 10;
+      #avahi = 10; # removed 2019-05-22
       #nagios = 11; # unused
       atd = 12;
       postfix = 13;
@@ -516,7 +516,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
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 9d19dd2a7ff2..e0c192246c0c 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -196,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..3ae60cb79160 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;
 
@@ -95,6 +95,7 @@ in
         PRETTY_NAME="NixOS ${cfg.version} (${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 6663744bef87..df453aac9fef 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -11,6 +11,7 @@
   ./config/xdg/icons.nix
   ./config/xdg/menus.nix
   ./config/xdg/mime.nix
+  ./config/xdg/portal.nix
   ./config/appstream.nix
   ./config/xdg/sounds.nix
   ./config/gtk/gtk-icon-cache.nix
@@ -25,6 +26,7 @@
   ./config/nsswitch.nix
   ./config/power-management.nix
   ./config/pulseaudio.nix
+  ./config/resolvconf.nix
   ./config/shells-environment.nix
   ./config/swap.nix
   ./config/sysctl.nix
@@ -50,6 +52,7 @@
   ./hardware/logitech.nix
   ./hardware/mcelog.nix
   ./hardware/network/b43.nix
+  ./hardware/network/intel-2200bg.nix
   ./hardware/nitrokey.nix
   ./hardware/opengl.nix
   ./hardware/pcmcia.nix
@@ -84,6 +87,7 @@
   ./misc/nixpkgs.nix
   ./misc/passthru.nix
   ./misc/version.nix
+  ./misc/nixops-autoluks.nix
   ./programs/adb.nix
   ./programs/atop.nix
   ./programs/autojump.nix
@@ -91,6 +95,7 @@
   ./programs/bcc.nix
   ./programs/blcr.nix
   ./programs/browserpass.nix
+  ./programs/captive-browser.nix
   ./programs/ccache.nix
   ./programs/cdemu.nix
   ./programs/chromium.nix
@@ -135,6 +140,7 @@
   ./programs/sway.nix
   ./programs/thefuck.nix
   ./programs/tmux.nix
+  ./programs/tsm-client.nix
   ./programs/udevil.nix
   ./programs/venus.nix
   ./programs/vim.nix
@@ -188,9 +194,11 @@
   ./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
@@ -205,6 +213,7 @@
   ./services/backup/restic-rest-server.nix
   ./services/backup/rsnapshot.nix
   ./services/backup/tarsnap.nix
+  ./services/backup/tsm.nix
   ./services/backup/znapzend.nix
   ./services/cluster/hadoop/default.nix
   ./services/cluster/kubernetes/addons/dns.nix
@@ -415,6 +424,7 @@
   ./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
@@ -443,7 +453,6 @@
   ./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
@@ -467,6 +476,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
@@ -490,6 +500,7 @@
   ./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
@@ -509,10 +520,12 @@
   ./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
@@ -682,6 +695,7 @@
   ./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
@@ -768,6 +782,7 @@
   ./services/web-apps/icingaweb2/module-monitoring.nix
   ./services/web-apps/limesurvey.nix
   ./services/web-apps/mattermost.nix
+  ./services/web-apps/mediawiki.nix
   ./services/web-apps/miniflux.nix
   ./services/web-apps/nextcloud.nix
   ./services/web-apps/nexus.nix
@@ -777,7 +792,9 @@
   ./services/web-apps/tt-rss.nix
   ./services/web-apps/selfoss.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/fcgiwrap.nix
@@ -801,6 +818,7 @@
   ./services/web-servers/uwsgi.nix
   ./services/web-servers/varnish/default.nix
   ./services/web-servers/zope2.nix
+  ./services/x11/extra-layouts.nix
   ./services/x11/colord.nix
   ./services/x11/compton.nix
   ./services/x11/unclutter.nix
diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
index 87bf66333c61..9e9ddd4f3788 100644
--- a/nixos/modules/profiles/hardened.nix
+++ b/nixos/modules/profiles/hardened.nix
@@ -26,7 +26,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;
 
@@ -42,9 +44,6 @@ with lib;
 
     # Disable legacy virtual syscalls
     "vsyscall=none"
-
-    # Enable PTI even if CPU claims to be safe from meltdown
-    "pti=on"
   ];
 
   boot.blacklistedKernelModules = [
@@ -89,4 +88,37 @@ 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 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 true;
+  boot.kernel.sysctl."net.ipv4.conf.default.log_martians" = mkDefault true;
+  boot.kernel.sysctl."net.ipv4.conf.default.rp_filter" = mkDefault true;
+
+  # Ignore broadcast ICMP (mitigate SMURF)
+  boot.kernel.sysctl."net.ipv4.icmp_echo_ignore_broadcasts" = mkDefault true;
+
+  # Ignore route information from sender
+  boot.kernel.sysctl."net.ipv4.conf.all.accept_source_route" = mkDefault false;
+  boot.kernel.sysctl."net.ipv4.conf.default.accept_source_route" = mkDefault false;
+  boot.kernel.sysctl."net.ipv6.conf.all.accept_source_route" = mkDefault false;
+  boot.kernel.sysctl."net.ipv6.conf.default.accept_source_route" = mkDefault false;
+
+  # 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;
 }
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/shell.nix b/nixos/modules/programs/shell.nix
index 9842e2bef643..b7f7b91b5fbe 100644
--- a/nixos/modules/programs/shell.nix
+++ b/nixos/modules/programs/shell.nix
@@ -12,7 +12,7 @@ with lib;
       ''
         # 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
+        if [ "$(stat -c '%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
 
@@ -34,7 +34,7 @@ with lib;
           # 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
+          if [ "$(stat -c '%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
 
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index 46965dd35b71..733b8f7636fd 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";
 
@@ -128,6 +128,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,6 +235,7 @@ 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 =
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/xonsh.nix b/nixos/modules/programs/xonsh.nix
index ceab9b5db931..5cd2a49f8073 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.
@@ -46,11 +47,11 @@ in
 
     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/rename.nix b/nixos/modules/rename.nix
index a424e86038e7..4ae642222740 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -19,6 +19,7 @@ with lib;
         let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
         in if value != "" then [ value ] else []))
     (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
+    (mkRenamedOptionModule [ "services" "flatpak" "extraPortals" ] [ "xdg" "portal" "extraPortals" ])
     (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"])
@@ -62,6 +63,8 @@ with lib;
     (mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead")
     (mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead")
 
+    (mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ])
+
     # PAM
     (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
 
@@ -171,6 +174,9 @@ with lib;
        The starting time can be configured via <literal>services.postgresqlBackup.startAt</literal>.
     '')
 
+    # zabbixServer
+    (mkRenamedOptionModule [ "services" "zabbixServer" "dbServer" ] [ "services" "zabbixServer" "database" "host" ])
+
     # Profile splitting
     (mkRenamedOptionModule [ "virtualisation" "growPartition" ] [ "boot" "growPartition" ])
 
@@ -213,6 +219,8 @@ with lib;
     (mkRemovedOptionModule [ "boot" "zfs" "enableLegacyCrypto" ] "The corresponding package was removed from nixpkgs.")
     (mkRemovedOptionModule [ "services" "winstone" ] "The corresponding package was removed from nixpkgs.")
     (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.")
+    (mkRemovedOptionModule [ "services" "zabbixServer" "dbPassword" ] "Use services.zabbixServer.database.passwordFile instead.")
 
     # ZSH
     (mkRenamedOptionModule [ "programs" "zsh" "enableSyntaxHighlighting" ] [ "programs" "zsh" "syntaxHighlighting" "enable" ])
@@ -240,6 +248,15 @@ with lib;
     # binfmt
     (mkRenamedOptionModule [ "boot" "binfmtMiscRegistrations" ] [ "boot" "binfmt" "registrations" ])
 
+    # KSM
+    (mkRenamedOptionModule [ "hardware" "enableKSM" ] [ "hardware" "ksm" "enable" ])
+
+    # resolvconf
+    (mkRenamedOptionModule [ "networking" "dnsSingleRequest" ] [ "networking" "resolvconf" "dnsSingleRequest" ])
+    (mkRenamedOptionModule [ "networking" "dnsExtensionMechanism" ] [ "networking" "resolvconf" "dnsExtensionMechanism" ])
+    (mkRenamedOptionModule [ "networking" "extraResolvconfConf" ] [ "networking" "resolvconf" "extraConfig" ])
+    (mkRenamedOptionModule [ "networking" "resolvconfOptions" ] [ "networking" "resolvconf" "extraOptions" ])
+
   ] ++ (flip map [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
                    "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
                    "snmpExporter" "unifiExporter" "varnishExporter" ]
diff --git a/nixos/modules/security/misc.nix b/nixos/modules/security/misc.nix
index bf474ac0a546..16e3bfb14199 100644
--- a/nixos/modules/security/misc.nix
+++ b/nixos/modules/security/misc.nix
@@ -48,13 +48,25 @@ with lib;
         e.g., shared caches).  This attack vector is unproven.
 
         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).
       '';
     };
 
-    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 = ''
@@ -114,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/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/roon-server.nix b/nixos/modules/services/audio/roon-server.nix
new file mode 100644
index 000000000000..9562ad1b164e
--- /dev/null
+++ b/nixos/modules/services/audio/roon-server.nix
@@ -0,0 +1,49 @@
+{ 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
+        '';
+      };
+    };
+  };
+
+  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;
+        DynamicUser = true;
+        SupplementaryGroups = "audio";
+        StateDirectory = name;
+      };
+    };
+    
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPortRanges = [
+        { from = 9100; to = 9200; }
+      ];
+      allowedUDPPorts = [ 9003 ];
+    };
+  };
+}
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..e3556b2559c2
--- /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 ${spotifydConf}";
+        Restart = "always";
+        RestartSec = 12;
+        DynamicUser = true;
+        CacheDirectory = "spotifyd";
+        SupplementaryGroups = ["audio"];
+      };
+    };
+  };
+
+  meta.maintainers = [ maintainers.anderslundstedt ];
+}
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/tsm.nix b/nixos/modules/services/backup/tsm.nix
new file mode 100644
index 000000000000..3b2bb37491b5
--- /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..785cedb98694
--- /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/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index 5e46bfc4240f..143b41f57f6a 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -72,13 +72,6 @@ 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 {
 
   ###### interface
diff --git a/nixos/modules/services/cluster/kubernetes/kubelet.nix b/nixos/modules/services/cluster/kubernetes/kubelet.nix
index ccc8a16e788a..4c5df96bcc6a 100644
--- a/nixos/modules/services/cluster/kubernetes/kubelet.nix
+++ b/nixos/modules/services/cluster/kubernetes/kubelet.nix
@@ -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, ... }: {
diff --git a/nixos/modules/services/cluster/kubernetes/pki.nix b/nixos/modules/services/cluster/kubernetes/pki.nix
index e68660e8bdd4..47384ae50a07 100644
--- a/nixos/modules/services/cluster/kubernetes/pki.nix
+++ b/nixos/modules/services/cluster/kubernetes/pki.nix
@@ -118,7 +118,6 @@ in
     cfsslCertPathPrefix = "${config.services.cfssl.dataDir}/cfssl";
     cfsslCert = "${cfsslCertPathPrefix}.pem";
     cfsslKey = "${cfsslCertPathPrefix}-key.pem";
-    cfsslPort = toString config.services.cfssl.port;
 
     certmgrPaths = [
       top.caFile
diff --git a/nixos/modules/services/computing/boinc/client.nix b/nixos/modules/services/computing/boinc/client.nix
index 8abe3c5b8c9b..7022751b3f01 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"];
         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/databases/cassandra.nix b/nixos/modules/services/databases/cassandra.nix
index 688938868020..a9da3a3c5620 100644
--- a/nixos/modules/services/databases/cassandra.nix
+++ b/nixos/modules/services/databases/cassandra.nix
@@ -8,18 +8,21 @@ 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 cfg.listenAddress == null
@@ -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,11 +251,11 @@ 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 {
@@ -214,20 +266,135 @@ in {
           Options passed through to the incremental repair command.
         '';
     };
+    maxHeapSize = mkOption {
+      type = types.nullOr types.string;
+      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.string;
+      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.string;
+            description = "Username for JMX";
+          };
+          password = mkOption {
+            type = types.string;
+            description = "Password for JMX";
+          };
+        };
+      });
+    };
+    jmxRolesFile = mkOption {
+      type = types.nullOr types.path;
+      default = if (lib.versionAtLeast cfg.package.version "3.11")
+                then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile
+                else null;
+      example = "/var/lib/cassandra/jmx.password";
+      description = ''
+        Specify your own jmx roles file.
+
+        Make sure the permissions forbid "others" from reading the file if
+        you're using Cassandra below version 3.11.
+      '';
+    };
   };
 
   config = mkIf cfg.enable {
     assertions =
-      [ { assertion =
-          (cfg.listenAddress == null || cfg.listenInterface == null)
-          && !(cfg.listenAddress == null && cfg.listenInterface == null);
+      [ { assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null);
           message = "You have to set either listenAddress or listenInterface";
         }
-        { assertion =
-          (cfg.rpcAddress == null || cfg.rpcInterface == null)
-          && !(cfg.rpcAddress == null && cfg.rpcInterface == null);
+        { 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}" =
@@ -245,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 =
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 490c5e9d005a..3746b875c7f2 100644
--- a/nixos/modules/services/databases/foundationdb.nix
+++ b/nixos/modules/services/databases/foundationdb.nix
@@ -359,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";
 
@@ -396,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 0770 -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
       '';
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..589c8cf5ec80 100644
--- a/nixos/modules/services/databases/hbase.nix
+++ b/nixos/modules/services/databases/hbase.nix
@@ -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/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 66d55b650a45..df74cfc9a26b 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -204,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" ];
@@ -323,114 +317,107 @@ in
           RuntimeDirectoryMode = "0755";
           # 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 =
-          let
-            cmdWatchForMysqlSocket = ''
-              # 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
-            '';
-            cmdInitialDatabases = 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
-                    ''}
+          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
+                    ${concatMapStrings (database: ''
+                      # Create initial databases
+                      if ! test -e "${cfg.dataDir}/${database.name}"; then
+                          echo "Creating initial database: ${database.name}"
+                          ( echo 'create database `${database.name}`;'
+
+                            ${optionalString (database.schema != null) ''
+                            echo 'use `${database.name}`;'
+
+                            # TODO: this silently falls through if database.schema does not exist,
+                            # we should catch this somehow and exit, but can't do it here because we're in a subshell.
+                            if [ -f "${database.schema}" ]
+                            then
+                                cat ${database.schema}
+                            elif [ -d "${database.schema}" ]
+                            then
+                                cat ${database.schema}/mysql-databases/*.sql
+                            fi
+                            ''}
+                          ) | ${mysql}/bin/mysql -u root -N
+                      fi
+                    '') cfg.initialDatabases}
+
+                    ${optionalString (cfg.replication.role == "master")
+                      ''
+                        # Set up the replication master
+
+                        ( echo "use mysql;"
+                          echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
+                          echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
+                          echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
+                        ) | ${mysql}/bin/mysql -u root -N
+                      ''}
+
+                    ${optionalString (cfg.replication.role == "slave")
+                      ''
+                        # Set up the replication slave
+
+                        ( echo "stop slave;"
+                          echo "change master to master_host='${cfg.replication.masterHost}', master_user='${cfg.replication.masterUser}', master_password='${cfg.replication.masterPassword}';"
+                          echo "start slave;"
+                        ) | ${mysql}/bin/mysql -u root -N
+                      ''}
+
+                    ${optionalString (cfg.initialScript != null)
+                      ''
+                        # Execute initial script
+                        # using toString to avoid copying the file to nix store if given as path instead of string,
+                        # as it might contain credentials
+                        cat ${toString cfg.initialScript} | ${mysql}/bin/mysql -u root -N
+                      ''}
+
+                    rm /tmp/mysql_init
+                fi
+
+                ${optionalString (cfg.ensureDatabases != []) ''
+                  (
+                  ${concatMapStrings (database: ''
+                    echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
+                  '') cfg.ensureDatabases}
                   ) | ${mysql}/bin/mysql -u root -N
-              fi
-            '') cfg.initialDatabases;
-          in
-
-          lib.optionalString (!hasNotify) cmdWatchForMysqlSocket + ''
-            if [ -f /tmp/mysql_init ]
-            then
-                ${cmdInitialDatabases}
-                ${optionalString (cfg.replication.role == "master")
-                  ''
-                    # Set up the replication master
-
-                    ( echo "use mysql;"
-                      echo "CREATE USER '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' IDENTIFIED WITH mysql_native_password;"
-                      echo "SET PASSWORD FOR '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}' = PASSWORD('${cfg.replication.masterPassword}');"
-                      echo "GRANT REPLICATION SLAVE ON *.* TO '${cfg.replication.masterUser}'@'${cfg.replication.slaveHost}';"
-                    ) | ${mysql}/bin/mysql -u root -N
-                  ''}
+                ''}
 
-                ${optionalString (cfg.replication.role == "slave")
+                ${concatMapStrings (user:
                   ''
-                    # 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;"
+                    ( 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
-                  ''}
-
-                ${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.rootPassword != null)
-                  ''
-                    # Change root password
-
-                    ( echo "use mysql;"
-                      echo "update user set Password=password('$(cat ${cfg.rootPassword})') where User='root';"
-                      echo "flush privileges;"
-                    ) | ${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/openldap.nix b/nixos/modules/services/databases/openldap.nix
index c2f458c03794..d8e2c715afb9 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -237,8 +237,8 @@ in
   config = mkIf cfg.enable {
     assertions = [
       {
-        assertion = cfg.rootpwFile != null || cfg.rootpw != null;
-        message = "Either services.openldap.rootpw or services.openldap.rootpwFile must be set";
+        assertion = cfg.configDir != null || cfg.rootpwFile != null || cfg.rootpw != null;
+        message = "services.openldap: Unless configDir is set, either rootpw or rootpwFile must be set";
       }
     ];
 
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 5661edbee2db..7ff899970ccd 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.
         '';
@@ -192,17 +179,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 {
@@ -270,6 +251,10 @@ in
 
     environment.systemPackages = [ postgresql ];
 
+    environment.pathsToLink = [
+     "/share/postgresql"
+    ];
+
     systemd.services.postgresql =
       { description = "PostgreSQL Server";
 
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/desktops/deepin/deepin.nix b/nixos/modules/services/desktops/deepin/deepin.nix
index d9d5d539e422..931bac58aceb 100644
--- a/nixos/modules/services/desktops/deepin/deepin.nix
+++ b/nixos/modules/services/desktops/deepin/deepin.nix
@@ -33,8 +33,10 @@
       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
@@ -45,8 +47,10 @@
       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
diff --git a/nixos/modules/services/desktops/flatpak.nix b/nixos/modules/services/desktops/flatpak.nix
index cfca1893bd82..1492d855aa03 100644
--- a/nixos/modules/services/desktops/flatpak.nix
+++ b/nixos/modules/services/desktops/flatpak.nix
@@ -15,38 +15,28 @@ 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;
-    };
   };
 }
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/tumbler.nix b/nixos/modules/services/desktops/tumbler.nix
index ccbb6d1434d9..d18088d4634b 100644
--- a/nixos/modules/services/desktops/tumbler.nix
+++ b/nixos/modules/services/desktops/tumbler.nix
@@ -23,7 +23,7 @@ in
 
       package = mkOption {
         type = types.package;
-        default = pkgs.xfce4-13.tumbler;
+        default = pkgs.xfce4-14.tumbler;
         description = "Which tumbler package to use";
         example = pkgs.xfce4-12.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/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml
index 1ac53c818a7f..a3041ae22e78 100644
--- a/nixos/modules/services/editors/emacs.xml
+++ b/nixos/modules/services/editors/emacs.xml
@@ -95,7 +95,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>
 
@@ -238,8 +239,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
@@ -403,9 +404,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>
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/sane_extra_backends/brscan4_etc_files.nix b/nixos/modules/services/hardware/sane_extra_backends/brscan4_etc_files.nix
index bd114f0d2cca..fd19d8020fb8 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
 
@@ -40,7 +40,7 @@ stdenv.mkDerivation rec {
 
   nativeBuildInputs = [ brscan4 ];
 
-  configurePhase = ":";
+  dontConfigure = true;
 
   buildPhase = ''
     TARGET_DIR="$out/etc/opt/brother/scanner/brscan4"
diff --git a/nixos/modules/services/hardware/throttled.nix b/nixos/modules/services/hardware/throttled.nix
index cd5b01450e44..13fc5e4792e6 100644
--- a/nixos/modules/services/hardware/throttled.nix
+++ b/nixos/modules/services/hardware/throttled.nix
@@ -8,6 +8,12 @@ in {
   options = {
     services.throttled = {
       enable = mkEnableOption "fix for Intel CPU throttling";
+
+      extraConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = "Alternative configuration";
+      };
     };
   };
 
@@ -16,6 +22,9 @@ in {
     # 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 = "${pkgs.throttled}/etc/lenovo_fix.conf";
+    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/triggerhappy.nix b/nixos/modules/services/hardware/triggerhappy.nix
index bffe7353b10e..a500cb4fc367 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 {
diff --git a/nixos/modules/services/hardware/udev.nix b/nixos/modules/services/hardware/udev.nix
index 0266286aaacf..83ab93bd7cfc 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.
@@ -282,6 +278,8 @@ 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";
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/mail/dspam.nix b/nixos/modules/services/mail/dspam.nix
index 167b5aeccc84..72b8c4c08b92 100644
--- a/nixos/modules/services/mail/dspam.nix
+++ b/nixos/modules/services/mail/dspam.nix
@@ -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/opendkim.nix b/nixos/modules/services/mail/opendkim.nix
index 7855efb46c73..253823cbaf9c 100644
--- a/nixos/modules/services/mail/opendkim.nix
+++ b/nixos/modules/services/mail/opendkim.nix
@@ -101,13 +101,16 @@ in {
 
     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 +119,6 @@ in {
           cat ${cfg.selector}.txt
           echo "-------------------------------------------------------------"
         fi
-        chown ${cfg.user}:${cfg.group} ${cfg.selector}.private
       '';
 
       serviceConfig = {
@@ -124,7 +126,6 @@ in {
         User = cfg.user;
         Group = cfg.group;
         RuntimeDirectory = optional (cfg.socket == defaultSock) "opendkim";
-        PermissionsStartOnly = true;
       };
     };
 
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index d43733484ffa..dab1b29aa4be 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;
@@ -506,6 +508,19 @@ in
         '';
       };
 
+      localRecipients = mkOption {
+        type = with types; nullOr (listOf string);
+        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 = "
@@ -742,6 +757,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" ];
@@ -869,6 +885,9 @@ in
     (mkIf haveVirtual {
       services.postfix.mapFiles."virtual" = virtualFile;
     })
+    (mkIf haveLocalRecipients {
+      services.postfix.mapFiles."local_recipients" = localRecipientMapFile;
+    })
     (mkIf cfg.enableHeaderChecks {
       services.postfix.mapFiles."header_checks" = headerChecksFile;
     })
diff --git a/nixos/modules/services/mail/rspamd.nix b/nixos/modules/services/mail/rspamd.nix
index c9ba86780213..5541b8b79b7e 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, ... }: {
diff --git a/nixos/modules/services/misc/apache-kafka.nix b/nixos/modules/services/misc/apache-kafka.nix
index 363ac4411e11..9eeae9556992 100644
--- a/nixos/modules/services/misc/apache-kafka.nix
+++ b/nixos/modules/services/misc/apache-kafka.nix
@@ -131,6 +131,8 @@ in {
       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 +147,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/couchpotato.nix b/nixos/modules/services/misc/couchpotato.nix
index 70aa895f76d8..528af486b414 100644
--- a/nixos/modules/services/misc/couchpotato.nix
+++ b/nixos/modules/services/misc/couchpotato.nix
@@ -19,16 +19,11 @@ 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";
       };
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index 6fd4183bd6b4..59c1c104b9b9 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -38,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
@@ -54,6 +55,11 @@ let
     [service]
     DISABLE_REGISTRATION = ${boolToString cfg.disableRegistration}
 
+    ${optionalString (cfg.mailerPasswordFile != null) ''
+      [mailer]
+      PASSWD = #mailerpass#
+    ''}
+
     ${cfg.extraConfig}
   '';
 in
@@ -159,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.";
         };
@@ -173,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.";
         };
       };
 
@@ -256,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.
@@ -277,7 +288,47 @@ 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.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";
@@ -288,28 +339,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,#jwtsecet#,$JWTSECET,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" ]
@@ -319,43 +378,19 @@ 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";
       };
@@ -367,15 +402,17 @@ 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";
       };
     };
 
+    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.'';
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 71277b48ecd9..2f3f76d79ff3 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -52,7 +52,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 = {
@@ -109,7 +109,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,14 +132,9 @@ 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";
@@ -149,7 +144,7 @@ let
     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 \
@@ -164,7 +159,7 @@ let
     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 \
@@ -502,23 +497,44 @@ in {
     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+ ${cfg.statePath}/lib - - - - ${cfg.packages.gitlab}/share/gitlab/lib"
+      "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/gitlab.yml - - - - ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)}"
+      "L+ ${cfg.statePath}/config/database.yml - - - - ${pkgs.writeText "database.yml" (builtins.toJSON databaseConfig)}"
+      "L+ ${cfg.statePath}/config/secrets.yml - - - - ${pkgs.writeText "secrets.yml" (builtins.toJSON secretsConfig)}"
+      "L+ ${cfg.statePath}/config/unicorn.rb - - - - ${./defaultUnicornConfig.rb}"
+
+      "L+ ${cfg.statePath}/config/initializers/extra-gitlab.rb - - - - ${extraGitlabRb}"
+    ] ++ optional cfg.smtp.enable
+      "L+ ${cfg.statePath}/config/initializers/smtp_settings.rb - - - - ${smtpSettings}" ;
 
     systemd.services.gitlab-sidekiq = {
       after = [ "network.target" "redis.service" "gitlab.service" ];
@@ -570,6 +586,7 @@ in {
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [
+        exiftool
         gitAndTools.git
         gnutar
         gzip
@@ -609,40 +626,14 @@ in {
         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
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -f ${cfg.packages.gitlab}/share/gitlab/VERSION ${cfg.statePath}/VERSION
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} rm -rf ${cfg.statePath}/db/*
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} cp -rf --no-preserve=mode ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
 
-        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
+        ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/gitlab_shell_secret
+
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${cfg.packages.gitlab-shell}/bin/install
 
         if ! test -e "${cfg.statePath}/db-created"; then
           if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then
@@ -655,7 +646,7 @@ in {
 
           ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:schema:load
 
-          touch "${cfg.statePath}/db-created"
+          ${pkgs.sudo}/bin/sudo -u ${cfg.user} touch "${cfg.statePath}/db-created"
         fi
 
         # Always do the db migrations just to be sure the database is up-to-date
@@ -664,22 +655,13 @@ in {
         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"
+          ${pkgs.sudo}/bin/sudo -u ${cfg.user} 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
+        # We remove potentially broken links to old gitlab-shell versions
         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 = {
diff --git a/nixos/modules/services/misc/gitlab.xml b/nixos/modules/services/misc/gitlab.xml
index ab99d7bd3a60..5ff570a442f6 100644
--- a/nixos/modules/services/misc/gitlab.xml
+++ b/nixos/modules/services/misc/gitlab.xml
@@ -138,13 +138,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..cbe2c06ab651 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;
 
+    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..7653b415bf09 100644
--- a/nixos/modules/services/misc/gollum.nix
+++ b/nixos/modules/services/misc/gollum.nix
@@ -75,27 +75,24 @@ in
 
     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/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/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/lidarr.nix b/nixos/modules/services/misc/lidarr.nix
index f466402abfc7..40755c162171 100644
--- a/nixos/modules/services/misc/lidarr.nix
+++ b/nixos/modules/services/misc/lidarr.nix
@@ -9,6 +9,37 @@ in
   options = {
     services.lidarr = {
       enable = mkEnableOption "Lidarr";
+
+      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.
+        '';
+      };
     };
   };
 
@@ -20,9 +51,9 @@ in
 
       serviceConfig = {
         Type = "simple";
-        User = "lidarr";
-        Group = "lidarr";
-        ExecStart = "${pkgs.lidarr}/bin/Lidarr";
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${cfg.package}/bin/Lidarr";
         Restart = "on-failure";
 
         StateDirectory = "lidarr";
@@ -30,12 +61,22 @@ in
       };
     };
 
-    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/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index 8db3c44246f3..6bc88c66dc19 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -272,10 +272,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>.
         '';
       };
 
@@ -386,6 +388,7 @@ in
   config = {
 
     nix.binaryCachePublicKeys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
+    nix.binaryCaches = [ "https://cache.nixos.org/" ];
 
     environment.etc."nix/nix.conf".source = nixConf;
 
@@ -464,7 +467,7 @@ in
         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;
 
diff --git a/nixos/modules/services/misc/octoprint.nix b/nixos/modules/services/misc/octoprint.nix
index 52ae25201c35..8950010773cf 100644
--- a/nixos/modules/services/misc/octoprint.nix
+++ b/nixos/modules/services/misc/octoprint.nix
@@ -97,6 +97,10 @@ in
         gid = config.ids.gids.octoprint;
       });
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.octoprint = {
       description = "OctoPrint, web interface for 3D printers";
       wantedBy = [ "multi-user.target" ];
@@ -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/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/redmine.nix b/nixos/modules/services/misc/redmine.nix
index 91ddf2c3edf3..24b9e27ac2da 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,16 +52,15 @@ 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 {
@@ -107,7 +108,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 +126,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 +172,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 +217,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 +241,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 +298,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";
diff --git a/nixos/modules/services/misc/taskserver/default.nix b/nixos/modules/services/misc/taskserver/default.nix
index 07dbee69db0c..8a57277fafe7 100644
--- a/nixos/modules/services/misc/taskserver/default.nix
+++ b/nixos/modules/services/misc/taskserver/default.nix
@@ -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/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/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
index 9c555e8031c4..cf56ae89b399 100644
--- a/nixos/modules/services/misc/zoneminder.nix
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -50,7 +50,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 +155,7 @@ in {
           default = "zmpass";
           description = ''
             Username for accessing the database.
+            Not used if <literal>createLocally</literal> is set.
           '';
         };
       };
@@ -189,6 +190,12 @@ 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;
@@ -204,10 +211,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"; };
@@ -256,7 +262,7 @@ in {
                   fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress};
                 }
 
-                location /cache {
+                location /cache/ {
                   alias /var/cache/${dirName};
                 }
 
@@ -315,11 +321,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;
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/grafana-reporter.nix b/nixos/modules/services/monitoring/grafana-reporter.nix
index 827cf6322cfd..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;
 
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index 5d3f2e6ac28f..c2f6b585d493 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -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/kapacitor.nix b/nixos/modules/services/monitoring/kapacitor.nix
index a4bdfa8f8053..0f236d25c9ed 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;
 
@@ -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..4d11360d07e9
--- /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";
+        DecvicePolicy = "closed";
+        NoNewPrivileges = true;
+        WorkingDirectory = cfg.dataDir;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/nagios.nix b/nixos/modules/services/monitoring/nagios.nix
index 7f65236ed3d3..6a3b97769462 100644
--- a/nixos/modules/services/monitoring/nagios.nix
+++ b/nixos/modules/services/monitoring/nagios.nix
@@ -36,7 +36,7 @@ let
 
       # Uid/gid that the daemon runs under.
       nagios_user=nagios
-      nagios_group=nogroup
+      nagios_group=nagios
 
       # Misc. options.
       illegal_macro_output_chars=`~$&|'"<>
@@ -58,9 +58,7 @@ let
 
       <Directory "${pkgs.nagios}/sbin">
         Options ExecCGI
-        AllowOverride None
-        Order allow,deny
-        Allow from all
+        Require all granted
         SetEnv NAGIOS_CGI_CONFIG ${cfg.cgiConfigFile}
       </Directory>
 
@@ -68,9 +66,7 @@ let
 
       <Directory "${pkgs.nagios}/share">
         Options None
-        AllowOverride None
-        Order allow,deny
-        Allow from all
+        Require all granted
       </Directory>
     '';
 
@@ -149,9 +145,11 @@ 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 = [
@@ -169,16 +167,13 @@ in
 
       serviceConfig = {
         User = "nagios";
+        Group = "nagios";
         Restart = "always";
         RestartSec = 2;
-        PermissionsStartOnly = true;
+        LogsDirectory = "nagios";
+        StateDirectory = "nagios";
       };
 
-      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
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index a49555cf677f..f9b7550af23a 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 = [
@@ -140,12 +141,18 @@ in {
       path = (with pkgs; [ gawk curl ]) ++ 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";
       };
     };
 
@@ -157,6 +164,18 @@ 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;
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 20e7eba43412..2ab8910ff9db 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,25 +19,30 @@ 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; };
-    wireguard = import ./exporters/wireguard.nix { inherit config lib pkgs; };
-  };
+
+  exporterOpts = genAttrs [
+    "bind"
+    "blackbox"
+    "collectd"
+    "dnsmasq"
+    "dovecot"
+    "fritzbox"
+    "json"
+    "mail"
+    "minio"
+    "nginx"
+    "node"
+    "postfix"
+    "postgres"
+    "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";
@@ -81,7 +88,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.
@@ -89,7 +96,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.
@@ -97,9 +104,10 @@ let
     };
   });
 
-  mkSubModule = { name, port, extraOpts, ... }: {
+  mkSubModule = { name, port, extraOpts, imports }: {
     ${name} = mkOption {
       type = types.submodule {
+        inherit imports;
         options = (mkExporterOpts {
           inherit name port;
         } // extraOpts);
@@ -112,13 +120,30 @@ 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 = (mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) {
+        "${name}-exporter" = {
+          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"
@@ -129,7 +154,8 @@ 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;
       });
@@ -154,13 +180,19 @@ 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;
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..f69b389760f7 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/blackbox.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -18,7 +18,6 @@ in
   serviceOpts = {
     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} \
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 530206681d36..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,7 +26,6 @@ in
   };
   serviceOpts = {
     serviceConfig = {
-      DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-fritzbox-exporter}/bin/exporter \
           -listen-address ${cfg.listenAddress}:${toString cfg.port} \
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/nginx.nix b/nixos/modules/services/monitoring/prometheus/exporters/nginx.nix
index 431dd8b4ead7..554377df37ba 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; })
+  ];
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/node.nix b/nixos/modules/services/monitoring/prometheus/exporters/node.nix
index ee7bf39f199a..7e394e8463e0 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;
 
@@ -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..f40819e826b0 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} \
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/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
index c5b84e574b8d..8ae2c927b58c 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/wireguard.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs }:
+{ config, lib, pkgs, options }:
 
 with lib;
 
@@ -23,20 +23,39 @@ in {
         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 = {
-    script = ''
-      ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \
-        -p ${toString cfg.port} \
-        ${optionalString cfg.verbose "-v"} \
-        ${optionalString (cfg.wireguardConfig != null) "-n ${cfg.wireguardConfig}"}
-    '';
-
     path = [ pkgs.wireguard-tools ];
 
     serviceConfig = {
-      DynamicUser = true;
       AmbientCapabilities = [ "CAP_NET_ADMIN" ];
+      ExecStart = ''
+        ${pkgs.prometheus-wireguard-exporter}/bin/prometheus_wireguard_exporter \
+          -p ${toString cfg.port} \
+          ${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..9c400a1e3e46 100644
--- a/nixos/modules/services/monitoring/riemann-tools.nix
+++ b/nixos/modules/services/monitoring/riemann-tools.nix
@@ -54,7 +54,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..dc0899c7e684 100644
--- a/nixos/modules/services/monitoring/scollector.nix
+++ b/nixos/modules/services/monitoring/scollector.nix
@@ -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/thanos.nix b/nixos/modules/services/monitoring/thanos.nix
new file mode 100644
index 000000000000..b41e99b76477
--- /dev/null
+++ b/nixos/modules/services/monitoring/thanos.nix
@@ -0,0 +1,801 @@
+{ 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.
+          '';
+        };
+      };
+
+      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.
+          '';
+        };
+    };
+
+    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.
+          '';
+        };
+      };
+
+      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.
+          '';
+        };
+    };
+
+    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.prometheus2.stateDir}/data";
+          defaultText = "/var/lib/\${config.services.prometheus2.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.
+      '';
+    };
+
+    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.
+        '';
+      };
+
+      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.prometheus2.enable;
+          message =
+            "Please enable services.prometheus2 when enabling services.thanos.sidecar.";
+        }
+        {
+          assertion = !(config.services.prometheus2.globalConfig.external_labels == null ||
+                        config.services.prometheus2.globalConfig.external_labels == {});
+          message =
+            "services.thanos.sidecar requires uniquely identifying external labels " +
+            "to be configured in the Prometheus server. " +
+            "Please set services.prometheus2.globalConfig.external_labels.";
+        }
+      ];
+      systemd.services.thanos-sidecar = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network.target" "prometheus2.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/zabbix-agent.nix b/nixos/modules/services/monitoring/zabbix-agent.nix
index 0519e7c2ad6a..b1645f861101 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,38 @@ 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;
+    };
 
-        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..9cfcd1697c11
--- /dev/null
+++ b/nixos/modules/services/monitoring/zabbix-proxy.nix
@@ -0,0 +1,298 @@
+{ 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 = "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}
+          cat ${cfg.package}/share/zabbix/database/postgresql/images.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/postgresql/data.sql | ${pgsql.package}/bin/psql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString mysqlLocal ''
+        if ! test -e "${stateDir}/db-created"; then
+          cat ${cfg.package}/share/zabbix/database/mysql/schema.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/mysql/images.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          cat ${cfg.package}/share/zabbix/database/mysql/data.sql | ${mysql.package}/bin/mysql ${cfg.database.name}
+          touch "${stateDir}/db-created"
+        fi
+      '' + optionalString (cfg.database.passwordFile != null) ''
+        # create a copy of the supplied password file in a format zabbix can consume
+        touch ${passwordFile}
+        chmod 0600 ${passwordFile}
+        echo -n "DBPassword = " > ${passwordFile}
+        cat ${cfg.database.passwordFile} >> ${passwordFile}
+      '';
+
+      serviceConfig = {
+        ExecStart = "@${cfg.package}/sbin/zabbix_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..11311b466c3f 100644
--- a/nixos/modules/services/monitoring/zabbix-server.nix
+++ b/nixos/modules/services/monitoring/zabbix-server.nix
@@ -1,125 +1,292 @@
-# 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}"}
+    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}
+{
+  # interface
 
-      ${optionalString (cfg.dbServer != "localhost") ''
-        DBHost = ${cfg.dbServer}
-      ''}
+  options = {
 
-      DBName = zabbix
+    services.zabbixServer = {
+      enable = mkEnableOption "the Zabbix Server";
 
-      DBUser = zabbix
+      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.";
+      };
 
-      ${optionalString (cfg.dbPassword != "") ''
-        DBPassword = ${cfg.dbPassword}
-      ''}
+      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.
+        '';
+      };
 
-      ${config.services.zabbixServer.extraConfig}
-    '';
+      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/
+              ''';
+            };
+          }
+        '';
+      };
 
-  useLocalPostgres = cfg.dbServer == "localhost" || cfg.dbServer == "";
+      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.";
+        };
+      };
 
-in
+      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.
+          '';
+        };
+      };
 
-  ###### interface
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Zabbix Server.
+        '';
+      };
 
-  options = {
+      # 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.enable = mkOption {
-      default = false;
-      type = types.bool;
-      description = ''
-        Whether to run the Zabbix server on this machine.
-      '';
     };
 
-    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.";
-    };
+  # implementation
 
-    services.zabbixServer.extraConfig = mkOption {
-      default = "";
-      type = types.lines;
-      description = ''
-        Configuration that is injected verbatim into the configuration file.
-      '';
+  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/kbfs.nix b/nixos/modules/services/network-filesystems/kbfs.nix
index 7b2eea3b5850..263b70d04a56 100644
--- a/nixos/modules/services/network-filesystems/kbfs.nix
+++ b/nixos/modules/services/network-filesystems/kbfs.nix
@@ -48,6 +48,7 @@ in {
       requires = [ "keybase.service" ];
       after = [ "keybase.service" ];
       path = [ "/run/wrappers" ];
+      unitConfig.ConditionUser = "!@system";
       serviceConfig = {
         ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${cfg.mountPoint}";
         ExecStart = "${pkgs.kbfs}/bin/kbfsfuse ${toString cfg.extraFlags} ${cfg.mountPoint}";
diff --git a/nixos/modules/services/network-filesystems/samba.nix b/nixos/modules/services/network-filesystems/samba.nix
index 10dc58311212..69368441c62c 100644
--- a/nixos/modules/services/network-filesystems/samba.nix
+++ b/nixos/modules/services/network-filesystems/samba.nix
@@ -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>
         '';
       };
diff --git a/nixos/modules/services/networking/aria2.nix b/nixos/modules/services/networking/aria2.nix
index 98eb00861016..53829bf18863 100644
--- a/nixos/modules/services/networking/aria2.nix
+++ b/nixos/modules/services/networking/aria2.nix
@@ -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" ];
       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..a098a155e991 100644
--- a/nixos/modules/services/networking/autossh.nix
+++ b/nixos/modules/services/networking/autossh.nix
@@ -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/bind.nix b/nixos/modules/services/networking/bind.nix
index 7f89cff22329..06af4dbcca4e 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 }
@@ -131,6 +132,7 @@ in
           file = "/var/dns/example.com";
           masters = ["192.168.0.1"];
           slaves = [];
+          extraConfig = "";
         }];
       };
 
@@ -168,7 +170,9 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.bind.enable {
+  config = mkIf cfg.enable {
+
+    networking.resolvconf.useLocalResolver = mkDefault true;
 
     users.users = singleton
       { name = bindUser;
diff --git a/nixos/modules/services/networking/bitcoind.nix b/nixos/modules/services/networking/bitcoind.nix
index e94265564595..d3501636b41d 100644
--- a/nixos/modules/services/networking/bitcoind.nix
+++ b/nixos/modules/services/networking/bitcoind.nix
@@ -28,7 +28,7 @@ let
     "-datadir=${cfg.dataDir}"
     "-pid=${pidFile}"
   ];
-  hexStr = types.strMatching "[0-9a-f]+";
+
   rpcUserOpts = { name, ... }: {
     options = {
       name = mkOption {
diff --git a/nixos/modules/services/networking/charybdis.nix b/nixos/modules/services/networking/charybdis.nix
index 3d02dc8d1375..e3aba063f87b 100644
--- a/nixos/modules/services/networking/charybdis.nix
+++ b/nixos/modules/services/networking/charybdis.nix
@@ -83,6 +83,10 @@ in
         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,12 +97,7 @@ 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}
-        '';
       };
 
     }
diff --git a/nixos/modules/services/networking/ddclient.nix b/nixos/modules/services/networking/ddclient.nix
index 9a2e13e9553c..04ce5ca3a874 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
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index c217ccaa405a..7b2786034552 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -162,7 +162,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
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/dnsmasq.nix b/nixos/modules/services/networking/dnsmasq.nix
index 24d16046c63e..714a5903bff1 100644
--- a/nixos/modules/services/networking/dnsmasq.nix
+++ b/nixos/modules/services/networking/dnsmasq.nix
@@ -79,7 +79,7 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.dnsmasq.enable {
+  config = mkIf cfg.enable {
 
     networking.nameservers =
       optional cfg.resolveLocalQueries "127.0.0.1";
@@ -92,6 +92,15 @@ in
       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/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 7add48308f80..54a5bed2563f 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -30,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}
   '' ;
@@ -69,6 +70,14 @@ 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";
@@ -162,6 +171,7 @@ in
         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/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/keybase.nix b/nixos/modules/services/networking/keybase.nix
index a149f16a84cb..85f52be8a6ac 100644
--- a/nixos/modules/services/networking/keybase.nix
+++ b/nixos/modules/services/networking/keybase.nix
@@ -26,6 +26,7 @@ in {
 
     systemd.user.services.keybase = {
       description = "Keybase service";
+      unitConfig.ConditionUser = "!@system";
       serviceConfig = {
         ExecStart = ''
           ${pkgs.keybase}/bin/keybase service --auto-forked
diff --git a/nixos/modules/services/networking/kresd.nix b/nixos/modules/services/networking/kresd.nix
index ca34ff9df4ef..fc516c01230a 100644
--- a/nixos/modules/services/networking/kresd.nix
+++ b/nixos/modules/services/networking/kresd.nix
@@ -80,8 +80,11 @@ in
         # 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;
+      socketConfig = {
+        ListenDatagram = listenStreams;
+        FreeBind = true;
+        FileDescriptorName = "dns";
+      };
     };
 
     systemd.sockets.kresd-tls = mkIf (cfg.listenTLS != []) rec {
diff --git a/nixos/modules/services/networking/minidlna.nix b/nixos/modules/services/networking/minidlna.nix
index 1858f03cac1f..ed0c1044a570 100644
--- a/nixos/modules/services/networking/minidlna.nix
+++ b/nixos/modules/services/networking/minidlna.nix
@@ -98,16 +98,10 @@ in
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" "local-fs.target" ];
 
-        preStart =
-          ''
-            mkdir -p /var/cache/minidlna
-            chown -R minidlna:minidlna /var/cache/minidlna
-          '';
-
         serviceConfig =
           { User = "minidlna";
             Group = "minidlna";
-            PermissionsStartOnly = true;
+            CacheDirectory = "minidlna";
             RuntimeDirectory = "minidlna";
             PIDFile = "/run/minidlna/pid";
             ExecStart =
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index f1ba7dd4e4f2..551636a33d25 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -1,6 +1,5 @@
 { config, lib, pkgs, ... }:
 
-with pkgs;
 with lib;
 
 let
@@ -12,11 +11,13 @@ let
   # /var/lib/misc is for dnsmasq.leases.
   stateDirs = "/var/lib/NetworkManager /var/lib/dhclient /var/lib/misc";
 
-  configFile = writeText "NetworkManager.conf" ''
+  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 != [])
@@ -64,19 +65,19 @@ 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 = {
@@ -90,11 +91,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,6 +142,16 @@ 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.
         '';
       };
 
@@ -131,15 +160,24 @@ in {
         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
+        default = { inherit (pkgs)
+                            networkmanager modemmanager wpa_supplicant crda
                             networkmanager-openvpn networkmanager-vpnc
                             networkmanager-openconnect networkmanager-fortisslvpn
                             networkmanager-l2tp networkmanager-iodine; };
@@ -219,67 +257,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>.
         '';
       };
 
@@ -421,13 +407,10 @@ in {
       { 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";
       }
-    ] ++ optional (cfg.appendNameservers == [] || cfg.insertNameservers == [])
+    ] ++ optional (cfg.appendNameservers != [] || cfg.insertNameservers != [])
            { source = overrideNameserversScript;
              target = "NetworkManager/dispatcher.d/02overridedns";
            }
@@ -436,11 +419,15 @@ in {
         target = "NetworkManager/dispatcher.d/${dispatcherTypesSubdirMap.${s.type}}03userscript${lib.fixedWidthNumber 4 i}";
         mode = "0544";
       }) cfg.dispatcherScripts
-      ++ optional (dynamicHostsEnabled)
+      ++ optional dynamicHostsEnabled
            { target = "NetworkManager/dnsmasq.d/dyndns.conf";
              text = concatMapStrings (n: ''
                hostsdir=/run/NetworkManager/hostsdirs/${n}
              '') (attrNames cfg.dynamicHosts.hostsDirs);
+           }
+      ++ optional cfg.enableStrongSwan
+           { source = "${pkgs.networkmanager_strongswan}/lib/NetworkManager/VPN/nm-strongswan-service.name";
+             target = "NetworkManager/VPN/nm-strongswan-service.name";
            };
 
     environment.systemPackages = cfg.packages;
@@ -508,7 +495,7 @@ in {
     networking = {
       useDHCP = false;
       # use mkDefault to trigger the assertion about the conflict above
-      wireless.enable = lib.mkDefault false;
+      wireless.enable = mkDefault false;
     };
 
     security.polkit.extraConfig = polkitConf;
diff --git a/nixos/modules/services/networking/quassel.nix b/nixos/modules/services/networking/quassel.nix
index b223a48e0550..b495b3948fb5 100644
--- a/nixos/modules/services/networking/quassel.nix
+++ b/nixos/modules/services/networking/quassel.nix
@@ -104,6 +104,10 @@ in
         gid = config.ids.gids.quassel;
       }];
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' - ${user} - - -"
+    ];
+
     systemd.services.quassel =
       { description = "Quassel IRC client daemon";
 
@@ -111,11 +115,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 +125,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/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/smokeping.nix b/nixos/modules/services/networking/smokeping.nix
index fab3ed5bb39d..c41d0edaf17f 100644
--- a/nixos/modules/services/networking/smokeping.nix
+++ b/nixos/modules/services/networking/smokeping.nix
@@ -101,17 +101,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.'';
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 89f56a726f46..0f9d2420903b 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -4,7 +4,15 @@ with lib;
 
 let
 
-  sshconf = pkgs.runCommand "sshd.conf-validated" { nativeBuildInputs = [ cfgc.package ]; } ''
+  # 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
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index d78a54a3327b..126f5b7b527b 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -291,7 +291,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).
@@ -372,16 +372,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;
     };
 
@@ -403,18 +405,12 @@ in {
           Group = cfg.group;
           ExecStartPre = mkIf (cfg.declarative.cert != null || cfg.declarative.key != null)
             "+${pkgs.writers.writeBash "syncthing-copy-keys" ''
-              mkdir -p ${cfg.configDir}
-              chown ${cfg.user}:${cfg.group} ${cfg.configDir}
-              chmod 700 ${cfg.configDir}
+              install -dm700 -o ${cfg.user} -g ${cfg.group} ${cfg.configDir}
               ${optionalString (cfg.declarative.cert != null) ''
-                cp ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem
-                chown ${cfg.user}:${cfg.group} ${cfg.configDir}/cert.pem
-                chmod 400 ${cfg.configDir}/cert.pem
+                install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.cert} ${cfg.configDir}/cert.pem
               ''}
               ${optionalString (cfg.declarative.key != null) ''
-                cp ${toString cfg.declarative.key} ${cfg.configDir}/key.pem
-                chown ${cfg.user}:${cfg.group} ${cfg.configDir}/key.pem
-                chmod 400 ${cfg.configDir}/key.pem
+                install -Dm400 -o ${cfg.user} -g ${cfg.group} ${toString cfg.declarative.key} ${cfg.configDir}/key.pem
               ''}
             ''}"
           ;
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/thelounge.nix b/nixos/modules/services/networking/thelounge.nix
new file mode 100644
index 000000000000..b1d23372955e
--- /dev/null
+++ b/nixos/modules/services/networking/thelounge.nix
@@ -0,0 +1,75 @@
+{ pkgs, lib, config, ... }:
+
+with lib;
+
+let
+  cfg = config.services.thelounge;
+  dataDir = "/var/lib/thelounge";
+  configJsData = "module.exports = " + builtins.toJSON (
+    { private = cfg.private; port = cfg.port; } // cfg.extraConfig
+  );
+in {
+  options.services.thelounge = {
+    enable = mkEnableOption "The Lounge web IRC client";
+
+    private = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Make your The Lounge instance private. You will need to configure user
+        accounts by using the (<command>thelounge</command>) command or by adding
+        entries in <filename>${dataDir}/users</filename>. You might need to restart
+        The Lounge after making changes to the state directory.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 9000;
+      description = "TCP port to listen on for http connections.";
+    };
+
+    extraConfig = mkOption {
+      default = {};
+      type = types.attrs;
+      example = literalExample ''{
+        reverseProxy = true;
+        defaults = {
+          name = "Your Network";
+          host = "localhost";
+          port = 6697;
+        };
+      }'';
+      description = ''
+        The Lounge's <filename>config.js</filename> contents as attribute set (will be
+        converted to JSON to generate the configuration file).
+
+        The options defined here will be merged to the default configuration file.
+        Note: In case of duplicate configuration, options from <option>extraConfig</option> have priority.
+
+        Documentation: <link xlink:href="https://thelounge.chat/docs/server/configuration" />
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users.thelounge = {
+      description = "thelounge service user";
+      group = "thelounge";
+    };
+    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/unbound.nix b/nixos/modules/services/networking/unbound.nix
index 1a35979ad44c..3cf82e8839bb 100644
--- a/nixos/modules/services/networking/unbound.nix
+++ b/nixos/modules/services/networking/unbound.nix
@@ -101,6 +101,8 @@ in
       isSystemUser = true;
     };
 
+    networking.resolvconf.useLocalResolver = mkDefault true;
+
     systemd.services.unbound = {
       description = "Unbound recursive Domain Name Server";
       after = [ "network.target" ];
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index c82e0af2803d..6239c88b7e41 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"
       '';
 
@@ -177,7 +177,6 @@ in
         ExecStart = "${(removeSuffix "\n" cmd)} start";
         ExecStop = "${(removeSuffix "\n" cmd)} stop";
         User = "unifi";
-        PermissionsStartOnly = true;
         UMask = "0077";
         WorkingDirectory = "${stateDir}";
       };
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index 0bd9edf4a41c..63e59e7c8fac 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -204,6 +204,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
diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix
index 611a51c74ce2..f4988a902685 100644
--- a/nixos/modules/services/networking/zeronet.nix
+++ b/nixos/modules/services/networking/zeronet.nix
@@ -86,20 +86,17 @@ in with lib; {
       '';
     };
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.dataDir}' 750 zeronet zeronet - -"
+      "d '${cfg.logDir}' 750 zeronet zeronet - -"
+    ];
+
     systemd.services.zeronet = {
       description = "zeronet";
       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";
diff --git a/nixos/modules/services/security/sshguard.nix b/nixos/modules/services/security/sshguard.nix
index 3892cd5c72b8..25cec5b5b105 100644
--- a/nixos/modules/services/security/sshguard.nix
+++ b/nixos/modules/services/security/sshguard.nix
@@ -107,8 +107,6 @@ in {
       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
         ${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
       '';
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index 6f4852c3ba1a..abdc0cd78b4d 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 ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${obfs4}/bin/obfs4proxy managed
+      ServerTransportPlugin ${concatStringsSep "," cfg.relay.bridgeTransports} exec ${pkgs.obfs4}/bin/obfs4proxy managed
       ExtORPort auto
       ${optionalString (cfg.relay.role == "private-bridge") ''
         ExtraInfoStatistics 0
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 8e9286b94078..04595fc82fbb 100644
--- a/nixos/modules/services/system/localtime.nix
+++ b/nixos/modules/services/system/localtime.nix
@@ -28,33 +28,16 @@ in {
       };
     };
 
-    # so polkit will pick up the rules
-    environment.systemPackages = [ pkgs.localtime ];
-
-    users.users = [{
-      name = "localtimed";
-      description = "Taskserver user";
-    }];
+    # 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 ];
 
     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 01a5890a7845..48ec4d692e2f 100644
--- a/nixos/modules/services/torrent/deluge.nix
+++ b/nixos/modules/services/torrent/deluge.nix
@@ -118,36 +118,74 @@ in {
             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";
+
         port = mkOption {
-        type = types.port;
+          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 {
 
-    systemd.tmpfiles.rules = [ "d '${configDir}' 0770 deluge deluge" ]
+    # Provide a default set of `extraPackages`.
+    services.deluge.extraPackages = with pkgs; [ unzip gnutar xz p7zip bzip2 ];
+
+    systemd.tmpfiles.rules = [ "d '${configDir}' 0770 ${cfg.user} ${cfg.group}" ]
     ++ optional (cfg.config ? "download_location")
-      "d '${cfg.config.download_location}' 0770 deluge deluge"
+      "d '${cfg.config.download_location}' 0770 ${cfg.user} ${cfg.group}"
     ++ optional (cfg.config ? "torrentfiles_location")
-      "d '${cfg.config.torrentfiles_location}' 0770 deluge deluge"
+      "d '${cfg.config.torrentfiles_location}' 0770 ${cfg.user} ${cfg.group}"
     ++ optional (cfg.config ? "move_completed_path")
-      "d '${cfg.config.move_completed_path}' 0770 deluge deluge";
+      "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 \
@@ -157,8 +195,8 @@ in {
         # 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;
       };
@@ -177,26 +215,37 @@ in {
             --config ${configDir} \
             --port ${toString cfg.web.port}
         '';
-        User = "deluge";
-        Group = "deluge";
+        User = cfg.user;
+        Group = cfg.group;
       };
     };
 
-    networking.firewall = 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));
-    };
+    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 = cfg.dataDir;
-      createHome = true;
-      description = "Deluge Daemon user";
+    users.users = mkIf (cfg.user == "deluge") {
+      deluge = {
+        group = cfg.group;
+        uid = config.ids.uids.deluge;
+        home = cfg.dataDir;
+        createHome = true;
+        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/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix
index 15744d90cc79..cf163271d276 100644
--- a/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ b/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -149,6 +149,17 @@ in
 
     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..020ca8d89dbb 100644
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -117,6 +117,16 @@ in
 
     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..b0019e77ac27 100644
--- a/nixos/modules/services/web-apps/atlassian/jira.nix
+++ b/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -155,6 +155,17 @@ in
 
     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/frab.nix b/nixos/modules/services/web-apps/frab.nix
index fb95e024817c..e885dc69b3c0 100644
--- a/nixos/modules/services/web-apps/frab.nix
+++ b/nixos/modules/services/web-apps/frab.nix
@@ -182,16 +182,16 @@ in
 
     users.groups = [ { name = cfg.group; } ];
 
+    systemd.tmpfiles.rules = [
+      "d '${cfg.statePath}/system/attachments' - ${cfg.user} ${cfg.group} - -"
+    ];
+
     systemd.services.frab = {
       after = [ "network.target" "gitlab.service" ];
       wantedBy = [ "multi-user.target" ];
       environment = frabEnv;
 
       preStart = ''
-        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 +204,6 @@ in
       '';
 
       serviceConfig = {
-        PermissionsStartOnly = true;
         PrivateTmp = true;
         PrivateDevices = true;
         Type = "simple";
@@ -213,6 +212,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/limesurvey.nix b/nixos/modules/services/web-apps/limesurvey.nix
index f9e12e3642ea..5b2f3875aaa9 100644
--- a/nixos/modules/services/web-apps/limesurvey.nix
+++ b/nixos/modules/services/web-apps/limesurvey.nix
@@ -6,12 +6,12 @@ let
   inherit (lib) 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";
 
-  php = pkgs.php;
   pkg = pkgs.limesurvey;
 
   configType = with types; either (either (attrsOf configType) str) (either int bool) // {
@@ -130,7 +130,8 @@ in
         pm.max_requests = 500
       '';
       description = ''
-        Options for LimeSurvey's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+        Options for the LimeSurvey PHP pool. See the documentation on <literal>php-fpm.conf</literal>
+        for details on configuration directives.
       '';
     };
 
@@ -202,7 +203,6 @@ in
     };
 
     services.phpfpm.pools.limesurvey = {
-      phpPackage = php;
       listen = "/run/phpfpm/limesurvey.sock";
       extraConfig = ''
         listen.owner = ${config.services.httpd.user};
@@ -220,8 +220,8 @@ in
       enable = true;
       adminAddr = mkDefault cfg.virtualHost.adminAddr;
       extraModules = [ "proxy_fcgi" ];
-      virtualHosts = [
-        (cfg.virtualHost // {
+      virtualHosts = [ (mkMerge [
+        cfg.virtualHost {
           documentRoot = mkForce "${pkg}/share/limesurvey";
           extraConfig = ''
             Alias "/tmp" "${stateDir}/tmp"
@@ -241,7 +241,7 @@ in
             <Directory "${pkg}/share/limesurvey">
               <FilesMatch "\.php$">
                 <If "-f %{REQUEST_FILENAME}">
-                  SetHandler "proxy:unix:/run/phpfpm/limesurvey.sock|fcgi://localhost/"
+                  SetHandler "proxy:unix:${fpm.listen}|fcgi://localhost/"
                 </If>
               </FilesMatch>
 
@@ -250,8 +250,8 @@ in
               DirectoryIndex index.php
             </Directory>
           '';
-        })
-      ];
+        }
+      ]) ];
     };
 
     systemd.tmpfiles.rules = [
@@ -270,8 +270,8 @@ in
       environment.LIMESURVEY_CONFIG = limesurveyConfig;
       script = ''
         # update or install the database as required
-        ${php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php updatedb || \
-        ${php}/bin/php ${pkg}/share/limesurvey/application/commands/console.php install admin password admin admin@example.com verbose
+        ${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;
diff --git a/nixos/modules/services/web-apps/matomo-doc.xml b/nixos/modules/services/web-apps/matomo-doc.xml
index 021a89be3f63..8485492c51c7 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
diff --git a/nixos/modules/services/web-apps/mediawiki.nix b/nixos/modules/services/web-apps/mediawiki.nix
new file mode 100644
index 000000000000..5bd5977e592b
--- /dev/null
+++ b/nixos/modules/services/web-apps/mediawiki.nix
@@ -0,0 +1,473 @@
+{ config, pkgs, lib, ... }:
+
+let
+
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption;
+  inherit (lib) concatStringsSep literalExample mapAttrsToList optional optionals optionalString types;
+
+  cfg = config.services.mediawiki;
+  fpm = config.services.phpfpm.pools.mediawiki;
+  user = "mediawiki";
+  group = config.services.httpd.group;
+  cacheDir = "/var/cache/mediawiki";
+  stateDir = "/var/lib/mediawiki";
+
+  pkg = pkgs.stdenv.mkDerivation rec {
+    pname = "mediawiki-full";
+    version = src.version;
+    src = cfg.package;
+
+    installPhase = ''
+      mkdir -p $out
+      cp -r * $out/
+
+      rm -rf $out/share/mediawiki/skins/*
+      rm -rf $out/share/mediawiki/extensions/*
+
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+        ln -s ${v} $out/share/mediawiki/skins/${k}
+      '') cfg.skins)}
+
+      ${concatStringsSep "\n" (mapAttrsToList (k: v: ''
+        ln -s ${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.enableSSL 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 ({
+          options = import ../web-servers/apache-httpd/per-server-options.nix {
+            inherit lib;
+            forMainServer = false;
+          };
+        });
+        example = literalExample ''
+          {
+            hostName = "mediawiki.example.org";
+            enableSSL = true;
+            adminAddr = "webmaster@example.org";
+            sslServerCert = "/var/lib/acme/mediawiki.example.org/full.pem";
+            sslServerKey = "/var/lib/acme/mediawiki.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.
+        '';
+      };
+
+      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
+        '';
+        description = ''
+          Options for MediaWiki's 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 = {
+      listen = "/run/phpfpm/mediawiki.sock";
+      extraConfig = ''
+        listen.owner = ${config.services.httpd.user}
+        listen.group = ${config.services.httpd.group}
+        user = ${user}
+        group = ${group}
+
+        env[MEDIAWIKI_CONFIG] = ${mediawikiConfig}
+
+        ${cfg.poolConfig}
+      '';
+    };
+
+    services.httpd = {
+      enable = true;
+      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts = [ (mkMerge [
+        cfg.virtualHost {
+          documentRoot = mkForce "${pkg}/share/mediawiki";
+          extraConfig = ''
+            <Directory "${pkg}/share/mediawiki">
+              <FilesMatch "\.php$">
+                <If "-f %{REQUEST_FILENAME}">
+                  SetHandler "proxy:unix:${fpm.listen}|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;
+
+    environment.systemPackages = [ mediawikiScripts ];
+  };
+}
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
index bb39a5d1d714..a0214a75d93e 100644
--- a/nixos/modules/services/web-apps/nextcloud.nix
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }@args:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -297,8 +297,23 @@ in {
 
       systemd.services = {
         "nextcloud-setup" = let
+          c = cfg.config;
+          writePhpArrary = a: "[${concatMapStringsSep "," (val: ''"${toString val}"'') a}]";
           overrideConfig = pkgs.writeText "nextcloud-config.php" ''
             <?php
+            ${optionalString (c.dbpassFile != null) ''
+              function nix_read_pwd() {
+                $file = "${c.dbpassFile}";
+                if (!file_exists($file)) {
+                  throw new \RuntimeException(sprintf(
+                    "Cannot start Nextcloud, dbpass file %s set by NixOS doesn't exist!",
+                    $file
+                  ));
+                }
+
+                return trim(file_get_contents($file));
+              }
+            ''}
             $CONFIG = [
               'apps_paths' => [
                 [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
@@ -309,19 +324,27 @@ 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)},
             ];
           '';
           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}"'';
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
index d78d866086a6..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,35 +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>
-
-  <para>
-   Right now app installation and configuration is done imperatively in the nextcloud web ui or via the <literal>nextcloud-occ</literal> command line utility.
-   You can activate auto updates for your apps via
-   <literal><link linkend="opt-services.nextcloud.autoUpdateApps.enable">services.nextcloud.autoUpdateApps</link></literal>.
-  </para>
+   and can cause unwanted side-effects!</para>
+  </warning>
  </section>
 </chapter>
diff --git a/nixos/modules/services/web-apps/restya-board.nix b/nixos/modules/services/web-apps/restya-board.nix
index 2e5e0ea6622d..b200a89260ac 100644
--- a/nixos/modules/services/web-apps/restya-board.nix
+++ b/nixos/modules/services/web-apps/restya-board.nix
@@ -178,7 +178,7 @@ in
 
   config = mkIf cfg.enable {
 
-    services.phpfpm.poolConfigs = {
+    services.phpfpm.pools = {
       "${poolName}" = {
         listen = phpfpmSocketName;
         phpOptions = ''
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 08297c7275a4..1bd9de93735d 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 = {
@@ -552,9 +560,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 +575,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 +592,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 +604,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 +631,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 +656,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/wordpress.nix b/nixos/modules/services/web-apps/wordpress.nix
new file mode 100644
index 000000000000..624b0089a037
--- /dev/null
+++ b/nixos/modules/services/web-apps/wordpress.nix
@@ -0,0 +1,371 @@
+{ config, pkgs, lib, ... }:
+
+let
+  inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types;
+  inherit (lib) any attrValues concatMapStringsSep flatten literalExample;
+  inherit (lib) 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');
+    ?>
+  '';
+
+  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 = ''
+            # 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 = 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 = rec {
+          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 ({
+            options = import ../web-servers/apache-httpd/per-server-options.nix {
+              inherit lib;
+              forMainServer = false;
+            };
+          });
+          example = literalExample ''
+            {
+              enableSSL = true;
+              adminAddr = "webmaster@example.org";
+              sslServerCert = "/var/lib/acme/wordpress.example.org/full.pem";
+              sslServerKey = "/var/lib/acme/wordpress.example.org/key.pem";
+            }
+          '';
+          description = ''
+            Apache configuration can be done by adapting <option>services.httpd.virtualHosts</option>.
+          '';
+        };
+
+        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
+          '';
+          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}" {
+        listen = "/run/phpfpm/wordpress-${hostName}.sock";
+        extraConfig = ''
+          listen.owner = ${config.services.httpd.user}
+          listen.group = ${config.services.httpd.group}
+          user = ${user}
+          group = ${group}
+
+          ${cfg.poolConfig}
+        '';
+      }
+    )) eachSite;
+
+    services.httpd = {
+      enable = true;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts = mapAttrsToList (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:/run/phpfpm/wordpress-${hostName}.sock|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 = ''
+            if ! test -e "${stateDir hostName}/secret-keys.php"; then
+              echo "<?php" >> "${stateDir hostName}/secret-keys.php"
+              ${pkgs.curl}/bin/curl -s https://api.wordpress.org/secret-key/1.1/salt/ >> "${stateDir hostName}/secret-keys.php"
+              echo "?>" >> "${stateDir hostName}/secret-keys.php"
+              chmod 440 "${stateDir hostName}/secret-keys.php"
+            fi
+          '';
+
+          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;
+
+  };
+}
diff --git a/nixos/modules/services/web-apps/zabbix.nix b/nixos/modules/services/web-apps/zabbix.nix
new file mode 100644
index 000000000000..4b5334579a99
--- /dev/null
+++ b/nixos/modules/services/web-apps/zabbix.nix
@@ -0,0 +1,225 @@
+{ 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 ({
+          options = import ../web-servers/apache-httpd/per-server-options.nix {
+            inherit lib;
+            forMainServer = false;
+          };
+        });
+        example = {
+          hostName = "zabbix.example.org";
+          enableSSL = true;
+          adminAddr = "webmaster@example.org";
+          sslServerCert = "/var/lib/acme/zabbix.example.org/full.pem";
+          sslServerKey = "/var/lib/acme/zabbix.example.org/key.pem";
+        };
+        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 = 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
+        '';
+        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 = {
+      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
+      '';
+      listen = "/run/phpfpm/zabbix.sock";
+      extraConfig = ''
+        listen.owner = ${config.services.httpd.user};
+        listen.group = ${config.services.httpd.group};
+        user = ${user};
+        group = ${config.services.httpd.group};
+        env[ZABBIX_CONFIG] = ${zabbixConfig}
+        ${cfg.poolConfig}
+      '';
+    };
+
+    services.httpd = {
+      enable = true;
+      adminAddr = mkDefault cfg.virtualHost.adminAddr;
+      extraModules = [ "proxy_fcgi" ];
+      virtualHosts = [ (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.listen}|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 bf99f6c132af..ea9476a7c915 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -21,10 +21,9 @@ let
     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;
+    if cfg.listen == []
+      then defaultListen cfg
+      else cfg.listen;
 
   listenToString = l: "${l.ip}:${toString l.port}";
 
@@ -638,7 +637,7 @@ in
                      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);
+    warnings = map (cfg: "apache-httpd's extraSubservices option is deprecated. Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (lib.filter (cfg: cfg.extraSubservices != []) allHosts);
 
     users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton
       { name = "wwwrun";
@@ -672,7 +671,7 @@ in
 
         wantedBy = [ "multi-user.target" ];
         wants = [ "keys.target" ];
-        after = [ "network.target" "fs.target" "postgresql.service" "keys.target" ];
+        after = [ "network.target" "fs.target" "keys.target" ];
 
         path =
           [ httpd pkgs.coreutils pkgs.gnugrep ]
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/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..536e707137c6 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
@@ -24,14 +24,6 @@ with lib;
     '';
   };
 
-  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 (
           {
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 3dddda138fed..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 [ ! -S /run/mysqld/mysqld.sock ]; 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/lighttpd/cgit.nix b/nixos/modules/services/web-servers/lighttpd/cgit.nix
index 4ec4a5a3359e..9f25dc34f3f0 100644
--- a/nixos/modules/services/web-servers/lighttpd/cgit.nix
+++ b/nixos/modules/services/web-servers/lighttpd/cgit.nix
@@ -42,10 +42,10 @@ in
     configText = mkOption {
       default = "";
       example = ''
-        cache-size=1000
-        scan-path=/srv/git
         source-filter=''${pkgs.cgit}/lib/cgit/filters/syntax-highlighting.py
         about-filter=''${pkgs.cgit}/lib/cgit/filters/about-formatting.sh
+        cache-size=1000
+        scan-path=/srv/git
       '';
       type = types.lines;
       description = ''
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 d6653a65a95a..2b7fcb314041 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -269,17 +269,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}
diff --git a/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/compton.nix
index d4357324c870..c02c9bfd94e8 100644
--- a/nixos/modules/services/x11/compton.nix
+++ b/nixos/modules/services/x11/compton.nix
@@ -7,57 +7,35 @@ let
 
   cfg = config.services.compton;
 
-  literalAttrs = v:
-    if isString v then toString v
-    else if isAttrs v then "{\n"
-      + concatStringsSep "\n" (mapAttrsToList
-        (name: value: "${literalAttrs name} = ${literalAttrs value};")
-        v)
-      + "\n}"
-    else generators.toPretty {} v;
+  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: 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};
-
-      wintypes:
-      ${literalAttrs cfg.wintypes};
-
-      opacity-rule = [
-        ${opacityRules}
-      ];
-
-      # other options
-      backend = ${toJSON cfg.backend};
-      vsync = ${boolToString 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 "compton.mkValueString: unexpected type (v = ${v})";
+      in "${escape [ sep ] k}${sep}${mkValueString v};")
+      attrs);
+
+  configFile = pkgs.writeText "compton.conf" (toConf cfg.settings);
 
 in {
 
@@ -236,23 +214,13 @@ in {
       '';
     };
 
-    package = mkOption {
-      type = types.package;
-      default = pkgs.compton;
-      defaultText = "pkgs.compton";
-      example = literalExample "pkgs.compton";
-      description = ''
-        Compton derivation to use.
-      '';
-    };
-
-    extraOptions = mkOption {
-      type = types.lines;
-      default = "";
-      example = ''
-        unredir-if-possible = true;
-        dbe = true;
-      '';
+    settings = let
+      configTypes = with types; either bool (either int (either 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 = ''
         Additional Compton configuration.
       '';
@@ -260,6 +228,42 @@ in {
   };
 
   config = mkIf cfg.enable {
+    services.compton.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.compton = {
       description = "Compton composite manager";
       wantedBy = [ "graphical-session.target" ];
@@ -271,13 +275,13 @@ in {
       };
 
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/compton --config ${configFile}";
+        ExecStart = "${pkgs.compton}/bin/compton --config ${configFile}";
         RestartSec = 3;
         Restart = "always";
       };
     };
 
-    environment.systemPackages = [ cfg.package ];
+    environment.systemPackages = [ pkgs.compton ];
   };
 
 }
diff --git a/nixos/modules/services/x11/desktop-managers/default.nix b/nixos/modules/services/x11/desktop-managers/default.nix
index 2b1e9169e5f6..671a959cdde1 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
   ];
 
   options = {
diff --git a/nixos/modules/services/x11/desktop-managers/enlightenment.nix b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
index 4da146940648..527e4b18045b 100644
--- a/nixos/modules/services/x11/desktop-managers/enlightenment.nix
+++ b/nixos/modules/services/x11/desktop-managers/enlightenment.nix
@@ -34,7 +34,7 @@ in
       pkgs.gtk2 # To get GTK+'s themes.
       pkgs.tango-icon-theme
 
-      pkgs.gnome2.gnomeicontheme
+      pkgs.gnome2.gnome_icon_theme
       pkgs.xorg.xcursorthemes
     ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index ef6820d33260..cd7502421255 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -154,7 +154,8 @@ in {
     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 ];
+    xdg.portal.enable = true;
+    xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
 
     # If gnome3 is installed, build vim for gtk3 too.
     nixpkgs.config.vim.gui = "gtk3";
@@ -229,7 +230,7 @@ in {
 
     # Use the correct gnome3 packageSet
     networking.networkmanager.basePackages =
-      { inherit (pkgs) networkmanager modemmanager wpa_supplicant;
+      { inherit (pkgs) networkmanager modemmanager wpa_supplicant crda;
         inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
                               networkmanager-openconnect networkmanager-fortisslvpn
                               networkmanager-iodine networkmanager-l2tp; };
diff --git a/nixos/modules/services/x11/desktop-managers/pantheon.nix b/nixos/modules/services/x11/desktop-managers/pantheon.nix
index 9a0f77a856a7..8e1272f3c92b 100644
--- a/nixos/modules/services/x11/desktop-managers/pantheon.nix
+++ b/nixos/modules/services/x11/desktop-managers/pantheon.nix
@@ -102,6 +102,10 @@ in
           # Makes qt applications look less alien
           export QT_QPA_PLATFORMTHEME=gtk3
           export QT_STYLE_OVERRIDE=adwaita
+
+          # Settings from elementary-default-settings
+          export GTK_CSD=1
+          export GTK_MODULES=$GTK_MODULES:pantheon-filechooser-module
       fi
     '';
 
@@ -147,7 +151,7 @@ in
 
     networking.networkmanager.enable = mkDefault true;
     networking.networkmanager.basePackages =
-      { inherit (pkgs) networkmanager modemmanager wpa_supplicant;
+      { inherit (pkgs) networkmanager modemmanager wpa_supplicant crda;
         inherit (pkgs.gnome3) networkmanager-openvpn networkmanager-vpnc
                               networkmanager-openconnect networkmanager-fortisslvpn
                               networkmanager-iodine networkmanager-l2tp; };
@@ -195,7 +199,7 @@ in
       ]);
 
     fonts.fonts = with pkgs; [
-      opensans-ttf
+      open-sans
       roboto-mono
       pantheon.elementary-redacted-script # needed by screenshot-tool
     ];
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index dc8bfc7dc172..94a307ae1007 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -21,6 +21,13 @@ in
         description = "Enable the Plasma 5 (KDE 5) desktop environment.";
       };
 
+      phononBackend = mkOption {
+        type = types.enum [ "gstreamer" "vlc" ];
+        default = "gstreamer";
+        example = "vlc";
+        description = "Phonon audio backend to install.";
+      };
+
       enableQt4Support = mkOption {
         type = types.bool;
         default = true;
@@ -64,8 +71,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,15 +168,17 @@ in
 
           qtvirtualkeyboard
 
-          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 == "gstreamer" && cfg.enableQt4Support) pkgs.phonon-backend-gstreamer
+        ++ lib.optional (cfg.phononBackend == "vlc") libsForQt5.phonon-backend-vlc
+        ++ lib.optional (cfg.phononBackend == "vlc" && cfg.enableQt4Support) pkgs.phonon-backend-vlc
 
         # Optional hardware support features
-        ++ lib.optional config.hardware.bluetooth.enable bluedevil
+        ++ lib.optionals config.hardware.bluetooth.enable [ bluedevil bluez-qt ]
         ++ lib.optional config.networking.networkmanager.enable plasma-nm
         ++ lib.optional config.hardware.pulseaudio.enable plasma-pa
         ++ lib.optional config.powerManagement.enable powerdevil
@@ -224,6 +233,9 @@ in
       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 = ''
         # The KDE icon cache is supposed to update itself
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..232bbf5c55d4
--- /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.string;
+        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.string;
+        default = "";
+        description = ''
+          Screensaver settings, see <literal>man 1 xset</literal> for possible options.
+        '';
+      };
+
+      pointerButtonMap = mkOption {
+        type = types.string;
+        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.string;
+        default = "yes";
+        example = "no";
+        description = "Hide idle mouse pointer.";
+      };
+
+      extraConfig = mkOption {
+        type = types.string;
+        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.extraSessionFilePackages = [
+      pkgs.surf-display
+    ];
+
+    environment.etc."default/surf-display".text = surfDisplayConf;
+  };
+}
diff --git a/nixos/modules/services/x11/desktop-managers/xterm.nix b/nixos/modules/services/x11/desktop-managers/xterm.nix
index f386ebc4d3c1..ea441fbbe715 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;
+  xserverEnabled = config.services.xserver.enable;
 
 in
 
@@ -13,7 +14,8 @@ in
 
     services.xserver.desktopManager.xterm.enable = mkOption {
       type = types.bool;
-      default = true;
+      default = xserverEnabled;
+      defaultText = "config.services.xserver.enable";
       description = "Enable a xterm terminal as a desktop manager.";
     };
 
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index ff4b91923e02..3f1669d08516 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -96,6 +96,14 @@ in
         type = types.bool;
       };
 
+      autoSuspend = mkOption {
+        default = true;
+        description = ''
+          Suspend the machine after inactivity.
+        '';
+        type = types.bool;
+      };
+
     };
 
   };
@@ -176,10 +184,40 @@ in
 
     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.gnome3.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
diff --git a/nixos/modules/services/x11/extra-layouts.nix b/nixos/modules/services/x11/extra-layouts.nix
new file mode 100644
index 000000000000..5523dd2bf023
--- /dev/null
+++ b/nixos/modules/services/x11/extra-layouts.nix
@@ -0,0 +1,165 @@
+{ 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;
+        };
+      };
+
+    });
+
+    services.xserver.xkbDir = "${pkgs.xkb_patched}/etc/X11/xkb";
+
+  };
+
+}
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/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index 58fe702d4969..a0a5e2656852 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -178,7 +178,7 @@ in {
       };
 
       additionalOptions = mkOption {
-        type = types.str;
+        type = types.lines;
         default = "";
         example =
         ''
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 0f057c4ab730..b1a316706976 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 = {};
   };
@@ -243,7 +244,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"
@@ -664,10 +665,9 @@ in
         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 =
           ''
diff --git a/nixos/modules/system/boot/kernel.nix b/nixos/modules/system/boot/kernel.nix
index ab919099d112..ee43fe100238 100644
--- a/nixos/modules/system/boot/kernel.nix
+++ b/nixos/modules/system/boot/kernel.nix
@@ -196,7 +196,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;
 
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/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/systemd-boot/systemd-boot-builder.py b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot-builder.py
index ebe37ca10a2d..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
@@ -133,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$")
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 3e39dc5dd396..22d459ceb04f 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -22,6 +22,8 @@ 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;
@@ -57,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";
 
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 9dcb683eff43..b5c8d5241a3d 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -438,6 +438,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")
         ];
diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix
index 882db9130ea8..f1aa9064bef4 100644
--- a/nixos/modules/system/boot/networkd.nix
+++ b/nixos/modules/system/boot/networkd.nix
@@ -203,7 +203,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"
diff --git a/nixos/modules/system/boot/resolved.nix b/nixos/modules/system/boot/resolved.nix
index 5c66cf4a6e6e..3ea96f8e4645 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
 {
 
@@ -126,6 +130,12 @@ in
 
   config = mkIf cfg.enable {
 
+    assertions = [
+      { assertion = !config.networking.useHostResolvConf;
+        message = "Using host resolv.conf is not supported with systemd-resolved";
+      }
+    ];
+
     systemd.additionalUpstreamSystemUnits = [
       "systemd-resolved.service"
     ];
@@ -135,21 +145,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-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-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index 63f974b704f3..ee4ae845a7d5 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"
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 18ee2ef1b8f5..cf35504e5182 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -186,6 +186,9 @@ let
       "sockets.target"
       "sound.target"
       "systemd-exit.service"
+      "systemd-tmpfiles-clean.service"
+      "systemd-tmpfiles-clean.timer"
+      "systemd-tmpfiles-setup.service"
       "timers.target"
     ];
 
@@ -940,7 +943,6 @@ in
     # Don't bother with certain units in containers.
     systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
     systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
-
   };
 
   # FIXME: Remove these eventually.
@@ -949,5 +951,4 @@ in
       (mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ])
       (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ])
     ];
-
 }
diff --git a/nixos/modules/system/boot/timesyncd.nix b/nixos/modules/system/boot/timesyncd.nix
index 8d8bfe5900a9..8282cdd6f3aa 100644
--- a/nixos/modules/system/boot/timesyncd.nix
+++ b/nixos/modules/system/boot/timesyncd.nix
@@ -40,6 +40,15 @@ with lib;
     users.users.systemd-timesync.uid = config.ids.uids.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..18753ae0c1ae 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 = [ pkgs.coreutils pkgs.gnutar pkgs.xz.bin pkgs.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/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 93bbd141284d..ac06b6caee30 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,6 +11,7 @@ let
   cfgSnapshots = config.services.zfs.autoSnapshot;
   cfgSnapFlags = cfgSnapshots.flags;
   cfgScrub = config.services.zfs.autoScrub;
+  cfgTrim = config.services.zfs.trim;
 
   inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems;
   inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems;
@@ -179,10 +178,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
@@ -269,14 +267,26 @@ in
       };
     };
 
-    services.zfs.autoScrub = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
+    services.zfs.trim = {
+      enable = mkEnableOption "Enables periodic TRIM on all ZFS pools.";
+
+      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";
@@ -314,10 +324,6 @@ 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;
@@ -540,5 +546,17 @@ in
         };
       };
     })
+
+    (mkIf cfgTrim.enable {
+      systemd.services.zpool-trim = {
+        description = "ZFS pools trim";
+        after = [ "zfs-import.target" ];
+        path = [ packages.zfsUser ];
+        startAt = cfgTrim.interval;
+        script = ''
+          zpool list -H -o name | xargs -n1 zpool trim
+        '';
+      };
+    })
   ];
 }
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index c12ada7a030a..2b8a7944dc36 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) ''
diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix
index 2318bdd1d582..857aaf1e6e30 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 "both" else "no";
 
   slaves =
     concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds))
@@ -59,7 +59,14 @@ in
           in {
             DHCP = override (dhcpStr cfg.useDHCP);
           } // optionalAttrs (gateway != [ ]) {
-            gateway = override gateway;
+            routes = override [
+              {
+                routeConfig = {
+                  Gateway = gateway;
+                  GatewayOnLink = false;
+                };
+              }
+            ];
           } // optionalAttrs (domains != [ ]) {
             domains = override domains;
           };
diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix
index f9b0eb330bf8..c75d7cbc408b 100644
--- a/nixos/modules/tasks/network-interfaces.nix
+++ b/nixos/modules/tasks/network-interfaces.nix
@@ -1017,7 +1017,6 @@ in
         pkgs.iproute
         pkgs.iputils
         pkgs.nettools
-        pkgs.openresolv
       ]
       ++ optionals config.networking.wireless.enable [
         pkgs.wirelesstools # FIXME: obsolete?
@@ -1087,7 +1086,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.procps}/bin/sysctl net.ipv6.conf.%k.use_tempaddr=2"
+        '';
+      })
+      (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.${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";
@@ -1161,8 +1177,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/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix
index ed4cfa7805e2..1a11d9ce7c26 100644
--- a/nixos/modules/testing/test-instrumentation.nix
+++ b/nixos/modules/testing/test-instrumentation.nix
@@ -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/anbox.nix b/nixos/modules/virtualisation/anbox.nix
index 9cb89e7b2926..c63b971ead02 100644
--- a/nixos/modules/virtualisation/anbox.nix
+++ b/nixos/modules/virtualisation/anbox.nix
@@ -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/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/containers.nix b/nixos/modules/virtualisation/containers.nix
index 97243e3304ee..c10e2b162ccc 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -445,7 +445,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 +465,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 ];
diff --git a/nixos/modules/virtualisation/docker-containers.nix b/nixos/modules/virtualisation/docker-containers.nix
index 3e882a1383ff..59b0943f591f 100644
--- a/nixos/modules/virtualisation/docker-containers.nix
+++ b/nixos/modules/virtualisation/docker-containers.nix
@@ -5,7 +5,7 @@ let
   cfg = config.docker-containers;
 
   dockerContainer =
-    { name, config, ... }: {
+    { ... }: {
 
       options = {
 
diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix
index 2ee22b80f2f5..5c59188b68b2 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 = [
diff --git a/nixos/modules/virtualisation/kvmgt.nix b/nixos/modules/virtualisation/kvmgt.nix
index 132815a0ad63..289e26e17035 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;
       description = "UUID of VGPU device. You can generate one with <package>libossp_uuid</package>.";
     };
   };
+
 in {
   options = {
     virtualisation.kvmgt = {
@@ -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..394b4ce56563 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -23,14 +23,16 @@ let
     ''}
     ${cfg.qemuVerbatimConfig}
   '';
+  dirName = "libvirt";
+  subDirs = list: [ dirName ] ++ map (e: "${dirName}/${e}") list;
 
 in {
 
   ###### interface
 
-  options = {
+  options.virtualisation.libvirtd = {
 
-    virtualisation.libvirtd.enable = mkOption {
+    enable = mkOption {
       type = types.bool;
       default = false;
       description = ''
@@ -41,7 +43,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.qemuPackage = mkOption {
+    qemuPackage = mkOption {
       type = types.package;
       default = pkgs.qemu;
       description = ''
@@ -51,7 +53,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.extraConfig = mkOption {
+    extraConfig = mkOption {
       type = types.lines;
       default = "";
       description = ''
@@ -60,7 +62,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.qemuRunAsRoot = mkOption {
+    qemuRunAsRoot = mkOption {
       type = types.bool;
       default = true;
       description = ''
@@ -72,7 +74,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.qemuVerbatimConfig = mkOption {
+    qemuVerbatimConfig = mkOption {
       type = types.lines;
       default = ''
         namespaces = []
@@ -84,7 +86,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.qemuOvmf = mkOption {
+    qemuOvmf = mkOption {
       type = types.bool;
       default = true;
       description = ''
@@ -93,7 +95,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.extraOptions = mkOption {
+    extraOptions = mkOption {
       type = types.listOf types.str;
       default = [ ];
       example = [ "--verbose" ];
@@ -102,7 +104,7 @@ in {
       '';
     };
 
-    virtualisation.libvirtd.onShutdown = mkOption {
+    onShutdown = mkOption {
       type = types.enum ["shutdown" "suspend" ];
       default = "suspend";
       description = ''
@@ -113,6 +115,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 +130,12 @@ 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 cfg.qemuPackage ];
+    };
 
     boot.kernelModules = [ "tun" ];
 
@@ -134,30 +149,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 +169,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";
@@ -203,7 +227,7 @@ in {
     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 +239,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/openvswitch.nix b/nixos/modules/virtualisation/openvswitch.nix
index 47e07e7432cb..edec37402308 100644
--- a/nixos/modules/virtualisation/openvswitch.nix
+++ b/nixos/modules/virtualisation/openvswitch.nix
@@ -54,7 +54,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
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/release-small.nix b/nixos/release-small.nix
index b5b09dc38d06..84af457801a1 100644
--- a/nixos/release-small.nix
+++ b/nixos/release-small.nix
@@ -11,7 +11,7 @@ let
 
   nixpkgsSrc = nixpkgs; # urgh
 
-  pkgs = import ./.. {};
+  pkgs = import ./.. { system = "x86_64-linux"; };
 
   lib = pkgs.lib;
 
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 8b38e802e62e..25dd9f3f4283 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -36,6 +36,7 @@ in
   borgbackup = handleTest ./borgbackup.nix {};
   buildbot = handleTest ./buildbot.nix {};
   cadvisor = handleTestOn ["x86_64-linux"] ./cadvisor.nix {};
+  cassandra = handleTest ./cassandra.nix {};
   ceph = handleTestOn ["x86_64-linux"] ./ceph.nix {};
   certmgr = handleTest ./certmgr.nix {};
   cfssl = handleTestOn ["x86_64-linux"] ./cfssl.nix {};
@@ -76,12 +77,15 @@ in
   elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
   env = handleTest ./env.nix {};
   etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
+  etcd-cluster = handleTestOn ["x86_64-linux"] ./etcd-cluster.nix {};
   ferm = handleTest ./ferm.nix {};
   firefox = handleTest ./firefox.nix {};
   firewall = handleTest ./firewall.nix {};
   fish = handleTest ./fish.nix {};
   flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
   flatpak = handleTest ./flatpak.nix {};
+  flatpak-builder = handleTest ./flatpak-builder.nix {};
+  fluentd = handleTest ./fluentd.nix {};
   fsck = handleTest ./fsck.nix {};
   fwupd = handleTestOn ["x86_64-linux"] ./fwupd.nix {}; # libsmbios is unsupported on aarch64
   gdk-pixbuf = handleTest ./gdk-pixbuf.nix {};
@@ -97,6 +101,7 @@ in
   graphene = handleTest ./graphene.nix {};
   grafana = handleTest ./grafana.nix {};
   graphite = handleTest ./graphite.nix {};
+  graylog = handleTest ./graylog.nix {};
   hadoop.hdfs = handleTestOn [ "x86_64-linux" ] ./hadoop/hdfs.nix {};
   hadoop.yarn = handleTestOn [ "x86_64-linux" ] ./hadoop/yarn.nix {};
   handbrake = handleTestOn ["x86_64-linux"] ./handbrake.nix {};
@@ -137,10 +142,12 @@ in
   #lightdm = handleTest ./lightdm.nix {};
   limesurvey = handleTest ./limesurvey.nix {};
   login = handleTest ./login.nix {};
+  loki = handleTest ./loki.nix {};
   #logstash = handleTest ./logstash.nix {};
   mailcatcher = handleTest ./mailcatcher.nix {};
   mathics = handleTest ./mathics.nix {};
   matrix-synapse = handleTest ./matrix-synapse.nix {};
+  mediawiki = handleTest ./mediawiki.nix {};
   memcached = handleTest ./memcached.nix {};
   mesos = handleTest ./mesos.nix {};
   miniflux = handleTest ./miniflux.nix {};
@@ -176,6 +183,7 @@ in
   nginx = handleTest ./nginx.nix {};
   nginx-sso = handleTest ./nginx-sso.nix {};
   nix-ssh-serve = handleTest ./nix-ssh-serve.nix {};
+  nixos-generate-config = handleTest ./nixos-generate-config.nix {};
   novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
   nsd = handleTest ./nsd.nix {};
   nzbget = handleTest ./nzbget.nix {};
@@ -239,11 +247,12 @@ in
   syncthing-relay = handleTest ./syncthing-relay.nix {};
   systemd = handleTest ./systemd.nix {};
   systemd-confinement = handleTest ./systemd-confinement.nix {};
+  systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
   pdns-recursor = handleTest ./pdns-recursor.nix {};
   taskserver = handleTest ./taskserver.nix {};
   telegraf = handleTest ./telegraf.nix {};
+  tiddlywiki = handleTest ./tiddlywiki.nix {};
   tinydns = handleTest ./tinydns.nix {};
-  tomcat = handleTest ./tomcat.nix {};
   tor = handleTest ./tor.nix {};
   transmission = handleTest ./transmission.nix {};
   udisks2 = handleTest ./udisks2.nix {};
diff --git a/nixos/tests/avahi.nix b/nixos/tests/avahi.nix
index 56b21a401551..ae4f54d5266a 100644
--- a/nixos/tests/avahi.nix
+++ b/nixos/tests/avahi.nix
@@ -15,6 +15,7 @@ import ./make-test.nix ({ pkgs, ... } : {
         publish.enable = true;
         publish.userServices = true;
         publish.workstation = true;
+        extraServiceFiles.ssh = "${pkgs.avahi}/etc/avahi/services/ssh.service";
       };
     };
   in {
@@ -56,5 +57,11 @@ import ./make-test.nix ({ pkgs, ... } : {
        $one->succeed("getent hosts two.local >&2");
        $two->succeed("getent hosts one.local >&2");
        $two->succeed("getent hosts two.local >&2");
+
+       # extra service definitions
+       $one->succeed("avahi-browse -r -t _ssh._tcp | tee out >&2");
+       $one->succeed("test `wc -l < out` -gt 0");
+       $two->succeed("avahi-browse -r -t _ssh._tcp | tee out >&2");
+       $two->succeed("test `wc -l < out` -gt 0");
     '';
 })
diff --git a/nixos/tests/boot.nix b/nixos/tests/boot.nix
index c9bb1e77c6d0..57d8006d7ac3 100644
--- a/nixos/tests/boot.nix
+++ b/nixos/tests/boot.nix
@@ -17,46 +17,33 @@ let
         ];
     }).config.system.build.isoImage;
 
-  makeBootTest = name: machineConfig:
-    makeTest {
-      inherit iso;
-      name = "boot-" + name;
-      nodes = { };
-      testScript =
-        ''
-          my $machine = createMachine({ ${machineConfig}, qemuFlags => '-m 768' });
-          $machine->start;
-          $machine->waitForUnit("multi-user.target");
-          $machine->succeed("nix verify -r --no-trust /run/current-system");
+  perlAttrs = params: "{ ${concatStringsSep ", " (mapAttrsToList (name: param: "${name} => ${builtins.toJSON param}") params)} }";
 
-          # Test whether the channel got installed correctly.
-          $machine->succeed("nix-instantiate --dry-run '<nixpkgs>' -A hello");
-          $machine->succeed("nix-env --dry-run -iA nixos.procps");
-
-          $machine->shutdown;
-        '';
-    };
-in {
-
-    biosCdrom = makeBootTest "bios-cdrom" ''
-        cdrom => glob("${iso}/iso/*.iso")
-      '';
-
-    biosUsb = makeBootTest "bios-usb" ''
-        usb => glob("${iso}/iso/*.iso")
-      '';
+  makeBootTest = name: extraConfig:
+    let
+      machineConfig = perlAttrs ({ qemuFlags = "-m 768"; } // extraConfig);
+    in
+      makeTest {
+        inherit iso;
+        name = "boot-" + name;
+        nodes = { };
+        testScript =
+          ''
+            my $machine = createMachine(${machineConfig});
+            $machine->start;
+            $machine->waitForUnit("multi-user.target");
+            $machine->succeed("nix verify -r --no-trust /run/current-system");
 
-    uefiCdrom = makeBootTest "uefi-cdrom" ''
-        cdrom => glob("${iso}/iso/*.iso"),
-        bios => '${pkgs.OVMF.fd}/FV/OVMF.fd'
-      '';
+            # Test whether the channel got installed correctly.
+            $machine->succeed("nix-instantiate --dry-run '<nixpkgs>' -A hello");
+            $machine->succeed("nix-env --dry-run -iA nixos.procps");
 
-    uefiUsb = makeBootTest "uefi-usb" ''
-        usb => glob("${iso}/iso/*.iso"),
-        bios => '${pkgs.OVMF.fd}/FV/OVMF.fd'
-      '';
+            $machine->shutdown;
+          '';
+      };
 
-    netboot = let
+  makeNetbootTest = name: extraConfig:
+    let
       config = (import ../lib/eval-config.nix {
           inherit system;
           modules =
@@ -65,35 +52,55 @@ in {
               { key = "serial"; }
             ];
         }).config;
-      ipxeScriptDir = pkgs.writeTextFile {
-        name = "ipxeScriptDir";
-        text = ''
-          #!ipxe
-          dhcp
-          kernel bzImage init=${config.system.build.toplevel}/init ${toString config.boot.kernelParams} console=ttyS0
-          initrd initrd
-          boot
-        '';
-        destination = "/boot.ipxe";
-      };
       ipxeBootDir = pkgs.symlinkJoin {
         name = "ipxeBootDir";
         paths = [
           config.system.build.netbootRamdisk
           config.system.build.kernel
-          ipxeScriptDir
+          config.system.build.netbootIpxeScript
         ];
       };
+      machineConfig = perlAttrs ({
+        qemuFlags = "-boot order=n -m 2000";
+        netBackendArgs = "tftp=${ipxeBootDir},bootfile=netboot.ipxe";
+      } // extraConfig);
     in
       makeTest {
-        name = "boot-netboot";
+        name = "boot-netboot-" + name;
         nodes = { };
         testScript =
           ''
-            my $machine = createMachine({ qemuFlags => '-boot order=n -net nic,model=e1000 -net user,tftp=${ipxeBootDir}/,bootfile=boot.ipxe -m 2000M' });
+            my $machine = createMachine(${machineConfig});
             $machine->start;
             $machine->waitForUnit("multi-user.target");
             $machine->shutdown;
           '';
       };
+in {
+
+    biosCdrom = makeBootTest "bios-cdrom" {
+      cdrom = "${iso}/iso/${iso.isoName}";
+    };
+
+    biosUsb = makeBootTest "bios-usb" {
+      usb = "${iso}/iso/${iso.isoName}";
+    };
+
+    uefiCdrom = makeBootTest "uefi-cdrom" {
+      cdrom = "${iso}/iso/${iso.isoName}";
+      bios = "${pkgs.OVMF.fd}/FV/OVMF.fd";
+    };
+
+    uefiUsb = makeBootTest "uefi-usb" {
+      usb = "${iso}/iso/${iso.isoName}";
+      bios = "${pkgs.OVMF.fd}/FV/OVMF.fd";
+    };
+
+    biosNetboot = makeNetbootTest "bios" {};
+
+    uefiNetboot = makeNetbootTest "uefi" {
+      bios = "${pkgs.OVMF.fd}/FV/OVMF.fd";
+      # Custom ROM is needed for EFI PXE boot. I failed to understand exactly why, because QEMU should still use iPXE for EFI.
+      netFrontendArgs = "romfile=${pkgs.ipxe}/ipxe.efirom";
+    };
 }
diff --git a/nixos/tests/cassandra.nix b/nixos/tests/cassandra.nix
index 60d0c6d76068..c55733c9be7b 100644
--- a/nixos/tests/cassandra.nix
+++ b/nixos/tests/cassandra.nix
@@ -1,71 +1,117 @@
-import ./make-test.nix ({ pkgs, ...}:
+import ./make-test.nix ({ pkgs, lib, ... }:
 let
   # Change this to test a different version of Cassandra:
   testPackage = pkgs.cassandra;
-  cassandraCfg = 
+  clusterName = "NixOS Automated-Test Cluster";
+
+  testRemoteAuth = lib.versionAtLeast testPackage.version "3.11";
+  jmxRoles = [{ username = "me"; password = "password"; }];
+  jmxRolesFile = ./cassandra-jmx-roles;
+  jmxAuthArgs = "-u ${(builtins.elemAt jmxRoles 0).username} -pw ${(builtins.elemAt jmxRoles 0).password}";
+  jmxPort = 7200;  # Non-standard port so it doesn't accidentally work
+
+  # Would usually be assigned to 512M
+  numMaxHeapSize = "400";
+  getHeapLimitCommand = ''
+    nodetool info -p ${toString jmxPort} | grep "^Heap Memory" | awk \'{print $NF}\'
+  '';
+  checkHeapLimitCommand = ''
+    [ 1 -eq "$(echo "$(${getHeapLimitCommand}) < ${numMaxHeapSize}" | ${pkgs.bc}/bin/bc)" ]
+  '';
+
+  cassandraCfg = ipAddress:
     { enable = true;
-      listenAddress = null;
-      listenInterface = "eth1";
-      rpcAddress = null;
-      rpcInterface = "eth1";
-      extraConfig =
-        { start_native_transport = true;
-          seed_provider =
-            [{ class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
-               parameters = [ { seeds = "cass0"; } ];
-            }];
-        };
+      inherit clusterName;
+      listenAddress = ipAddress;
+      rpcAddress = ipAddress;
+      seedAddresses = [ "192.168.1.1" ];
       package = testPackage;
+      maxHeapSize = "${numMaxHeapSize}M";
+      heapNewSize = "100M";
+      inherit jmxPort;
     };
-  nodeCfg = extra: {pkgs, config, ...}:
-    { environment.systemPackages = [ testPackage ];
-      networking.firewall.enable = false;
-      services.cassandra = cassandraCfg // extra;
-      virtualisation.memorySize = 1024;
+  nodeCfg = ipAddress: extra: {pkgs, config, ...}: rec {
+    environment.systemPackages = [ testPackage ];
+    networking = {
+      firewall.allowedTCPPorts = [ 7000 9042 services.cassandra.jmxPort ];
+      useDHCP = false;
+      interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
+        { address = ipAddress; prefixLength = 24; }
+      ];
     };
+    services.cassandra = cassandraCfg ipAddress // extra;
+    virtualisation.memorySize = 1024;
+  };
 in
 {
   name = "cassandra-ci";
 
   nodes = {
-    cass0 = nodeCfg {};
-    cass1 = nodeCfg {};
-    cass2 = nodeCfg { jvmOpts = [ "-Dcassandra.replace_address=cass1" ]; };
+    cass0 = nodeCfg "192.168.1.1" {};
+    cass1 = nodeCfg "192.168.1.2" (lib.optionalAttrs testRemoteAuth { inherit jmxRoles; remoteJmx = true; });
+    cass2 = nodeCfg "192.168.1.3" { jvmOpts = [ "-Dcassandra.replace_address=cass1" ]; };
   };
 
-  testScript = ''
-    subtest "timers exist", sub {
+  testScript = let
+    jmxPortS = toString jmxPort;
+  in ''
+    # Check configuration
+    subtest "Timers exist", sub {
       $cass0->succeed("systemctl list-timers | grep cassandra-full-repair.timer");
       $cass0->succeed("systemctl list-timers | grep cassandra-incremental-repair.timer");
     };
-    subtest "can connect via cqlsh", sub {
+    subtest "Can connect via cqlsh", sub {
       $cass0->waitForUnit("cassandra.service");
       $cass0->waitUntilSucceeds("nc -z cass0 9042");
       $cass0->succeed("echo 'show version;' | cqlsh cass0");
     };
-    subtest "nodetool is operational", sub {
+    subtest "Nodetool is operational", sub {
       $cass0->waitForUnit("cassandra.service");
-      $cass0->waitUntilSucceeds("nc -z localhost 7199");
-      $cass0->succeed("nodetool status --resolve-ip | egrep '^UN[[:space:]]+cass0'");
+      $cass0->waitUntilSucceeds("nc -z localhost ${jmxPortS}");
+      $cass0->succeed("nodetool status -p ${jmxPortS} --resolve-ip | egrep '^UN[[:space:]]+cass0'");
     };
-    subtest "bring up cluster", sub {
+    subtest "Cluster name was set", sub {
+      $cass0->waitForUnit("cassandra.service");
+      $cass0->waitUntilSucceeds("nc -z localhost ${jmxPortS}");
+      $cass0->waitUntilSucceeds("nodetool describecluster -p ${jmxPortS} | grep 'Name: ${clusterName}'");
+    };
+    subtest "Heap limit set correctly", sub {
+      # Nodetool takes a while until it can display info
+      $cass0->waitUntilSucceeds('nodetool info -p ${jmxPortS}');
+      $cass0->succeed('${checkHeapLimitCommand}');
+    };
+
+    # Check cluster interaction
+    subtest "Bring up cluster", sub {
       $cass1->waitForUnit("cassandra.service");
-      $cass1->waitUntilSucceeds("nodetool status | egrep -c '^UN' | grep 2");
-      $cass0->succeed("nodetool status --resolve-ip | egrep '^UN[[:space:]]+cass1'");
+      $cass1->waitUntilSucceeds("nodetool -p ${jmxPortS} ${jmxAuthArgs} status | egrep -c '^UN' | grep 2");
+      $cass0->succeed("nodetool status -p ${jmxPortS} --resolve-ip | egrep '^UN[[:space:]]+cass1'");
+    };
+  '' + lib.optionalString testRemoteAuth ''
+    subtest "Remote authenticated jmx", sub {
+      # Doesn't work if not enabled
+      $cass0->waitUntilSucceeds("nc -z localhost ${jmxPortS}");
+      $cass1->fail("nc -z 192.168.1.1 ${toString jmxPort}");
+      $cass1->fail("nodetool -p ${jmxPortS} -h 192.168.1.1 status");
+
+      # Works if enabled
+      $cass1->waitUntilSucceeds("nc -z localhost ${toString jmxPort}");
+      $cass0->succeed("nodetool -p ${jmxPortS} -h 192.168.1.2 ${jmxAuthArgs} status");
     };
-    subtest "break and fix node", sub {
+  '' + ''
+    subtest "Break and fix node", sub {
       $cass1->block;
-      $cass0->waitUntilSucceeds("nodetool status --resolve-ip | egrep -c '^DN[[:space:]]+cass1'");
-      $cass0->succeed("nodetool status | egrep -c '^UN'  | grep 1");
+      $cass0->waitUntilSucceeds("nodetool status -p ${jmxPortS} --resolve-ip | egrep -c '^DN[[:space:]]+cass1'");
+      $cass0->succeed("nodetool status -p ${jmxPortS} | egrep -c '^UN'  | grep 1");
       $cass1->unblock;
-      $cass1->waitUntilSucceeds("nodetool status | egrep -c '^UN'  | grep 2");
-      $cass0->succeed("nodetool status | egrep -c '^UN'  | grep 2");
+      $cass1->waitUntilSucceeds("nodetool -p ${jmxPortS} ${jmxAuthArgs} status | egrep -c '^UN'  | grep 2");
+      $cass0->succeed("nodetool status -p ${jmxPortS} | egrep -c '^UN'  | grep 2");
     };
-    subtest "replace crashed node", sub {
+    subtest "Replace crashed node", sub {
       $cass1->crash;
       $cass2->waitForUnit("cassandra.service");
-      $cass0->waitUntilFails("nodetool status --resolve-ip | egrep '^UN[[:space:]]+cass1'");
-      $cass0->waitUntilSucceeds("nodetool status --resolve-ip | egrep '^UN[[:space:]]+cass2'");
+      $cass0->waitUntilFails("nodetool status -p ${jmxPortS} --resolve-ip | egrep '^UN[[:space:]]+cass1'");
+      $cass0->waitUntilSucceeds("nodetool status -p ${jmxPortS} --resolve-ip | egrep '^UN[[:space:]]+cass2'");
     };
   '';
 })
diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix
index 0c101037aa7f..2e7e4b2f1d69 100644
--- a/nixos/tests/containers-imperative.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -35,7 +35,17 @@ import ./make-test.nix ({ pkgs, ...} : {
       ];
     };
 
-  testScript =
+  testScript = let
+    tmpfilesContainerConfig = pkgs.writeText "container-config-tmpfiles" ''
+      {
+        systemd.tmpfiles.rules = [ "d /foo - - - - -" ];
+        systemd.services.foo = {
+          serviceConfig.Type = "oneshot";
+          script = "ls -al /foo";
+          wantedBy = [ "multi-user.target" ];
+        };
+      }
+    ''; in
     ''
       # Make sure we have a NixOS tree (required by ‘nixos-container create’).
       $machine->succeed("PAGER=cat nix-env -qa -A nixos.hello >&2");
@@ -93,6 +103,15 @@ import ./make-test.nix ({ pkgs, ...} : {
       $machine->succeed("nixos-container stop $id1");
       $machine->succeed("nixos-container start $id1");
 
+      # Ensure tmpfiles are present
+      $machine->log("creating container tmpfiles");
+      $machine->succeed("nixos-container create tmpfiles --config-file ${tmpfilesContainerConfig}");
+      $machine->log("created, starting…");
+      $machine->succeed("nixos-container start tmpfiles");
+      $machine->log("done starting, investigating…");
+      $machine->succeed("echo \$(nixos-container run tmpfiles -- systemctl is-active foo.service) | grep -q active;");
+      $machine->succeed("nixos-container destroy tmpfiles");
+
       # Execute commands via the root shell.
       $machine->succeed("nixos-container run $id1 -- uname") =~ /Linux/ or die;
 
diff --git a/nixos/tests/deluge.nix b/nixos/tests/deluge.nix
index 22ad84e7bff1..b58030409b5c 100644
--- a/nixos/tests/deluge.nix
+++ b/nixos/tests/deluge.nix
@@ -8,9 +8,11 @@ import ./make-test.nix ({ pkgs, ...} : {
     simple = {
       services.deluge = {
         enable = true;
-        web.enable = true;
+        web = {
+          enable = true;
+          openFirewall = true;
+        };
       };
-      networking.firewall.allowedTCPPorts = [ 8112 ];
     };
 
     declarative =
diff --git a/nixos/tests/etcd-cluster.nix b/nixos/tests/etcd-cluster.nix
index 3c4de5950a79..43fde7d59205 100644
--- a/nixos/tests/etcd-cluster.nix
+++ b/nixos/tests/etcd-cluster.nix
@@ -140,7 +140,7 @@ in {
     };
 
     subtest "should add another member", sub {
-      $node1->succeed("etcdctl member add node3 https://node3:2380");
+      $node1->waitUntilSucceeds("etcdctl member add node3 https://node3:2380");
       $node3->start();
       $node3->waitForUnit("etcd.service");
       $node3->waitUntilSucceeds("etcdctl member list | grep 'node3'");
diff --git a/nixos/tests/flatpak-builder.nix b/nixos/tests/flatpak-builder.nix
new file mode 100644
index 000000000000..49b97e8ca99e
--- /dev/null
+++ b/nixos/tests/flatpak-builder.nix
@@ -0,0 +1,20 @@
+# run installed tests
+import ./make-test.nix ({ pkgs, ... }:
+
+{
+  name = "flatpak-builder";
+  meta = {
+    maintainers = pkgs.flatpak-builder.meta.maintainers;
+  };
+
+  machine = { pkgs, ... }: {
+    services.flatpak.enable = true;
+    xdg.portal.enable = true;
+    environment.systemPackages = with pkgs; [ gnome-desktop-testing flatpak-builder ] ++ flatpak-builder.installedTestsDependencies;
+    virtualisation.diskSize = 2048;
+  };
+
+  testScript = ''
+    $machine->succeed("gnome-desktop-testing-runner -d '${pkgs.flatpak-builder.installedTests}/share' --timeout 3600");
+  '';
+})
diff --git a/nixos/tests/fluentd.nix b/nixos/tests/fluentd.nix
new file mode 100644
index 000000000000..e5c4c3d21631
--- /dev/null
+++ b/nixos/tests/fluentd.nix
@@ -0,0 +1,46 @@
+import ./make-test.nix ({ pkgs, lib, ... }: {
+  name = "fluentd";
+
+  machine = { pkgs, ... }: {
+    services.fluentd = {
+      enable = true;
+      config = ''
+        <source>
+          @type http
+          port 9880
+        </source>
+
+        <match **>
+          type copy
+          <store>
+            @type file
+            format json
+            path /tmp/fluentd
+            symlink_path /tmp/current-log
+          </store>
+          <store>
+            @type stdout
+          </store>
+        </match>
+      '';
+    };
+  };
+
+  testScript = let
+    testMessage = "an example log message";
+
+    payload = pkgs.writeText "test-message.json" (builtins.toJSON {
+      inherit testMessage;
+    });
+  in ''
+    $machine->start;
+    $machine->waitForUnit('fluentd.service');
+    $machine->waitForOpenPort(9880);
+
+    $machine->succeed("curl -fsSL -X POST -H 'Content-type: application/json' -d @${payload} http://localhost:9880/test.tag");
+
+    $machine->succeed("systemctl stop fluentd"); # blocking flush
+
+    $machine->succeed("grep '${testMessage}' /tmp/current-log");
+  '';
+})
diff --git a/nixos/tests/gdk-pixbuf.nix b/nixos/tests/gdk-pixbuf.nix
index 005c5111da2b..9a62b593f46d 100644
--- a/nixos/tests/gdk-pixbuf.nix
+++ b/nixos/tests/gdk-pixbuf.nix
@@ -3,12 +3,12 @@ import ./make-test.nix ({ pkgs, ... }: {
   name = "gdk-pixbuf";
 
   meta = {
-    maintainers = pkgs.gdk_pixbuf.meta.maintainers;
+    maintainers = pkgs.gdk-pixbuf.meta.maintainers;
   };
 
   machine = { pkgs, ... }: {
     environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
-    environment.variables.XDG_DATA_DIRS = [ "${pkgs.gdk_pixbuf.installedTests}/share" ];
+    environment.variables.XDG_DATA_DIRS = [ "${pkgs.gdk-pixbuf.installedTests}/share" ];
 
     # Tests allocate a lot of memory trying to exploit a CVE
     # but qemu-system-i386 has a 2047M memory limit
diff --git a/nixos/tests/gitea.nix b/nixos/tests/gitea.nix
index cccf8c7cd44f..b8ab6dabc8c1 100644
--- a/nixos/tests/gitea.nix
+++ b/nixos/tests/gitea.nix
@@ -13,18 +13,8 @@ with pkgs.lib;
 
     machine =
       { config, pkgs, ... }:
-      { services.mysql.enable = true;
-        services.mysql.package = pkgs.mariadb;
-        services.mysql.ensureDatabases = [ "gitea" ];
-        services.mysql.ensureUsers = [
-          { name = "gitea";
-            ensurePermissions = { "gitea.*" = "ALL PRIVILEGES"; };
-          }
-        ];
-
-        services.gitea.enable = true;
+      { services.gitea.enable = true;
         services.gitea.database.type = "mysql";
-        services.gitea.database.socket = "/run/mysqld/mysqld.sock";
       };
 
     testScript = ''
@@ -42,10 +32,8 @@ with pkgs.lib;
 
     machine =
       { config, pkgs, ... }:
-      {
-        services.gitea.enable = true;
+      { services.gitea.enable = true;
         services.gitea.database.type = "postgres";
-        services.gitea.database.passwordFile = pkgs.writeText "db-password" "secret";
       };
 
     testScript = ''
diff --git a/nixos/tests/grafana.nix b/nixos/tests/grafana.nix
index 9dc765a879bc..7a1b4c8ffbbc 100644
--- a/nixos/tests/grafana.nix
+++ b/nixos/tests/grafana.nix
@@ -1,25 +1,91 @@
-import ./make-test.nix ({ lib, ... }:
-{
-  name = "grafana";
+import ./make-test.nix ({ lib, pkgs, ... }:
 
-  meta = with lib.maintainers; {
-    maintainers = [ willibutz ];
-  };
+let
+  inherit (lib) mkMerge nameValuePair maintainers;
 
-  machine = { ... }: {
+  baseGrafanaConf = {
     services.grafana = {
       enable = true;
       addr = "localhost";
       analytics.reporting.enable = false;
       domain = "localhost";
-      security.adminUser = "testusername";
+      security = {
+        adminUser = "testadmin";
+        adminPassword = "snakeoilpwd";
+      };
     };
   };
 
+  extraNodeConfs = {
+    postgresql = {
+      services.grafana.database = {
+        host = "127.0.0.1:5432";
+        user = "grafana";
+      };
+      services.postgresql = {
+        enable = true;
+        ensureDatabases = [ "grafana" ];
+        ensureUsers = [{
+          name = "grafana";
+          ensurePermissions."DATABASE grafana" = "ALL PRIVILEGES";
+        }];
+      };
+      systemd.services.grafana.after = [ "postgresql.service" ];
+    };
+
+    mysql = {
+      services.grafana.database.user = "grafana";
+      services.mysql = {
+        enable = true;
+        ensureDatabases = [ "grafana" ];
+        ensureUsers = [{
+          name = "grafana";
+          ensurePermissions."grafana.*" = "ALL PRIVILEGES";
+        }];
+        package = pkgs.mariadb;
+      };
+      systemd.services.grafana.after = [ "mysql.service" ];
+    };
+  };
+
+  nodes = builtins.listToAttrs (map (dbName:
+    nameValuePair dbName (mkMerge [
+    baseGrafanaConf
+    (extraNodeConfs.${dbName} or {})
+  ])) [ "sqlite" "postgresql" "mysql" ]);
+
+in {
+  name = "grafana";
+
+  meta = with maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  inherit nodes;
+
   testScript = ''
-    $machine->start;
-    $machine->waitForUnit("grafana.service");
-    $machine->waitForOpenPort(3000);
-    $machine->succeed("curl -sSfL http://127.0.0.1:3000/");
+    startAll();
+
+    subtest "Grafana sqlite", sub {
+      $sqlite->waitForUnit("grafana.service");
+      $sqlite->waitForOpenPort(3000);
+      $sqlite->succeed("curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep -q testadmin\@localhost");
+    };
+
+    subtest "Grafana postgresql", sub {
+      $postgresql->waitForUnit("grafana.service");
+      $postgresql->waitForUnit("postgresql.service");
+      $postgresql->waitForOpenPort(3000);
+      $postgresql->waitForOpenPort(5432);
+      $postgresql->succeed("curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep -q testadmin\@localhost");
+    };
+
+    subtest "Grafana mysql", sub {
+      $mysql->waitForUnit("grafana.service");
+      $mysql->waitForUnit("mysql.service");
+      $mysql->waitForOpenPort(3000);
+      $mysql->waitForOpenPort(3306);
+      $mysql->succeed("curl -sSfN -u testadmin:snakeoilpwd http://127.0.0.1:3000/api/org/users | grep -q testadmin\@localhost");
+    };
   '';
 })
diff --git a/nixos/tests/graylog.nix b/nixos/tests/graylog.nix
new file mode 100644
index 000000000000..dc54afd1d26d
--- /dev/null
+++ b/nixos/tests/graylog.nix
@@ -0,0 +1,111 @@
+import ./make-test.nix ({ pkgs, lib, ... }: {
+  name = "graylog";
+  meta.maintainers = with lib.maintainers; [ ma27 ];
+
+  machine = { pkgs, ... }: {
+    virtualisation.memorySize = 4096;
+    virtualisation.diskSize = 4096;
+
+    services.mongodb.enable = true;
+    services.elasticsearch.enable = true;
+    services.elasticsearch.package = pkgs.elasticsearch-oss;
+    services.elasticsearch.extraConf = ''
+      network.publish_host: 127.0.0.1
+      network.bind_host: 127.0.0.1
+    '';
+
+    services.graylog = {
+      enable = true;
+      passwordSecret = "YGhZ59wXMrYOojx5xdgEpBpDw2N6FbhM4lTtaJ1KPxxmKrUvSlDbtWArwAWMQ5LKx1ojHEVrQrBMVRdXbRyZLqffoUzHfssc";
+      elasticsearchHosts = [ "http://localhost:9200" ];
+
+      # `echo -n "nixos" | shasum -a 256`
+      rootPasswordSha2 = "6ed332bcfa615381511d4d5ba44a293bb476f368f7e9e304f0dff50230d1a85b";
+    };
+
+    environment.systemPackages = [ pkgs.jq ];
+
+    systemd.services.graylog.path = [ pkgs.netcat ];
+    systemd.services.graylog.preStart = ''
+      until nc -z localhost 9200; do
+        sleep 2
+      done
+    '';
+  };
+
+  testScript = let
+    payloads.login = pkgs.writeText "login.json" (builtins.toJSON {
+      host = "127.0.0.1:9000";
+      username = "admin";
+      password = "nixos";
+    });
+
+    payloads.input = pkgs.writeText "input.json" (builtins.toJSON {
+      title = "Demo";
+      global = false;
+      type = "org.graylog2.inputs.gelf.udp.GELFUDPInput";
+      node = "@node@";
+      configuration = {
+        bind_address = "0.0.0.0";
+        decompress_size_limit = 8388608;
+        number_worker_threads = 1;
+        override_source = null;
+        port = 12201;
+        recv_buffer_size = 262144;
+      };
+    });
+
+    payloads.gelf_message = pkgs.writeText "gelf.json" (builtins.toJSON {
+      host = "example.org";
+      short_message = "A short message";
+      full_message = "A long message";
+      version = "1.1";
+      level = 5;
+      facility = "Test";
+    });
+  in ''
+    $machine->start;
+    $machine->waitForUnit("graylog.service");
+    $machine->waitForOpenPort(9000);
+    $machine->succeed("curl -sSfL http://127.0.0.1:9000/");
+
+    my $session = $machine->succeed("curl -X POST "
+                                  . "-sSfL http://127.0.0.1:9000/api/system/sessions "
+                                  . "-d \$(cat ${payloads.login}) "
+                                  . "-H 'Content-Type: application/json' "
+                                  . "-H 'Accept: application/json' "
+                                  . "-H 'x-requested-by: cli' "
+                                  . "| jq .session_id | xargs echo"
+                                  );
+
+    chomp($session);
+
+    $machine->succeed("curl -X POST "
+                    . "-sSfL http://127.0.0.1:9000/api/system/inputs -u $session:session "
+                    . "-d \$(cat ${payloads.input} | sed -e \"s,\@node\@,\$(cat /var/lib/graylog/server/node-id),\") "
+                    . "-H 'Accept: application/json' "
+                    . "-H 'Content-Type: application/json' "
+                    . "-H 'x-requested-by: cli' "
+                    );
+
+    $machine->waitUntilSucceeds("test \"\$(curl -sSfL 'http://127.0.0.1:9000/api/cluster/inputstates' "
+                              . "-u $session:session "
+                              . "-H 'Accept: application/json' "
+                              . "-H 'Content-Type: application/json' "
+                              . "-H 'x-requested-by: cli'"
+                              . "| jq 'to_entries[]|.value|.[0]|.state' | xargs echo"
+                              . ")\" = \"RUNNING\""
+                              );
+
+    $machine->succeed("echo -n \$(cat ${payloads.gelf_message}) | nc -w10 -u 127.0.0.1 12201");
+
+    $machine->succeed("test \"\$(curl -X GET "
+                    . "-sSfL 'http://127.0.0.1:9000/api/search/universal/relative?query=*' "
+                    . "-u $session:session "
+                    . "-H 'Accept: application/json' "
+                    . "-H 'Content-Type: application/json' "
+                    . "-H 'x-requested-by: cli'"
+                    . " | jq '.total_results' | xargs echo)\" = \"1\""
+                    );
+  '';
+})
diff --git a/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix
index 882bced86d39..f99b367ac9b7 100644
--- a/nixos/tests/hydra/default.nix
+++ b/nixos/tests/hydra/default.nix
@@ -17,7 +17,7 @@ let
 
   createTrivialProject = pkgs.stdenv.mkDerivation {
     name = "create-trivial-project";
-    unpackPhase = ":";
+    dontUnpack = true;
     buildInputs = [ pkgs.makeWrapper ];
     installPhase = "install -m755 -D ${./create-trivial-project.sh} $out/bin/create-trivial-project.sh";
     postFixup = ''
diff --git a/nixos/tests/initdb.nix b/nixos/tests/initdb.nix
new file mode 100644
index 000000000000..749d7857a134
--- /dev/null
+++ b/nixos/tests/initdb.nix
@@ -0,0 +1,26 @@
+let
+  pkgs = import <nixpkgs> { };
+in
+with import <nixpkgs/nixos/lib/testing.nix> { inherit pkgs; system = builtins.currentSystem; };
+with pkgs.lib;
+
+makeTest {
+    name = "pg-initdb";
+
+    machine = {...}:
+      {
+        documentation.enable = false;
+        services.postgresql.enable = true;
+        services.postgresql.package = pkgs.postgresql_9_6;
+        environment.pathsToLink = [
+          "/share/postgresql"
+        ];
+      };
+
+    testScript = ''
+      $machine->start;
+      $machine->succeed("sudo -u postgres initdb -D /tmp/testpostgres2");
+      $machine->shutdown;
+    '';
+
+  }
\ No newline at end of file
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 5e363f5d09e9..a136678c6eff 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -67,6 +67,7 @@ let
   # partitions and filesystems.
   testScriptFun = { bootLoader, createPartitions, grubVersion, grubDevice, grubUseEfi
                   , grubIdentifier, preBootCommands, extraConfig
+                  , testCloneConfig
                   }:
     let
       iface = if grubVersion == 1 then "ide" else "virtio";
@@ -85,6 +86,7 @@ let
     in if !isEfi && !(pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) then
       throw "Non-EFI boot methods are only supported on i686 / x86_64"
     else ''
+
       $machine->start;
 
       # Make sure that we get a login prompt etc.
@@ -185,6 +187,43 @@ let
       ${preBootCommands}
       $machine->waitForUnit("network.target");
       $machine->shutdown;
+
+      # Tests for validating clone configuration entries in grub menu
+      ${optionalString testCloneConfig ''
+        # Reboot Machine
+        $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "clone-default-config" });
+        ${preBootCommands}
+        $machine->waitForUnit("multi-user.target");
+
+        # Booted configuration name should be Home
+        # This is not the name that shows in the grub menu.
+        # The default configuration is always shown as "Default"
+        $machine->succeed("cat /run/booted-system/configuration-name >&2");
+        $machine->succeed("cat /run/booted-system/configuration-name | grep Home");
+
+        # We should find **not** a file named /etc/gitconfig
+        $machine->fail("test -e /etc/gitconfig");
+
+        # Set grub to boot the second configuration
+        $machine->succeed("grub-reboot 1");
+
+        $machine->shutdown;
+
+        # Reboot Machine
+        $machine = createMachine({ ${hdFlags} qemuFlags => "${qemuFlags}", name => "clone-alternate-config" });
+        ${preBootCommands}
+
+        $machine->waitForUnit("multi-user.target");
+        # Booted configuration name should be Work
+        $machine->succeed("cat /run/booted-system/configuration-name >&2");
+        $machine->succeed("cat /run/booted-system/configuration-name | grep Work");
+
+        # We should find a file named /etc/gitconfig
+        $machine->succeed("test -e /etc/gitconfig");
+
+        $machine->shutdown;
+      ''}
+
     '';
 
 
@@ -194,6 +233,7 @@ let
     , bootLoader ? "grub" # either "grub" or "systemd-boot"
     , grubVersion ? 2, grubDevice ? "/dev/vda", grubIdentifier ? "uuid", grubUseEfi ? false
     , enableOCR ? false, meta ? {}
+    , testCloneConfig ? false
     }:
     makeTest {
       inherit enableOCR;
@@ -269,11 +309,12 @@ let
 
       testScript = testScriptFun {
         inherit bootLoader createPartitions preBootCommands
-                grubVersion grubDevice grubIdentifier grubUseEfi extraConfig;
+                grubVersion grubDevice grubIdentifier grubUseEfi extraConfig
+                testCloneConfig;
       };
     };
 
-    makeLuksRootTest = name: luksFormatOpts: makeInstallerTest "luksroot-format2"
+    makeLuksRootTest = name: luksFormatOpts: makeInstallerTest name
       { createPartitions = ''
           $machine->succeed(
             "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
@@ -304,32 +345,24 @@ let
         '';
       };
 
-
-in {
-
-  # !!! `parted mkpart' seems to silently create overlapping partitions.
-
-
   # The (almost) simplest partitioning scheme: a swap partition and
   # one big filesystem partition.
-  simple = makeInstallerTest "simple"
-    { createPartitions =
-        ''
-          $machine->succeed(
-              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
-              . " mkpart primary linux-swap 1M 1024M"
-              . " mkpart primary ext2 1024M -1s",
-              "udevadm settle",
-              "mkswap /dev/vda1 -L swap",
-              "swapon -L swap",
-              "mkfs.ext3 -L nixos /dev/vda2",
-              "mount LABEL=nixos /mnt",
-          );
-        '';
-    };
-
-  # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
-  simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot"
+  simple-test-config = { createPartitions =
+       ''
+         $machine->succeed(
+             "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+             . " mkpart primary linux-swap 1M 1024M"
+             . " mkpart primary ext2 1024M -1s",
+             "udevadm settle",
+             "mkswap /dev/vda1 -L swap",
+             "swapon -L swap",
+             "mkfs.ext3 -L nixos /dev/vda2",
+             "mount LABEL=nixos /mnt",
+         );
+       '';
+   };
+
+  simple-uefi-grub-config =
     { createPartitions =
         ''
           $machine->succeed(
@@ -348,10 +381,45 @@ in {
               "mount LABEL=BOOT /mnt/boot",
           );
         '';
-        bootLoader = "systemd-boot";
+        bootLoader = "grub";
+        grubUseEfi = true;
     };
 
-  simpleUefiGrub = makeInstallerTest "simpleUefiGrub"
+  clone-test-extraconfig = { extraConfig =
+         ''
+         environment.systemPackages = [ pkgs.grub2 ];
+         boot.loader.grub.configurationName = "Home";
+         nesting.clone = [
+         {
+           boot.loader.grub.configurationName = lib.mkForce "Work";
+
+           environment.etc = {
+             "gitconfig".text = "
+               [core]
+                 gitproxy = none for work.com
+                 ";
+           };
+         }
+         ];
+         '';
+       testCloneConfig = true;
+  };
+
+
+in {
+
+  # !!! `parted mkpart' seems to silently create overlapping partitions.
+
+
+  # The (almost) simplest partitioning scheme: a swap partition and
+  # one big filesystem partition.
+  simple = makeInstallerTest "simple" simple-test-config;
+
+  # Test cloned configurations with the simple grub configuration
+  simpleClone = makeInstallerTest "simpleClone" (simple-test-config // clone-test-extraconfig);
+
+  # Simple GPT/UEFI configuration using systemd-boot with 3 partitions: ESP, swap & root filesystem
+  simpleUefiSystemdBoot = makeInstallerTest "simpleUefiSystemdBoot"
     { createPartitions =
         ''
           $machine->succeed(
@@ -370,10 +438,14 @@ in {
               "mount LABEL=BOOT /mnt/boot",
           );
         '';
-        bootLoader = "grub";
-        grubUseEfi = true;
+        bootLoader = "systemd-boot";
     };
 
+  simpleUefiGrub = makeInstallerTest "simpleUefiGrub" simple-uefi-grub-config;
+
+  # Test cloned configurations with the uefi grub configuration
+  simpleUefiGrubClone = makeInstallerTest "simpleUefiGrubClone" (simple-uefi-grub-config // clone-test-extraconfig);
+
   # Same as the previous, but now with a separate /boot partition.
   separateBoot = makeInstallerTest "separateBoot"
     { createPartitions =
diff --git a/nixos/tests/ipv6.nix b/nixos/tests/ipv6.nix
index 14f24c29cfe2..d11eba764da3 100644
--- a/nixos/tests/ipv6.nix
+++ b/nixos/tests/ipv6.nix
@@ -1,14 +1,16 @@
 # Test of IPv6 functionality in NixOS, including whether router
 # solicication/advertisement using radvd works.
 
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test.nix ({ pkgs, lib, ...} : {
   name = "ipv6";
   meta = with pkgs.stdenv.lib.maintainers; {
     maintainers = [ eelco ];
   };
 
   nodes =
-    { client = { ... }: { };
+    # Remove the interface configuration provided by makeTest so that the
+    # interfaces are all configured implicitly
+    { client = { ... }: { networking.interfaces = lib.mkForce {}; };
 
       server =
         { ... }:
@@ -73,6 +75,11 @@ import ./make-test.nix ({ pkgs, ...} : {
           $client->succeed("curl --fail -g http://[$serverIp]");
           $client->fail("curl --fail -g http://[$clientIp]");
       };
+      subtest "privacy extensions", sub {
+          my $ip = waitForAddress $client, "eth1", "global temporary";
+          # Default route should have "src <temporary address>" in it
+          $client->succeed("ip r g ::2 | grep $ip");
+      };
 
       # TODO: test reachability of a machine on another network.
     '';
diff --git a/nixos/tests/loki.nix b/nixos/tests/loki.nix
new file mode 100644
index 000000000000..9c3058d02f84
--- /dev/null
+++ b/nixos/tests/loki.nix
@@ -0,0 +1,37 @@
+import ./make-test.nix ({ lib, pkgs, ... }:
+
+{
+  name = "loki";
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  machine = { ... }: {
+    services.loki = {
+      enable = true;
+      configFile = "${pkgs.grafana-loki.src}/cmd/loki/loki-local-config.yaml";
+    };
+    systemd.services.promtail = {
+      description = "Promtail service for Loki test";
+      wantedBy = [ "multi-user.target" ];
+
+      serviceConfig = {
+        ExecStart = ''
+          ${pkgs.grafana-loki}/bin/promtail --config.file ${pkgs.grafana-loki.src}/cmd/promtail/promtail-local-config.yaml
+        '';
+        DynamicUser = true;
+      };
+    };
+  };
+
+  testScript = ''
+    $machine->start;
+    $machine->waitForUnit("loki.service");
+    $machine->waitForUnit("promtail.service");
+    $machine->waitForOpenPort(3100);
+    $machine->waitForOpenPort(9080);
+    $machine->succeed("echo 'Loki Ingestion Test' > /var/log/testlog");
+    $machine->waitUntilSucceeds("${pkgs.grafana-loki}/bin/logcli --addr='http://localhost:3100' query --no-labels '{job=\"varlogs\",filename=\"/var/log/testlog\"}' | grep -q 'Loki Ingestion Test'");
+  '';
+})
diff --git a/nixos/tests/mediawiki.nix b/nixos/tests/mediawiki.nix
new file mode 100644
index 000000000000..6293e8a2f465
--- /dev/null
+++ b/nixos/tests/mediawiki.nix
@@ -0,0 +1,19 @@
+import ./make-test.nix ({ pkgs, lib, ... }: {
+  name = "mediawiki";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  machine =
+    { ... }:
+    { services.mediawiki.enable = true;
+      services.mediawiki.virtualHost.hostName = "localhost";
+      services.mediawiki.virtualHost.adminAddr = "root@example.com";
+      services.mediawiki.passwordFile = pkgs.writeText "password" "correcthorsebatterystaple";
+    };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit('phpfpm-mediawiki.service');
+    $machine->succeed('curl -L http://localhost/') =~ /MediaWiki has been installed/ or die;
+  '';
+})
diff --git a/nixos/tests/mysql.nix b/nixos/tests/mysql.nix
index cfe10bc41b0c..05bd968de02d 100644
--- a/nixos/tests/mysql.nix
+++ b/nixos/tests/mysql.nix
@@ -19,7 +19,7 @@ import ./make-test.nix ({ pkgs, ...} : {
         services.mysql.initialScript = pkgs.writeText "mysql-init.sql" ''
           CREATE USER 'passworduser'@'localhost' IDENTIFIED BY 'password123';
         '';
-        services.mysql.package = pkgs.mysql;
+        services.mysql.package = pkgs.mysql57;
       };
 
     mariadb =
@@ -28,6 +28,12 @@ import ./make-test.nix ({ pkgs, ...} : {
       {
         users.users.testuser = { };
         services.mysql.enable = true;
+        services.mysql.initialScript = pkgs.writeText "mariadb-init.sql" ''
+          ALTER USER root@localhost IDENTIFIED WITH unix_socket;
+          DELETE FROM mysql.user WHERE password = ''' AND plugin = ''';
+          DELETE FROM mysql.user WHERE user = ''';
+          FLUSH PRIVILEGES;
+        '';
         services.mysql.ensureDatabases = [ "testdb" ];
         services.mysql.ensureUsers = [{
           name = "testuser";
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index ed9f287d5582..949d946bdc4c 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -510,7 +510,7 @@ let
           '';
         };
       };
-      nodes.client = { pkgs, ... }: with pkgs.lib; {
+      nodes.clientWithPrivacy = { pkgs, ... }: with pkgs.lib; {
         virtualisation.vlans = [ 1 ];
         networking = {
           useNetworkd = networkd;
@@ -522,21 +522,39 @@ let
           };
         };
       };
+      nodes.client = { pkgs, ... }: with pkgs.lib; {
+        virtualisation.vlans = [ 1 ];
+        networking = {
+          useNetworkd = networkd;
+          useDHCP = true;
+          interfaces.eth1 = {
+            preferTempAddress = false;
+            ipv4.addresses = mkOverride 0 [ ];
+            ipv6.addresses = mkOverride 0 [ ];
+          };
+        };
+      };
       testScript = { ... }:
         ''
           startAll;
 
           $client->waitForUnit("network.target");
+          $clientWithPrivacy->waitForUnit("network.target");
           $router->waitForUnit("network-online.target");
 
           # Wait until we have an ip address
+          $clientWithPrivacy->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
           $client->waitUntilSucceeds("ip addr show dev eth1 | grep -q 'fd00:1234:5678:1:'");
 
           # Test vlan 1
+          $clientWithPrivacy->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
           $client->waitUntilSucceeds("ping -c 1 fd00:1234:5678:1::1");
 
           # Test address used is temporary
-          $client->waitUntilSucceeds("! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'");
+          $clientWithPrivacy->waitUntilSucceeds("! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'");
+
+          # Test address used is EUI-64
+          $client->waitUntilSucceeds("ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'");
         '';
     };
     routes = {
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
index 0351d4db69ac..8a840a608753 100644
--- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -27,10 +27,7 @@ in {
           dbtype = "pgsql";
           dbname = "nextcloud";
           dbuser = "nextcloud";
-          dbhost = "localhost";
-          dbpassFile = toString (pkgs.writeText "db-pass-file" ''
-            hunter2
-          '');
+          dbhost = "/run/postgresql";
           inherit adminuser;
           adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
             ${adminpass}
@@ -84,10 +81,12 @@ in {
 
       services.postgresql = {
         enable = true;
-        initialScript = pkgs.writeText "psql-init" ''
-          create role nextcloud with login password 'hunter2';
-          create database nextcloud with owner nextcloud;
-        '';
+        ensureDatabases = [ "nextcloud" ];
+        ensureUsers = [
+          { name = "nextcloud";
+            ensurePermissions."DATABASE nextcloud" = "ALL PRIVILEGES";
+          }
+        ];
       };
     };
   };
diff --git a/nixos/tests/nixos-generate-config.nix b/nixos/tests/nixos-generate-config.nix
new file mode 100644
index 000000000000..15a173e024b4
--- /dev/null
+++ b/nixos/tests/nixos-generate-config.nix
@@ -0,0 +1,24 @@
+import ./make-test.nix ({ lib, ... } : {
+  name = "nixos-generate-config";
+  meta.maintainers = with lib.maintainers; [ basvandijk ];
+  machine = {
+    system.nixos-generate-config.configuration = ''
+      # OVERRIDDEN
+      { config, pkgs, ... }: {
+        imports = [ ./hardware-configuration.nix ];
+      $bootLoaderConfig
+      }
+    '';
+  };
+  testScript = ''
+    startAll;
+    $machine->waitForUnit("multi-user.target");
+    $machine->succeed("nixos-generate-config");
+
+    # Test if the configuration really is overridden
+    $machine->succeed("grep 'OVERRIDDEN' /etc/nixos/configuration.nix");
+
+    # Test of if the Perl variable $bootLoaderConfig is spliced correctly:
+    $machine->succeed("grep 'boot\\.loader\\.grub\\.enable = true;' /etc/nixos/configuration.nix");
+  '';
+})
diff --git a/nixos/tests/ostree.nix b/nixos/tests/ostree.nix
index 8b19004874e7..d7ad84a1a5f0 100644
--- a/nixos/tests/ostree.nix
+++ b/nixos/tests/ostree.nix
@@ -12,7 +12,7 @@ import ./make-test.nix ({ pkgs, lib, ... }: {
       gnome-desktop-testing ostree gnupg (python3.withPackages (p: with p; [ pyyaml ]))
     ];
 
-    environment.variables.GI_TYPELIB_PATH = lib.makeSearchPath "lib/girepository-1.0" (with pkgs; [ gtk3 pango.out ostree gdk_pixbuf atk ]); # for GJS tests
+    environment.variables.GI_TYPELIB_PATH = lib.makeSearchPath "lib/girepository-1.0" (with pkgs; [ gtk3 pango.out ostree gdk-pixbuf atk ]); # for GJS tests
   };
 
   testScript = ''
diff --git a/nixos/tests/phabricator.nix b/nixos/tests/phabricator.nix
deleted file mode 100644
index db23331842cb..000000000000
--- a/nixos/tests/phabricator.nix
+++ /dev/null
@@ -1,77 +0,0 @@
-import ./make-test.nix ({ pkgs, ... }: {
-  name = "phabricator";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ ];
-  };
-
-  nodes = {
-    storage =
-      { ... }:
-      { services.nfs.server.enable = true;
-        services.nfs.server.exports = ''
-          /repos 192.168.1.0/255.255.255.0(rw,no_root_squash)
-        '';
-        services.nfs.server.createMountPoints = true;
-      };
-
-    webserver =
-      { pkgs, ... }:
-      { fileSystems = pkgs.lib.mkVMOverride
-          [ { mountPoint = "/repos";
-              device = "storage:/repos";
-              fsType = "nfs";
-            }
-          ];
-        networking.firewall.enable = false;
-        networking.useDHCP = false;
-
-        services = {
-          httpd = {
-            enable = true;
-            adminAddr = "root@localhost";
-            virtualHosts = [{
-              hostName = "phabricator.local";
-              extraSubservices = [{serviceType = "phabricator";}];
-            }];
-          };
-
-          phd = {
-            enable = true;
-          };
-
-          mysql = {
-            enable = true;
-            package = pkgs.mysql;
-            extraOptions = ''
-              sql_mode=STRICT_ALL_TABLES
-            '';
-          };
-        };
-
-        environment.systemPackages = [ pkgs.php ];
-      };
-
-    client =
-      { ... }:
-      { imports = [ ./common/x11.nix ];
-        services.xserver.desktopManager.plasma5.enable = true;
-      };
-  };
-
-  testScript =
-    ''
-      startAll;
-
-      $client->waitForX;
-
-      $webserver->waitForUnit("mysql");
-      $webserver->waitForUnit("httpd");
-      $webserver->execute("cd /nix/store; less >/repos/log1");
-
-      $client->sleep(30); # loading takes a long time
-      $client->execute("konqueror http://webserver/ &");
-      $client->sleep(90); # loading takes a long time
-
-      $client->screenshot("screen");
-    '';
-})
diff --git a/nixos/tests/prometheus-2.nix b/nixos/tests/prometheus-2.nix
index d7035d49ad4d..219c47c73d95 100644
--- a/nixos/tests/prometheus-2.nix
+++ b/nixos/tests/prometheus-2.nix
@@ -1,9 +1,44 @@
-import ./make-test.nix {
+let
+  grpcPort   = 19090;
+  queryPort  =  9090;
+  minioPort  =  9000;
+  pushgwPort =  9091;
+
+  s3 = {
+    accessKey = "BKIKJAA5BMMU2RHO6IBB";
+    secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
+  };
+
+  objstore.config = {
+    type = "S3";
+    config = {
+      bucket = "thanos-bucket";
+      endpoint = "s3:${toString minioPort}";
+      region =  "us-east-1";
+      access_key = s3.accessKey;
+      secret_key = s3.secretKey;
+      insecure = true;
+      signature_version2 = false;
+      encrypt_sse =  false;
+      put_user_metadata = {};
+      http_config = {
+        idle_conn_timeout = "0s";
+        insecure_skip_verify = false;
+      };
+      trace = {
+        enable = false;
+      };
+    };
+  };
+
+in import ./make-test.nix {
   name = "prometheus-2";
 
   nodes = {
-    one = { pkgs, ... }: {
+    prometheus = { pkgs, ... }: {
+      virtualisation.diskSize = 2 * 1024;
       environment.systemPackages = [ pkgs.jq ];
+      networking.firewall.allowedTCPPorts = [ grpcPort ];
       services.prometheus2 = {
         enable = true;
         scrapeConfigs = [
@@ -11,7 +46,7 @@ import ./make-test.nix {
             job_name = "prometheus";
             static_configs = [
               {
-                targets = [ "127.0.0.1:9090" ];
+                targets = [ "127.0.0.1:${toString queryPort}" ];
                 labels = { instance = "localhost"; };
               }
             ];
@@ -21,7 +56,7 @@ import ./make-test.nix {
             scrape_interval = "1s";
             static_configs = [
               {
-                targets = [ "127.0.0.1:9091" ];
+                targets = [ "127.0.0.1:${toString pushgwPort}" ];
               }
             ];
           }
@@ -35,33 +70,170 @@ import ./make-test.nix {
                     expr: count(up{job="prometheus"})
           ''
         ];
+        globalConfig = {
+          external_labels = {
+            some_label = "required by thanos";
+          };
+        };
+        extraFlags = [
+          # Required by thanos
+          "--storage.tsdb.min-block-duration=5s"
+          "--storage.tsdb.max-block-duration=5s"
+        ];
       };
       services.prometheus.pushgateway = {
         enable = true;
+        web.listen-address = ":${toString pushgwPort}";
         persistMetrics = true;
         persistence.interval = "1s";
         stateDir = "prometheus-pushgateway";
       };
+      services.thanos = {
+        sidecar = {
+          enable = true;
+          grpc-address = "0.0.0.0:${toString grpcPort}";
+          inherit objstore;
+        };
+
+        # TODO: Add some tests for these services:
+        #rule = {
+        #  enable = true;
+        #  http-address = "0.0.0.0:19194";
+        #  grpc-address = "0.0.0.0:19193";
+        #  query.addresses = [
+        #    "localhost:19191"
+        #  ];
+        #  labels = {
+        #    just = "some";
+        #    nice = "labels";
+        #  };
+        #};
+        #
+        #receive = {
+        #  http-address = "0.0.0.0:19195";
+        #  enable = true;
+        #  labels = {
+        #    just = "some";
+        #    nice = "labels";
+        #  };
+        #};
+      };
+    };
+
+    query = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.jq ];
+      services.thanos.query = {
+        enable = true;
+        http-address = "0.0.0.0:${toString queryPort}";
+        store.addresses = [
+          "prometheus:${toString grpcPort}"
+        ];
+      };
+    };
+
+    store = { pkgs, ... }: {
+      virtualisation.diskSize = 2 * 1024;
+      environment.systemPackages = with pkgs; [ jq thanos ];
+      services.thanos.store = {
+        enable = true;
+        http-address = "0.0.0.0:10902";
+        grpc-address = "0.0.0.0:${toString grpcPort}";
+        inherit objstore;
+        sync-block-duration = "1s";
+      };
+      services.thanos.compact = {
+        enable = true;
+        http-address = "0.0.0.0:10903";
+        inherit objstore;
+        consistency-delay = "5s";
+      };
+      services.thanos.query = {
+        enable = true;
+        http-address = "0.0.0.0:${toString queryPort}";
+        store.addresses = [
+          "localhost:${toString grpcPort}"
+        ];
+      };
+    };
+
+    s3 = { pkgs, ... } : {
+      # Minio requires at least 1GiB of free disk space to run.
+      virtualisation.diskSize = 2 * 1024;
+      networking.firewall.allowedTCPPorts = [ minioPort ];
+
+      services.minio = {
+        enable = true;
+        inherit (s3) accessKey secretKey;
+      };
+
+      environment.systemPackages = [ pkgs.minio-client ];
     };
   };
 
-  testScript = ''
-    startAll;
-    $one->waitForUnit("prometheus2.service");
-    $one->waitForOpenPort(9090);
-    $one->succeed("curl -s http://127.0.0.1:9090/metrics");
+  testScript = { nodes, ... } : ''
+    # Before starting the other machines we first make sure that our S3 service is online
+    # and has a bucket added for thanos:
+    $s3->start;
+    $s3->waitForUnit("minio.service");
+    $s3->waitForOpenPort(${toString minioPort});
+    $s3->succeed(
+      "mc config host add minio " .
+      "http://localhost:${toString minioPort} ${s3.accessKey} ${s3.secretKey} S3v4");
+    $s3->succeed("mc mb minio/thanos-bucket");
+
+    # Now that s3 has started we can start the other machines:
+    $prometheus->start;
+    $query->start;
+    $store->start;
 
-    # Let's test if pushing a metric to the pushgateway succeeds
-    # and whether that metric gets ingested by prometheus.
-    $one->waitForUnit("pushgateway.service");
-    $one->succeed(
+    # Check if prometheus responds to requests:
+    $prometheus->waitForUnit("prometheus2.service");
+    $prometheus->waitForOpenPort(${toString queryPort});
+    $prometheus->succeed("curl -s http://127.0.0.1:${toString queryPort}/metrics");
+
+    # Let's test if pushing a metric to the pushgateway succeeds:
+    $prometheus->waitForUnit("pushgateway.service");
+    $prometheus->succeed(
       "echo 'some_metric 3.14' | " .
-      "curl --data-binary \@- http://127.0.0.1:9091/metrics/job/some_job");
-    $one->waitUntilSucceeds(
-      "curl -sf 'http://127.0.0.1:9090/api/v1/query?query=some_metric' " .
-      "| jq '.data.result[0].value[1]' | grep '\"3.14\"'");
+      "curl --data-binary \@- http://127.0.0.1:${toString pushgwPort}/metrics/job/some_job");
+
+    # Now check whether that metric gets ingested by prometheus.
+    # Since we'll check for the metric several times on different machines
+    # we abstract the test using the following function:
+
+    # Function to check if the metric "some_metric" has been received and returns the correct value.
+    local *Machine::waitForMetric = sub {
+      my ($self) = @_;
+      $self->waitUntilSucceeds(
+        "curl -sf 'http://127.0.0.1:${toString queryPort}/api/v1/query?query=some_metric' " .
+        "| jq '.data.result[0].value[1]' | grep '\"3.14\"'");
+    };
+
+    $prometheus->waitForMetric;
 
     # Let's test if the pushgateway persists metrics to the configured location.
-    $one->waitUntilSucceeds("test -e /var/lib/prometheus-pushgateway/metrics");
+    $prometheus->waitUntilSucceeds("test -e /var/lib/prometheus-pushgateway/metrics");
+
+    # Test thanos
+    $prometheus->waitForUnit("thanos-sidecar.service");
+
+    # Test if the Thanos query service can correctly retrieve the metric that was send above.
+    $query->waitForUnit("thanos-query.service");
+    $query->waitForMetric;
+
+    # Test if the Thanos sidecar has correctly uploaded its TSDB to S3, if the
+    # Thanos storage service has correctly downloaded it from S3 and if the Thanos
+    # query service running on $store can correctly retrieve the metric:
+    $store->waitForUnit("thanos-store.service");
+    $store->waitForMetric;
+
+    $store->waitForUnit("thanos-compact.service");
+
+    # Test if the Thanos bucket command is able to retrieve blocks from the S3 bucket
+    # and check if the blocks have the correct labels:
+    $store->succeed(
+      "thanos bucket ls" .
+      " --objstore.config-file=${nodes.store.config.services.thanos.store.objstore.config-file}" .
+      " --output=json | jq .thanos.labels.some_label | grep 'required by thanos'");
   '';
 }
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
index 90c7c9701f60..02d83f82f338 100644
--- a/nixos/tests/prometheus-exporters.nix
+++ b/nixos/tests/prometheus-exporters.nix
@@ -3,10 +3,11 @@
 , pkgs ? import ../.. { inherit system config; }
 }:
 
-with pkgs.lib;
-with import ../lib/testing.nix { inherit system pkgs; };
-
 let
+  inherit (import ../lib/testing.nix { inherit system pkgs; }) makeTest;
+  inherit (pkgs.lib) concatStringsSep maintainers mapAttrs mkMerge
+                     removeSuffix replaceChars singleton splitString;
+
   escape' = str: replaceChars [''"'' "$" "\n"] [''\\\"'' "\\$" ""] str;
 
 /*
@@ -73,7 +74,7 @@ let
       exporterTest = ''
         waitForUnit("prometheus-bind-exporter.service");
         waitForOpenPort(9119);
-        succeed("curl -sSf http://localhost:9119/metrics" | grep -q 'bind_query_recursions_total 0');
+        succeed("curl -sSf http://localhost:9119/metrics | grep -q 'bind_query_recursions_total 0'");
       '';
     };
 
@@ -187,6 +188,47 @@ let
       '';
     };
 
+    mail = {
+      exporterConfig = {
+        enable = true;
+        configuration = {
+          monitoringInterval = "2s";
+          mailCheckTimeout = "10s";
+          servers = [ {
+            name = "testserver";
+            server = "localhost";
+            port = 25;
+            from = "mail-exporter@localhost";
+            to = "mail-exporter@localhost";
+            detectionDir = "/var/spool/mail/mail-exporter/new";
+          } ];
+        };
+      };
+      metricProvider = {
+        services.postfix.enable = true;
+        systemd.services.prometheus-mail-exporter = {
+          after = [ "postfix.service" ];
+          requires = [ "postfix.service" ];
+          preStart = ''
+            mkdir -p 0600 mail-exporter/new
+          '';
+          serviceConfig = {
+            ProtectHome = true;
+            ReadOnlyPaths = "/";
+            ReadWritePaths = "/var/spool/mail";
+            WorkingDirectory = "/var/spool/mail";
+          };
+        };
+        users.users.mailexporter.isSystemUser = true;
+      };
+      exporterTest = ''
+        waitForUnit("postfix.service")
+        waitForUnit("prometheus-mail-exporter.service")
+        waitForOpenPort(9225)
+        waitUntilSucceeds("curl -sSf http://localhost:9225/metrics | grep -q 'mail_deliver_success{configname=\"testserver\"} 1'")
+      '';
+    };
+
     nginx = {
       exporterConfig = {
         enable = true;
@@ -231,6 +273,30 @@ let
       '';
     };
 
+    postgres = {
+      exporterConfig = {
+        enable = true;
+        runAsLocalSuperUser = true;
+      };
+      metricProvider = {
+        services.postgresql.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-postgres-exporter.service");
+        waitForOpenPort(9187);
+        waitForUnit("postgresql.service");
+        succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_exporter_last_scrape_error 0'");
+        succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_up 1'");
+        systemctl("stop postgresql.service");
+        succeed("curl -sSf http://localhost:9187/metrics | grep -qv 'pg_exporter_last_scrape_error 0'");
+        succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_up 0'");
+        systemctl("start postgresql.service");
+        waitForUnit("postgresql.service");
+        succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_exporter_last_scrape_error 0'");
+        succeed("curl -sSf http://localhost:9187/metrics | grep -q 'pg_up 1'");
+      '';
+    };
+
     snmp = {
       exporterConfig = {
         enable = true;
@@ -311,6 +377,7 @@ let
       };
       exporterTest = ''
         waitForUnit("prometheus-varnish-exporter.service");
+        waitForOpenPort(6081);
         waitForOpenPort(9131);
         succeed("curl -sSf http://localhost:9131/metrics | grep -q 'varnish_up 1'");
       '';
@@ -331,11 +398,12 @@ let
             inherit (snakeoil.peer1) publicKey;
           };
         };
+        systemd.services.prometheus-wireguard-exporter.after = [ "wireguard-wg0.service" ];
       };
       exporterTest = ''
         waitForUnit("prometheus-wireguard-exporter.service");
         waitForOpenPort(9586);
-        succeed("curl -sSf http://localhost:9586/metrics | grep '${snakeoil.peer1.publicKey}'");
+        waitUntilSucceeds("curl -sSf http://localhost:9586/metrics | grep '${snakeoil.peer1.publicKey}'");
       '';
     };
   };
diff --git a/nixos/tests/radicale.nix b/nixos/tests/radicale.nix
index bf22fc9291af..607964255640 100644
--- a/nixos/tests/radicale.nix
+++ b/nixos/tests/radicale.nix
@@ -85,7 +85,7 @@ in
       $radicale->succeed('mv /tmp/collections-new/collection-root /tmp/collections');
       $radicale->succeed('${switchToConfig "radicale2_verify"} >&2');
       $radicale->waitUntilFails('systemctl status radicale');
-      my ($retcode, $logs) = $radicale->execute('journalctl -u radicale -n 5');
+      my ($retcode, $logs) = $radicale->execute('journalctl -u radicale -n 10');
       if ($retcode != 0 || index($logs, 'Verifying storage') == -1) {
         die "Radicale 2 didn't verify storage"
       }
diff --git a/nixos/tests/redmine.nix b/nixos/tests/redmine.nix
index cbdb5c8d2954..2d4df288b055 100644
--- a/nixos/tests/redmine.nix
+++ b/nixos/tests/redmine.nix
@@ -10,19 +10,9 @@ let
   mysqlTest = package: makeTest {
     machine =
       { config, pkgs, ... }:
-      { services.mysql.enable = true;
-        services.mysql.package = pkgs.mariadb;
-        services.mysql.ensureDatabases = [ "redmine" ];
-        services.mysql.ensureUsers = [
-          { name = "redmine";
-            ensurePermissions = { "redmine.*" = "ALL PRIVILEGES"; };
-          }
-        ];
-
-        services.redmine.enable = true;
+      { services.redmine.enable = true;
         services.redmine.package = package;
         services.redmine.database.type = "mysql2";
-        services.redmine.database.socket = "/run/mysqld/mysqld.sock";
         services.redmine.plugins = {
           redmine_env_auth = pkgs.fetchurl {
             url = https://github.com/Intera/redmine_env_auth/archive/0.7.zip;
@@ -48,19 +38,9 @@ let
   pgsqlTest = package: makeTest {
     machine =
       { config, pkgs, ... }:
-      { services.postgresql.enable = true;
-        services.postgresql.ensureDatabases = [ "redmine" ];
-        services.postgresql.ensureUsers = [
-          { name = "redmine";
-            ensurePermissions = { "DATABASE redmine" = "ALL PRIVILEGES"; };
-          }
-        ];
-
-        services.redmine.enable = true;
+      { services.redmine.enable = true;
         services.redmine.package = package;
         services.redmine.database.type = "postgresql";
-        services.redmine.database.host = "";
-        services.redmine.database.port = 5432;
         services.redmine.plugins = {
           redmine_env_auth = pkgs.fetchurl {
             url = https://github.com/Intera/redmine_env_auth/archive/0.7.zip;
diff --git a/nixos/tests/systemd-timesyncd.nix b/nixos/tests/systemd-timesyncd.nix
new file mode 100644
index 000000000000..d12b8eb2bf7e
--- /dev/null
+++ b/nixos/tests/systemd-timesyncd.nix
@@ -0,0 +1,52 @@
+# Regression test for systemd-timesync having moved the state directory without
+# upstream providing a migration path. https://github.com/systemd/systemd/issues/12131
+
+import ./make-test.nix (let
+  common = { lib, ... }: {
+    # override the `false` value from the qemu-vm base profile
+    services.timesyncd.enable = lib.mkForce true;
+  };
+  mkVM = conf: { imports = [ conf common ]; };
+in {
+  name = "systemd-timesyncd";
+  nodes = {
+    current = mkVM {};
+    pre1909 = mkVM ({lib, ... }: with lib; {
+      # create the path that should be migrated by our activation script when
+      # upgrading to a newer nixos version
+      system.stateVersion = "19.03";
+      system.activationScripts.simulate-old-timesync-state-dir = mkBefore ''
+        rm -f /var/lib/systemd/timesync
+        mkdir -p /var/lib/systemd /var/lib/private/systemd/timesync
+        ln -s /var/lib/private/systemd/timesync /var/lib/systemd/timesync
+        chown systemd-timesync: /var/lib/private/systemd/timesync
+      '';
+    });
+  };
+
+  testScript = ''
+    startAll;
+    $current->succeed('systemctl status systemd-timesyncd.service');
+    # on a new install with a recent systemd there should not be any
+    # leftovers from the dynamic user mess
+    $current->succeed('test -e /var/lib/systemd/timesync');
+    $current->succeed('test ! -L /var/lib/systemd/timesync');
+
+    # timesyncd should be running on the upgrading system since we fixed the
+    # file bits in the activation script
+    $pre1909->succeed('systemctl status systemd-timesyncd.service');
+
+    # the path should be gone after the migration
+    $pre1909->succeed('test ! -e /var/lib/private/systemd/timesync');
+
+    # and the new path should no longer be a symlink
+    $pre1909->succeed('test -e /var/lib/systemd/timesync');
+    $pre1909->succeed('test ! -L /var/lib/systemd/timesync');
+
+    # after a restart things should still work and not fail in the activation
+    # scripts and cause the boot to fail..
+    $pre1909->shutdown;
+    $pre1909->start;
+    $pre1909->succeed('systemctl status systemd-timesyncd.service');
+  '';
+})
diff --git a/nixos/tests/tiddlywiki.nix b/nixos/tests/tiddlywiki.nix
new file mode 100644
index 000000000000..4a2014a4ec91
--- /dev/null
+++ b/nixos/tests/tiddlywiki.nix
@@ -0,0 +1,67 @@
+import ./make-test.nix ({ ... }: {
+  name = "tiddlywiki";
+  nodes = {
+    default = {
+      services.tiddlywiki.enable = true;
+    };
+    configured = {
+      boot.postBootCommands = ''
+        echo "username,password
+        somelogin,somesecret" > /var/lib/wikiusers.csv
+      '';
+      services.tiddlywiki = {
+        enable = true;
+        listenOptions = {
+          port = 3000;
+          credentials="../wikiusers.csv";
+          readers="(authenticated)";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    subtest "by default works without configuration", sub {
+      $default->waitForUnit("tiddlywiki.service");
+    };
+
+    subtest "by default available on port 8080 without auth", sub {
+      $default->waitForUnit("tiddlywiki.service");
+      $default->waitForOpenPort(8080);
+      $default->succeed("curl --fail 127.0.0.1:8080");
+    };
+
+    subtest "by default creates empty wiki", sub {
+      $default->succeed("test -f /var/lib/tiddlywiki/tiddlywiki.info");
+    };
+
+    subtest "configured on port 3000 with basic auth", sub {
+      $configured->waitForUnit("tiddlywiki.service");
+      $configured->waitForOpenPort(3000);
+      $configured->fail("curl --fail 127.0.0.1:3000");
+      $configured->succeed("curl --fail 127.0.0.1:3000 --user somelogin:somesecret");
+    };
+
+    subtest "configured with different wikifolder", sub {
+      $configured->succeed("test -f /var/lib/tiddlywiki/tiddlywiki.info");
+    };
+
+    subtest "restart preserves changes", sub {
+      # given running wiki
+      $default->waitForUnit("tiddlywiki.service");
+      # with some changes
+      $default->succeed("curl --fail --request PUT --header 'X-Requested-With:TiddlyWiki' --data '{ \"title\": \"title\", \"text\": \"content\" }' --url 127.0.0.1:8080/recipes/default/tiddlers/somepage ");
+      $default->succeed("sleep 2"); # server syncs to filesystem on timer
+
+      # when wiki is cycled
+      $default->systemctl("restart tiddlywiki.service");
+      $default->waitForUnit("tiddlywiki.service");
+      $default->waitForOpenPort(8080);
+
+      # the change is preserved
+      $default->succeed("curl --fail 127.0.0.1:8080/recipes/default/tiddlers/somepage");
+    };
+  '';
+})
diff --git a/nixos/tests/tomcat.nix b/nixos/tests/tomcat.nix
deleted file mode 100644
index 8e7b886dd302..000000000000
--- a/nixos/tests/tomcat.nix
+++ /dev/null
@@ -1,30 +0,0 @@
-import ./make-test.nix ({ pkgs, ...} : {
-  name = "tomcat";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ eelco ];
-  };
-
-  nodes = {
-    server =
-      { ... }:
-
-      { services.tomcat.enable = true;
-        services.httpd.enable = true;
-        services.httpd.adminAddr = "foo@bar.com";
-        services.httpd.extraSubservices =
-          [ { serviceType = "tomcat-connector"; } ];
-        networking.firewall.allowedTCPPorts = [ 80 ];
-      };
-
-    client = { };
-  };
-
-  testScript = ''
-    startAll;
-
-    $server->waitForUnit("tomcat");
-    $client->waitForUnit("network.target");
-    $client->waitUntilSucceeds("curl --fail http://server/examples/servlets/servlet/HelloWorldExample");
-    $client->waitUntilSucceeds("curl --fail http://server/examples/jsp/jsp2/simpletag/hello.jsp");
-  '';
-})
diff --git a/nixos/tests/trac.nix b/nixos/tests/trac.nix
deleted file mode 100644
index 8ec11ebda2cf..000000000000
--- a/nixos/tests/trac.nix
+++ /dev/null
@@ -1,74 +0,0 @@
-import ./make-test.nix ({ pkgs, ... }: {
-  name = "trac";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ eelco ];
-  };
-
-  nodes = {
-    storage =
-      { ... }:
-      { services.nfs.server.enable = true;
-        services.nfs.server.exports = ''
-          /repos 192.168.1.0/255.255.255.0(rw,no_root_squash)
-        '';
-        services.nfs.server.createMountPoints = true;
-      };
-
-    postgresql =
-      { pkgs, ... }:
-      { services.postgresql.enable = true;
-        services.postgresql.package = pkgs.postgresql;
-        services.postgresql.enableTCPIP = true;
-        services.postgresql.authentication = ''
-          # Generated file; do not edit!
-          local all all                trust
-          host  all all 127.0.0.1/32   trust
-          host  all all ::1/128        trust
-          host  all all 192.168.1.0/24 trust
-        '';
-      };
-
-    webserver =
-      { pkgs, ... }:
-      { fileSystems = pkgs.lib.mkVMOverride
-          [ { mountPoint = "/repos";
-              device = "storage:/repos";
-              fsType = "nfs";
-            }
-          ];
-        services.httpd.enable = true;
-        services.httpd.adminAddr = "root@localhost";
-        services.httpd.extraSubservices = [ { serviceType = "trac"; } ];
-        environment.systemPackages = [ pkgs.pythonPackages.trac pkgs.subversion ];
-      };
-
-    client =
-      { ... }:
-      { imports = [ ./common/x11.nix ];
-        services.xserver.desktopManager.plasma5.enable = true;
-      };
-  };
-
-  testScript =
-    ''
-      startAll;
-
-      $postgresql->waitForUnit("postgresql");
-      $postgresql->succeed("createdb trac");
-
-      $webserver->succeed("mkdir -p /repos/trac");
-      $webserver->succeed("svnadmin create /repos/trac");
-
-      $webserver->waitForUnit("httpd");
-      $webserver->waitForFile("/var/trac");
-      $webserver->succeed("mkdir -p /var/trac/projects/test");
-      $webserver->succeed("PYTHONPATH=${pkgs.pythonPackages.psycopg2}/lib/${pkgs.python.libPrefix}/site-packages trac-admin /var/trac/projects/test initenv Test postgres://root\@postgresql/trac svn /repos/trac");
-
-      $client->waitForX;
-      $client->execute("konqueror http://webserver/projects/test &");
-      $client->waitForWindow(qr/Test.*Konqueror/);
-      $client->sleep(30); # loading takes a long time
-
-      $client->screenshot("screen");
-    '';
-})
diff --git a/nixos/tests/wordpress.nix b/nixos/tests/wordpress.nix
index 5003e25a7d5b..774ef6293b51 100644
--- a/nixos/tests/wordpress.nix
+++ b/nixos/tests/wordpress.nix
@@ -6,48 +6,37 @@ import ./make-test.nix ({ pkgs, ... }:
     maintainers = [ grahamc ]; # under duress!
   };
 
-  nodes =
-    { web =
-        { pkgs, ... }:
-        {
-          services.mysql = {
-            enable = true;
-            package = pkgs.mysql;
-          };
-          services.httpd = {
-            enable = true;
-            logPerVirtualHost = true;
-            adminAddr="js@lastlog.de";
-
-            virtualHosts = [
-              {
-                hostName = "wordpress";
-                extraSubservices =
-                  [
-                    {
-                      serviceType = "wordpress";
-                      dbPassword = "wordpress";
-                      dbHost = "127.0.0.1";
-                      languages = [ "de_DE" "en_GB" ];
-                    }
-                  ];
-              }
-            ];
-          };
-        };
-    };
-
-  testScript =
+  machine =
     { ... }:
-    ''
-      startAll;
+    { services.httpd.adminAddr = "webmaster@site.local";
+      services.httpd.logPerVirtualHost = true;
+
+      services.wordpress."site1.local" = {
+        database.tablePrefix = "site1_";
+      };
 
-      $web->waitForUnit("mysql");
-      $web->waitForUnit("httpd");
+      services.wordpress."site2.local" = {
+        database.tablePrefix = "site2_";
+      };
+
+      networking.hosts."127.0.0.1" = [ "site1.local" "site2.local" ];
+
+      # required for wordpress-init.service to succeed
+      systemd.tmpfiles.rules = [
+        "F /var/lib/wordpress/site1.local/secret-keys.php 0440 wordpress wwwrun - -"
+        "F /var/lib/wordpress/site2.local/secret-keys.php 0440 wordpress wwwrun - -"
+      ];
+    };
 
-      $web->succeed("curl -L 127.0.0.1:80 | grep 'Welcome to the famous'");
+  testScript = ''
+    startAll;
 
+    $machine->waitForUnit("httpd");
+    $machine->waitForUnit("phpfpm-wordpress-site1.local");
+    $machine->waitForUnit("phpfpm-wordpress-site2.local");
 
-    '';
+    $machine->succeed("curl -L site1.local | grep 'Welcome to the famous'");
+    $machine->succeed("curl -L site2.local | grep 'Welcome to the famous'");
+  '';
 
 })