about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorrembo10 <801525+rembo10@users.noreply.github.com>2019-02-18 09:14:14 +0800
committerGitHub <noreply@github.com>2019-02-18 09:14:14 +0800
commit8e151c1e86c88995b10328f600f3c54f2af399a5 (patch)
treeb6f2df0c43409a2f3e2fef0897e82135e06658db /nixos
parent8d1ad4317c9a90724effa4a72bf18f1e7c0847e4 (diff)
parentfdc9cbb793c8a90bda0e25acd3fa38ef0200a001 (diff)
Merge branch 'master' into headphones
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/Makefile2
-rw-r--r--nixos/doc/manual/administration/container-networking.xml4
-rw-r--r--nixos/doc/manual/administration/declarative-containers.xml2
-rw-r--r--nixos/doc/manual/configuration/adding-custom-packages.xml2
-rw-r--r--nixos/doc/manual/configuration/config-file.xml4
-rw-r--r--nixos/doc/manual/configuration/configuration.xml1
-rw-r--r--nixos/doc/manual/configuration/firewall.xml9
-rw-r--r--nixos/doc/manual/configuration/linux-kernel.xml19
-rw-r--r--nixos/doc/manual/configuration/modularity.xml27
-rw-r--r--nixos/doc/manual/configuration/profiles.xml39
-rw-r--r--nixos/doc/manual/configuration/profiles/all-hardware.xml20
-rw-r--r--nixos/doc/manual/configuration/profiles/base.xml15
-rw-r--r--nixos/doc/manual/configuration/profiles/clone-config.xml14
-rw-r--r--nixos/doc/manual/configuration/profiles/demo.xml13
-rw-r--r--nixos/doc/manual/configuration/profiles/docker-container.xml15
-rw-r--r--nixos/doc/manual/configuration/profiles/graphical.xml21
-rw-r--r--nixos/doc/manual/configuration/profiles/hardened.xml22
-rw-r--r--nixos/doc/manual/configuration/profiles/headless.xml18
-rw-r--r--nixos/doc/manual/configuration/profiles/installation-device.xml35
-rw-r--r--nixos/doc/manual/configuration/profiles/minimal.xml17
-rw-r--r--nixos/doc/manual/configuration/profiles/qemu-guest.xml16
-rw-r--r--nixos/doc/manual/configuration/wireless.xml5
-rw-r--r--nixos/doc/manual/configuration/x-windows.xml4
-rw-r--r--nixos/doc/manual/default.nix8
-rw-r--r--nixos/doc/manual/development/building-parts.xml2
-rw-r--r--nixos/doc/manual/development/option-types.xml13
-rw-r--r--nixos/doc/manual/development/running-nixos-tests-interactively.xml10
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.xml2
-rw-r--r--nixos/doc/manual/installation/installing-behind-a-proxy.xml47
-rw-r--r--nixos/doc/manual/installation/installing-usb.xml50
-rw-r--r--nixos/doc/manual/installation/installing-virtualbox-guest.xml8
-rw-r--r--nixos/doc/manual/installation/installing.xml707
-rw-r--r--nixos/doc/manual/man-nixos-generate-config.xml8
-rw-r--r--nixos/doc/manual/man-nixos-rebuild.xml53
-rw-r--r--nixos/doc/manual/manual.xml4
-rw-r--r--nixos/doc/manual/release-notes/rl-1412.xml2
-rw-r--r--nixos/doc/manual/release-notes/rl-1509.xml10
-rw-r--r--nixos/doc/manual/release-notes/rl-1809.xml702
-rw-r--r--nixos/doc/manual/release-notes/rl-1903.xml485
-rw-r--r--nixos/lib/build-vms.nix20
-rw-r--r--nixos/lib/eval-config.nix3
-rw-r--r--nixos/lib/make-channel.nix4
-rw-r--r--nixos/lib/make-disk-image.nix9
-rw-r--r--nixos/lib/make-ext4-fs.nix80
-rw-r--r--nixos/lib/make-iso9660-image.sh5
-rw-r--r--nixos/lib/make-squashfs.nix5
-rw-r--r--nixos/lib/make-system-tarball.nix20
-rw-r--r--nixos/lib/make-system-tarball.sh9
-rw-r--r--nixos/lib/test-driver/Logger.pm3
-rw-r--r--nixos/lib/test-driver/Machine.pm6
-rw-r--r--nixos/lib/testing.nix41
-rw-r--r--nixos/lib/utils.nix5
-rwxr-xr-xnixos/maintainers/scripts/gce/create-gce.sh4
-rw-r--r--nixos/maintainers/scripts/openstack/nova-image.nix26
-rw-r--r--nixos/maintainers/scripts/openstack/openstack-image.nix26
-rw-r--r--nixos/modules/config/appstream.nix25
-rw-r--r--nixos/modules/config/fonts/fontconfig-penultimate.nix2
-rw-r--r--nixos/modules/config/gtk/gtk-icon-cache.nix86
-rw-r--r--nixos/modules/config/i18n.nix14
-rw-r--r--nixos/modules/config/iproute2.nix35
-rw-r--r--nixos/modules/config/krb5/default.nix2
-rw-r--r--nixos/modules/config/ldap.nix56
-rw-r--r--nixos/modules/config/networking.nix75
-rw-r--r--nixos/modules/config/no-x-libs.nix2
-rw-r--r--nixos/modules/config/nsswitch.nix8
-rw-r--r--nixos/modules/config/pulseaudio.nix37
-rw-r--r--nixos/modules/config/shells-environment.nix12
-rw-r--r--nixos/modules/config/system-path.nix14
-rw-r--r--nixos/modules/config/users-groups.nix6
-rw-r--r--nixos/modules/config/xdg/mime.nix4
-rw-r--r--nixos/modules/config/xdg/sounds.nix22
-rw-r--r--nixos/modules/config/zram.nix81
-rw-r--r--nixos/modules/hardware/all-firmware.nix2
-rw-r--r--nixos/modules/hardware/bladeRF.nix28
-rw-r--r--nixos/modules/hardware/ckb-next.nix49
-rw-r--r--nixos/modules/hardware/ckb.nix40
-rw-r--r--nixos/modules/hardware/opengl.nix40
-rw-r--r--nixos/modules/hardware/raid/hpsa.nix4
-rw-r--r--nixos/modules/hardware/steam-hardware.nix25
-rw-r--r--nixos/modules/hardware/video/amdgpu-pro.nix4
-rw-r--r--nixos/modules/hardware/video/ati.nix4
-rw-r--r--nixos/modules/hardware/video/nvidia.nix136
-rw-r--r--nixos/modules/i18n/input-method/default.xml319
-rw-r--r--nixos/modules/installer/cd-dvd/channel.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix49
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix63
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix49
-rw-r--r--nixos/modules/installer/cd-dvd/installation-cd-minimal.nix2
-rw-r--r--nixos/modules/installer/cd-dvd/iso-image.nix86
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix7
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-aarch64.nix10
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix9
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix9
-rw-r--r--nixos/modules/installer/cd-dvd/sd-image.nix4
-rw-r--r--nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix2
-rw-r--r--nixos/modules/installer/tools/nix-fallback-paths.nix8
-rw-r--r--nixos/modules/installer/tools/nixos-build-vms/build-vms.nix6
-rw-r--r--nixos/modules/installer/tools/nixos-generate-config.pl33
-rw-r--r--nixos/modules/installer/tools/nixos-install.sh1
-rw-r--r--nixos/modules/installer/tools/nixos-option.sh10
-rw-r--r--nixos/modules/installer/tools/nixos-rebuild.sh4
-rw-r--r--nixos/modules/installer/tools/tools.nix2
-rw-r--r--nixos/modules/installer/virtualbox-demo.nix38
-rw-r--r--nixos/modules/misc/documentation.nix102
-rw-r--r--nixos/modules/misc/ids.nix38
-rw-r--r--nixos/modules/misc/nixpkgs.nix57
-rw-r--r--nixos/modules/misc/version.nix10
-rw-r--r--nixos/modules/module-list.nix54
-rw-r--r--nixos/modules/profiles/base.nix7
-rw-r--r--nixos/modules/profiles/clone-config.nix9
-rw-r--r--nixos/modules/profiles/docker-container.nix16
-rw-r--r--nixos/modules/profiles/graphical.nix7
-rw-r--r--nixos/modules/profiles/hardened.nix39
-rw-r--r--nixos/modules/profiles/headless.nix1
-rw-r--r--nixos/modules/profiles/installation-device.nix8
-rw-r--r--nixos/modules/profiles/minimal.nix3
-rw-r--r--nixos/modules/programs/adb.nix3
-rw-r--r--nixos/modules/programs/bash/bash.nix22
-rw-r--r--nixos/modules/programs/command-not-found/command-not-found.nix2
-rw-r--r--nixos/modules/programs/digitalbitbox/doc.xml97
-rw-r--r--nixos/modules/programs/dmrconfig.nix38
-rw-r--r--nixos/modules/programs/fish.nix13
-rw-r--r--nixos/modules/programs/gnupg.nix2
-rw-r--r--nixos/modules/programs/iotop.nix17
-rw-r--r--nixos/modules/programs/light.nix5
-rw-r--r--nixos/modules/programs/mininet.nix39
-rw-r--r--nixos/modules/programs/nano.nix5
-rw-r--r--nixos/modules/programs/nm-applet.nix14
-rw-r--r--nixos/modules/programs/plotinus.xml37
-rw-r--r--nixos/modules/programs/rootston.nix103
-rw-r--r--nixos/modules/programs/shell.nix10
-rw-r--r--nixos/modules/programs/singularity.nix21
-rw-r--r--nixos/modules/programs/ssh.nix12
-rw-r--r--nixos/modules/programs/sway-beta.nix91
-rw-r--r--nixos/modules/programs/sway.nix13
-rw-r--r--nixos/modules/programs/thefuck.nix2
-rw-r--r--nixos/modules/programs/wavemon.nix28
-rw-r--r--nixos/modules/programs/way-cooler.nix2
-rw-r--r--nixos/modules/programs/xss-lock.nix3
-rw-r--r--nixos/modules/programs/yabar.nix17
-rw-r--r--nixos/modules/programs/zsh/oh-my-zsh.xml188
-rw-r--r--nixos/modules/programs/zsh/zsh-autosuggestions.nix8
-rw-r--r--nixos/modules/programs/zsh/zsh-syntax-highlighting.nix22
-rw-r--r--nixos/modules/programs/zsh/zsh.nix12
-rw-r--r--nixos/modules/rename.nix14
-rw-r--r--nixos/modules/security/acme.xml104
-rw-r--r--nixos/modules/security/apparmor-suid.nix2
-rw-r--r--nixos/modules/security/dhparams.nix2
-rw-r--r--nixos/modules/security/google_oslogin.nix68
-rw-r--r--nixos/modules/security/hidepid.xml37
-rw-r--r--nixos/modules/security/lock-kernel-modules.nix4
-rw-r--r--nixos/modules/security/misc.nix125
-rw-r--r--nixos/modules/security/pam.nix150
-rw-r--r--nixos/modules/security/polkit.nix10
-rw-r--r--nixos/modules/security/rngd.nix4
-rw-r--r--nixos/modules/security/wrappers/default.nix29
-rw-r--r--nixos/modules/services/admin/salt/master.nix3
-rw-r--r--nixos/modules/services/admin/salt/minion.nix21
-rw-r--r--nixos/modules/services/amqp/rabbitmq.nix109
-rw-r--r--nixos/modules/services/backup/bacula.nix24
-rw-r--r--nixos/modules/services/backup/borgbackup.nix3
-rw-r--r--nixos/modules/services/backup/postgresql-backup.nix53
-rw-r--r--nixos/modules/services/backup/restic.nix7
-rw-r--r--nixos/modules/services/cluster/kubernetes/default.nix16
-rw-r--r--nixos/modules/services/cluster/kubernetes/dns.nix386
-rw-r--r--nixos/modules/services/computing/slurm/slurm.nix127
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/master.nix67
-rw-r--r--nixos/modules/services/continuous-integration/buildbot/worker.nix77
-rw-r--r--nixos/modules/services/continuous-integration/jenkins/job-builder.nix36
-rw-r--r--nixos/modules/services/databases/aerospike.nix1
-rw-r--r--nixos/modules/services/databases/cassandra.nix22
-rw-r--r--nixos/modules/services/databases/clickhouse.nix5
-rw-r--r--nixos/modules/services/databases/cockroachdb.nix217
-rw-r--r--nixos/modules/services/databases/foundationdb.xml621
-rw-r--r--nixos/modules/services/databases/mysql.nix55
-rw-r--r--nixos/modules/services/databases/openldap.nix9
-rw-r--r--nixos/modules/services/databases/postgresql.nix17
-rw-r--r--nixos/modules/services/databases/postgresql.xml96
-rw-r--r--nixos/modules/services/desktops/accountsservice.nix4
-rw-r--r--nixos/modules/services/desktops/flatpak.xml71
-rw-r--r--nixos/modules/services/desktops/geoclue2.nix2
-rw-r--r--nixos/modules/services/desktops/gnome3/file-roller.nix32
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-keyring.nix2
-rw-r--r--nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix18
-rw-r--r--nixos/modules/services/desktops/gnome3/seahorse.nix2
-rw-r--r--nixos/modules/services/desktops/gsignond.nix43
-rw-r--r--nixos/modules/services/desktops/profile-sync-daemon.nix136
-rw-r--r--nixos/modules/services/development/jupyter/default.nix1
-rw-r--r--nixos/modules/services/editors/emacs.xml821
-rw-r--r--nixos/modules/services/games/minecraft-server.nix201
-rw-r--r--nixos/modules/services/games/minetest-server.nix3
-rw-r--r--nixos/modules/services/hardware/bluetooth.nix7
-rw-r--r--nixos/modules/services/hardware/fwupd.nix26
-rw-r--r--nixos/modules/services/hardware/lirc.nix100
-rw-r--r--nixos/modules/services/hardware/ratbagd.nix32
-rw-r--r--nixos/modules/services/hardware/tlp.nix2
-rw-r--r--nixos/modules/services/hardware/trezord.nix17
-rw-r--r--nixos/modules/services/hardware/triggerhappy.nix114
-rw-r--r--nixos/modules/services/hardware/upower.nix26
-rw-r--r--nixos/modules/services/hardware/vdr.nix81
-rw-r--r--nixos/modules/services/logging/journaldriver.nix2
-rw-r--r--nixos/modules/services/mail/clamsmtp.nix2
-rw-r--r--nixos/modules/services/mail/dkimproxy-out.nix2
-rw-r--r--nixos/modules/services/mail/dovecot.nix6
-rw-r--r--nixos/modules/services/mail/exim.nix18
-rw-r--r--nixos/modules/services/mail/postfix.nix18
-rw-r--r--nixos/modules/services/mail/rmilter.nix7
-rw-r--r--nixos/modules/services/mail/roundcube.nix167
-rw-r--r--nixos/modules/services/mail/rspamd.nix266
-rw-r--r--nixos/modules/services/mail/rss2email.nix136
-rw-r--r--nixos/modules/services/misc/airsonic.nix36
-rw-r--r--nixos/modules/services/misc/bees.nix123
-rw-r--r--nixos/modules/services/misc/docker-registry.nix2
-rw-r--r--nixos/modules/services/misc/emby.nix19
-rw-r--r--nixos/modules/services/misc/exhibitor.nix3
-rw-r--r--nixos/modules/services/misc/gitea.nix29
-rw-r--r--nixos/modules/services/misc/gitlab.nix227
-rw-r--r--nixos/modules/services/misc/gitlab.xml124
-rw-r--r--nixos/modules/services/misc/home-assistant.nix82
-rw-r--r--nixos/modules/services/misc/lidarr.nix46
-rw-r--r--nixos/modules/services/misc/matrix-synapse.nix11
-rw-r--r--nixos/modules/services/misc/nix-daemon.nix29
-rw-r--r--nixos/modules/services/misc/nix-optimise.nix2
-rw-r--r--nixos/modules/services/misc/nixos-manual.nix133
-rw-r--r--nixos/modules/services/misc/nscd-sssd.conf36
-rw-r--r--nixos/modules/services/misc/nzbget.nix57
-rw-r--r--nixos/modules/services/misc/packagekit.nix3
-rw-r--r--nixos/modules/services/misc/plex.nix3
-rw-r--r--nixos/modules/services/misc/pykms.nix67
-rw-r--r--nixos/modules/services/misc/redmine.nix200
-rw-r--r--nixos/modules/services/misc/rippled.nix346
-rw-r--r--nixos/modules/services/misc/sickbeard.nix92
-rw-r--r--nixos/modules/services/misc/sonarr.nix60
-rw-r--r--nixos/modules/services/misc/sssd.nix1
-rw-r--r--nixos/modules/services/misc/taskserver/doc.xml209
-rw-r--r--nixos/modules/services/misc/weechat.nix4
-rw-r--r--nixos/modules/services/misc/weechat.xml75
-rw-r--r--nixos/modules/services/misc/zoneminder.nix360
-rw-r--r--nixos/modules/services/monitoring/alerta.nix116
-rw-r--r--nixos/modules/services/monitoring/apcupsd.nix2
-rw-r--r--nixos/modules/services/monitoring/collectd.nix2
-rw-r--r--nixos/modules/services/monitoring/datadog-agent.nix25
-rw-r--r--nixos/modules/services/monitoring/grafana-reporter.nix66
-rw-r--r--nixos/modules/services/monitoring/grafana.nix300
-rw-r--r--nixos/modules/services/monitoring/kapacitor.nix192
-rw-r--r--nixos/modules/services/monitoring/monit.nix33
-rw-r--r--nixos/modules/services/monitoring/munin.nix221
-rw-r--r--nixos/modules/services/monitoring/netdata.nix74
-rw-r--r--nixos/modules/services/monitoring/osquery.nix2
-rw-r--r--nixos/modules/services/monitoring/prometheus/alertmanager.nix98
-rw-r--r--nixos/modules/services/monitoring/prometheus/default.nix43
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.nix23
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters.xml148
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/bind.nix55
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix24
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/snmp.nix8
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/tor.nix45
-rw-r--r--nixos/modules/services/monitoring/prometheus/exporters/varnish.nix1
-rw-r--r--nixos/modules/services/monitoring/systemhealth.nix2
-rw-r--r--nixos/modules/services/network-filesystems/diod.nix1
-rw-r--r--nixos/modules/services/network-filesystems/glusterfs.nix8
-rw-r--r--nixos/modules/services/network-filesystems/ipfs.nix2
-rw-r--r--nixos/modules/services/network-filesystems/openafs/client.nix2
-rw-r--r--nixos/modules/services/network-filesystems/openafs/server.nix1
-rw-r--r--nixos/modules/services/networking/bitlbee.nix43
-rw-r--r--nixos/modules/services/networking/charybdis.nix2
-rw-r--r--nixos/modules/services/networking/chrony.nix57
-rw-r--r--nixos/modules/services/networking/consul.nix9
-rw-r--r--nixos/modules/services/networking/dhcpcd.nix2
-rw-r--r--nixos/modules/services/networking/dnscrypt-proxy.xml69
-rw-r--r--nixos/modules/services/networking/epmd.nix56
-rw-r--r--nixos/modules/services/networking/eternal-terminal.nix89
-rw-r--r--nixos/modules/services/networking/firefox/sync-server.nix26
-rw-r--r--nixos/modules/services/networking/firewall.nix19
-rw-r--r--nixos/modules/services/networking/flannel.nix41
-rw-r--r--nixos/modules/services/networking/flashpolicyd.nix10
-rw-r--r--nixos/modules/services/networking/hostapd.nix6
-rw-r--r--nixos/modules/services/networking/iwd.nix2
-rw-r--r--nixos/modules/services/networking/miniupnpd.nix24
-rw-r--r--nixos/modules/services/networking/murmur.nix2
-rw-r--r--nixos/modules/services/networking/mxisd.nix125
-rw-r--r--nixos/modules/services/networking/ndppd.nix170
-rw-r--r--nixos/modules/services/networking/networkmanager.nix2
-rw-r--r--nixos/modules/services/networking/nsd.nix12
-rw-r--r--nixos/modules/services/networking/ntpd.nix40
-rw-r--r--nixos/modules/services/networking/nullidentdmod.nix34
-rw-r--r--nixos/modules/services/networking/nylon.nix1
-rw-r--r--nixos/modules/services/networking/oidentd.nix3
-rw-r--r--nixos/modules/services/networking/pptpd.nix2
-rw-r--r--nixos/modules/services/networking/prosody.nix2
-rw-r--r--nixos/modules/services/networking/redsocks.nix2
-rw-r--r--nixos/modules/services/networking/shairport-sync.nix3
-rw-r--r--nixos/modules/services/networking/ssh/sshd.nix11
-rw-r--r--nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix83
-rw-r--r--nixos/modules/services/networking/syncthing-relay.nix121
-rw-r--r--nixos/modules/services/networking/syncthing.nix35
-rw-r--r--nixos/modules/services/networking/teamspeak3.nix24
-rw-r--r--nixos/modules/services/networking/tinc.nix8
-rw-r--r--nixos/modules/services/networking/unifi.nix4
-rw-r--r--nixos/modules/services/networking/wireguard.nix2
-rw-r--r--nixos/modules/services/networking/wpa_supplicant.nix25
-rw-r--r--nixos/modules/services/networking/xl2tpd.nix2
-rw-r--r--nixos/modules/services/networking/xrdp.nix8
-rw-r--r--nixos/modules/services/networking/zerotierone.nix3
-rw-r--r--nixos/modules/services/networking/znc.nix431
-rw-r--r--nixos/modules/services/networking/znc/default.nix306
-rw-r--r--nixos/modules/services/networking/znc/options.nix270
-rw-r--r--nixos/modules/services/printing/cupsd.nix6
-rw-r--r--nixos/modules/services/search/elasticsearch-curator.nix5
-rw-r--r--nixos/modules/services/search/kibana.nix5
-rw-r--r--nixos/modules/services/search/solr.nix181
-rw-r--r--nixos/modules/services/security/certmgr.nix11
-rw-r--r--nixos/modules/services/security/clamav.nix7
-rw-r--r--nixos/modules/services/security/munge.nix4
-rw-r--r--nixos/modules/services/security/nginx-sso.nix58
-rw-r--r--nixos/modules/services/security/sks.nix28
-rw-r--r--nixos/modules/services/security/sshguard.nix99
-rw-r--r--nixos/modules/services/security/tor.nix14
-rw-r--r--nixos/modules/services/system/cloud-init.nix37
-rw-r--r--nixos/modules/services/system/kerberos.nix64
-rw-r--r--nixos/modules/services/system/kerberos/default.nix80
-rw-r--r--nixos/modules/services/system/kerberos/heimdal.nix68
-rw-r--r--nixos/modules/services/system/kerberos/mit.nix68
-rw-r--r--nixos/modules/services/system/nscd.conf34
-rw-r--r--nixos/modules/services/system/saslauthd.nix2
-rw-r--r--nixos/modules/services/torrent/transmission.nix3
-rw-r--r--nixos/modules/services/ttys/kmscon.nix13
-rw-r--r--nixos/modules/services/web-apps/atlassian/confluence.nix11
-rw-r--r--nixos/modules/services/web-apps/atlassian/crowd.nix14
-rw-r--r--nixos/modules/services/web-apps/atlassian/jira.nix11
-rw-r--r--nixos/modules/services/web-apps/codimd.nix920
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix626
-rw-r--r--nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix157
-rw-r--r--nixos/modules/services/web-apps/matomo-doc.xml155
-rw-r--r--nixos/modules/services/web-apps/matomo.nix112
-rw-r--r--nixos/modules/services/web-apps/nextcloud.nix489
-rw-r--r--nixos/modules/services/web-apps/nextcloud.xml99
-rw-r--r--nixos/modules/services/web-apps/quassel-webserver.nix101
-rw-r--r--nixos/modules/services/web-apps/selfoss.nix4
-rw-r--r--nixos/modules/services/web-apps/tt-rss.nix93
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/default.nix25
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/limesurvey.nix2
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/mediawiki.nix10
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/owncloud.nix608
-rw-r--r--nixos/modules/services/web-servers/apache-httpd/wordpress.nix8
-rw-r--r--nixos/modules/services/web-servers/lighttpd/collectd.nix2
-rw-r--r--nixos/modules/services/web-servers/nginx/default.nix52
-rw-r--r--nixos/modules/services/web-servers/nginx/location-options.nix10
-rw-r--r--nixos/modules/services/web-servers/nginx/vhost-options.nix2
-rw-r--r--nixos/modules/services/web-servers/phpfpm/default.nix38
-rw-r--r--nixos/modules/services/web-servers/phpfpm/pool-options.nix24
-rw-r--r--nixos/modules/services/web-servers/tomcat.nix58
-rw-r--r--nixos/modules/services/x11/compton.nix6
-rw-r--r--nixos/modules/services/x11/desktop-managers/gnome3.nix93
-rw-r--r--nixos/modules/services/x11/desktop-managers/lxqt.nix11
-rw-r--r--nixos/modules/services/x11/desktop-managers/mate.nix10
-rw-r--r--nixos/modules/services/x11/desktop-managers/plasma5.nix16
-rw-r--r--nixos/modules/services/x11/desktop-managers/xfce.nix3
-rw-r--r--nixos/modules/services/x11/display-managers/default.nix33
-rw-r--r--nixos/modules/services/x11/display-managers/gdm.nix19
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix159
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix37
-rw-r--r--nixos/modules/services/x11/display-managers/lightdm.nix44
-rw-r--r--nixos/modules/services/x11/display-managers/sddm.nix13
-rw-r--r--nixos/modules/services/x11/display-managers/startx.nix44
-rw-r--r--nixos/modules/services/x11/display-managers/xpra.nix44
-rw-r--r--nixos/modules/services/x11/gdk-pixbuf.nix45
-rw-r--r--nixos/modules/services/x11/hardware/libinput.nix2
-rw-r--r--nixos/modules/services/x11/urxvtd.nix56
-rw-r--r--nixos/modules/services/x11/xautolock.nix2
-rw-r--r--nixos/modules/services/x11/xserver.nix17
-rw-r--r--nixos/modules/system/activation/activation-script.nix65
-rw-r--r--nixos/modules/system/activation/switch-to-configuration.pl3
-rw-r--r--nixos/modules/system/activation/top-level.nix72
-rw-r--r--nixos/modules/system/boot/initrd-network.nix30
-rw-r--r--nixos/modules/system/boot/initrd-ssh.nix1
-rw-r--r--nixos/modules/system/boot/kernel_config.nix137
-rw-r--r--nixos/modules/system/boot/kexec.nix2
-rw-r--r--nixos/modules/system/boot/loader/generations-dir/generations-dir.nix4
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix2
-rw-r--r--nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix4
-rw-r--r--nixos/modules/system/boot/loader/grub/grub.nix2
-rw-r--r--nixos/modules/system/boot/loader/init-script/init-script.nix4
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/builder_uboot.sh29
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix10
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh (renamed from nixos/modules/system/boot/loader/raspberrypi/builder.sh)74
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix30
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix (renamed from nixos/modules/system/boot/loader/raspberrypi/builder_uboot.nix)22
-rw-r--r--nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh38
-rw-r--r--nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix2
-rw-r--r--nixos/modules/system/boot/luksroot.nix69
-rw-r--r--nixos/modules/system/boot/stage-1-init.sh9
-rw-r--r--nixos/modules/system/boot/stage-1.nix32
-rw-r--r--nixos/modules/system/boot/stage-2-init.sh8
-rw-r--r--nixos/modules/system/boot/systemd-lib.nix2
-rw-r--r--nixos/modules/system/boot/systemd-nspawn.nix17
-rw-r--r--nixos/modules/system/boot/systemd-unit-options.nix11
-rw-r--r--nixos/modules/system/boot/systemd.nix80
-rw-r--r--nixos/modules/system/etc/etc.nix2
-rw-r--r--nixos/modules/tasks/auto-upgrade.nix (renamed from nixos/modules/installer/tools/auto-upgrade.nix)2
-rw-r--r--nixos/modules/tasks/cpu-freq.nix74
-rw-r--r--nixos/modules/tasks/encrypted-devices.nix6
-rw-r--r--nixos/modules/tasks/filesystems.nix8
-rw-r--r--nixos/modules/tasks/filesystems/bcachefs.nix61
-rw-r--r--nixos/modules/tasks/filesystems/f2fs.nix6
-rw-r--r--nixos/modules/tasks/filesystems/zfs.nix3
-rw-r--r--nixos/modules/tasks/network-interfaces-scripted.nix3
-rw-r--r--nixos/modules/testing/service-runner.nix27
-rw-r--r--nixos/modules/virtualisation/amazon-image.nix37
-rw-r--r--nixos/modules/virtualisation/container-config.nix4
-rw-r--r--nixos/modules/virtualisation/containers.nix62
-rw-r--r--nixos/modules/virtualisation/docker-image.nix38
-rw-r--r--nixos/modules/virtualisation/docker-preloader.nix134
-rw-r--r--nixos/modules/virtualisation/ec2-amis.nix19
-rw-r--r--nixos/modules/virtualisation/ec2-metadata-fetcher.nix23
-rw-r--r--nixos/modules/virtualisation/gce-images.nix4
-rw-r--r--nixos/modules/virtualisation/google-compute-config.nix236
-rw-r--r--nixos/modules/virtualisation/google-compute-image.nix387
-rw-r--r--nixos/modules/virtualisation/hyperv-guest.nix35
-rw-r--r--nixos/modules/virtualisation/kvmgt.nix8
-rw-r--r--nixos/modules/virtualisation/libvirtd.nix2
-rw-r--r--nixos/modules/virtualisation/nova-config.nix60
-rw-r--r--nixos/modules/virtualisation/openstack-config.nix57
-rw-r--r--nixos/modules/virtualisation/parallels-guest.nix4
-rw-r--r--nixos/modules/virtualisation/qemu-guest-agent.nix2
-rw-r--r--nixos/modules/virtualisation/qemu-vm.nix5
-rw-r--r--nixos/modules/virtualisation/virtualbox-image.nix10
-rw-r--r--nixos/modules/virtualisation/xe-guest-utilities.nix2
-rw-r--r--nixos/release-combined.nix49
-rw-r--r--nixos/release.nix263
-rw-r--r--nixos/tests/acme.nix29
-rw-r--r--nixos/tests/all-tests.nix234
-rw-r--r--nixos/tests/atd.nix1
-rw-r--r--nixos/tests/bees.nix55
-rw-r--r--nixos/tests/bittorrent.nix101
-rw-r--r--nixos/tests/boot.nix7
-rw-r--r--nixos/tests/borgbackup.nix3
-rw-r--r--nixos/tests/buildbot.nix219
-rw-r--r--nixos/tests/ceph.nix7
-rw-r--r--nixos/tests/certmgr.nix7
-rw-r--r--nixos/tests/chromium.nix15
-rw-r--r--nixos/tests/cjdns.nix3
-rw-r--r--nixos/tests/clickhouse.nix25
-rw-r--r--nixos/tests/cloud-init.nix7
-rw-r--r--nixos/tests/cockroachdb.nix126
-rw-r--r--nixos/tests/codimd.nix54
-rw-r--r--nixos/tests/common/ec2.nix49
-rw-r--r--nixos/tests/common/letsencrypt/common.nix27
-rw-r--r--nixos/tests/common/webroot/news-rss.xml15
-rw-r--r--nixos/tests/containers-bridge.nix18
-rw-r--r--nixos/tests/containers-extra_veth.nix2
-rw-r--r--nixos/tests/containers-imperative.nix11
-rw-r--r--nixos/tests/containers-ipv4.nix1
-rw-r--r--nixos/tests/containers-ipv6.nix1
-rw-r--r--nixos/tests/containers-portforward.nix1
-rw-r--r--nixos/tests/containers-restart_networking.nix1
-rw-r--r--nixos/tests/docker-preloader.nix27
-rw-r--r--nixos/tests/docker-tools.nix16
-rw-r--r--nixos/tests/ec2.nix69
-rw-r--r--nixos/tests/elk.nix10
-rw-r--r--nixos/tests/ferm.nix4
-rw-r--r--nixos/tests/flannel.nix5
-rw-r--r--nixos/tests/fwupd.nix2
-rw-r--r--nixos/tests/gdk-pixbuf.nix6
-rw-r--r--nixos/tests/gitea.nix79
-rw-r--r--nixos/tests/gitlab.nix21
-rw-r--r--nixos/tests/gnome3-gdm.nix32
-rw-r--r--nixos/tests/gnome3.nix4
-rw-r--r--nixos/tests/google-oslogin/default.nix52
-rw-r--r--nixos/tests/google-oslogin/server.nix29
-rw-r--r--nixos/tests/google-oslogin/server.py96
-rw-r--r--nixos/tests/handbrake.nix25
-rw-r--r--nixos/tests/hardened.nix22
-rw-r--r--nixos/tests/hibernate.nix2
-rw-r--r--nixos/tests/home-assistant.nix32
-rw-r--r--nixos/tests/hound.nix2
-rwxr-xr-xnixos/tests/hydra/create-trivial-project.sh3
-rw-r--r--nixos/tests/hydra/default.nix160
-rw-r--r--nixos/tests/i3wm.nix2
-rw-r--r--nixos/tests/incron.nix52
-rw-r--r--nixos/tests/installer.nix98
-rw-r--r--nixos/tests/jackett.nix18
-rw-r--r--nixos/tests/kafka.nix12
-rw-r--r--nixos/tests/kerberos/default.nix7
-rw-r--r--nixos/tests/kerberos/heimdal.nix53
-rw-r--r--nixos/tests/kerberos/mit.nix45
-rw-r--r--nixos/tests/keymap.nix9
-rw-r--r--nixos/tests/kubernetes/base.nix7
-rw-r--r--nixos/tests/kubernetes/dns.nix4
-rw-r--r--nixos/tests/kubernetes/kubernetes-common.nix1
-rw-r--r--nixos/tests/ldap.nix383
-rw-r--r--nixos/tests/lidarr.nix18
-rw-r--r--nixos/tests/make-test.nix8
-rw-r--r--nixos/tests/matrix-synapse.nix50
-rw-r--r--nixos/tests/misc.nix4
-rw-r--r--nixos/tests/mongodb.nix2
-rw-r--r--nixos/tests/munin.nix4
-rw-r--r--nixos/tests/mxisd.nix21
-rw-r--r--nixos/tests/nat.nix4
-rw-r--r--nixos/tests/ndppd.nix61
-rw-r--r--nixos/tests/neo4j.nix20
-rw-r--r--nixos/tests/networking.nix20
-rw-r--r--nixos/tests/nextcloud/basic.nix56
-rw-r--r--nixos/tests/nextcloud/default.nix9
-rw-r--r--nixos/tests/nextcloud/with-mysql-and-memcached.nix97
-rw-r--r--nixos/tests/nextcloud/with-postgresql-and-redis.nix130
-rw-r--r--nixos/tests/nexus.nix2
-rw-r--r--nixos/tests/nfs.nix2
-rw-r--r--nixos/tests/nginx-sso.nix44
-rw-r--r--nixos/tests/nix-ssh-serve.nix4
-rw-r--r--nixos/tests/opensmtpd.nix8
-rw-r--r--nixos/tests/openstack-image.nix88
-rw-r--r--nixos/tests/osrm-backend.nix53
-rw-r--r--nixos/tests/owncloud.nix39
-rw-r--r--nixos/tests/pam-u2f.nix23
-rw-r--r--nixos/tests/plasma5.nix21
-rw-r--r--nixos/tests/postgis.nix7
-rw-r--r--nixos/tests/postgresql.nix32
-rw-r--r--nixos/tests/predictable-interface-names.nix7
-rw-r--r--nixos/tests/printing.nix2
-rw-r--r--nixos/tests/prometheus-exporters.nix335
-rw-r--r--nixos/tests/prometheus.nix22
-rw-r--r--nixos/tests/prosody.nix3
-rw-r--r--nixos/tests/quagga.nix1
-rw-r--r--nixos/tests/radarr.nix18
-rw-r--r--nixos/tests/redmine.nix58
-rw-r--r--nixos/tests/roundcube.nix30
-rw-r--r--nixos/tests/rspamd.nix207
-rw-r--r--nixos/tests/rss2email.nix66
-rw-r--r--nixos/tests/rsyslogd.nix42
-rw-r--r--nixos/tests/run-in-machine.nix26
-rw-r--r--nixos/tests/sddm.nix7
-rw-r--r--nixos/tests/slurm.nix67
-rw-r--r--nixos/tests/solr.nix47
-rw-r--r--nixos/tests/sonarr.nix18
-rw-r--r--nixos/tests/statsd.nix51
-rw-r--r--nixos/tests/switch-test.nix13
-rw-r--r--nixos/tests/syncthing-relay.nix22
-rw-r--r--nixos/tests/systemd.nix5
-rw-r--r--nixos/tests/telegraf.nix30
-rw-r--r--nixos/tests/upnp.nix94
-rw-r--r--nixos/tests/virtualbox.nix13
-rw-r--r--nixos/tests/xmonad.nix2
-rw-r--r--nixos/tests/xss-lock.nix3
-rw-r--r--nixos/tests/yabar.nix16
-rw-r--r--nixos/tests/zfs.nix7
546 files changed, 20676 insertions, 7584 deletions
diff --git a/nixos/doc/manual/Makefile b/nixos/doc/manual/Makefile
index 2e9adf70c3963..b251a1f5e2c3d 100644
--- a/nixos/doc/manual/Makefile
+++ b/nixos/doc/manual/Makefile
@@ -4,7 +4,7 @@ all: manual-combined.xml format
 .PHONY: debug
 debug: generated manual-combined.xml
 
-manual-combined.xml: generated *.xml
+manual-combined.xml: generated *.xml **/*.xml
 	rm -f ./manual-combined.xml
 	nix-shell --packages xmloscopy \
 		--run "xmloscopy --docbook5 ./manual.xml ./manual-combined.xml"
diff --git a/nixos/doc/manual/administration/container-networking.xml b/nixos/doc/manual/administration/container-networking.xml
index 4b977d1d82eb4..2ee8bfdd50f1b 100644
--- a/nixos/doc/manual/administration/container-networking.xml
+++ b/nixos/doc/manual/administration/container-networking.xml
@@ -52,4 +52,8 @@ $ ping -c1 10.233.4.2
 networking.networkmanager.unmanaged = [ "interface-name:ve-*" ];
 </programlisting>
  </para>
+
+ <para>
+  You may need to restart your system for the changes to take effect.
+ </para>
 </section>
diff --git a/nixos/doc/manual/administration/declarative-containers.xml b/nixos/doc/manual/administration/declarative-containers.xml
index 2a98fb1262310..d03dbc4d70556 100644
--- a/nixos/doc/manual/administration/declarative-containers.xml
+++ b/nixos/doc/manual/administration/declarative-containers.xml
@@ -15,7 +15,7 @@ containers.database =
   { config =
       { config, pkgs, ... }:
       { <xref linkend="opt-services.postgresql.enable"/> = true;
-      <xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql96;
+      <xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_9_6;
       };
   };
 </programlisting>
diff --git a/nixos/doc/manual/configuration/adding-custom-packages.xml b/nixos/doc/manual/configuration/adding-custom-packages.xml
index 028a94275345d..cdcfa10b8200e 100644
--- a/nixos/doc/manual/configuration/adding-custom-packages.xml
+++ b/nixos/doc/manual/configuration/adding-custom-packages.xml
@@ -31,7 +31,7 @@ $ cd nixpkgs
  <para>
   The second possibility is to add the package outside of the Nixpkgs tree. For
   instance, here is how you specify a build of the
-  <link xlink:href="http://www.gnu.org/software/hello/">GNU Hello</link>
+  <link xlink:href="https://www.gnu.org/software/hello/">GNU Hello</link>
   package directly in <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-environment.systemPackages"/> =
diff --git a/nixos/doc/manual/configuration/config-file.xml b/nixos/doc/manual/configuration/config-file.xml
index 8a1a39c98c10d..c77cfe137baa2 100644
--- a/nixos/doc/manual/configuration/config-file.xml
+++ b/nixos/doc/manual/configuration/config-file.xml
@@ -197,10 +197,10 @@ swapDevices = [ { device = "/dev/disk/by-label/swap"; } ];
     pkgs.emacs
   ];
 
-<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql90;
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_10;
 </programlisting>
       The latter option definition changes the default PostgreSQL package used
-      by NixOS’s PostgreSQL service to 9.0. For more information on packages,
+      by NixOS’s PostgreSQL service to 10.x. For more information on packages,
       including how to add new ones, see <xref linkend="sec-custom-packages"/>.
      </para>
     </listitem>
diff --git a/nixos/doc/manual/configuration/configuration.xml b/nixos/doc/manual/configuration/configuration.xml
index 8d05dcd34b4d4..cebc4122c6c66 100644
--- a/nixos/doc/manual/configuration/configuration.xml
+++ b/nixos/doc/manual/configuration/configuration.xml
@@ -22,5 +22,6 @@
  <xi:include href="networking.xml" />
  <xi:include href="linux-kernel.xml" />
  <xi:include href="../generated/modules.xml" xpointer="xpointer(//section[@id='modules']/*)" />
+ <xi:include href="profiles.xml" />
 <!-- Apache; libvirtd virtualisation -->
 </part>
diff --git a/nixos/doc/manual/configuration/firewall.xml b/nixos/doc/manual/configuration/firewall.xml
index b66adcedce6e8..47a19ac82c0fe 100644
--- a/nixos/doc/manual/configuration/firewall.xml
+++ b/nixos/doc/manual/configuration/firewall.xml
@@ -34,13 +34,4 @@
   Similarly, UDP port ranges can be opened through
   <xref linkend="opt-networking.firewall.allowedUDPPortRanges"/>.
  </para>
-
- <para>
-  Also of interest is
-<programlisting>
-<xref linkend="opt-networking.firewall.allowPing"/> = true;
-</programlisting>
-  to allow the machine to respond to ping requests. (ICMPv6 pings are always
-  allowed.)
- </para>
 </section>
diff --git a/nixos/doc/manual/configuration/linux-kernel.xml b/nixos/doc/manual/configuration/linux-kernel.xml
index 2f766f2b32f77..644d3a33ffd2c 100644
--- a/nixos/doc/manual/configuration/linux-kernel.xml
+++ b/nixos/doc/manual/configuration/linux-kernel.xml
@@ -84,18 +84,17 @@ nixpkgs.config.packageOverrides = pkgs:
     allowImportFromDerivation = true;
   };
   ]]></screen>
-
-You can edit the config with this snippet (by default <command>make menuconfig</command> won't work
-  out of the box on nixos):
-  <screen><![CDATA[
+   You can edit the config with this snippet (by default <command>make
+   menuconfig</command> won't work out of the box on nixos):
+<screen><![CDATA[
       nix-shell -E 'with import <nixpkgs> {}; kernelToOverride.overrideAttrs (o: {nativeBuildInputs=o.nativeBuildInputs ++ [ pkgconfig ncurses ];})'
   ]]></screen>
-
-
-  or you can let nixpkgs generate the configuration.
-  Nixpkgs generates it via answering the interactive kernel utility <command>make config</command>.
-  The answers depend on parameters passed to <filename>pkgs/os-specific/linux/kernel/generic.nix</filename>
-  (which you can influence by overriding <literal>extraConfig, autoModules, modDirVersion, preferBuiltin, extraConfig</literal>).
+   or you can let nixpkgs generate the configuration. Nixpkgs generates it via
+   answering the interactive kernel utility <command>make config</command>. The
+   answers depend on parameters passed to
+   <filename>pkgs/os-specific/linux/kernel/generic.nix</filename> (which you
+   can influence by overriding <literal>extraConfig, autoModules,
+   modDirVersion, preferBuiltin, extraConfig</literal>).
 <screen><![CDATA[
 
   mptcp93.override ({
diff --git a/nixos/doc/manual/configuration/modularity.xml b/nixos/doc/manual/configuration/modularity.xml
index 298ffd661f678..5ff5bc22c85ed 100644
--- a/nixos/doc/manual/configuration/modularity.xml
+++ b/nixos/doc/manual/configuration/modularity.xml
@@ -113,12 +113,10 @@ $ nixos-option <xref linkend="opt-boot.kernelModules"/>
 [ "tun" "ipv6" "loop" <replaceable>...</replaceable> ]
 </screen>
   Interactive exploration of the configuration is possible using
-  <command
-xlink:href="https://github.com/edolstra/nix-repl">nix-repl</command>,
-  a read-eval-print loop for Nix expressions. It’s not installed by default;
-  run <literal>nix-env -i nix-repl</literal> to get it. A typical use:
+  <command>nix repl</command>, a read-eval-print loop for Nix expressions.
+  A typical use:
 <screen>
-$ nix-repl '&lt;nixpkgs/nixos>'
+$ nix repl '&lt;nixpkgs/nixos>'
 
 nix-repl> config.<xref linkend="opt-networking.hostName"/>
 "mandark"
@@ -127,4 +125,23 @@ nix-repl> map (x: x.hostName) config.<xref linkend="opt-services.httpd.virtualHo
 [ "example.org" "example.gov" ]
 </screen>
  </para>
+
+ <para>
+   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, ... }:
+
+     let netConfig = { hostName }: {
+       networking.hostName = hostName;
+       networking.useDHCP = false;
+    };
+
+    in
+
+    { imports = [ (netConfig "nixos.localdomain") ]; }
+  </screen>
+</para>
 </section>
diff --git a/nixos/doc/manual/configuration/profiles.xml b/nixos/doc/manual/configuration/profiles.xml
new file mode 100644
index 0000000000000..92c0f6202f283
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles.xml
@@ -0,0 +1,39 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="ch-profiles">
+ <title>Profiles</title>
+ <para>
+  In some cases, it may be desirable to take advantage of commonly-used,
+  predefined configurations provided by nixpkgs, but different from those that
+  come as default. This is a role fulfilled by NixOS's Profiles, which come as
+  files living in <filename>&lt;nixpkgs/nixos/modules/profiles&gt;</filename>.
+  That is to say, expected usage is to add them to the imports list of your
+  <filename>/etc/configuration.nix</filename> as such:
+ </para>
+ <programlisting>
+  imports = [
+   &lt;nixpkgs/nixos/modules/profiles/profile-name.nix&gt;
+  ];
+ </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.
+ </para>
+ <para>
+  What follows is a brief explanation on the purpose and use-case for each
+  profile. Detailing each option configured by each one is out of scope.
+ </para>
+ <xi:include href="profiles/all-hardware.xml" />
+ <xi:include href="profiles/base.xml" />
+ <xi:include href="profiles/clone-config.xml" />
+ <xi:include href="profiles/demo.xml" />
+ <xi:include href="profiles/docker-container.xml" />
+ <xi:include href="profiles/graphical.xml" />
+ <xi:include href="profiles/hardened.xml" />
+ <xi:include href="profiles/headless.xml" />
+ <xi:include href="profiles/installation-device.xml" />
+ <xi:include href="profiles/minimal.xml" />
+ <xi:include href="profiles/qemu-guest.xml" />
+</chapter>
diff --git a/nixos/doc/manual/configuration/profiles/all-hardware.xml b/nixos/doc/manual/configuration/profiles/all-hardware.xml
new file mode 100644
index 0000000000000..172975199474e
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/all-hardware.xml
@@ -0,0 +1,20 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-all-hardware">
+ <title>All Hardware</title>
+ <para>
+  Enables all hardware supported by NixOS: i.e., all firmware is
+  included, and all devices from which one may boot are enabled in the initrd.
+  Its primary use is in the NixOS installation CDs.
+ </para>
+ <para>
+  The enabled kernel modules include support for SATA and PATA, SCSI
+  (partially), USB, Firewire (untested), Virtio (QEMU, KVM, etc.), VMware, and
+  Hyper-V. Additionally, <xref linkend="opt-hardware.enableAllFirmware"/> is
+  enabled, and the firmware for the ZyDAS ZD1211 chipset is specifically
+  installed.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/base.xml b/nixos/doc/manual/configuration/profiles/base.xml
new file mode 100644
index 0000000000000..f58a35d626ed3
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/base.xml
@@ -0,0 +1,15 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-base">
+ <title>Base</title>
+ <para>
+  Defines the software packages included in the "minimal"
+  installation CD. It installs several utilities useful in a simple recovery or
+  install media, such as a text-mode web browser, and tools for manipulating
+  block devices, networking, hardware diagnostics, and filesystems (with their
+  respective kernel modules).
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/clone-config.xml b/nixos/doc/manual/configuration/profiles/clone-config.xml
new file mode 100644
index 0000000000000..87c8b9ee31b69
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/clone-config.xml
@@ -0,0 +1,14 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-clone-config">
+ <title>Clone Config</title>
+ <para>
+  This profile is used in installer images.
+  It provides an editable configuration.nix that imports all the modules that
+  were also used when creating the image in the first place.
+  As a result it allows users to edit and rebuild the live-system.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/demo.xml b/nixos/doc/manual/configuration/profiles/demo.xml
new file mode 100644
index 0000000000000..98829e4696df7
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/demo.xml
@@ -0,0 +1,13 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-demo">
+ <title>Demo</title>
+ <para>
+  This profile just enables a <systemitem class="username">demo</systemitem> user, with password <literal>demo</literal>, uid <literal>1000</literal>, <systemitem class="groupname">wheel</systemitem>
+  group and <link linkend="opt-services.xserver.displayManager.sddm.autoLogin">
+   autologin in the SDDM display manager</link>.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/docker-container.xml b/nixos/doc/manual/configuration/profiles/docker-container.xml
new file mode 100644
index 0000000000000..bf962442ccefa
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/docker-container.xml
@@ -0,0 +1,15 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-docker-container">
+ <title>Docker Container</title>
+ <para>
+  This is the profile from which the Docker images are generated. It prepares a
+  working system by importing the <link linkend="sec-profile-minimal">Minimal</link> and
+  <link linkend="sec-profile-clone-config">Clone Config</link> profiles, and setting appropriate
+  configuration options that are useful inside a container context, like
+  <xref linkend="opt-boot.isContainer"/>.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/graphical.xml b/nixos/doc/manual/configuration/profiles/graphical.xml
new file mode 100644
index 0000000000000..5ded61d9763b0
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/graphical.xml
@@ -0,0 +1,21 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-graphical">
+ <title>Graphical</title>
+ <para>
+  Defines a NixOS configuration with the Plasma 5 desktop. It's used by the
+  graphical installation CD.
+ </para>
+ <para>
+  It sets <xref linkend="opt-services.xserver.enable"/>,
+  <xref linkend="opt-services.xserver.displayManager.sddm.enable"/>,
+  <xref linkend="opt-services.xserver.desktopManager.plasma5.enable"/> (
+  <link linkend="opt-services.xserver.desktopManager.plasma5.enableQt4Support">
+   without Qt4 Support</link>), and
+  <xref linkend="opt-services.xserver.libinput.enable"/> to true. It also
+  includes glxinfo and firefox in the system packages list.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/hardened.xml b/nixos/doc/manual/configuration/profiles/hardened.xml
new file mode 100644
index 0000000000000..b3b433792f534
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/hardened.xml
@@ -0,0 +1,22 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-hardened">
+ <title>Hardened</title>
+ <para>
+  A profile with most (vanilla) hardening options enabled by default,
+  potentially at the cost of features and performance.
+ </para>
+ <para>
+  This includes a hardened kernel, and limiting the system information
+  available to processes through the <filename>/sys</filename> and
+  <filename>/proc</filename> filesystems. It also disables the User Namespaces
+  feature of the kernel, which stops Nix from being able to build anything
+  (this particular setting can be overriden via
+  <xref linkend="opt-security.allowUserNamespaces"/>). See the <literal
+   xlink:href="https://github.com/nixos/nixpkgs/tree/master/nixos/modules/profiles/hardened.nix">
+   profile source</literal> for further detail on which settings are altered.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/headless.xml b/nixos/doc/manual/configuration/profiles/headless.xml
new file mode 100644
index 0000000000000..54dc61f236e0b
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/headless.xml
@@ -0,0 +1,18 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-headless">
+ <title>Headless</title>
+ <para>
+  Common configuration for headless machines (e.g., Amazon EC2 instances).
+ </para>
+ <para>
+  Disables <link linkend="opt-sound.enable">sound</link>,
+  <link linkend="opt-boot.vesa">vesa</link>, serial consoles,
+  <link linkend="opt-systemd.enableEmergencyMode">emergency mode</link>,
+  <link linkend="opt-boot.loader.grub.splashImage">grub splash images</link> and
+  configures the kernel to reboot automatically on panic.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/installation-device.xml b/nixos/doc/manual/configuration/profiles/installation-device.xml
new file mode 100644
index 0000000000000..44ccfc538ad1b
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/installation-device.xml
@@ -0,0 +1,35 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-installation-device">
+ <title>Installation Device</title>
+ <para>
+  Provides a basic configuration for installation devices like CDs. This means
+  enabling hardware scans, using the <link linkend="sec-profile-clone-config">
+   Clone Config profile</link> to guarantee
+  <filename>/etc/nixos/configuration.nix</filename> exists (for
+  <command>nixos-rebuild</command> to work), a copy of the Nixpkgs channel
+  snapshot used to create the install media.
+ </para>
+ <para>
+  Additionally, documentation for <link linkend="opt-documentation.enable">
+   Nixpkgs</link> and <link linkend="opt-documentation.nixos.enable">NixOS
+   </link> are forcefully enabled (to override the
+   <link linkend="sec-profile-minimal">Minimal profile</link> preference); the
+   NixOS manual is shown automatically on TTY 8, sudo and udisks are disabled.
+   Autologin is enabled as root.
+ </para>
+ <para>
+  A message is shown to the user to start a display manager if needed,
+  ssh with <xref linkend="opt-services.openssh.permitRootLogin"/> are enabled (but
+  doesn't autostart). WPA Supplicant is also enabled without autostart.
+ </para>
+ <para>
+  Finally, vim is installed, root is set to not have a password, the kernel is
+  made more silent for remote public IP installs, and several settings are
+  tweaked so that the installer has a better chance of succeeding under
+  low-memory environments.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/minimal.xml b/nixos/doc/manual/configuration/profiles/minimal.xml
new file mode 100644
index 0000000000000..a24af21bd7f78
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/minimal.xml
@@ -0,0 +1,17 @@
+
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-minimal">
+ <title>Minimal</title>
+ <para>
+  This profile defines a small NixOS configuration. It does not contain any
+  graphical stuff. It's a very short file that enables
+  <link linkend="opt-environment.noXlibs">noXlibs</link>, sets
+  <link linkend="opt-i18n.supportedLocales">i18n.supportedLocales</link>
+  to only support the user-selected locale,
+  <link linkend="opt-documentation.enable">disables packages' documentation
+  </link>, and <link linkend="opt-sound.enable">disables sound</link>.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/profiles/qemu-guest.xml b/nixos/doc/manual/configuration/profiles/qemu-guest.xml
new file mode 100644
index 0000000000000..d08068650fbe9
--- /dev/null
+++ b/nixos/doc/manual/configuration/profiles/qemu-guest.xml
@@ -0,0 +1,16 @@
+<section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-profile-qemu-guest">
+ <title>QEMU Guest</title>
+ <para>
+  This profile contains common configuration for virtual machines running under
+  QEMU (using virtio).
+ </para>
+ <para>
+  It makes virtio modules available on the initrd, sets the system time from
+  the hardware clock to work around a bug in qemu-kvm, and
+  <link linkend="opt-security.rngd.enable">enables rngd</link>.
+ </para>
+</section>
diff --git a/nixos/doc/manual/configuration/wireless.xml b/nixos/doc/manual/configuration/wireless.xml
index 999447234ad1f..f7e99ff0e35c2 100644
--- a/nixos/doc/manual/configuration/wireless.xml
+++ b/nixos/doc/manual/configuration/wireless.xml
@@ -29,7 +29,10 @@
   networks are set, it will default to using a configuration file at
   <literal>/etc/wpa_supplicant.conf</literal>. You should edit this file
   yourself to define wireless networks, WPA keys and so on (see
-  wpa_supplicant.conf(5)).
+  <citerefentry>
+    <refentrytitle>wpa_supplicant.conf</refentrytitle>
+    <manvolnum>5</manvolnum>
+  </citerefentry>).
  </para>
 
  <para>
diff --git a/nixos/doc/manual/configuration/x-windows.xml b/nixos/doc/manual/configuration/x-windows.xml
index 703a1b8b7f09a..e7d66f391f55c 100644
--- a/nixos/doc/manual/configuration/x-windows.xml
+++ b/nixos/doc/manual/configuration/x-windows.xml
@@ -35,11 +35,11 @@
  </para>
  <para>
   NixOS’s default <emphasis>display manager</emphasis> (the program that
-  provides a graphical login prompt and manages the X server) is SLiM. You can
+  provides a graphical login prompt and manages the X server) is LightDM. You can
   select an alternative one by picking one of the following lines:
 <programlisting>
 <xref linkend="opt-services.xserver.displayManager.sddm.enable"/> = true;
-<xref linkend="opt-services.xserver.displayManager.lightdm.enable"/> = true;
+<xref linkend="opt-services.xserver.displayManager.slim.enable"/> = true;
 </programlisting>
  </para>
  <para>
diff --git a/nixos/doc/manual/default.nix b/nixos/doc/manual/default.nix
index aaa6e0da545f9..02b91773f5dad 100644
--- a/nixos/doc/manual/default.nix
+++ b/nixos/doc/manual/default.nix
@@ -252,7 +252,7 @@ in rec {
     ''; # */
 
   # Generate the NixOS manual.
-  manual = runCommand "nixos-manual"
+  manualHTML = runCommand "nixos-manual-html"
     { inherit sources;
       nativeBuildInputs = [ buildPackages.libxml2.bin buildPackages.libxslt.bin ];
       meta.description = "The NixOS manual in HTML format";
@@ -265,6 +265,7 @@ in rec {
       xsltproc \
         ${manualXsltprocOptions} \
         --stringparam target.database.document "${olinkDB}/olinkdb.xml" \
+        --stringparam id.warnings "1" \
         --nonet --output $dst/ \
         ${docbook_xsl_ns}/xml/xsl/docbook/xhtml/chunktoc.xsl \
         ${manual-combined}/manual-combined.xml
@@ -281,6 +282,11 @@ in rec {
       echo "doc manual $dst" >> $out/nix-support/hydra-build-products
     ''; # */
 
+  # Alias for backward compatibility. TODO(@oxij): remove eventually.
+  manual = manualHTML;
+
+  # Index page of the NixOS manual.
+  manualHTMLIndex = "${manualHTML}/share/doc/nixos/index.html";
 
   manualEpub = runCommand "nixos-manual-epub"
     { inherit sources;
diff --git a/nixos/doc/manual/development/building-parts.xml b/nixos/doc/manual/development/building-parts.xml
index eaffc0ef47c2f..b4791b72970f6 100644
--- a/nixos/doc/manual/development/building-parts.xml
+++ b/nixos/doc/manual/development/building-parts.xml
@@ -34,7 +34,7 @@ $ nix-build -A system</screen>
    </varlistentry>
    <varlistentry>
     <term>
-     <varname>system.build.manual.manual</varname>
+     <varname>system.build.manual.manualHTML</varname>
     </term>
     <listitem>
      <para>
diff --git a/nixos/doc/manual/development/option-types.xml b/nixos/doc/manual/development/option-types.xml
index e6c9eae11a728..d993e47bc914b 100644
--- a/nixos/doc/manual/development/option-types.xml
+++ b/nixos/doc/manual/development/option-types.xml
@@ -106,7 +106,7 @@
      </para>
     </listitem>
    </varlistentry>
-   <varlistentry>
+   <varlistentry xml:id='types.ints.ux'>
     <term>
      <varname>types.ints.{u8, u16, u32}</varname>
     </term>
@@ -131,6 +131,17 @@
      </para>
     </listitem>
    </varlistentry>
+   <varlistentry>
+    <term>
+     <varname>types.port</varname>
+    </term>
+    <listitem>
+     <para>
+      A port number. This type is an alias to
+      <link linkend='types.ints.ux'><varname>types.ints.u16</varname></link>.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
 
   <para>
diff --git a/nixos/doc/manual/development/running-nixos-tests-interactively.xml b/nixos/doc/manual/development/running-nixos-tests-interactively.xml
index 862b364a6d79b..c15ad448317fd 100644
--- a/nixos/doc/manual/development/running-nixos-tests-interactively.xml
+++ b/nixos/doc/manual/development/running-nixos-tests-interactively.xml
@@ -19,6 +19,7 @@ starting VDE switch for network 1
 &gt; startAll
 &gt; testScript
 &gt; $machine->succeed("touch /tmp/foo")
+&gt; 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.
@@ -33,8 +34,11 @@ $ nix-build nixos/tests/login.nix -A driver
 $ ./result/bin/nixos-run-vms
 </screen>
   The script <command>nixos-run-vms</command> starts the virtual machines
-  defined by test. The root file system of the VMs is created on the fly and
-  kept across VM restarts in
-  <filename>./</filename><varname>hostname</varname><filename>.qcow2</filename>.
+  defined by test.
+ </para>
+
+ <para>
+  The machine state is kept across VM restarts in
+  <filename>/tmp/vm-state-</filename><varname>machinename</varname>.
  </para>
 </section>
diff --git a/nixos/doc/manual/development/writing-nixos-tests.xml b/nixos/doc/manual/development/writing-nixos-tests.xml
index 5935fbc049bde..4a2615c9407b0 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.xml
+++ b/nixos/doc/manual/development/writing-nixos-tests.xml
@@ -108,7 +108,7 @@ xlink:href="https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/virtualis
 <programlisting>
 $machine->start;
 $machine->waitForUnit("default.target");
-$machine->succeed("uname") =~ /Linux/;
+$machine->succeed("uname") =~ /Linux/ or die;
 </programlisting>
   The first line is actually unnecessary; machines are implicitly started when
   you first execute an action on them (such as <literal>waitForUnit</literal>
diff --git a/nixos/doc/manual/installation/installing-behind-a-proxy.xml b/nixos/doc/manual/installation/installing-behind-a-proxy.xml
index c59d073c61c73..8f9baff44b51e 100644
--- a/nixos/doc/manual/installation/installing-behind-a-proxy.xml
+++ b/nixos/doc/manual/installation/installing-behind-a-proxy.xml
@@ -5,28 +5,29 @@
          xml:id="sec-installing-behind-proxy">
  <title>Installing behind a proxy</title>
 
-<para>
+ <para>
   To install NixOS behind a proxy, do the following before running
   <literal>nixos-install</literal>.
-</para>
-<orderedlist numeration="arabic">
+ </para>
+
+ <orderedlist numeration="arabic">
   <listitem>
-    <para>
-      Update proxy configuration in
-      <literal>/mnt/etc/nixos/configuration.nix</literal> to keep the
-      internet accessible after reboot.
-    </para>
-    <programlisting>
+   <para>
+    Update proxy configuration in
+    <literal>/mnt/etc/nixos/configuration.nix</literal> to keep the internet
+    accessible after reboot.
+   </para>
+<programlisting>
 networking.proxy.default = &quot;http://user:password@proxy:port/&quot;;
 networking.proxy.noProxy = &quot;127.0.0.1,localhost,internal.domain&quot;;
 </programlisting>
   </listitem>
   <listitem>
-    <para>
-      Setup the proxy environment variables in the shell where you are
-      running <literal>nixos-install</literal>.
-    </para>
-    <programlisting>
+   <para>
+    Setup the proxy environment variables in the shell where you are running
+    <literal>nixos-install</literal>.
+   </para>
+<programlisting>
 # proxy_url=&quot;http://user:password@proxy:port/&quot;
 # export http_proxy=&quot;$proxy_url&quot;
 # export HTTP_PROXY=&quot;$proxy_url&quot;
@@ -34,14 +35,14 @@ networking.proxy.noProxy = &quot;127.0.0.1,localhost,internal.domain&quot;;
 # export HTTPS_PROXY=&quot;$proxy_url&quot;
 </programlisting>
   </listitem>
-</orderedlist>
+ </orderedlist>
 
-<note>
-<para>
-  If you are switching networks with different proxy configurations, use the
-  <literal>nesting.clone</literal> option in
-  <literal>configuration.nix</literal> to switch proxies at runtime.
-  Refer to <xref linkend="ch-options" /> for more information.
-</para>
-</note>
+ <note>
+  <para>
+   If you are switching networks with different proxy configurations, use the
+   <literal>nesting.clone</literal> option in
+   <literal>configuration.nix</literal> to switch proxies at runtime. Refer to
+   <xref linkend="ch-options" /> for more information.
+  </para>
+ </note>
 </section>
diff --git a/nixos/doc/manual/installation/installing-usb.xml b/nixos/doc/manual/installation/installing-usb.xml
index c5934111749cf..c0372e8ebd9bb 100644
--- a/nixos/doc/manual/installation/installing-usb.xml
+++ b/nixos/doc/manual/installation/installing-usb.xml
@@ -9,13 +9,12 @@
   For systems without CD drive, the NixOS live CD can be booted from a USB
   stick. You can use the <command>dd</command> utility to write the image:
   <command>dd if=<replaceable>path-to-image</replaceable>
-  of=<replaceable>/dev/sdb</replaceable></command>. Be careful about specifying
+  of=<replaceable>/dev/sdX</replaceable></command>. Be careful about specifying
   the correct drive; you can use the <command>lsblk</command> command to get a
   list of block devices.
- </para>
-
- <para>
-  On macOS:
+  <note>
+   <title>On macOS</title>
+   <para>
 <programlisting>
 $ diskutil list
 [..]
@@ -24,45 +23,18 @@ $ diskutil list
 [..]
 $ diskutil unmountDisk diskN
 Unmount of all volumes on diskN was successful
-$ sudo dd bs=1m if=nix.iso of=/dev/rdiskN
+$ sudo dd if=nix.iso of=/dev/rdiskN
 </programlisting>
-  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 not
-  readable by this computer" will pop up, which can be ignored.
+    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
+    not readable by this computer" will pop up, which can be ignored.
+   </para>
+  </note>
  </para>
 
  <para>
   The <command>dd</command> utility will write the image verbatim to the drive,
   making it the recommended option for both UEFI and non-UEFI installations.
-  For non-UEFI installations, you can alternatively use
-  <link xlink:href="http://unetbootin.sourceforge.net/">unetbootin</link>. If
-  you cannot use <command>dd</command> for a UEFI installation, you can also
-  mount the ISO, copy its contents verbatim to your drive, then either:
-  <itemizedlist>
-   <listitem>
-    <para>
-     Change the label of the disk partition to the label of the ISO (visible
-     with the blkid command), or
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     Edit <filename>loader/entries/nixos-livecd.conf</filename> on the drive
-     and change the <literal>root=</literal> field in the
-     <literal>options</literal> line to point to your drive (see the
-     documentation on <literal>root=</literal> in
-     <link xlink:href="https://www.kernel.org/doc/Documentation/admin-guide/kernel-parameters.txt">
-     the kernel documentation</link> for more details).
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     If you want to load the contents of the ISO to ram after bootin (So you
-     can remove the stick after bootup) you can append the parameter
-     <literal>copytoram</literal> to the <literal>options</literal> field.
-    </para>
-   </listitem>
-  </itemizedlist>
  </para>
 </section>
diff --git a/nixos/doc/manual/installation/installing-virtualbox-guest.xml b/nixos/doc/manual/installation/installing-virtualbox-guest.xml
index da78b480f5aa9..766785dfe070e 100644
--- a/nixos/doc/manual/installation/installing-virtualbox-guest.xml
+++ b/nixos/doc/manual/installation/installing-virtualbox-guest.xml
@@ -77,18 +77,22 @@
   Shared folders can be given a name and a path in the host system in the
   VirtualBox settings (Machine / Settings / Shared Folders, then click on the
   "Add" icon). Add the following to the
-  <literal>/etc/nixos/configuration.nix</literal> to auto-mount them:
+  <literal>/etc/nixos/configuration.nix</literal> to auto-mount them. If you 
+   do not add <literal>"nofail"</literal>, the system will no boot properly. 
+   The same goes for disabling <literal>rngd</literal> which is normally used 
+   to get randomness but this does not work in virtual machines.
  </para>
 
 <programlisting>
 { config, pkgs, ...} :
 {
+  security.rngd.enable = false; // otherwise vm will not boot
   ...
 
   fileSystems."/virtualboxshare" = {
     fsType = "vboxsf";
     device = "nameofthesharedfolder";
-    options = [ "rw" ];
+    options = [ "rw" "nofail" ];
   };
 }
 </programlisting>
diff --git a/nixos/doc/manual/installation/installing.xml b/nixos/doc/manual/installation/installing.xml
index 916384559e24b..8e94f946c5eef 100644
--- a/nixos/doc/manual/installation/installing.xml
+++ b/nixos/doc/manual/installation/installing.xml
@@ -4,60 +4,46 @@
             version="5.0"
             xml:id="sec-installation">
  <title>Installing NixOS</title>
- <para>
-  NixOS can be installed on BIOS or UEFI systems. The procedure for a UEFI
-  installation is by and large the same as a BIOS installation. The differences
-  are mentioned in the steps that follow.
- </para>
- <orderedlist>
-  <listitem>
-   <para>
-    Boot from the CD.
-   </para>
-   <variablelist>
-    <varlistentry>
-     <term>
-      UEFI systems
-     </term>
-     <listitem>
-      <para>
-       You should boot the live CD in UEFI mode (consult your specific
-       hardware's documentation for instructions). You may find the
-       <link xlink:href="http://www.rodsbooks.com/refind">rEFInd boot
-       manager</link> useful.
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-  </listitem>
-  <listitem>
-   <para>
-    The CD contains a basic NixOS installation. (It also contains Memtest86+,
-    useful if you want to test new hardware). When it’s finished booting, it
-    should have detected most of your hardware.
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    The NixOS manual is available on virtual console 8 (press Alt+F8 to access)
-    or by running <command>nixos-help</command>.
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    You get logged in as <literal>root</literal> (with empty password).
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    If you downloaded the graphical ISO image, you can run <command>systemctl
-    start display-manager</command> to start KDE. If you want to continue on
-    the terminal, you can use <command>loadkeys</command> to switch to your
-    preferred keyboard layout. (We even provide neo2 via <command>loadkeys de
-    neo</command>!)
-   </para>
-  </listitem>
-  <listitem>
+ <section xml:id="sec-installation-booting">
+  <title>Booting the system</title>
+
+  <para>
+   NixOS can be installed on BIOS or UEFI systems. The procedure for a UEFI
+   installation is by and large the same as a BIOS installation. The
+   differences are mentioned in the steps that follow.
+  </para>
+
+  <para>
+   The installation media can be burned to a CD, or now more commonly, "burned"
+   to a USB drive (see <xref linkend="sec-booting-from-usb"/>).
+  </para>
+
+  <para>
+   The installation media contains a basic NixOS installation. When it’s
+   finished booting, it should have detected most of your hardware.
+  </para>
+
+  <para>
+   The NixOS manual is available on virtual console 8 (press Alt+F8 to access)
+   or by running <command>nixos-help</command>.
+  </para>
+
+  <para>
+   You are logged-in automatically as <literal>root</literal>. (The
+   <literal>root</literal> user account has an empty password.)
+  </para>
+
+  <para>
+   If you downloaded the graphical ISO image, you can run <command>systemctl
+   start display-manager</command> to start KDE. If you want to continue on the
+   terminal, you can use <command>loadkeys</command> to switch to your
+   preferred keyboard layout. (We even provide neo2 via <command>loadkeys de
+   neo</command>!)
+  </para>
+
+  <section xml:id="sec-installation-booting-networking">
+   <title>Networking in the installer</title>
+
    <para>
     The boot process should have brought up networking (check <command>ip
     a</command>). Networking is necessary for the installer, since it will
@@ -65,60 +51,167 @@
     binaries). It’s best if you have a DHCP server on your network. Otherwise
     configure networking manually using <command>ifconfig</command>.
    </para>
+
    <para>
     To manually configure the network on the graphical installer, first disable
     network-manager with <command>systemctl stop network-manager</command>.
    </para>
+
    <para>
     To manually configure the wifi on the minimal installer, run
     <command>wpa_supplicant -B -i interface -c &lt;(wpa_passphrase 'SSID'
     'key')</command>.
    </para>
-  </listitem>
-  <listitem>
+
    <para>
     If you would like to continue the installation from a different machine you
     need to activate the SSH daemon via <literal>systemctl start
     sshd</literal>. In order to be able to login you also need to set a
     password for <literal>root</literal> using <literal>passwd</literal>.
    </para>
-  </listitem>
-  <listitem>
+  </section>
+ </section>
+ <section xml:id="sec-installation-partitioning">
+  <title>Partitioning and formatting</title>
+
+  <para>
+   The NixOS installer doesn’t do any partitioning or formatting, so you need
+   to do that yourself.
+  </para>
+
+  <para>
+   The NixOS installer ships with multiple partitioning tools. The examples
+   below use <command>parted</command>, but also provides
+   <command>fdisk</command>, <command>gdisk</command>,
+   <command>cfdisk</command>, and <command>cgdisk</command>.
+  </para>
+
+  <para>
+   The recommended partition scheme differs depending if the computer uses
+   <emphasis>Legacy Boot</emphasis> or <emphasis>UEFI</emphasis>.
+  </para>
+
+  <section xml:id="sec-installation-partitioning-UEFI">
+   <title>UEFI (GPT)</title>
+
    <para>
-    The NixOS installer doesn’t do any partitioning or formatting yet, so you
-    need to do that yourself. Use the following commands:
-    <itemizedlist>
+    Here's an example partition scheme for UEFI, using
+    <filename>/dev/sda</filename> as the device.
+    <note>
+     <para>
+      You can safely ignore <command>parted</command>'s informational message
+      about needing to update /etc/fstab.
+     </para>
+    </note>
+   </para>
+
+   <para>
+    <orderedlist>
      <listitem>
       <para>
-       For partitioning: <command>fdisk</command>.
-<screen>
-# fdisk /dev/sda # <lineannotation>(or whatever device you want to install on)</lineannotation>
--- for UEFI systems only
-> n      # <lineannotation>(create a new partition for /boot)</lineannotation>
-> 3      # <lineannotation>(make it a partition number 3)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
-> +512M  # <lineannotation>(the size of the UEFI boot partition)</lineannotation>
-> t      # <lineannotation>(change the partition type ...)</lineannotation>
-> 3      # <lineannotation>(... of the boot partition ...)</lineannotation>
-> 1      # <lineannotation>(... to 'UEFI System')</lineannotation>
--- for BIOS or UEFI systems
-> n      # <lineannotation>(create a new partition for /swap)</lineannotation>
-> 2      # <lineannotation>(make it a partition number 2)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
-> +8G    # <lineannotation>(the size of the swap partition, set to whatever you like)</lineannotation>
-> n      # <lineannotation>(create a new partition for /)</lineannotation>
-> 1      # <lineannotation>(make it a partition number 1)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
->        # <lineannotation>(press enter to accept the default and use the rest of the remaining space)</lineannotation>
-> a      # <lineannotation>(make the partition bootable)</lineannotation>
-> x      # <lineannotation>(enter expert mode)</lineannotation>
-> f      # <lineannotation>(fix up the partition ordering)</lineannotation>
-> r      # <lineannotation>(exit expert mode)</lineannotation>
-> w      # <lineannotation>(write the partition table to disk and exit)</lineannotation></screen>
+       Create a <emphasis>GPT</emphasis> partition table.
+<screen language="commands"># parted /dev/sda -- mklabel gpt</screen>
       </para>
      </listitem>
      <listitem>
       <para>
+       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>
+      </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>
+       <note>
+        <para>
+         The swap partition size rules are no different than for other Linux
+         distributions.
+        </para>
+       </note>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       Finally, the <emphasis>boot</emphasis> partition. NixOS by default uses
+       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>
+      </para>
+     </listitem>
+    </orderedlist>
+   </para>
+
+   <para>
+    Once complete, you can follow with
+    <xref linkend="sec-installation-partitioning-formatting"/>.
+   </para>
+  </section>
+
+  <section xml:id="sec-installation-partitioning-MBR">
+   <title>Legacy Boot (MBR)</title>
+
+   <para>
+    Here's an example partition scheme for Legacy Boot, using
+    <filename>/dev/sda</filename> as the device.
+    <note>
+     <para>
+      You can safely ignore <command>parted</command>'s informational message
+      about needing to update /etc/fstab.
+     </para>
+    </note>
+   </para>
+
+   <para>
+    <orderedlist>
+     <listitem>
+      <para>
+       Create a <emphasis>MBR</emphasis> partition table.
+<screen language="commands"># 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>
+      </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>
+       <note>
+        <para>
+         The swap partition size rules are no different than for other Linux
+         distributions.
+        </para>
+       </note>
+      </para>
+     </listitem>
+    </orderedlist>
+   </para>
+
+   <para>
+    Once complete, you can follow with
+    <xref linkend="sec-installation-partitioning-formatting"/>.
+   </para>
+  </section>
+
+  <section xml:id="sec-installation-partitioning-formatting">
+   <title>Formatting</title>
+
+   <para>
+    Use the following commands:
+    <itemizedlist>
+     <listitem>
+      <para>
        For initialising Ext4 partitions: <command>mkfs.ext4</command>. It is
        recommended that you assign a unique symbolic label to the file system
        using the option <option>-L <replaceable>label</replaceable></option>,
@@ -169,242 +262,249 @@
      </listitem>
     </itemizedlist>
    </para>
-  </listitem>
-  <listitem>
-   <para>
-    Mount the target file system on which NixOS should be installed on
-    <filename>/mnt</filename>, e.g.
+  </section>
+ </section>
+ <section xml:id="sec-installation-installing">
+  <title>Installing</title>
+
+  <orderedlist>
+   <listitem>
+    <para>
+     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
 </screen>
-   </para>
-  </listitem>
-  <listitem>
-   <variablelist>
-    <varlistentry>
-     <term>
-      UEFI systems
-     </term>
-     <listitem>
-      <para>
-       Mount the boot file system on <filename>/mnt/boot</filename>, e.g.
+    </para>
+   </listitem>
+   <listitem>
+    <variablelist>
+     <varlistentry>
+      <term>
+       UEFI systems
+      </term>
+      <listitem>
+       <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
 </screen>
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-  </listitem>
-  <listitem>
-   <para>
-    If your machine has a limited amount of memory, you may want to activate
-    swap devices now (<command>swapon
-    <replaceable>device</replaceable></command>). The installer (or rather, the
-    build actions that it may spawn) may need quite a bit of RAM, depending on
-    your configuration.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </listitem>
+   <listitem>
+    <para>
+     If your machine has a limited amount of memory, you may want to activate
+     swap devices now (<command>swapon
+     <replaceable>device</replaceable></command>). The installer (or rather,
+     the build actions that it may spawn) may need quite a bit of RAM,
+     depending on your configuration.
 <screen>
 # swapon /dev/sda2</screen>
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    You now need to create a file
-    <filename>/mnt/etc/nixos/configuration.nix</filename> that specifies the
-    intended configuration of the system. This is because NixOS has a
-    <emphasis>declarative</emphasis> configuration model: you create or edit a
-    description of the desired configuration of your system, and then NixOS
-    takes care of making it happen. The syntax of the NixOS configuration file
-    is described in <xref linkend="sec-configuration-syntax"/>, while a list of
-    available configuration options appears in
-    <xref
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     You now need to create a file
+     <filename>/mnt/etc/nixos/configuration.nix</filename> that specifies the
+     intended configuration of the system. This is because NixOS has a
+     <emphasis>declarative</emphasis> configuration model: you create or edit a
+     description of the desired configuration of your system, and then NixOS
+     takes care of making it happen. The syntax of the NixOS configuration file
+     is described in <xref linkend="sec-configuration-syntax"/>, while a list
+     of available configuration options appears in
+     <xref
     linkend="ch-options"/>. A minimal example is shown in
-    <xref
+     <xref
     linkend="ex-config"/>.
-   </para>
-   <para>
-    The command <command>nixos-generate-config</command> can generate an
-    initial configuration file for you:
+    </para>
+    <para>
+     The command <command>nixos-generate-config</command> can generate an
+     initial configuration file for you:
 <screen>
 # nixos-generate-config --root /mnt</screen>
-    You should then edit <filename>/mnt/etc/nixos/configuration.nix</filename>
-    to suit your needs:
+     You should then edit <filename>/mnt/etc/nixos/configuration.nix</filename>
+     to suit your needs:
 <screen>
 # 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
-    install other editors — for instance, you can install Emacs by running
-    <literal>nix-env -i emacs</literal>.
-   </para>
-   <variablelist>
-    <varlistentry>
-     <term>
-      BIOS systems
-     </term>
-     <listitem>
-      <para>
-       You <emphasis>must</emphasis> set the option
-       <xref linkend="opt-boot.loader.grub.device"/> to specify on which disk
-       the GRUB boot loader is to be installed. Without it, NixOS cannot boot.
-      </para>
-     </listitem>
-    </varlistentry>
-    <varlistentry>
-     <term>
-      UEFI systems
-     </term>
-     <listitem>
-      <para>
-       You <emphasis>must</emphasis> set the option
-       <xref linkend="opt-boot.loader.systemd-boot.enable"/> to
-       <literal>true</literal>. <command>nixos-generate-config</command> should
-       do this automatically for new configurations when booted in UEFI mode.
-      </para>
-      <para>
-       You may want to look at the options starting with
-       <option><link linkend="opt-boot.loader.efi.canTouchEfiVariables">boot.loader.efi</link></option>
-       and
-       <option><link linkend="opt-boot.loader.systemd-boot.enable">boot.loader.systemd</link></option>
-       as well.
-      </para>
-     </listitem>
-    </varlistentry>
-   </variablelist>
-   <para>
-    If there are other operating systems running on the machine before
-    installing NixOS, the <xref linkend="opt-boot.loader.grub.useOSProber"/>
-    option can be set to <literal>true</literal> to automatically add them to
-    the grub menu.
-   </para>
-   <para>
-    Another critical option is <option>fileSystems</option>, specifying the
-    file systems that need to be mounted by NixOS. However, you typically
-    don’t need to set it yourself, because
-    <command>nixos-generate-config</command> sets it automatically in
-    <filename>/mnt/etc/nixos/hardware-configuration.nix</filename> from your
-    currently mounted file systems. (The configuration file
-    <filename>hardware-configuration.nix</filename> is included from
-    <filename>configuration.nix</filename> and will be overwritten by future
-    invocations of <command>nixos-generate-config</command>; thus, you
-    generally should not modify it.)
-   </para>
-   <note>
+     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
+     install other editors — for instance, you can install Emacs by running
+     <literal>nix-env -i emacs</literal>.
+    </para>
+    <variablelist>
+     <varlistentry>
+      <term>
+       BIOS systems
+      </term>
+      <listitem>
+       <para>
+        You <emphasis>must</emphasis> set the option
+        <xref linkend="opt-boot.loader.grub.device"/> to specify on which disk
+        the GRUB boot loader is to be installed. Without it, NixOS cannot boot.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term>
+       UEFI systems
+      </term>
+      <listitem>
+       <para>
+        You <emphasis>must</emphasis> set the option
+        <xref linkend="opt-boot.loader.systemd-boot.enable"/> to
+        <literal>true</literal>. <command>nixos-generate-config</command>
+        should do this automatically for new configurations when booted in UEFI
+        mode.
+       </para>
+       <para>
+        You may want to look at the options starting with
+        <option><link linkend="opt-boot.loader.efi.canTouchEfiVariables">boot.loader.efi</link></option>
+        and
+        <option><link linkend="opt-boot.loader.systemd-boot.enable">boot.loader.systemd</link></option>
+        as well.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
     <para>
-     Depending on your hardware configuration or type of file system, you may
-     need to set the option <option>boot.initrd.kernelModules</option> to
-     include the kernel modules that are necessary for mounting the root file
-     system, otherwise the installed system will not be able to boot. (If this
-     happens, boot from the CD again, mount the target file system on
-     <filename>/mnt</filename>, fix
-     <filename>/mnt/etc/nixos/configuration.nix</filename> and rerun
-     <filename>nixos-install</filename>.) In most cases,
-     <command>nixos-generate-config</command> will figure out the required
-     modules.
+     If there are other operating systems running on the machine before
+     installing NixOS, the <xref linkend="opt-boot.loader.grub.useOSProber"/>
+     option can be set to <literal>true</literal> to automatically add them to
+     the grub menu.
     </para>
-   </note>
-  </listitem>
-  <listitem>
-   <para>
-    Do the installation:
+    <para>
+     Another critical option is <option>fileSystems</option>, specifying the
+     file systems that need to be mounted by NixOS. However, you typically
+     don’t need to set it yourself, because
+     <command>nixos-generate-config</command> sets it automatically in
+     <filename>/mnt/etc/nixos/hardware-configuration.nix</filename> from your
+     currently mounted file systems. (The configuration file
+     <filename>hardware-configuration.nix</filename> is included from
+     <filename>configuration.nix</filename> and will be overwritten by future
+     invocations of <command>nixos-generate-config</command>; thus, you
+     generally should not modify it.)
+    </para>
+    <note>
+     <para>
+      Depending on your hardware configuration or type of file system, you may
+      need to set the option <option>boot.initrd.kernelModules</option> to
+      include the kernel modules that are necessary for mounting the root file
+      system, otherwise the installed system will not be able to boot. (If this
+      happens, boot from the installation media again, mount the target file
+      system on <filename>/mnt</filename>, fix
+      <filename>/mnt/etc/nixos/configuration.nix</filename> and rerun
+      <filename>nixos-install</filename>.) In most cases,
+      <command>nixos-generate-config</command> will figure out the required
+      modules.
+     </para>
+    </note>
+   </listitem>
+   <listitem>
+    <para>
+     Do the installation:
 <screen>
 # 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
-    <filename>configuration.nix</filename> and then re-run
-    <command>nixos-install</command>.
-   </para>
-   <para>
-    As the last step, <command>nixos-install</command> will ask you to set the
-    password for the <literal>root</literal> user, e.g.
+     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
+     <filename>configuration.nix</filename> and then re-run
+     <command>nixos-install</command>.
+    </para>
+    <para>
+     As the last step, <command>nixos-install</command> will ask you to set the
+     password for the <literal>root</literal> user, e.g.
 <screen>
 setting root password...
 Enter new UNIX password: ***
-Retype new UNIX password: ***
-    </screen>
-    <note>
-     <para>
-      For unattended installations, it is possible to use
-      <command>nixos-install --no-root-passwd</command>
-      in order to disable the password prompt entirely.
-     </para>
-    </note>
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    If everything went well:
+Retype new UNIX password: ***</screen>
+     <note>
+      <para>
+       For unattended installations, it is possible to use
+       <command>nixos-install --no-root-passwd</command> in order to disable
+       the password prompt entirely.
+      </para>
+     </note>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     If everything went well:
 <screen>
-        # reboot</screen>
-   </para>
-  </listitem>
-  <listitem>
-   <para>
-    You should now be able to boot into the installed NixOS. The GRUB boot menu
-    shows a list of <emphasis>available configurations</emphasis> (initially
-    just one). Every time you change the NixOS configuration (see
-    <link
+# reboot</screen>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     You should now be able to boot into the installed NixOS. The GRUB boot
+     menu shows a list of <emphasis>available configurations</emphasis>
+     (initially just one). Every time you change the NixOS configuration (see
+     <link
         linkend="sec-changing-config">Changing Configuration</link>
-    ), a new item is added to the menu. This allows you to easily roll back to
-    a previous configuration if something goes wrong.
-   </para>
-   <para>
-    You should log in and change the <literal>root</literal> password with
-    <command>passwd</command>.
-   </para>
-   <para>
-    You’ll probably want to create some user accounts as well, which can be
-    done with <command>useradd</command>:
+     ), a new item is added to the menu. This allows you to easily roll back to
+     a previous configuration if something goes wrong.
+    </para>
+    <para>
+     You should log in and change the <literal>root</literal> password with
+     <command>passwd</command>.
+    </para>
+    <para>
+     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>
-   </para>
-   <para>
-    You may also want to install some software. For instance,
+    </para>
+    <para>
+     You may also want to install some software. For instance,
 <screen>
 $ nix-env -qa \*</screen>
-    shows what packages are available, and
+     shows what packages are available, and
 <screen>
 $ nix-env -i w3m</screen>
-    install the <literal>w3m</literal> browser.
-   </para>
-  </listitem>
- </orderedlist>
- <para>
-  To summarise, <xref linkend="ex-install-sequence" /> shows a typical sequence
-  of commands for installing NixOS on an empty hard drive (here
-  <filename>/dev/sda</filename>). <xref linkend="ex-config"
+     install the <literal>w3m</literal> browser.
+    </para>
+   </listitem>
+  </orderedlist>
+ </section>
+ <section xml:id="sec-installation-summary">
+  <title>Installation summary</title>
+
+  <para>
+   To summarise, <xref linkend="ex-install-sequence" /> shows a typical
+   sequence of commands for installing NixOS on an empty hard drive (here
+   <filename>/dev/sda</filename>). <xref linkend="ex-config"
 /> shows a
-  corresponding configuration Nix expression.
- </para>
- <example xml:id='ex-install-sequence'>
-  <title>Commands for Installing NixOS on <filename>/dev/sda</filename></title>
-<screen>
-# fdisk /dev/sda # <lineannotation>(or whatever device you want to install on)</lineannotation>
--- for UEFI systems only
-> n      # <lineannotation>(create a new partition for /boot)</lineannotation>
-> 3      # <lineannotation>(make it a partition number 3)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
-> +512M  # <lineannotation>(the size of the UEFI boot partition)</lineannotation>
-> t      # <lineannotation>(change the partition type ...)</lineannotation>
-> 3      # <lineannotation>(... of the boot partition ...)</lineannotation>
-> 1      # <lineannotation>(... to 'UEFI System')</lineannotation>
--- for BIOS or UEFI systems
-> n      # <lineannotation>(create a new partition for /swap)</lineannotation>
-> 2      # <lineannotation>(make it a partition number 2)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
-> +8G    # <lineannotation>(the size of the swap partition)</lineannotation>
-> n      # <lineannotation>(create a new partition for /)</lineannotation>
-> 1      # <lineannotation>(make it a partition number 1)</lineannotation>
->        # <lineannotation>(press enter to accept the default)</lineannotation>
->        # <lineannotation>(press enter to accept the default and use the rest of the remaining space)</lineannotation>
-> a      # <lineannotation>(make the partition bootable)</lineannotation>
-> x      # <lineannotation>(enter expert mode)</lineannotation>
-> f      # <lineannotation>(fix up the partition ordering)</lineannotation>
-> r      # <lineannotation>(exit expert mode)</lineannotation>
-> w      # <lineannotation>(write the partition table to disk and exit)</lineannotation>
+   corresponding configuration Nix expression.
+  </para>
+
+  <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>
+  </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>
+  </example>
+
+  <example xml:id="ex-install-sequence">
+   <title>Commands for Installing NixOS on <filename>/dev/sda</filename></title>
+   <para>
+    With a partitioned disk.
+<screen language="commands">
 # mkfs.ext4 -L nixos /dev/sda1
 # mkswap -L swap /dev/sda2
 # swapon /dev/sda2
@@ -416,9 +516,11 @@ $ nix-env -i w3m</screen>
 # nano /mnt/etc/nixos/configuration.nix
 # nixos-install
 # reboot</screen>
- </example>
- <example xml:id='ex-config'>
-  <title>NixOS Configuration</title>
+   </para>
+  </example>
+
+  <example xml:id='ex-config'>
+   <title>NixOS Configuration</title>
 <screen>
 { config, pkgs, ... }: {
   imports = [
@@ -438,10 +540,19 @@ $ nix-env -i w3m</screen>
   services.sshd.enable = true;
 }
   </screen>
- </example>
- <xi:include href="installing-usb.xml" />
- <xi:include href="installing-pxe.xml" />
- <xi:include href="installing-virtualbox-guest.xml" />
- <xi:include href="installing-from-other-distro.xml" />
- <xi:include href="installing-behind-a-proxy.xml" />
+  </example>
+ </section>
+ <section xml:id="sec-installation-additional-notes">
+  <title>Additional installation notes</title>
+
+  <xi:include href="installing-usb.xml" />
+
+  <xi:include href="installing-pxe.xml" />
+
+  <xi:include href="installing-virtualbox-guest.xml" />
+
+  <xi:include href="installing-from-other-distro.xml" />
+
+  <xi:include href="installing-behind-a-proxy.xml" />
+ </section>
 </chapter>
diff --git a/nixos/doc/manual/man-nixos-generate-config.xml b/nixos/doc/manual/man-nixos-generate-config.xml
index 1227873f57800..43d6c2696a281 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>
@@ -167,7 +167,7 @@ $ nixos-generate-config --root /mnt
 
 {
   imports =
-    [ &lt;nixos/modules/installer/scan/not-detected.nix>
+    [ &lt;nixos/modules/installer/scan/not-detected.nix&gt;
     ];
 
   boot.initrd.availableKernelModules = [ "ehci_hcd" "ahci" ];
diff --git a/nixos/doc/manual/man-nixos-rebuild.xml b/nixos/doc/manual/man-nixos-rebuild.xml
index 551a65f5e96be..b6a247286d4b2 100644
--- a/nixos/doc/manual/man-nixos-rebuild.xml
+++ b/nixos/doc/manual/man-nixos-rebuild.xml
@@ -13,35 +13,35 @@
  </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>build-vm</option>
    </arg>
-    
+
    <arg choice='plain'>
     <option>build-vm-with-bootloader</option>
    </arg>
@@ -50,29 +50,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>
@@ -316,6 +320,27 @@ $ ./result/bin/run-*-vm
     </listitem>
    </varlistentry>
    <varlistentry>
+     <term>
+       <option>--builders</option>
+       <replaceable>builder-spec</replaceable>
+     </term>
+     <listitem>
+       <para>
+         Allow ad-hoc remote builders for building the new system.
+         This requires the user executing <command>nixos-rebuild</command> (usually
+         root) to be configured as a trusted user in the Nix daemon. This can be
+         achieved by using the <literal>nix.trustedUsers</literal> NixOS option.
+         Examples values for that option are described in the
+		 <literal>Remote builds chapter</literal> in the Nix manual,
+         (i.e. <command>--builders "ssh://bigbrother x86_64-linux"</command>).
+         By specifying an empty string existing builders specified in
+         <filename>/etc/nix/machines</filename> can be ignored:
+         <command>--builders ""</command> for example when they are not
+         reachable due to network connectivity.
+       </para>
+     </listitem>
+   </varlistentry>
+   <varlistentry>
     <term>
      <option>--profile-name</option>
     </term>
diff --git a/nixos/doc/manual/manual.xml b/nixos/doc/manual/manual.xml
index a5efde32885cf..12f52e1997c8d 100644
--- a/nixos/doc/manual/manual.xml
+++ b/nixos/doc/manual/manual.xml
@@ -17,8 +17,8 @@
   <para>
    If you encounter problems, please report them on the
    <literal
-    xlink:href="https://discourse.nixos.org">Discourse</literal>
-   or on the <link
+    xlink:href="https://discourse.nixos.org">Discourse</literal> or
+   on the <link
     xlink:href="irc://irc.freenode.net/#nixos">
    <literal>#nixos</literal> channel on Freenode</link>. Bugs should be
    reported in
diff --git a/nixos/doc/manual/release-notes/rl-1412.xml b/nixos/doc/manual/release-notes/rl-1412.xml
index 4d93aa644c1d6..139f61c2a5509 100644
--- a/nixos/doc/manual/release-notes/rl-1412.xml
+++ b/nixos/doc/manual/release-notes/rl-1412.xml
@@ -17,7 +17,7 @@
    </listitem>
    <listitem>
     <para>
-     <link xlink:href="http://thread.gmane.org/gmane.linux.distributions.nixos/15165">
+     <link xlink:href="https://www.mail-archive.com/nix-dev@lists.science.uu.nl/msg13957.html">
      Nix has been updated to 1.8.</link>
     </para>
    </listitem>
diff --git a/nixos/doc/manual/release-notes/rl-1509.xml b/nixos/doc/manual/release-notes/rl-1509.xml
index 4eb2f9aa0a956..e500c9d634224 100644
--- a/nixos/doc/manual/release-notes/rl-1509.xml
+++ b/nixos/doc/manual/release-notes/rl-1509.xml
@@ -435,11 +435,11 @@ system.autoUpgrade.enable = true;
 <programlisting>
 system.stateVersion = "14.12";
 </programlisting>
-     The new option <option>system.stateVersion</option> ensures that
-     certain configuration changes that could break existing systems (such as
-     the <command>sshd</command> host key setting) will maintain compatibility
-     with the specified NixOS release. NixOps sets the state version of
-     existing deployments automatically.
+     The new option <option>system.stateVersion</option> ensures that certain
+     configuration changes that could break existing systems (such as the
+     <command>sshd</command> host key setting) will maintain compatibility with
+     the specified NixOS release. NixOps sets the state version of existing
+     deployments automatically.
     </para>
    </listitem>
    <listitem>
diff --git a/nixos/doc/manual/release-notes/rl-1809.xml b/nixos/doc/manual/release-notes/rl-1809.xml
index 9c8cd7c49c8d9..8715a05f508b6 100644
--- a/nixos/doc/manual/release-notes/rl-1809.xml
+++ b/nixos/doc/manual/release-notes/rl-1809.xml
@@ -3,7 +3,7 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="sec-release-18.09">
- <title>Release 18.09 (“Jellyfish”, 2018/09/??)</title>
+ <title>Release 18.09 (“Jellyfish”, 2018/10/05)</title>
 
  <section xmlns="http://docbook.org/ns/docbook"
          xmlns:xlink="http://www.w3.org/1999/xlink"
@@ -14,18 +14,56 @@
 
   <para>
    In addition to numerous new and upgraded packages, this release has the
-   following highlights:
+   following notable updates:
   </para>
 
   <itemizedlist>
    <listitem>
-     <para>
-       Support for wrapping binaries using <literal>firejail</literal> has been
-       added through <varname>programs.firejail.wrappedBinaries</varname>.
-     </para>
-     <para>
-       For example
-     </para>
+    <para>
+     End of support is planned for end of April 2019, handing over to 19.03.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Platform support: x86_64-linux and x86_64-darwin as always. Support for
+     aarch64-linux is as with the previous releases, not equivalent to the
+     x86-64-linux release, but with efforts to reach parity.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Nix has been updated to 2.1; see its
+     <link xlink:href="https://nixos.org/nix/manual/#ssec-relnotes-2.1">release
+     notes</link>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Core versions: linux: 4.14 LTS (unchanged), glibc: 2.26 → 2.27, gcc: 7
+     (unchanged), systemd: 237 → 239.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Desktop version changes: gnome: 3.26 → 3.28, (KDE) plasma-desktop: 5.12
+     → 5.13.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   Notable changes and additions for 18.09 include:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Support for wrapping binaries using <literal>firejail</literal> has been
+     added through <varname>programs.firejail.wrappedBinaries</varname>.
+    </para>
+    <para>
+     For example
+    </para>
 <programlisting>
 programs.firejail = {
   enable = true;
@@ -35,9 +73,10 @@ programs.firejail = {
   };
 };
 </programlisting>
-      <para>
-        This will place <literal>firefox</literal> and <literal>mpv</literal> binaries in the global path wrapped by firejail.
-      </para>
+    <para>
+     This will place <literal>firefox</literal> and <literal>mpv</literal>
+     binaries in the global path wrapped by firejail.
+    </para>
    </listitem>
    <listitem>
     <para>
@@ -69,52 +108,355 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
   <title>New Services</title>
 
   <para>
-   The following new services were added since the last release:
+   A curated selection of new services that were added since the last release:
   </para>
 
   <itemizedlist>
    <listitem>
     <para>
-     The <varname>services.cassandra</varname> module has been reworked and
-     was rewritten from scratch. The service has succeeding tests for
-     the versions 2.1, 2.2, 3.0 and 3.11 of <link
-     xlink:href="https://cassandra.apache.org/">Apache Cassandra</link>.
+     The <varname>services.cassandra</varname> module has been reworked and was
+     rewritten from scratch. The service has succeeding tests for the versions
+     2.1, 2.2, 3.0 and 3.11 of
+     <link
+     xlink:href="https://cassandra.apache.org/">Apache
+     Cassandra</link>.
     </para>
    </listitem>
    <listitem>
     <para>
-     There is a new <varname>services.foundationdb</varname> module for deploying
-     <link xlink:href="https://www.foundationdb.org">FoundationDB</link> clusters.
+     There is a new <varname>services.foundationdb</varname> module for
+     deploying
+     <link xlink:href="https://www.foundationdb.org">FoundationDB</link>
+     clusters.
     </para>
    </listitem>
    <listitem>
     <para>
      When enabled the <literal>iproute2</literal> will copy the files expected
      by ip route (e.g., <filename>rt_tables</filename>) in
-     <filename>/run/iproute2</filename>. This allows to write aliases for
+     <filename>/etc/iproute2</filename>. This allows to write aliases for
      routing tables for instance.
     </para>
    </listitem>
    <listitem>
     <para>
-      <varname>services.strongswan-swanctl</varname>
-      is a modern replacement for <varname>services.strongswan</varname>.
-      You can use either one of them to setup IPsec VPNs but not both at the same time.
+     <varname>services.strongswan-swanctl</varname> is a modern replacement for
+     <varname>services.strongswan</varname>. You can use either one of them to
+     setup IPsec VPNs but not both at the same time.
+    </para>
+    <para>
+     <varname>services.strongswan-swanctl</varname> uses the
+     <link xlink:href="https://wiki.strongswan.org/projects/strongswan/wiki/swanctl">swanctl</link>
+     command which uses the modern
+     <link xlink:href="https://github.com/strongswan/strongswan/blob/master/src/libcharon/plugins/vici/README.md">vici</link>
+     <emphasis>Versatile IKE Configuration Interface</emphasis>. The deprecated
+     <literal>ipsec</literal> command used in
+     <varname>services.strongswan</varname> is using the legacy
+     <link xlink:href="https://github.com/strongswan/strongswan/blob/master/README_LEGACY.md">stroke
+     configuration interface</link>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The new <varname>services.elasticsearch-curator</varname> service
+     periodically curates or manages, your Elasticsearch indices and snapshots.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   Every new services:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     <literal>./config/xdg/autostart.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./config/xdg/icons.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./config/xdg/menus.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./config/xdg/mime.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./hardware/brightnessctl.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./hardware/onlykey.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./hardware/video/uvcvideo/default.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./misc/documentation.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/firejail.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/iftop.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/sedutil.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/singularity.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/xss-lock.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./programs/zsh/zsh-autosuggestions.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/admin/oxidized.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/backup/duplicati.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/backup/restic.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/backup/restic-rest-server.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/cluster/hadoop/default.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/databases/aerospike.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/databases/monetdb.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/desktops/bamf.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/desktops/flatpak.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/desktops/zeitgeist.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/development/bloop.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/development/jupyter/default.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/hardware/lcd.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/hardware/undervolt.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/clipmenu.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/gitweb.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/serviio.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/safeeyes.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/sysprof.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/misc/weechat.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/monitoring/datadog-agent.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/monitoring/incron.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/dnsdist.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/freeradius.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/hans.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/morty.nix</literal>
     </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/ndppd.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
     <para>
-      <varname>services.strongswan-swanctl</varname> uses the
-      <link xlink:href="https://wiki.strongswan.org/projects/strongswan/wiki/swanctl">swanctl</link>
-      command which uses the modern
-      <link xlink:href="https://github.com/strongswan/strongswan/blob/master/src/libcharon/plugins/vici/README.md">vici</link>
-      <emphasis>Versatile IKE Configuration Interface</emphasis>.
-      The deprecated <literal>ipsec</literal> command used in <varname>services.strongswan</varname> is using the legacy
-      <link xlink:href="https://github.com/strongswan/strongswan/blob/master/README_LEGACY.md">stroke configuration interface</link>.
+     <literal>./services/networking/ocserv.nix</literal>
     </para>
    </listitem>
    <listitem>
     <para>
-      The new <varname>services.elasticsearch-curator</varname> service
-      periodically curates or manages, your Elasticsearch indices and snapshots.
+     <literal>./services/networking/owamp.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/quagga.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/shadowsocks.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/stubby.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/networking/zeronet.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/security/certmgr.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/security/cfssl.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/security/oauth2_proxy_nginx.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-apps/virtlyst.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-apps/youtrack.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-servers/hitch/default.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-servers/hydron.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-servers/meguca.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./services/web-servers/nginx/gitweb.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./virtualisation/kvmgt.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>./virtualisation/qemu-guest-agent.nix</literal>
     </para>
    </listitem>
   </itemizedlist>
@@ -135,53 +477,51 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
   <itemizedlist>
    <listitem>
     <para>
-     The deprecated <varname>services.cassandra</varname> module has
-     seen a complete rewrite. (See above.)
-    </para>
-   </listitem>
-   <listitem>
-    <para>
-     The minimum version of Nix required to evaluate Nixpkgs is now 2.0.
-    </para>
-    <itemizedlist>
-     <listitem>
-      <para>
-       For users of NixOS 18.03, NixOS 18.03 defaulted to Nix 2.0, but
-       supported using Nix 1.11 by setting <literal>nix.package =
-       pkgs.nix1;</literal>. If this option is set to a Nix 1.11 package, you
-       will need to either unset the option or upgrade it to Nix 2.0.
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       For users of NixOS 17.09, you will first need to upgrade Nix by setting
-       <literal>nix.package = pkgs.nixStable2;</literal> and run
-       <command>nixos-rebuild switch</command> as the <literal>root</literal>
-       user.
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       For users of a daemon-less Nix installation on Linux or macOS, you can
-       upgrade Nix by running <command>curl https://nixos.org/nix/install |
-       sh</command>, or prior to doing a channel update, running
-       <command>nix-env -iA nix</command>.
-      </para>
-      <para>
-       If you have already run a channel update and Nix is no longer able to
-       evaluate Nixpkgs, the error message printed should provide adequate
-       directions for upgrading Nix.
-      </para>
-     </listitem>
-     <listitem>
-      <para>
-       For users of the Nix daemon on macOS, you can upgrade Nix by running
-       <command>sudo -i sh -c 'nix-channel --update &amp;&amp; nix-env -iA
-       nixpkgs.nix'; sudo launchctl stop org.nixos.nix-daemon; sudo launchctl
-       start org.nixos.nix-daemon</command>.
-      </para>
-     </listitem>
-    </itemizedlist>
+     Some licenses that were incorrectly not marked as unfree now are. This is
+     the case for:
+     <itemizedlist>
+      <listitem>
+       <para>
+        cc-by-nc-sa-20: Creative Commons Attribution Non Commercial Share Alike
+        2.0
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        cc-by-nc-sa-25: Creative Commons Attribution Non Commercial Share Alike
+        2.5
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        cc-by-nc-sa-30: Creative Commons Attribution Non Commercial Share Alike
+        3.0
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        cc-by-nc-sa-40: Creative Commons Attribution Non Commercial Share Alike
+        4.0
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        cc-by-nd-30: Creative Commons Attribution-No Derivative Works v3.00
+       </para>
+      </listitem>
+      <listitem>
+       <para>
+        msrla: Microsoft Research License Agreement
+       </para>
+      </listitem>
+     </itemizedlist>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The deprecated <varname>services.cassandra</varname> module has seen a
+     complete rewrite. (See above.)
+    </para>
    </listitem>
    <listitem>
     <para>
@@ -230,41 +570,44 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
    </listitem>
    <listitem>
     <para>
-     <varname>services.munge</varname> now runs as user (and group) <literal>munge</literal> instead of root.
-     Make sure the key file is accessible to the daemon.
+     <varname>services.munge</varname> now runs as user (and group)
+     <literal>munge</literal> instead of root. Make sure the key file is
+     accessible to the daemon.
     </para>
    </listitem>
    <listitem>
     <para>
-      <varname>dockerTools.buildImage</varname> now uses <literal>null</literal> as default value for <varname>tag</varname>,
-      which indicates that the nix output hash will be used as tag.
+     <varname>dockerTools.buildImage</varname> now uses <literal>null</literal>
+     as default value for <varname>tag</varname>, which indicates that the nix
+     output hash will be used as tag.
     </para>
    </listitem>
    <listitem>
     <para>
-     The ELK stack: <varname>elasticsearch</varname>, <varname>logstash</varname> and <varname>kibana</varname>
-     has been upgraded from 2.* to 6.3.*.
-     The 2.* versions have been <link xlink:href="https://www.elastic.co/support/eol">unsupported since last year</link>
-     so they have been removed. You can still use the 5.* versions under the names
-     <varname>elasticsearch5</varname>, <varname>logstash5</varname> and
-     <varname>kibana5</varname>.
+     The ELK stack: <varname>elasticsearch</varname>,
+     <varname>logstash</varname> and <varname>kibana</varname> has been
+     upgraded from 2.* to 6.3.*. The 2.* versions have been
+     <link xlink:href="https://www.elastic.co/support/eol">unsupported since
+     last year</link> so they have been removed. You can still use the 5.*
+     versions under the names <varname>elasticsearch5</varname>,
+     <varname>logstash5</varname> and <varname>kibana5</varname>.
     </para>
     <para>
-     The elastic beats:
-     <varname>filebeat</varname>, <varname>heartbeat</varname>,
-     <varname>metricbeat</varname> and <varname>packetbeat</varname>
-     have had the same treatment: they now target 6.3.* as well.
-     The 5.* versions are available under the names:
+     The elastic beats: <varname>filebeat</varname>,
+     <varname>heartbeat</varname>, <varname>metricbeat</varname> and
+     <varname>packetbeat</varname> have had the same treatment: they now target
+     6.3.* as well. The 5.* versions are available under the names:
      <varname>filebeat5</varname>, <varname>heartbeat5</varname>,
      <varname>metricbeat5</varname> and <varname>packetbeat5</varname>
     </para>
     <para>
      The ELK-6.3 stack now comes with
-     <link xlink:href="https://www.elastic.co/products/x-pack/open">X-Pack by default</link>.
-     Since X-Pack is licensed under the
-     <link xlink:href="https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE.txt">Elastic License</link>
-     the ELK packages now have an unfree license. To use them you need to specify
-     <literal>allowUnfree = true;</literal> in your nixpkgs configuration.
+     <link xlink:href="https://www.elastic.co/products/x-pack/open">X-Pack by
+     default</link>. Since X-Pack is licensed under the
+     <link xlink:href="https://github.com/elastic/elasticsearch/blob/master/licenses/ELASTIC-LICENSE.txt">Elastic
+     License</link> the ELK packages now have an unfree license. To use them
+     you need to specify <literal>allowUnfree = true;</literal> in your nixpkgs
+     configuration.
     </para>
     <para>
      Fortunately there is also a free variant of the ELK stack without X-Pack.
@@ -275,20 +618,28 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
    </listitem>
    <listitem>
     <para>
-      Options
-      <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.ramfsMountPoint</literal>
-      <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.storage.mountPoint</literal>
-      were removed. <literal>luksroot.nix</literal> module never supported more than one YubiKey at
-      a time anyway, hence those options never had any effect. You should be able to remove them
-      from your config without any issues.
+     Options
+     <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.ramfsMountPoint</literal>
+     <literal>boot.initrd.luks.devices.<replaceable>name</replaceable>.yubikey.storage.mountPoint</literal>
+     were removed. <literal>luksroot.nix</literal> module never supported more
+     than one YubiKey at a time anyway, hence those options never had any
+     effect. You should be able to remove them from your config without any
+     issues.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>stdenv.system</literal> and <literal>system</literal> in nixpkgs
+     now refer to the host platform instead of the build platform. For native
+     builds this is not change, let alone a breaking one. For cross builds, it
+     is a breaking change, and <literal>stdenv.buildPlatform.system</literal>
+     can be used instead for the old behavior. They should be using that
+     anyways for clarity.
     </para>
    </listitem>
    <listitem>
     <para>
-      <literal>stdenv.system</literal> and <literal>system</literal> in nixpkgs now refer to the host platform instead of the build platform.
-      For native builds this is not change, let alone a breaking one.
-      For cross builds, it is a breaking change, and <literal>stdenv.buildPlatform.system</literal> can be used instead for the old behavior.
-      They should be using that anyways for clarity.
+        Groups <literal>kvm</literal> and <literal>render</literal> are introduced now, as systemd requires them.
     </para>
    </listitem>
   </itemizedlist>
@@ -342,26 +693,33 @@ $ nix-instantiate -E '(import &lt;nixpkgsunstable&gt; {}).gitFull'
    </listitem>
    <listitem>
     <para>
-      The <literal>pkgs</literal> argument to NixOS modules can now be set directly using <literal>nixpkgs.pkgs</literal>. Previously, only the <literal>system</literal>, <literal>config</literal> and <literal>overlays</literal> arguments could be used to influence <literal>pkgs</literal>.
+     The <literal>pkgs</literal> argument to NixOS modules can now be set
+     directly using <literal>nixpkgs.pkgs</literal>. Previously, only the
+     <literal>system</literal>, <literal>config</literal> and
+     <literal>overlays</literal> arguments could be used to influence
+     <literal>pkgs</literal>.
     </para>
    </listitem>
    <listitem>
     <para>
-      A NixOS system can now be constructed more easily based on a preexisting invocation of Nixpkgs. For example:
-      <programlisting>
+     A NixOS system can now be constructed more easily based on a preexisting
+     invocation of Nixpkgs. For example:
+<programlisting>
 inherit (pkgs.nixos {
   boot.loader.grub.enable = false;
   fileSystems."/".device = "/dev/xvda1";
 }) toplevel kernel initialRamdisk manual;
       </programlisting>
-
-      This benefits evaluation performance, lets you write Nixpkgs packages that depend on NixOS images and is consistent with a deployment architecture that would be centered around Nixpkgs overlays.
+     This benefits evaluation performance, lets you write Nixpkgs packages that
+     depend on NixOS images and is consistent with a deployment architecture
+     that would be centered around Nixpkgs overlays.
     </para>
    </listitem>
    <listitem>
     <para>
-      <literal>lib.traceValIfNot</literal> has been deprecated. Use
-      <literal>if/then/else</literal> and <literal>lib.traceValSeq</literal> instead.
+     <literal>lib.traceValIfNot</literal> has been deprecated. Use
+     <literal>if/then/else</literal> and <literal>lib.traceValSeq</literal>
+     instead.
     </para>
    </listitem>
    <listitem>
@@ -380,9 +738,9 @@ inherit (pkgs.nixos {
    </listitem>
    <listitem>
     <para>
-     <literal>lib.recursiveUpdateUntil</literal> was not acting according to its
-     specification. It has been fixed to act according to the docstring, and a
-     test has been added.
+     <literal>lib.recursiveUpdateUntil</literal> was not acting according to
+     its specification. It has been fixed to act according to the docstring,
+     and a test has been added.
     </para>
    </listitem>
    <listitem>
@@ -452,11 +810,11 @@ inherit (pkgs.nixos {
     </para>
    </listitem>
    <listitem>
-     <para>
-     The Kubernetes package has been bumped to major version 1.11.
-     Please consult the
-     <link xlink:href="https://github.com/kubernetes/kubernetes/blob/release-1.11/CHANGELOG-1.11.md">release notes</link>
-     for details on new features and api changes.
+    <para>
+     The Kubernetes package has been bumped to major version 1.11. Please
+     consult the
+     <link xlink:href="https://github.com/kubernetes/kubernetes/blob/release-1.11/CHANGELOG-1.11.md">release
+     notes</link> for details on new features and api changes.
     </para>
    </listitem>
    <listitem>
@@ -476,8 +834,8 @@ inherit (pkgs.nixos {
    </listitem>
    <listitem>
     <para>
-     The option <varname>services.kubernetes.apiserver.address</varname>
-     was renamed to <varname>services.kubernetes.apiserver.bindAddress</varname>.
+     The option <varname>services.kubernetes.apiserver.address</varname> was
+     renamed to <varname>services.kubernetes.apiserver.bindAddress</varname>.
      Note that the default value has changed from 127.0.0.1 to 0.0.0.0.
     </para>
    </listitem>
@@ -489,76 +847,86 @@ inherit (pkgs.nixos {
    </listitem>
    <listitem>
     <para>
-     The option <varname>services.kubernetes.addons.dashboard.enableRBAC</varname>
-     was renamed to <varname>services.kubernetes.addons.dashboard.rbac.enable</varname>.
+     The option
+     <varname>services.kubernetes.addons.dashboard.enableRBAC</varname> was
+     renamed to
+     <varname>services.kubernetes.addons.dashboard.rbac.enable</varname>.
     </para>
    </listitem>
    <listitem>
     <para>
      The Kubernetes Dashboard now has only minimal RBAC permissions by default.
-     If dashboard cluster-admin rights are desired,
-     set <varname>services.kubernetes.addons.dashboard.rbac.clusterAdmin</varname> to true.
-     On existing clusters, in order for the revocation of privileges to take effect,
-     the current ClusterRoleBinding for kubernetes-dashboard must be manually removed:
-     <literal>kubectl delete clusterrolebinding kubernetes-dashboard</literal>
+     If dashboard cluster-admin rights are desired, set
+     <varname>services.kubernetes.addons.dashboard.rbac.clusterAdmin</varname>
+     to true. On existing clusters, in order for the revocation of privileges
+     to take effect, the current ClusterRoleBinding for kubernetes-dashboard
+     must be manually removed: <literal>kubectl delete clusterrolebinding
+     kubernetes-dashboard</literal>
     </para>
    </listitem>
    <listitem>
     <para>
      The <varname>programs.screen</varname> module provides allows to configure
-     <literal>/etc/screenrc</literal>, however the module behaved fairly counterintuitive as
-     the config exists, but the package wasn't available. Since 18.09 <literal>pkgs.screen</literal>
-     will be added to <literal>environment.systemPackages</literal>.
+     <literal>/etc/screenrc</literal>, however the module behaved fairly
+     counterintuitive as the config exists, but the package wasn't available.
+     Since 18.09 <literal>pkgs.screen</literal> will be added to
+     <literal>environment.systemPackages</literal>.
     </para>
    </listitem>
    <listitem>
     <para>
-      The module <option>services.networking.hostapd</option> now uses WPA2 by default.
+     The module <option>services.networking.hostapd</option> now uses WPA2 by
+     default.
     </para>
    </listitem>
    <listitem>
     <para>
-      <varname>s6Dns</varname>, <varname>s6Networking</varname>,
-      <varname>s6LinuxUtils</varname> and <varname>s6PortableUtils</varname>
-      renamed to
-      <varname>s6-dns</varname>, <varname>s6-networking</varname>,
-      <varname>s6-linux-utils</varname> and <varname>s6-portable-utils</varname> respectively.
+     <varname>s6Dns</varname>, <varname>s6Networking</varname>,
+     <varname>s6LinuxUtils</varname> and <varname>s6PortableUtils</varname>
+     renamed to <varname>s6-dns</varname>, <varname>s6-networking</varname>,
+     <varname>s6-linux-utils</varname> and <varname>s6-portable-utils</varname>
+     respectively.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      The module option <option>nix.useSandbox</option> is now defaulted to <literal>true</literal>.
+     The module option <option>nix.useSandbox</option> is now defaulted to
+     <literal>true</literal>.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      The config activation script of <literal>nixos-rebuild</literal> now
-      <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemctl.html#Manager%20Lifecycle%20Commands">reloads</link>
-      all user units for each authenticated user.
+     The config activation script of <literal>nixos-rebuild</literal> now
+     <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemctl.html#Manager%20Lifecycle%20Commands">reloads</link>
+     all user units for each authenticated user.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      The default display manager is now LightDM.
-      To use SLiM set <literal>services.xserver.displayManager.slim.enable</literal>
-      to <literal>true</literal>.
+     The default display manager is now LightDM. To use SLiM set
+     <literal>services.xserver.displayManager.slim.enable</literal> to
+     <literal>true</literal>.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      NixOS option descriptions are now automatically broken up into individual
-      paragraphs if the text contains two consecutive newlines, so it's no
-      longer necessary to use <code>&lt;/para&gt;&lt;para&gt;</code> to start
-      a new paragraph.
+     NixOS option descriptions are now automatically broken up into individual
+     paragraphs if the text contains two consecutive newlines, so it's no
+     longer necessary to use <code>&lt;/para&gt;&lt;para&gt;</code> to start a
+     new paragraph.
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      Top-level <literal>buildPlatform</literal>, <literal>hostPlatform</literal>, and <literal>targetPlatform</literal> in Nixpkgs are deprecated.
-      Please use their equivalents in <literal>stdenv</literal> instead:
-      <literal>stdenv.buildPlatform</literal>, <literal>stdenv.hostPlatform</literal>, and <literal>stdenv.targetPlatform</literal>.
+     Top-level <literal>buildPlatform</literal>,
+     <literal>hostPlatform</literal>, and <literal>targetPlatform</literal> in
+     Nixpkgs are deprecated. Please use their equivalents in
+     <literal>stdenv</literal> instead:
+     <literal>stdenv.buildPlatform</literal>,
+     <literal>stdenv.hostPlatform</literal>, and
+     <literal>stdenv.targetPlatform</literal>.
     </para>
-  </listitem>
+   </listitem>
   </itemizedlist>
  </section>
 </section>
diff --git a/nixos/doc/manual/release-notes/rl-1903.xml b/nixos/doc/manual/release-notes/rl-1903.xml
index 9ae34dd58ab0b..0937a681d151f 100644
--- a/nixos/doc/manual/release-notes/rl-1903.xml
+++ b/nixos/doc/manual/release-notes/rl-1903.xml
@@ -19,7 +19,9 @@
 
   <itemizedlist>
    <listitem>
-    <para />
+    <para>
+    The default Python 3 interpreter is now CPython 3.7 instead of CPython 3.6.
+    </para>
    </listitem>
   </itemizedlist>
  </section>
@@ -37,7 +39,361 @@
 
   <itemizedlist>
    <listitem>
-    <para />
+    <para>
+     <literal>./programs/nm-applet.nix</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     There is a new <varname>security.googleOsLogin</varname> module for using
+     <link xlink:href="https://cloud.google.com/compute/docs/instances/managing-instance-access">OS Login</link>
+     to manage SSH access to Google Compute Engine instances, which supersedes
+     the imperative and broken <literal>google-accounts-daemon</literal> used
+     in <literal>nixos/modules/virtualisation/google-compute-config.nix</literal>.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+
+ <section xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="sec-release-19.03-incompatibilities">
+  <title>Backward Incompatibilities</title>
+
+  <para>
+   When upgrading from a previous release, please be aware of the following
+   incompatible changes:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     The minimum version of Nix required to evaluate Nixpkgs is now 2.0.
+    </para>
+    <itemizedlist>
+     <listitem>
+      <para>
+       For users of NixOS 18.03 and 19.03, NixOS defaults to Nix 2.0, but
+       supports using Nix 1.11 by setting <literal>nix.package =
+       pkgs.nix1;</literal>. If this option is set to a Nix 1.11 package, you
+       will need to either unset the option or upgrade it to Nix 2.0.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       For users of NixOS 17.09, you will first need to upgrade Nix by setting
+       <literal>nix.package = pkgs.nixStable2;</literal> and run
+       <command>nixos-rebuild switch</command> as the <literal>root</literal>
+       user.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       For users of a daemon-less Nix installation on Linux or macOS, you can
+       upgrade Nix by running <command>curl https://nixos.org/nix/install |
+       sh</command>, or prior to doing a channel update, running
+       <command>nix-env -iA nix</command>.
+      </para>
+      <para>
+       If you have already run a channel update and Nix is no longer able to
+       evaluate Nixpkgs, the error message printed should provide adequate
+       directions for upgrading Nix.
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       For users of the Nix daemon on macOS, you can upgrade Nix by running
+       <command>sudo -i sh -c 'nix-channel --update &amp;&amp; nix-env -iA
+       nixpkgs.nix'; sudo launchctl stop org.nixos.nix-daemon; sudo launchctl
+       start org.nixos.nix-daemon</command>.
+      </para>
+     </listitem>
+    </itemizedlist>
+   </listitem>
+   <listitem>
+    <para>
+      The Syncthing state and configuration data has been moved from
+      <varname>services.syncthing.dataDir</varname> to the newly defined
+      <varname>services.syncthing.configDir</varname>, which default to
+      <literal>/var/lib/syncthing/.config/syncthing</literal>.
+      This change makes possible to share synced directories using ACLs
+      without Syncthing resetting the permission on every start.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      The <literal>ntp</literal> module now has sane default restrictions.
+      If you're relying on the previous defaults, which permitted all queries
+      and commands from all firewall-permitted sources, you can set
+      <varname>services.ntp.restrictDefault</varname> and
+      <varname>services.ntp.restrictSource</varname> to
+      <literal>[]</literal>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Package <varname>rabbitmq_server</varname> is renamed to
+     <varname>rabbitmq-server</varname>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>light</literal> module no longer uses setuid binaries, but
+     udev rules. As a consequence users of that module have to belong to the
+     <literal>video</literal> group in order to use the executable (i.e.
+     <literal>users.users.yourusername.extraGroups = ["video"];</literal>).
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Buildbot now supports Python 3 and its packages have been moved to
+     <literal>pythonPackages</literal>. The options
+     <option>services.buildbot-master.package</option> and
+     <option>services.buildbot-worker.package</option> can be used to select
+     the Python 2 or 3 version of the package.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      Options
+      <literal>services.znc.confOptions.networks.<replaceable>name</replaceable>.userName</literal> and
+      <literal>services.znc.confOptions.networks.<replaceable>name</replaceable>.modulePackages</literal>
+      were removed. They were never used for anything and can therefore safely be removed.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Package <literal>wasm</literal> has been renamed <literal>proglodyte-wasm</literal>. The package
+     <literal>wasm</literal> will be pointed to <literal>ocamlPackages.wasm</literal> in 19.09, so
+     make sure to update your configuration if you want to keep <literal>proglodyte-wasm</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     When the <literal>nixpkgs.pkgs</literal> option is set, NixOS will no
+     longer ignore the <literal>nixpkgs.overlays</literal> option. The old
+     behavior can be recovered by setting <literal>nixpkgs.overlays =
+     lib.mkForce [];</literal>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     OpenSMTPD has been upgraded to version 6.4.0p1. This release makes
+     backwards-incompatible changes to the configuration file format. See
+     <command>man smtpd.conf</command> for more information on the new file
+     format.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The versioned <varname>postgresql</varname> have been renamed to use
+     underscore number seperators. For example, <varname>postgresql96</varname>
+     has been renamed to <varname>postgresql_9_6</varname>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Package <literal>consul-ui</literal> and passthrough <literal>consul.ui</literal> have been removed.
+     The package <literal>consul</literal> now uses upstream releases that vendor the UI into the binary.
+     See <link xlink:href="https://github.com/NixOS/nixpkgs/pull/48714#issuecomment-433454834">#48714</link>
+     for details.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      Slurm introduces the new option
+      <literal>services.slurm.stateSaveLocation</literal>,
+      which is now set to <literal>/var/spool/slurm</literal> by default
+      (instead of <literal>/var/spool</literal>).
+      Make sure to move all files to the new directory or to set the option accordingly.
+    </para>
+    <para>
+      The slurmctld now runs as user <literal>slurm</literal> instead of <literal>root</literal>.
+      If you want to keep slurmctld running as <literal>root</literal>, set
+      <literal>services.slurm.user = root</literal>.
+    </para>
+    <para>
+      The options <literal>services.slurm.nodeName</literal> and
+      <literal>services.slurm.partitionName</literal> are now sets of
+      strings to correctly reflect that fact that each of these
+      options can occour more than once in the configuration.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      The <literal>solr</literal> package has been upgraded from 4.10.3 to 7.5.0 and has undergone
+      some major changes. The <literal>services.solr</literal> module has been updated to reflect
+      these changes. Please review http://lucene.apache.org/solr/ carefully before upgrading.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Package <literal>ckb</literal> is renamed to <literal>ckb-next</literal>,
+     and options <literal>hardware.ckb.*</literal> are renamed to
+     <literal>hardware.ckb-next.*</literal>.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The option <literal>services.xserver.displayManager.job.logToFile</literal> which was
+     previously set to <literal>true</literal> when using the display managers
+     <literal>lightdm</literal>, <literal>sddm</literal> or <literal>xpra</literal> has been
+     reset to the default value (<literal>false</literal>).
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Network interface indiscriminate NixOS firewall options
+     (<literal>networking.firewall.allow*</literal>) are now preserved when also
+     setting interface specific rules such as <literal>networking.firewall.interfaces.en0.allow*</literal>.
+     These rules continue to use the pseudo device "default"
+     (<literal>networking.firewall.interfaces.default.*</literal>), and assigning
+     to this pseudo device will override the (<literal>networking.firewall.allow*</literal>)
+     options.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+     The <literal>nscd</literal> service now disables all caching of
+     <literal>passwd</literal> and <literal>group</literal> databases by
+     default. This was interferring with the correct functioning of the
+     <literal>libnss_systemd.so</literal> module which is used by
+     <literal>systemd</literal> to manage uids and usernames in the presence of
+     <literal>DynamicUser=</literal> in systemd services.  This was already the
+     default behaviour in presence of <literal>services.sssd.enable =
+       true</literal> because nscd caching would interfere with
+     <literal>sssd</literal> in unpredictable ways as well.  Because we're
+     using nscd not for caching, but for convincing glibc to find NSS modules
+     in the nix store instead of an absolute path, we have decided to disable
+     caching globally now, as it's usually not the behaviour the user wants and
+     can lead to surprising behaviour.  Furthermore, negative caching of host
+     lookups is also disabled now by default. This should fix the issue of dns
+     lookups failing in the presence of an unreliable network.
+   </para>
+   <para>
+     If the old behaviour is desired, this can be restored by setting
+     the <literal>services.nscd.config</literal> option
+     with the desired caching parameters.
+     <programlisting>
+     services.nscd.config =
+     ''
+     server-user             nscd
+     threads                 1
+     paranoia                no
+     debug-level             0
+
+     enable-cache            passwd          yes
+     positive-time-to-live   passwd          600
+     negative-time-to-live   passwd          20
+     suggested-size          passwd          211
+     check-files             passwd          yes
+     persistent              passwd          no
+     shared                  passwd          yes
+
+     enable-cache            group           yes
+     positive-time-to-live   group           3600
+     negative-time-to-live   group           60
+     suggested-size          group           211
+     check-files             group           yes
+     persistent              group           no
+     shared                  group           yes
+
+     enable-cache            hosts           yes
+     positive-time-to-live   hosts           600
+     negative-time-to-live   hosts           5
+     suggested-size          hosts           211
+     check-files             hosts           yes
+     persistent              hosts           no
+     shared                  hosts           yes
+     '';
+     </programlisting>
+     See <link xlink:href="https://github.com/NixOS/nixpkgs/pull/50316">#50316</link>
+     for details.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+     GitLab Shell previously used the nix store paths for the
+     <literal>gitlab-shell</literal> command in its
+     <literal>authorized_keys</literal> file, which might stop working after
+     garbage collection. To circumvent that, we regenerated that file on each
+     startup.  As <literal>gitlab-shell</literal> has now been changed to use
+     <literal>/var/run/current-system/sw/bin/gitlab-shell</literal>, this is
+     not necessary anymore, but there might be leftover lines with a nix store
+     path. Regenerate the <literal>authorized_keys</literal> file via
+     <command>sudo -u git -H gitlab-rake gitlab:shell:setup</command> in that
+     case.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+     The <literal>pam_unix</literal> account module is now loaded with its
+     control field set to <literal>required</literal> instead of
+     <literal>sufficient</literal>, so that later PAM account modules that
+     might do more extensive checks are being executed.
+     Previously, the whole account module verification was exited prematurely
+     in case a nss module provided the account name to
+     <literal>pam_unix</literal>.
+     The LDAP and SSSD NixOS modules already add their NSS modules when
+     enabled. In case your setup breaks due to some later PAM account module
+     previosuly shadowed, or failing NSS lookups, please file a bug. You can
+     get back the old behaviour by manually setting
+     <literal><![CDATA[security.pam.services.<name?>.text]]></literal>.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+     The <literal>pam_unix</literal> password module is now loaded with its
+     control field set to <literal>sufficient</literal> instead of
+     <literal>required</literal>, so that password managed only
+     by later PAM password modules are being executed.
+     Previously, for example, changing an LDAP account's password through PAM
+     was not possible: the whole password module verification
+     was exited prematurely by <literal>pam_unix</literal>,
+     preventing <literal>pam_ldap</literal> to manage the password as it should.
+   </para>
+  </listitem>
+   <listitem>
+    <para>
+     <literal>fish</literal> has been upgraded to 3.0.
+     It comes with a number of improvements and backwards incompatible changes.
+     See the <literal>fish</literal> <link xlink:href="https://github.com/fish-shell/fish-shell/releases/tag/3.0.0">release notes</link> for more information.
+    </para>
+   </listitem>
+  <listitem>
+    <para>
+      The ibus-table input method has had a change in config format, which
+      causes all previous settings to be lost. See
+      <link xlink:href="https://github.com/mike-fabian/ibus-table/commit/f9195f877c5212fef0dfa446acb328c45ba5852b">this commit message</link>
+      for details.
+    </para>
+  </listitem>
+  <listitem>
+   <para>
+    Support for NixOS module system type <literal>types.optionSet</literal> and
+    <literal>lib.mkOption</literal> argument <literal>options</literal> is removed.
+    Use <literal>types.submodule</literal> instead.
+    (<link xlink:href="https://github.com/NixOS/nixpkgs/pull/54637">#54637</link>)
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+    <literal>matrix-synapse</literal> has been updated to version 0.99. It will
+    <link xlink:href="https://github.com/matrix-org/synapse/pull/4509">no longer generate a self-signed certificate on first launch</link>
+    and will be <link xlink:href="https://matrix.org/blog/2019/02/05/synapse-0-99-0/">the last version to accept self-signed certificates</link>.
+    As such, it is now recommended to use a proper certificate verified by a
+    root CA (for example Let's Encrypt).
+   </para>
+  </listitem>
+   <listitem>
+    <para>
+     <literal>mailutils</literal> now works by default when
+     <literal>sendmail</literal> is not in a setuid wrapper. As a consequence,
+     the <literal>sendmailPath</literal> argument, having lost its main use, has
+     been removed.
+    </para>
    </listitem>
   </itemizedlist>
  </section>
@@ -51,7 +407,130 @@
 
   <itemizedlist>
    <listitem>
-    <para />
+    <para>
+     The <option>services.matomo</option> module gained the option
+     <option>services.matomo.package</option> which determines the used
+     Matomo version.
+    </para>
+    <para>
+     The Matomo module now also comes with the systemd service <literal>matomo-archive-processing.service</literal>
+     and a timer that automatically triggers archive processing every hour.
+     This means that you can safely
+     <link xlink:href="https://matomo.org/docs/setup-auto-archiving/#disable-browser-triggers-for-matomo-archiving-and-limit-matomo-reports-to-updating-every-hour">
+      disable browser triggers for Matomo archiving
+     </link> at <literal>Administration > System > General Settings</literal>.
+    </para>
+    <para>
+     Additionally, you can enable to
+     <link xlink:href="https://matomo.org/docs/privacy/#step-2-delete-old-visitors-logs">
+      delete old visitor logs
+     </link> at <literal>Administration > System > Privacy</literal>,
+     but make sure that you run <literal>systemctl start matomo-archive-processing.service</literal>
+     at least once without errors if you have already collected data before,
+     so that the reports get archived before the source data gets deleted.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     <literal>composableDerivation</literal> along with supporting library functions
+     has been removed.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The deprecated <literal>truecrypt</literal> package has been removed
+     and <literal>truecrypt</literal> attribute is now an alias for
+     <literal>veracrypt</literal>. VeraCrypt is backward-compatible with
+     TrueCrypt volumes. Note that <literal>cryptsetup</literal> also
+     supports loading TrueCrypt volumes.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+      The Kubernetes DNS addons, kube-dns, has been replaced with CoreDNS.
+      This change is made in accordance with Kubernetes making CoreDNS the official default
+      starting from
+      <link xlink:href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.11.md#sig-cluster-lifecycle">Kubernetes v1.11</link>.
+      Please beware that upgrading DNS-addon on existing clusters might induce
+      minor downtime while the DNS-addon terminates and re-initializes.
+      Also note that the DNS-service now runs with 2 pod replicas by default.
+      The desired number of replicas can be configured using:
+      <option>services.kubernetes.addons.dns.replicas</option>.
+    </para>
+   </listitem>
+   <listitem>
+     <para>
+       The quassel-webserver package and module was removed from nixpkgs due to the lack
+       of maintainers.
+     </para>
+   </listitem>
+   <listitem>
+     <para>
+       The astah-community package was removed from nixpkgs due to it being discontinued and the downloads not being available anymore.
+     </para>
+   </listitem>
+   <listitem>
+     <para>
+       The httpd service now saves log files with a .log file extension by default for
+       easier integration with the logrotate service.
+     </para>
+   </listitem>
+   <listitem>
+     <para>
+       The owncloud server packages and httpd subservice module were removed
+       from nixpkgs due to the lack of maintainers.
+     </para>
+   </listitem>
+   <listitem>
+     <para>
+       It is possible now to uze ZRAM devices as general purpose ephemeral block devices,
+       not only as swap. Using more than 1 device as ZRAM swap is no longer recommended,
+       but is still possible by setting <literal>zramSwap.swapDevices</literal> explicitly.
+     </para>
+     <para>
+      Default algorithm for ZRAM swap was changed to <literal>zstd</literal>.
+     </para>
+     <para>
+      Changes to ZRAM algorithm are applied during <literal>nixos-rebuild switch</literal>,
+      so make sure you have enough swap space on disk to survive ZRAM device rebuild. Alternatively,
+      use <literal>nixos-rebuild boot; reboot</literal>.
+     </para>
+   </listitem>
+   <listitem>
+    <para>
+      Flat volumes are now disabled by default in <literal>hardware.pulseaudio</literal>.
+      This has been done to prevent applications, which are unaware of this feature, setting
+      their volumes to 100% on startup causing harm to your audio hardware and potentially your ears.
+    </para>
+    <note>
+     <para>
+      With this change application specific volumes are relative to the master volume which can be
+      adjusted independently, whereas before they were absolute; meaning that in effect, it scaled the
+      device-volume with the volume of the loudest application.
+     </para>
+    </note>
+   </listitem>
+   <listitem>
+    <para>
+     The <link xlink:href="https://github.com/DanielAdolfsson/ndppd"><literal>ndppd</literal></link> module
+     now supports <link linkend="opt-services.ndppd.enable">all config options</link> provided by the current
+     upstream version as service options. Additionally the <literal>ndppd</literal> package doesn't contain
+     the systemd unit configuration from upstream anymore, the unit is completely configured by the NixOS module now.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     New installs of NixOS will default to the Redmine 4.x series unless otherwise specified in
+     <literal>services.redmine.package</literal> while existing installs of NixOS will default to
+     the Redmine 3.x series.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <link linkend="opt-services.grafana.enable">Grafana module</link> now supports declarative
+     <link xlink:href="http://docs.grafana.org/administration/provisioning/">datasource and dashboard</link>
+     provisioning.
+    </para>
    </listitem>
   </itemizedlist>
  </section>
diff --git a/nixos/lib/build-vms.nix b/nixos/lib/build-vms.nix
index 48288cf5962c4..a5580f4712ebc 100644
--- a/nixos/lib/build-vms.nix
+++ b/nixos/lib/build-vms.nix
@@ -1,6 +1,13 @@
-{ system, minimal ? false, config ? {} }:
-
-let pkgs = import ../.. { inherit system config; }; in
+{ system
+, # Use a minimal kernel?
+  minimal ? false
+, # Ignored
+  config ? null
+  # Nixpkgs, for qemu, lib and more
+, pkgs
+, # NixOS configuration to add to the VMs
+  extraConfigurations ? []
+}:
 
 with pkgs.lib;
 with import ../lib/qemu-flags.nix { inherit pkgs; };
@@ -28,9 +35,10 @@ rec {
       modules = configurations ++
         [ ../modules/virtualisation/qemu-vm.nix
           ../modules/testing/test-instrumentation.nix # !!! should only get added for automated test runs
-          { key = "no-manual"; services.nixosManual.enable = false; }
+          { key = "no-manual"; documentation.nixos.enable = false; }
           { key = "qemu"; system.build.qemu = qemu; }
-        ] ++ optional minimal ../modules/testing/minimal-kernel.nix;
+        ] ++ optional minimal ../modules/testing/minimal-kernel.nix
+          ++ extraConfigurations;
       extraArgs = { inherit nodes; };
     };
 
@@ -75,6 +83,8 @@ rec {
                     (m': let config = (getAttr m' nodes).config; in
                       optionalString (config.networking.primaryIPAddress != "")
                         ("${config.networking.primaryIPAddress} " +
+                         optionalString (config.networking.domain != null)
+                           "${config.networking.hostName}.${config.networking.domain} " +
                          "${config.networking.hostName}\n"));
 
                   virtualisation.qemu.options =
diff --git a/nixos/lib/eval-config.nix b/nixos/lib/eval-config.nix
index f71e264c3478f..5f05b037bdde3 100644
--- a/nixos/lib/eval-config.nix
+++ b/nixos/lib/eval-config.nix
@@ -53,7 +53,8 @@ in rec {
     inherit prefix check;
     modules = modules ++ extraModules ++ baseModules ++ [ pkgsModule ];
     args = extraArgs;
-    specialArgs = { modulesPath = ../modules; } // specialArgs;
+    specialArgs =
+      { modulesPath = builtins.toString ../modules; } // specialArgs;
   }) config options;
 
   # These are the extra arguments passed to every module.  In
diff --git a/nixos/lib/make-channel.nix b/nixos/lib/make-channel.nix
index fd805f7f943f2..9b920b989fcf3 100644
--- a/nixos/lib/make-channel.nix
+++ b/nixos/lib/make-channel.nix
@@ -1,3 +1,7 @@
+/* Build a channel tarball. These contain, in addition to the nixpkgs
+ * expressions themselves, files that indicate the version of nixpkgs
+ * that they represent.
+ */
 { pkgs, nixpkgs, version, versionSuffix }:
 
 pkgs.releaseTools.makeSourceTarball {
diff --git a/nixos/lib/make-disk-image.nix b/nixos/lib/make-disk-image.nix
index bf32a36895c5e..5e86ea479d518 100644
--- a/nixos/lib/make-disk-image.nix
+++ b/nixos/lib/make-disk-image.nix
@@ -27,6 +27,9 @@
 , # The root file system type.
   fsType ? "ext4"
 
+, # Filesystem label
+  label ? "nixos"
+
 , # The initial NixOS configuration file to be copied to
   # /etc/nixos/configuration.nix.
   configFile ? null
@@ -84,7 +87,7 @@ let format' = format; in let
   # FIXME: merge with channel.nix / make-channel.nix.
   channelSources = pkgs.runCommand "nixos-${config.system.nixos.version}" {} ''
     mkdir -p $out
-    cp -prd ${nixpkgs} $out/nixos
+    cp -prd ${nixpkgs.outPath} $out/nixos
     chmod -R u+w $out/nixos
     if [ ! -e $out/nixos/nixpkgs ]; then
       ln -s . $out/nixos/nixpkgs
@@ -134,9 +137,9 @@ let format' = format; in let
       # Get start & length of the root partition in sectors to $START and $SECTORS.
       eval $(partx $diskImage -o START,SECTORS --nr ${rootPartition} --pairs)
 
-      mkfs.${fsType} -F -L nixos $diskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
+      mkfs.${fsType} -F -L ${label} $diskImage -E offset=$(sectorsToBytes $START) $(sectorsToKilobytes $SECTORS)K
     '' else ''
-      mkfs.${fsType} -F -L nixos $diskImage
+      mkfs.${fsType} -F -L ${label} $diskImage
     ''}
 
     root="$PWD/root"
diff --git a/nixos/lib/make-ext4-fs.nix b/nixos/lib/make-ext4-fs.nix
index 88be8b73ab37b..47c6374c81ada 100644
--- a/nixos/lib/make-ext4-fs.nix
+++ b/nixos/lib/make-ext4-fs.nix
@@ -9,6 +9,7 @@
 , e2fsprogs
 , libfaketime
 , perl
+, lkl
 }:
 
 let
@@ -18,16 +19,13 @@ in
 pkgs.stdenv.mkDerivation {
   name = "ext4-fs.img";
 
-  nativeBuildInputs = [e2fsprogs.bin libfaketime perl];
+  nativeBuildInputs = [e2fsprogs.bin libfaketime perl lkl];
 
   buildCommand =
     ''
       # Add the closures of the top-level store objects.
       storePaths=$(cat ${sdClosureInfo}/store-paths)
 
-      # Also include a manifest of the closures in a format suitable for nix-store --load-db.
-      cp ${sdClosureInfo}/registration nix-path-registration
-
       # 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)
@@ -38,55 +36,16 @@ pkgs.stdenv.mkDerivation {
       truncate -s $bytes $out
       faketime -f "1970-01-01 00:00:01" mkfs.ext4 -L ${volumeLabel} -U ${uuid} $out
 
-      # Populate the image contents by piping a bunch of commands to the `debugfs` tool from e2fsprogs.
-      # For example, to copy /nix/store/abcd...efg-coreutils-8.23/bin/sleep:
-      #   cd /nix/store/abcd...efg-coreutils-8.23/bin
-      #   write /nix/store/abcd...efg-coreutils-8.23/bin/sleep sleep
-      #   sif sleep mode 040555
-      #   sif sleep gid 30000
-      # In particular, debugfs doesn't handle absolute target paths; you have to 'cd' in the virtual
-      # filesystem first. Likewise the intermediate directories must already exist (using `find`
-      # handles that for us). And when setting the file's permissions, the inode type flags (__S_IFDIR,
-      # __S_IFREG) need to be set as well.
-      (
-        echo write nix-path-registration nix-path-registration
-        echo mkdir nix
-        echo cd /nix
-        echo mkdir store
-
-        # XXX: This explodes in exciting ways if anything in /nix/store has a space in it.
-        find $storePaths -printf '%y %f %h %m\n'| while read -r type file dir perms; do
-          # echo "TYPE=$type DIR=$dir FILE=$file PERMS=$perms" >&2
+      # Also include a manifest of the closures in a format suitable for nix-store --load-db.
+      cp ${sdClosureInfo}/registration nix-path-registration
+      cptofs -t ext4 -i $out nix-path-registration /
 
-          echo "cd $dir"
-          case $type in
-            d)
-              echo "mkdir $file"
-              echo sif $file mode $((040000 | 0$perms)) # magic constant is __S_IFDIR
-              ;;
-            f)
-              echo "write $dir/$file $file"
-              echo sif $file mode $((0100000 | 0$perms)) # magic constant is __S_IFREG
-              ;;
-            l)
-              echo "symlink $file $(readlink "$dir/$file")"
-              ;;
-            *)
-              echo "Unknown entry: $type $dir $file $perms" >&2
-              exit 1
-              ;;
-          esac
+      # Create nix/store before copying paths
+      faketime -f "1970-01-01 00:00:01" mkdir -p nix/store
+      cptofs -t ext4 -i $out nix /
 
-          echo sif $file gid 30000 # chgrp to nixbld
-        done
-      ) | faketime -f "1970-01-01 00:00:01" debugfs -w $out -f /dev/stdin > errorlog 2>&1
-
-      # The debugfs tool doesn't terminate on error nor exit with a non-zero status. Check manually.
-      if egrep -q 'Could not allocate|File not found' errorlog; then
-        cat errorlog
-        echo "--- Failed to create EXT4 image of $bytes bytes (numInodes=$numInodes, numDataBlocks=$numDataBlocks) ---"
-        return 1
-      fi
+      echo "copying store paths to image..."
+      cptofs -t ext4 -i $out $storePaths /nix/store/
 
       # 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
@@ -94,5 +53,24 @@ pkgs.stdenv.mkDerivation {
         cat errorlog
         return 1
       fi
+
+      (
+        # Resizes **snugly** to its actual limits (or closer to)
+        free=$(dumpe2fs $out | grep '^Free blocks:')
+        blocksize=$(dumpe2fs $out | grep '^Block size:')
+        blocks=$(dumpe2fs $out | grep '^Block count:')
+        blocks=$((''${blocks##*:})) # format the number.
+        blocksize=$((''${blocksize##*:})) # format the number.
+        # System can't boot with 0 blocks free.
+        # Add 16MiB of free space
+        fudge=$(( 16 * 1024 * 1024 / blocksize ))
+        size=$(( blocks - ''${free##*:} + fudge ))
+
+        echo "Resizing from $blocks blocks to $size blocks. (~ $((size*blocksize/1024/1024))MiB)"
+        EXT2FS_NO_MTAB_OK=yes resize2fs $out -f $size
+      )
+
+      # And a final fsck, because of the previous truncating.
+      fsck.ext4 -n -f $out
     '';
 }
diff --git a/nixos/lib/make-iso9660-image.sh b/nixos/lib/make-iso9660-image.sh
index 45cdef1ef4df5..b7b1ab52a6373 100644
--- a/nixos/lib/make-iso9660-image.sh
+++ b/nixos/lib/make-iso9660-image.sh
@@ -47,7 +47,8 @@ if test -n "$bootable"; then
 
     isoBootFlags="-eltorito-boot ${bootImage}
                   -eltorito-catalog .boot.cat
-                  -no-emul-boot -boot-load-size 4 -boot-info-table"
+                  -no-emul-boot -boot-load-size 4 -boot-info-table
+                  --sort-weight 1 /isolinux" # Make sure isolinux is near the beginning of the ISO
 fi
 
 if test -n "$usbBootable"; then
@@ -112,7 +113,7 @@ xorriso="xorriso
  -r
  -path-list pathlist
  --sort-weight 0 /
- --sort-weight 1 /isolinux" # Make sure isolinux is near the beginning of the ISO
+"
 
 $xorriso -output $out/iso/$isoName
 
diff --git a/nixos/lib/make-squashfs.nix b/nixos/lib/make-squashfs.nix
index 7ab84e47f53b5..ee76c9c5bf240 100644
--- a/nixos/lib/make-squashfs.nix
+++ b/nixos/lib/make-squashfs.nix
@@ -3,6 +3,9 @@
 , # The root directory of the squashfs filesystem is filled with the
   # closures of the Nix store paths listed here.
   storeContents ? []
+, # Compression parameters.
+  # For zstd compression you can use "zstd -Xcompression-level 6".
+  comp ? "xz -Xdict-size 100%"
 }:
 
 stdenv.mkDerivation {
@@ -20,6 +23,6 @@ stdenv.mkDerivation {
 
       # Generate the squashfs image.
       mksquashfs nix-path-registration $(cat $closureInfo/store-paths) $out \
-        -keep-as-directory -all-root -b 1048576 -comp xz -Xdict-size 100%
+        -keep-as-directory -all-root -b 1048576 -comp ${comp}
     '';
 }
diff --git a/nixos/lib/make-system-tarball.nix b/nixos/lib/make-system-tarball.nix
index 846013b02d149..dee91a6ce3f49 100644
--- a/nixos/lib/make-system-tarball.nix
+++ b/nixos/lib/make-system-tarball.nix
@@ -1,4 +1,4 @@
-{ stdenv, perl, pixz, pathsFromGraph
+{ stdenv, closureInfo, pixz
 
 , # The file name of the resulting tarball
   fileName ? "nixos-system-${stdenv.hostPlatform.system}"
@@ -29,24 +29,28 @@
 , extraInputs ? [ pixz ]
 }:
 
+let
+  symlinks = map (x: x.symlink) storeContents;
+  objects = map (x: x.object) storeContents;
+in
+
 stdenv.mkDerivation {
   name = "tarball";
   builder = ./make-system-tarball.sh;
-  buildInputs = [ perl ] ++ extraInputs;
+  buildInputs = extraInputs;
 
-  inherit fileName pathsFromGraph extraArgs extraCommands compressCommand;
+  inherit fileName extraArgs extraCommands compressCommand;
 
   # !!! should use XML.
   sources = map (x: x.source) contents;
   targets = map (x: x.target) contents;
 
   # !!! should use XML.
-  objects = map (x: x.object) storeContents;
-  symlinks = map (x: x.symlink) storeContents;
+  inherit symlinks objects;
 
-  # For obtaining the closure of `storeContents'.
-  exportReferencesGraph =
-    map (x: [("closure-" + baseNameOf x.object) x.object]) storeContents;
+  closureInfo = closureInfo {
+    rootPaths = objects;
+  };
 
   extension = compressionExtension;
 }
diff --git a/nixos/lib/make-system-tarball.sh b/nixos/lib/make-system-tarball.sh
index 1a52a284a2573..1a0017a1799a4 100644
--- a/nixos/lib/make-system-tarball.sh
+++ b/nixos/lib/make-system-tarball.sh
@@ -3,7 +3,6 @@ source $stdenv/setup
 sources_=($sources)
 targets_=($targets)
 
-echo $objects
 objects=($objects)
 symlinks=($symlinks)
 
@@ -14,8 +13,6 @@ stripSlash() {
     if test "${res:0:1}" = /; then res=${res:1}; fi
 }
 
-touch pathlist
-
 # Add the individual files.
 for ((i = 0; i < ${#targets_[@]}; i++)); do
     stripSlash "${targets_[$i]}"
@@ -25,9 +22,9 @@ done
 
 
 # Add the closures of the top-level store objects.
+chmod +w .
 mkdir -p nix/store
-storePaths=$(perl $pathsFromGraph closure-*)
-for i in $storePaths; do
+for i in $(< $closureInfo/store-paths); do
     cp -a "$i" "${i:1}"
 done
 
@@ -35,7 +32,7 @@ done
 # TODO tar ruxo
 # Also include a manifest of the closures in a format suitable for
 # nix-store --load-db.
-printRegistration=1 perl $pathsFromGraph closure-* > nix-path-registration
+cp $closureInfo/registration nix-path-registration
 
 # Add symlinks to the top-level store objects.
 for ((n = 0; n < ${#objects[*]}; n++)); do
diff --git a/nixos/lib/test-driver/Logger.pm b/nixos/lib/test-driver/Logger.pm
index 3fe5ef67c144e..080310ea34e08 100644
--- a/nixos/lib/test-driver/Logger.pm
+++ b/nixos/lib/test-driver/Logger.pm
@@ -4,6 +4,7 @@ use strict;
 use Thread::Queue;
 use XML::Writer;
 use Encode qw(decode encode);
+use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
 
 sub new {
     my ($class) = @_;
@@ -46,10 +47,12 @@ sub nest {
     print STDERR maybePrefix("$msg\n", $attrs);
     $self->{log}->startTag("nest");
     $self->{log}->dataElement("head", $msg, %{$attrs});
+    my $now = clock_gettime(CLOCK_MONOTONIC);
     $self->drainLogQueue();
     eval { &$coderef };
     my $res = $@;
     $self->drainLogQueue();
+    $self->log(sprintf("(%.2f seconds)", clock_gettime(CLOCK_MONOTONIC) - $now));
     $self->{log}->endTag("nest");
     die $@ if $@;
 }
diff --git a/nixos/lib/test-driver/Machine.pm b/nixos/lib/test-driver/Machine.pm
index b18f48464ceec..006da889671ee 100644
--- a/nixos/lib/test-driver/Machine.pm
+++ b/nixos/lib/test-driver/Machine.pm
@@ -10,6 +10,7 @@ use Cwd;
 use File::Basename;
 use File::Path qw(make_path);
 use File::Slurp;
+use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
 
 
 my $showGraphics = defined $ENV{'DISPLAY'};
@@ -247,12 +248,15 @@ sub connect {
 
         $self->start;
 
+        my $now = clock_gettime(CLOCK_MONOTONIC);
         local $SIG{ALRM} = sub { die "timed out waiting for the VM to connect\n"; };
-        alarm 300;
+        alarm 600;
         readline $self->{socket} or die "the VM quit before connecting\n";
         alarm 0;
 
         $self->log("connected to guest root shell");
+        # We're interested in tracking how close we are to `alarm`.
+        $self->log(sprintf("(connecting took %.2f seconds)", clock_gettime(CLOCK_MONOTONIC) - $now));
         $self->{connected} = 1;
 
     });
diff --git a/nixos/lib/testing.nix b/nixos/lib/testing.nix
index 42a0c60c7e19c..a13e76a695603 100644
--- a/nixos/lib/testing.nix
+++ b/nixos/lib/testing.nix
@@ -1,6 +1,13 @@
-{ system, minimal ? false, config ? {} }:
-
-with import ./build-vms.nix { inherit system minimal config; };
+{ system
+, pkgs ? import ../.. { inherit system config; }
+  # Use a minimal kernel?
+, minimal ? false
+  # Ignored
+, config ? {}
+  # Modules to add to each VM
+, extraConfigurations ? [] }:
+
+with import ./build-vms.nix { inherit system pkgs minimal extraConfigurations; };
 with pkgs;
 
 let
@@ -27,14 +34,14 @@ in rec {
         cp ${./test-driver/test-driver.pl} $out/bin/nixos-test-driver
         chmod u+x $out/bin/nixos-test-driver
 
-        libDir=$out/lib/perl5/site_perl
+        libDir=$out/${perl.libPrefix}
         mkdir -p $libDir
         cp ${./test-driver/Machine.pm} $libDir/Machine.pm
         cp ${./test-driver/Logger.pm} $libDir/Logger.pm
 
         wrapProgram $out/bin/nixos-test-driver \
           --prefix PATH : "${lib.makeBinPath [ qemu_test vde2 netpbm coreutils ]}" \
-          --prefix PERL5LIB : "${with perlPackages; lib.makePerlPath [ TermReadLineGnu XMLWriter IOTty FileSlurp ]}:$out/lib/perl5/site_perl"
+          --prefix PERL5LIB : "${with perlPackages; makePerlPath [ TermReadLineGnu XMLWriter IOTty FileSlurp ]}:$out/${perl.libPrefix}"
       '';
   };
 
@@ -69,7 +76,7 @@ in rec {
             mkdir -p $out/coverage-data
             mv $i $out/coverage-data/$(dirname $(dirname $i))
           done
-        ''; # */
+        '';
     };
 
 
@@ -109,7 +116,7 @@ in rec {
 
       vms = map (m: m.config.system.build.vm) (lib.attrValues nodes);
 
-      ocrProg = tesseract_4.override { enableLanguages = [ "eng" ]; };
+      ocrProg = tesseract4.override { enableLanguages = [ "eng" ]; };
 
       imagemagick_tiff = imagemagick_light.override { inherit libtiff; };
 
@@ -149,9 +156,23 @@ in rec {
       test = passMeta (runTests driver);
       report = passMeta (releaseTools.gcovReport { coverageRuns = [ test ]; });
 
-    in (if makeCoverageReport then report else test) // {
-      inherit nodes driver test;
-    };
+      nodeNames = builtins.attrNames nodes;
+      invalidNodeNames = lib.filter
+        (node: builtins.match "^[A-z_][A-z0-9_]+$" node == null) nodeNames;
+
+    in
+      if lib.length invalidNodeNames > 0 then
+        throw ''
+          Cannot create machines out of (${lib.concatStringsSep ", " invalidNodeNames})!
+          All machines are referenced as perl variables in the testing framework which will break the
+          script when special characters are used.
+
+          Please stick to alphanumeric chars and underscores as separation.
+        ''
+      else
+        (if makeCoverageReport then report else test) // {
+          inherit nodes driver test;
+        };
 
   runInMachine =
     { drv
diff --git a/nixos/lib/utils.nix b/nixos/lib/utils.nix
index 1ef915d406120..b68e55a40b908 100644
--- a/nixos/lib/utils.nix
+++ b/nixos/lib/utils.nix
@@ -7,9 +7,8 @@ rec {
                      || elem fs.mountPoint [ "/" "/nix" "/nix/store" "/var" "/var/log" "/var/lib" "/etc" ];
 
   # Check whenever `b` depends on `a` as a fileSystem
-  # FIXME: it's incorrect to simply use hasPrefix here: "/dev/a" is not a parent of "/dev/ab"
-  fsBefore = a: b: ((any (x: elem x [ "bind" "move" ]) b.options) && (a.mountPoint == b.device))
-                || (hasPrefix a.mountPoint b.mountPoint);
+  fsBefore = a: b: a.mountPoint == b.device
+                || hasPrefix "${a.mountPoint}${optionalString (!(hasSuffix "/" a.mountPoint)) "/"}" b.mountPoint;
 
   # Escape a path according to the systemd rules, e.g. /dev/xyzzy
   # becomes dev-xyzzy.  FIXME: slow.
diff --git a/nixos/maintainers/scripts/gce/create-gce.sh b/nixos/maintainers/scripts/gce/create-gce.sh
index 0fd26d34d07f9..48748a59d298a 100755
--- a/nixos/maintainers/scripts/gce/create-gce.sh
+++ b/nixos/maintainers/scripts/gce/create-gce.sh
@@ -7,9 +7,9 @@ BUCKET_NAME="${BUCKET_NAME:-nixos-cloud-images}"
 TIMESTAMP="$(date +%Y%m%d%H%M)"
 export TIMESTAMP
 
-nix-build '<nixpkgs/nixos>' \
+nix-build '<nixpkgs/nixos/lib/eval-config.nix>' \
    -A config.system.build.googleComputeImage \
-   --arg configuration "{ imports = [ <nixpkgs/nixos/modules/virtualisation/google-compute-image.nix> ]; }" \
+   --arg modules "[ <nixpkgs/nixos/modules/virtualisation/google-compute-image.nix> ]" \
    --argstr system x86_64-linux \
    -o gce \
    -j 10
diff --git a/nixos/maintainers/scripts/openstack/nova-image.nix b/nixos/maintainers/scripts/openstack/nova-image.nix
deleted file mode 100644
index b6f3a5b152003..0000000000000
--- a/nixos/maintainers/scripts/openstack/nova-image.nix
+++ /dev/null
@@ -1,26 +0,0 @@
-# nix-build '<nixpkgs/nixos>' -A config.system.build.novaImage --arg configuration "{ imports = [ ./nixos/maintainers/scripts/openstack/nova-image.nix ]; }"
-
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-{
-  imports =
-    [ ../../../modules/installer/cd-dvd/channel.nix
-      ../../../modules/virtualisation/nova-config.nix
-    ];
-
-  system.build.novaImage = import ../../../lib/make-disk-image.nix {
-    inherit lib config;
-    pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package
-    diskSize = 8192;
-    format = "qcow2";
-    configFile = pkgs.writeText "configuration.nix"
-      ''
-        {
-          imports = [ <nixpkgs/nixos/modules/virtualisation/nova-config.nix> ];
-        }
-      '';
-  };
-
-}
diff --git a/nixos/maintainers/scripts/openstack/openstack-image.nix b/nixos/maintainers/scripts/openstack/openstack-image.nix
new file mode 100644
index 0000000000000..4c464f43f61d8
--- /dev/null
+++ b/nixos/maintainers/scripts/openstack/openstack-image.nix
@@ -0,0 +1,26 @@
+# nix-build '<nixpkgs/nixos>' -A config.system.build.openstackImage --arg configuration "{ imports = [ ./nixos/maintainers/scripts/openstack/openstack-image.nix ]; }"
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  imports =
+    [ ../../../modules/installer/cd-dvd/channel.nix
+      ../../../modules/virtualisation/openstack-config.nix
+    ];
+
+  system.build.openstackImage = import ../../../lib/make-disk-image.nix {
+    inherit lib config;
+    pkgs = import ../../../.. { inherit (pkgs) system; }; # ensure we use the regular qemu-kvm package
+    diskSize = 8192;
+    format = "qcow2";
+    configFile = pkgs.writeText "configuration.nix"
+      ''
+        {
+          imports = [ <nixpkgs/nixos/modules/virtualisation/openstack-config.nix> ];
+        }
+      '';
+  };
+
+}
diff --git a/nixos/modules/config/appstream.nix b/nixos/modules/config/appstream.nix
new file mode 100644
index 0000000000000..483ac9c3cd762
--- /dev/null
+++ b/nixos/modules/config/appstream.nix
@@ -0,0 +1,25 @@
+{ config, lib, ... }:
+
+with lib;
+{
+  options = {
+    appstream.enable = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to install files to support the 
+        <link xlink:href="https://www.freedesktop.org/software/appstream/docs/index.html">AppStream metadata specification</link>.
+      '';
+    };
+  };
+
+  config = mkIf config.appstream.enable {
+    environment.pathsToLink = [ 
+      # per component metadata
+      "/share/metainfo" 
+      # legacy path for above
+      "/share/appdata" 
+    ];
+  };
+
+}
diff --git a/nixos/modules/config/fonts/fontconfig-penultimate.nix b/nixos/modules/config/fonts/fontconfig-penultimate.nix
index fc01c15acb9b8..7e05e77d967a2 100644
--- a/nixos/modules/config/fonts/fontconfig-penultimate.nix
+++ b/nixos/modules/config/fonts/fontconfig-penultimate.nix
@@ -269,7 +269,7 @@ in
 
   };
 
-  config = mkIf (config.fonts.fontconfig.enable && cfg.enable) {
+  config = mkIf (config.fonts.fontconfig.enable && config.fonts.fontconfig.penultimate.enable) {
 
     fonts.fontconfig.confPackages = [ penultimateConf ];
 
diff --git a/nixos/modules/config/gtk/gtk-icon-cache.nix b/nixos/modules/config/gtk/gtk-icon-cache.nix
new file mode 100644
index 0000000000000..9c5d993b9c592
--- /dev/null
+++ b/nixos/modules/config/gtk/gtk-icon-cache.nix
@@ -0,0 +1,86 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+{
+  options = {
+    gtk.iconCache.enable = mkOption {
+      type = types.bool;
+      default = config.services.xserver.enable;
+      description = ''
+        Whether to build icon theme caches for GTK+ applications.
+      '';
+    };
+  };
+
+  config = mkIf config.gtk.iconCache.enable {
+
+    # (Re)build icon theme caches
+    # ---------------------------
+    # Each icon theme has its own cache. The difficult is that many
+    # packages may contribute with icons to the same theme by installing
+    # some icons.
+    #
+    # For instance, on my current NixOS system, the following packages
+    # (among many others) have icons installed into the hicolor icon
+    # theme: hicolor-icon-theme, psensor, wpa_gui, caja, etc.
+    #
+    # As another example, the mate icon theme has icons installed by the
+    # packages mate-icon-theme, mate-settings-daemon, and libmateweather.
+    #
+    # The HighContrast icon theme also has icons from different packages,
+    # like gnome-theme-extras and meld.
+
+    # When the cache is built all of its icons has to be known. How to
+    # implement this?
+    #
+    # I think that most themes have all icons installed by only one
+    # package. On my system there are 71 themes installed. Only 3 of them
+    # have icons installed from more than one package.
+    #
+    # If the main package of the theme provides a cache, presumably most
+    # of its icons will be available to applications without running this
+    # module. But additional icons offered by other packages will not be
+    # available. Therefore I think that it is good that the main theme
+    # package installs a cache (although it does not completely fixes the
+    # situation for packages installed with nix-env).
+    #
+    # The module solution presented here keeps the cache when there is
+    # only one package contributing with icons to the theme. Otherwise it
+    # rebuilds the cache taking into account the icons provided all
+    # packages.
+
+    environment.extraSetup = ''
+      # For each icon theme directory ...
+
+      find $out/share/icons -mindepth 1 -maxdepth 1 -print0 | while read -d $'\0' themedir
+      do
+
+        # In order to build the cache, the theme dir should be
+        # writable. When the theme dir is a symbolic link to somewhere
+        # in the nix store it is not writable and it means that only
+        # one package is contributing to the theme. If it already has
+        # a cache, no rebuild is needed. Otherwise a cache has to be
+        # built, and to be able to do that we first remove the
+        # symbolic link and make a directory, and then make symbolic
+        # links from the original directory into the new one.
+
+        if [ ! -w "$themedir" -a -L "$themedir" -a ! -r "$themedir"/icon-theme.cache ]; then
+          name=$(basename "$themedir")
+          path=$(readlink -f "$themedir")
+          rm "$themedir"
+          mkdir -p "$themedir"
+          ln -s "$path"/* "$themedir"/
+        fi
+
+        # (Re)build the cache if the theme dir is writable, replacing any
+        # existing cache for the theme
+
+        if [ -w "$themedir" ]; then
+          rm -f "$themedir"/icon-theme.cache
+          ${pkgs.gtk3.out}/bin/gtk-update-icon-cache --ignore-theme-index "$themedir"
+        fi
+      done
+    '';
+  };
+
+}
diff --git a/nixos/modules/config/i18n.nix b/nixos/modules/config/i18n.nix
index 6bf8c653e113a..dc7305b1ba24c 100644
--- a/nixos/modules/config/i18n.nix
+++ b/nixos/modules/config/i18n.nix
@@ -34,6 +34,17 @@ with lib;
         '';
       };
 
+      extraLocaleSettings = mkOption {
+        type = types.attrsOf types.str;
+        default = {};
+        example = { LC_MESSAGES = "en_US.UTF-8"; LC_TIME = "de_DE.UTF-8"; };
+        description = ''
+          A set of additional system-wide locale settings other than
+          <literal>LANG</literal> which can be configured with
+          <option>i18n.defaultLocale</option>.
+        '';
+      };
+
       supportedLocales = mkOption {
         type = types.listOf types.str;
         default = ["all"];
@@ -129,7 +140,7 @@ with lib;
     environment.sessionVariables =
       { LANG = config.i18n.defaultLocale;
         LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
-      };
+      } // config.i18n.extraLocaleSettings;
 
     systemd.globalEnvironment = mkIf (config.i18n.supportedLocales != []) {
       LOCALE_ARCHIVE = "${config.i18n.glibcLocales}/lib/locale/locale-archive";
@@ -141,6 +152,7 @@ with lib;
         source = pkgs.writeText "locale.conf"
           ''
             LANG=${config.i18n.defaultLocale}
+            ${concatStringsSep "\n" (mapAttrsToList (n: v: ''${n}=${v}'') config.i18n.extraLocaleSettings)}
           '';
       };
 
diff --git a/nixos/modules/config/iproute2.nix b/nixos/modules/config/iproute2.nix
index 881ad671a6275..a1d9ebcec66bf 100644
--- a/nixos/modules/config/iproute2.nix
+++ b/nixos/modules/config/iproute2.nix
@@ -4,20 +4,29 @@ with lib;
 
 let
   cfg = config.networking.iproute2;
-  confDir = "/run/iproute2";
 in
 {
-  options.networking.iproute2.enable = mkEnableOption "copy IP route configuration files";
-
-  config = mkMerge [
-    ({ nixpkgs.config.iproute2.confDir = confDir; })
-
-    (mkIf cfg.enable {
-      system.activationScripts.iproute2 = ''
-        cp -R ${pkgs.iproute}/etc/iproute2 ${confDir}
-        chmod -R 664 ${confDir}
-        chmod +x ${confDir}
+  options.networking.iproute2 = {
+    enable = mkEnableOption "copy IP route configuration files";
+    rttablesExtraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        Verbatim lines to add to /etc/iproute2/rt_tables
       '';
-    })
-  ];
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."iproute2/bpf_pinning" = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/bpf_pinning"; };
+    environment.etc."iproute2/ematch_map"  = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/ematch_map";  };
+    environment.etc."iproute2/group"       = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/group";       };
+    environment.etc."iproute2/nl_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/nl_protos";   };
+    environment.etc."iproute2/rt_dsfield"  = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_dsfield";  };
+    environment.etc."iproute2/rt_protos"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_protos";   };
+    environment.etc."iproute2/rt_realms"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_realms";   };
+    environment.etc."iproute2/rt_scopes"   = { mode = "0644"; text = fileContents "${pkgs.iproute}/etc/iproute2/rt_scopes";   };
+    environment.etc."iproute2/rt_tables"   = { mode = "0644"; text = (fileContents "${pkgs.iproute}/etc/iproute2/rt_tables")
+                                                                   + (optionalString (cfg.rttablesExtraConfig != "") "\n\n${cfg.rttablesExtraConfig}"); };
+  };
 }
diff --git a/nixos/modules/config/krb5/default.nix b/nixos/modules/config/krb5/default.nix
index c22e99a0a2f18..87021a27d34f2 100644
--- a/nixos/modules/config/krb5/default.nix
+++ b/nixos/modules/config/krb5/default.nix
@@ -79,7 +79,7 @@ in {
 
   options = {
     krb5 = {
-      enable = mkEnableOption "Whether to enable Kerberos V.";
+      enable = mkEnableOption "building krb5.conf, configuration file for Kerberos V";
 
       kerberos = mkOption {
         type = types.package;
diff --git a/nixos/modules/config/ldap.nix b/nixos/modules/config/ldap.nix
index 0693e896f7151..f65a3fc50d548 100644
--- a/nixos/modules/config/ldap.nix
+++ b/nixos/modules/config/ldap.nix
@@ -38,6 +38,8 @@ let
       bind_timelimit ${toString cfg.bind.timeLimit}
       ${optionalString (cfg.bind.distinguishedName != "")
         "binddn ${cfg.bind.distinguishedName}" }
+      ${optionalString (cfg.daemon.rootpwmoddn != "")
+        "rootpwmoddn ${cfg.daemon.rootpwmoddn}" }
       ${optionalString (cfg.daemon.extraConfig != "") cfg.daemon.extraConfig }
     '';
   };
@@ -126,6 +128,26 @@ in
             the end of the nslcd configuration file (nslcd.conf).
           '' ;
         } ;
+
+        rootpwmoddn = mkOption {
+          default = "";
+          example = "cn=admin,dc=example,dc=com";
+          type = types.str;
+          description = ''
+            The distinguished name to use to bind to the LDAP server
+            when the root user tries to modify a user's password.
+          '';
+        };
+
+        rootpwmodpw = mkOption {
+          default = "";
+          example = "/run/keys/nslcd.rootpwmodpw";
+          type = types.str;
+          description = ''
+            The path to a file containing the credentials with which
+            to bind to the LDAP server if the root user tries to change a user's password
+          '';
+        };
       };
 
       bind = {
@@ -203,9 +225,11 @@ in
     system.activationScripts = mkIf insertLdapPassword {
       ldap = stringAfter [ "etc" "groups" "users" ] ''
         if test -f "${cfg.bind.password}" ; then
-          echo "bindpw "$(cat ${cfg.bind.password})"" | cat ${ldapConfig.source} - > /etc/ldap.conf.bindpw
-          mv -fT /etc/ldap.conf.bindpw /etc/ldap.conf
-          chmod 600 /etc/ldap.conf
+          umask 0077
+          conf="$(mktemp)"
+          printf 'bindpw %s\n' "$(cat ${cfg.bind.password})" |
+          cat ${ldapConfig.source} - >"$conf"
+          mv -fT "$conf" /etc/ldap.conf
         fi
       '';
     };
@@ -232,21 +256,31 @@ in
         wantedBy = [ "multi-user.target" ];
 
         preStart = ''
-          mkdir -p /run/nslcd
-          rm -f /run/nslcd/nslcd.pid;
-          chown nslcd.nslcd /run/nslcd
-          ${optionalString (cfg.bind.distinguishedName != "") ''
-            if test -s "${cfg.bind.password}" ; then
-              ln -sfT "${cfg.bind.password}" /run/nslcd/bindpw
-            fi
-          ''}
+          umask 0077
+          conf="$(mktemp)"
+          {
+            cat ${nslcdConfig.source}
+            test -z '${cfg.bind.distinguishedName}' -o ! -f '${cfg.bind.password}' ||
+            printf 'bindpw %s\n' "$(cat '${cfg.bind.password}')"
+            test -z '${cfg.daemon.rootpwmoddn}' -o ! -f '${cfg.daemon.rootpwmodpw}' ||
+            printf 'rootpwmodpw %s\n' "$(cat '${cfg.daemon.rootpwmodpw}')"
+          } >"$conf"
+          mv -fT "$conf" /etc/nslcd.conf
         '';
 
+        # NOTE: because one cannot pass a custom config path to `nslcd`
+        # (which is only able to use `/etc/nslcd.conf`)
+        # changes in `nslcdConfig` won't change `serviceConfig`,
+        # and thus won't restart `nslcd`.
+        # Therefore `restartTriggers` is used on `/etc/nslcd.conf`.
+        restartTriggers = [ nslcdConfig.source ];
+
         serviceConfig = {
           ExecStart = "${nss_pam_ldapd}/sbin/nslcd";
           Type = "forking";
           PIDFile = "/run/nslcd/nslcd.pid";
           Restart = "always";
+          RuntimeDirectory = [ "nslcd" ];
         };
       };
 
diff --git a/nixos/modules/config/networking.nix b/nixos/modules/config/networking.nix
index 1ef5313d3fdd5..25253cf02e1d9 100644
--- a/nixos/modules/config/networking.nix
+++ b/nixos/modules/config/networking.nix
@@ -16,6 +16,13 @@ let
   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";
+
+  localhostMultiple = any (elem "localhost") (attrValues (removeAttrs cfg.hosts [ "127.0.0.1" "::1" ]));
+
 in
 
 {
@@ -23,8 +30,7 @@ in
   options = {
 
     networking.hosts = lib.mkOption {
-      type = types.attrsOf ( types.listOf types.str );
-      default = {};
+      type = types.attrsOf (types.listOf types.str);
       example = literalExample ''
         {
           "127.0.0.1" = [ "foo.bar.baz" ];
@@ -192,6 +198,29 @@ in
 
   config = {
 
+    assertions = [{
+      assertion = localhostMapped4;
+      message = ''`networking.hosts` doesn't map "127.0.0.1" to "localhost"'';
+    } {
+      assertion = !cfg.enableIPv6 || localhostMapped6;
+      message = ''`networking.hosts` doesn't map "::1" to "localhost"'';
+    } {
+      assertion = !localhostMultiple;
+      message = ''
+        `networking.hosts` maps "localhost" to something other than "127.0.0.1"
+        or "::1". This will break some applications. Please use
+        `networking.extraHosts` if you really want to add such a mapping.
+      '';
+    }];
+
+    networking.hosts = {
+      "127.0.0.1" = [ "localhost" ];
+    } // optionalAttrs (cfg.hostName != "") {
+      "127.0.1.1" = [ cfg.hostName ];
+    } // optionalAttrs cfg.enableIPv6 {
+      "::1" = [ "localhost" ];
+    };
+
     environment.etc =
       { # /etc/services: TCP/UDP port assignments.
         "services".source = pkgs.iana-etc + "/etc/services";
@@ -199,29 +228,14 @@ in
         # /etc/protocols: IP protocol numbers.
         "protocols".source  = pkgs.iana-etc + "/etc/protocols";
 
-        # /etc/rpc: RPC program numbers.
-        "rpc".source = pkgs.glibc.out + "/etc/rpc";
-
         # /etc/hosts: Hostname-to-IP mappings.
-        "hosts".text =
-          let oneToString = set : ip : ip + " " + concatStringsSep " " ( getAttr ip set );
-              allToString = set : concatMapStringsSep "\n" ( oneToString set ) ( attrNames set );
-              userLocalHosts = optionalString
-                ( builtins.hasAttr "127.0.0.1" cfg.hosts )
-                ( concatStringsSep " " ( remove "localhost" cfg.hosts."127.0.0.1" ));
-              userLocalHosts6 = optionalString
-                ( builtins.hasAttr "::1" cfg.hosts )
-                ( concatStringsSep " " ( remove "localhost" cfg.hosts."::1" ));
-              otherHosts = allToString ( removeAttrs cfg.hosts [ "127.0.0.1" "::1" ]);
-          in
-          ''
-            127.0.0.1 ${userLocalHosts} localhost
-            ${optionalString cfg.enableIPv6 ''
-              ::1 ${userLocalHosts6} localhost
-            ''}
-            ${otherHosts}
-            ${cfg.extraHosts}
-          '';
+        "hosts".text = let
+          oneToString = set: ip: ip + " " + concatStringsSep " " set.${ip};
+          allToString = set: concatMapStringsSep "\n" (oneToString set) (attrNames set);
+        in ''
+          ${allToString cfg.hosts}
+          ${cfg.extraHosts}
+        '';
 
         # /etc/host.conf: resolver configuration file
         "host.conf".text = cfg.hostConf;
@@ -233,6 +247,10 @@ in
               # 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}'
@@ -246,11 +264,14 @@ in
             '';
 
       } // optionalAttrs config.services.resolved.enable {
-        # symlink the static version of resolv.conf as recommended by upstream:
+        # 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 = "${pkgs.systemd}/lib/systemd/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";
       };
 
       networking.proxy.envVars =
@@ -296,4 +317,4 @@ in
 
   };
 
-  }
+}
diff --git a/nixos/modules/config/no-x-libs.nix b/nixos/modules/config/no-x-libs.nix
index d9ecaa4818ba6..37e66c6454294 100644
--- a/nixos/modules/config/no-x-libs.nix
+++ b/nixos/modules/config/no-x-libs.nix
@@ -35,7 +35,7 @@ with lib;
       networkmanager-vpnc = super.networkmanager-vpnc.override { withGnome = false; };
       networkmanager-iodine = super.networkmanager-iodine.override { withGnome = false; };
       pinentry = super.pinentry_ncurses;
-      gobjectIntrospection = super.gobjectIntrospection.override { x11Support = false; };
+      gobject-introspection = super.gobject-introspection.override { x11Support = false; };
     }));
   };
 }
diff --git a/nixos/modules/config/nsswitch.nix b/nixos/modules/config/nsswitch.nix
index a74d551f50df4..b601e908e49f0 100644
--- a/nixos/modules/config/nsswitch.nix
+++ b/nixos/modules/config/nsswitch.nix
@@ -1,6 +1,6 @@
 # Configuration for the Name Service Switch (/etc/nsswitch.conf).
 
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -15,6 +15,7 @@ let
   ldap = canLoadExternalModules && (config.users.ldap.enable && config.users.ldap.nsswitch);
   sssd = canLoadExternalModules && config.services.sssd.enable;
   resolved = canLoadExternalModules && config.services.resolved.enable;
+  googleOsLogin = canLoadExternalModules && config.security.googleOsLogin.enable;
 
   hostArray = [ "files" ]
     ++ optional mymachines "mymachines"
@@ -29,6 +30,7 @@ let
     ++ optional sssd "sss"
     ++ optional ldap "ldap"
     ++ optional mymachines "mymachines"
+    ++ optional googleOsLogin "cache_oslogin oslogin"
     ++ [ "systemd" ];
 
   shadowArray = [ "files" ]
@@ -97,7 +99,7 @@ in {
     # configured IP addresses, or ::1 and 127.0.0.2 as
     # fallbacks. Systemd also provides nss-mymachines to return IP
     # addresses of local containers.
-    system.nssModules = optionals canLoadExternalModules [ config.systemd.package.out ];
-
+    system.nssModules = (optionals canLoadExternalModules [ config.systemd.package.out ])
+      ++ optional googleOsLogin pkgs.google-compute-engine-oslogin.out;
   };
 }
diff --git a/nixos/modules/config/pulseaudio.nix b/nixos/modules/config/pulseaudio.nix
index e16a021ec20b8..e61a3a7312018 100644
--- a/nixos/modules/config/pulseaudio.nix
+++ b/nixos/modules/config/pulseaudio.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, pkgs_i686, ... }:
+{ config, lib, pkgs, ... }:
 
 with pkgs;
 with lib;
@@ -19,7 +19,7 @@ let
 
   # Forces 32bit pulseaudio and alsaPlugins to be built/supported for apps
   # using 32bit alsa on 64bit linux.
-  enable32BitAlsaPlugins = cfg.support32Bit && stdenv.isx86_64 && (pkgs_i686.alsaLib != null && pkgs_i686.libpulseaudio != null);
+  enable32BitAlsaPlugins = cfg.support32Bit && stdenv.isx86_64 && (pkgs.pkgsi686Linux.alsaLib != null && pkgs.pkgsi686Linux.libpulseaudio != null);
 
 
   myConfigFile =
@@ -63,7 +63,7 @@ let
     pcm_type.pulse {
       libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;
       ${lib.optionalString enable32BitAlsaPlugins
-     "libs.32Bit = ${pkgs_i686.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
+     "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_pcm_pulse.so ;"}
     }
     pcm.!default {
       type pulse
@@ -72,7 +72,7 @@ let
     ctl_type.pulse {
       libs.native = ${pkgs.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;
       ${lib.optionalString enable32BitAlsaPlugins
-     "libs.32Bit = ${pkgs_i686.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
+     "libs.32Bit = ${pkgs.pkgsi686Linux.alsaPlugins}/lib/alsa-lib/libasound_module_ctl_pulse.so ;"}
     }
     ctl.!default {
       type pulse
@@ -154,6 +154,18 @@ in {
         '';
       };
 
+      extraModules = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        example = literalExample "[ pkgs.pulseaudio-modules-bt ]";
+        description = ''
+          Extra pulseaudio modules to use. This is intended for out-of-tree
+          pulseaudio modules like extra bluetooth codecs.
+
+          Extra modules take precedence over built-in pulseaudio modules.
+        '';
+      };
+
       daemon = {
         logLevel = mkOption {
           type = types.str;
@@ -168,7 +180,7 @@ in {
           type = types.attrsOf types.unspecified;
           default = {};
           description = ''Config of the pulse daemon. See <literal>man pulse-daemon.conf</literal>.'';
-          example = literalExample ''{ flat-volumes = "no"; }'';
+          example = literalExample ''{ realtime-scheduling = "yes"; }'';
         };
       };
 
@@ -230,12 +242,27 @@ in {
           source = writeText "libao.conf" "default_driver=pulse"; }
       ];
 
+      # Disable flat volumes to enable relative ones
+      hardware.pulseaudio.daemon.config.flat-volumes = mkDefault "no";
+
       # Allow PulseAudio to get realtime priority using rtkit.
       security.rtkit.enable = true;
 
       systemd.packages = [ overriddenPackage ];
     })
 
+    (mkIf (cfg.extraModules != []) {
+      hardware.pulseaudio.daemon.config.dl-search-path = let
+        overriddenModules = builtins.map
+          (drv: drv.override { pulseaudio = overriddenPackage; })
+          cfg.extraModules;
+        modulePaths = builtins.map
+          (drv: "${drv}/lib/pulse-${overriddenPackage.version}/modules")
+          # User-provided extra modules take precedence
+          (overriddenModules ++ [ overriddenPackage ]);
+      in lib.concatStringsSep ":" modulePaths;
+    })
+
     (mkIf hasZeroconf {
       services.avahi.enable = true;
     })
diff --git a/nixos/modules/config/shells-environment.nix b/nixos/modules/config/shells-environment.nix
index 555db459f57a6..6379b52870ea2 100644
--- a/nixos/modules/config/shells-environment.nix
+++ b/nixos/modules/config/shells-environment.nix
@@ -108,14 +108,14 @@ in
     };
 
     environment.shellAliases = mkOption {
-      default = {};
-      example = { ll = "ls -l"; };
+      example = { l = null; ll = "ls -l"; };
       description = ''
         An attribute set that maps aliases (the top level attribute names in
         this option) to command strings or directly to build outputs. The
         aliases are added to all users' shells.
+        Aliases mapped to <code>null</code> are ignored.
       '';
-      type = types.attrs; # types.attrsOf types.stringOrPath;
+      type = with types; attrsOf (nullOr (either str path));
     };
 
     environment.binsh = mkOption {
@@ -157,6 +157,12 @@ in
     # terminal instead of logging out of X11).
     environment.variables = config.environment.sessionVariables;
 
+    environment.shellAliases = mapAttrs (name: mkDefault) {
+      ls = "ls --color=tty";
+      ll = "ls -l";
+      l  = "ls -alh";
+    };
+
     environment.etc."shells".text =
       ''
         ${concatStringsSep "\n" (map utils.toShellPath cfg.shells)}
diff --git a/nixos/modules/config/system-path.nix b/nixos/modules/config/system-path.nix
index ffb437491f6c7..7a65e44e828d4 100644
--- a/nixos/modules/config/system-path.nix
+++ b/nixos/modules/config/system-path.nix
@@ -7,19 +7,21 @@ with lib;
 
 let
 
-  requiredPackages =
+  requiredPackages = map lib.lowPrio
     [ config.nix.package
       pkgs.acl
       pkgs.attr
       pkgs.bashInteractive # bash with ncurses support
       pkgs.bzip2
-      pkgs.coreutils
+      pkgs.coreutils-full
       pkgs.cpio
       pkgs.curl
       pkgs.diffutils
       pkgs.findutils
       pkgs.gawk
-      pkgs.glibc # for ldd, getent
+      pkgs.stdenv.cc.libc
+      pkgs.getent
+      pkgs.getconf
       pkgs.gnugrep
       pkgs.gnupatch
       pkgs.gnused
@@ -133,14 +135,10 @@ in
       # outputs TODO: note that the tools will often not be linked by default
       postBuild =
         ''
-          if [ -x $out/bin/gtk-update-icon-cache -a -f $out/share/icons/hicolor/index.theme ]; then
-              $out/bin/gtk-update-icon-cache $out/share/icons/hicolor
-          fi
-
           if [ -x $out/bin/glib-compile-schemas -a -w $out/share/glib-2.0/schemas ]; then
               $out/bin/glib-compile-schemas $out/share/glib-2.0/schemas
           fi
-          
+
           ${config.environment.extraSetup}
         '';
     };
diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix
index 426e1666a814f..c3f228c9bcc4d 100644
--- a/nixos/modules/config/users-groups.nix
+++ b/nixos/modules/config/users-groups.nix
@@ -266,7 +266,7 @@ let
         (mkIf config.isNormalUser {
           group = mkDefault "users";
           createHome = mkDefault true;
-          home = mkDefault "/home/${name}";
+          home = mkDefault "/home/${config.name}";
           useDefaultShell = mkDefault true;
           isSystemUser = mkDefault false;
         })
@@ -534,8 +534,8 @@ in {
         install -m 0755 -d /home
 
         ${pkgs.perl}/bin/perl -w \
-          -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl \
-          -I${pkgs.perlPackages.JSON}/lib/perl5/site_perl \
+          -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix} \
+          -I${pkgs.perlPackages.JSON}/${pkgs.perl.libPrefix} \
           ${./update-users-groups.pl} ${spec}
       '';
 
diff --git a/nixos/modules/config/xdg/mime.nix b/nixos/modules/config/xdg/mime.nix
index 4323a49ea1dd0..cd1064630fbcc 100644
--- a/nixos/modules/config/xdg/mime.nix
+++ b/nixos/modules/config/xdg/mime.nix
@@ -24,11 +24,11 @@ with lib;
 
     environment.extraSetup = ''
       if [ -w $out/share/mime ] && [ -d $out/share/mime/packages ]; then
-          XDG_DATA_DIRS=$out/share ${pkgs.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
+          XDG_DATA_DIRS=$out/share ${pkgs.buildPackages.shared-mime-info}/bin/update-mime-database -V $out/share/mime > /dev/null
       fi
 
       if [ -w $out/share/applications ]; then
-          ${pkgs.desktop-file-utils}/bin/update-desktop-database $out/share/applications
+          ${pkgs.buildPackages.desktop-file-utils}/bin/update-desktop-database $out/share/applications
       fi
     '';
   };
diff --git a/nixos/modules/config/xdg/sounds.nix b/nixos/modules/config/xdg/sounds.nix
new file mode 100644
index 0000000000000..148240d631cf2
--- /dev/null
+++ b/nixos/modules/config/xdg/sounds.nix
@@ -0,0 +1,22 @@
+{ config, lib, ... }:
+
+with lib;
+{
+  options = {
+    xdg.sounds.enable = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to install files to support the
+        <link xlink:href="https://www.freedesktop.org/wiki/Specifications/sound-theme-spec/">XDG Sound Theme specification</link>.
+      '';
+    };
+  };
+
+  config = mkIf config.xdg.sounds.enable {
+    environment.pathsToLink = [
+      "/share/sounds"
+    ];
+  };
+
+}
diff --git a/nixos/modules/config/zram.nix b/nixos/modules/config/zram.nix
index c1748812821e7..925d945c081ee 100644
--- a/nixos/modules/config/zram.nix
+++ b/nixos/modules/config/zram.nix
@@ -6,10 +6,27 @@ let
 
   cfg = config.zramSwap;
 
-  devices = map (nr: "zram${toString nr}") (range 0 (cfg.numDevices - 1));
+  # don't set swapDevices as mkDefault, so we can detect user had read our warning
+  # (see below) and made an action (or not)
+  devicesCount = if cfg.swapDevices != null then cfg.swapDevices else cfg.numDevices;
+
+  devices = map (nr: "zram${toString nr}") (range 0 (devicesCount - 1));
 
   modprobe = "${pkgs.kmod}/bin/modprobe";
 
+  warnings =
+  assert cfg.swapDevices != null -> cfg.numDevices >= cfg.swapDevices;
+  flatten [
+    (optional (cfg.numDevices > 1 && cfg.swapDevices == null) ''
+      Using several small zram devices as swap is no better than using one large.
+      Set either zramSwap.numDevices = 1 or explicitly set zramSwap.swapDevices.
+
+      Previously multiple zram devices were used to enable multithreaded
+      compression. Linux supports multithreaded compression for 1 device
+      since 3.15. See https://lkml.org/lkml/2014/2/28/404 for details.
+    '')
+  ];
+
 in
 
 {
@@ -24,9 +41,11 @@ in
         default = false;
         type = types.bool;
         description = ''
-          Enable in-memory compressed swap space provided by the zram kernel
-          module.
-          See https://www.kernel.org/doc/Documentation/blockdev/zram.txt
+          Enable in-memory compressed devices and swap space provided by the zram
+          kernel module.
+          See <link xlink:href="https://www.kernel.org/doc/Documentation/blockdev/zram.txt">
+            https://www.kernel.org/doc/Documentation/blockdev/zram.txt
+          </link>.
         '';
       };
 
@@ -34,7 +53,19 @@ in
         default = 1;
         type = types.int;
         description = ''
-          Number of zram swap devices to create.
+          Number of zram devices to create. See also
+          <literal>zramSwap.swapDevices</literal>
+        '';
+      };
+
+      swapDevices = mkOption {
+        default = null;
+        example = 1;
+        type = with types; nullOr int;
+        description = ''
+          Number of zram devices to be used as swap. Must be
+          <literal>&lt;= zramSwap.numDevices</literal>.
+          Default is same as <literal>zramSwap.numDevices</literal>, recommended is 1.
         '';
       };
 
@@ -44,7 +75,8 @@ in
         description = ''
           Maximum amount of memory that can be used by the zram swap devices
           (as a percentage of your total memory). Defaults to 1/2 of your total
-          RAM.
+          RAM. Run <literal>zramctl</literal> to check how good memory is
+          compressed.
         '';
       };
 
@@ -58,12 +90,26 @@ in
         '';
       };
 
+      algorithm = mkOption {
+        default = "zstd";
+        example = "lzo";
+        type = with types; either (enum [ "lzo" "lz4" "zstd" ]) str;
+        description = ''
+          Compression algorithm. <literal>lzo</literal> has good compression,
+          but is slow. <literal>lz4</literal> has bad compression, but is fast.
+          <literal>zstd</literal> is both good compression and fast.
+          You can check what other algorithms are supported by your zram device with
+          <programlisting>cat /sys/class/block/zram*/comp_algorithm</programlisting>
+        '';
+      };
     };
 
   };
 
   config = mkIf cfg.enable {
 
+    inherit warnings;
+
     system.requiredKernelConfig = with config.lib.kernelConfig; [
       (isModule "ZRAM")
     ];
@@ -85,25 +131,25 @@ in
         createZramInitService = dev:
           nameValuePair "zram-init-${dev}" {
             description = "Init swap on zram-based device ${dev}";
-            bindsTo = [ "dev-${dev}.swap" ];
             after = [ "dev-${dev}.device" "zram-reloader.service" ];
             requires = [ "dev-${dev}.device" "zram-reloader.service" ];
             before = [ "dev-${dev}.swap" ];
             requiredBy = [ "dev-${dev}.swap" ];
+            unitConfig.DefaultDependencies = false; # needed to prevent a cycle
             serviceConfig = {
               Type = "oneshot";
               RemainAfterExit = true;
               ExecStop = "${pkgs.runtimeShell} -c 'echo 1 > /sys/class/block/${dev}/reset'";
             };
             script = ''
-              set -u
-              set -o pipefail
-              
+              set -euo pipefail
+
               # Calculate memory to use for zram
-              totalmem=$(${pkgs.gnugrep}/bin/grep 'MemTotal: ' /proc/meminfo | ${pkgs.gawk}/bin/awk '{print $2}')
-              mem=$(((totalmem * ${toString cfg.memoryPercent} / 100 / ${toString cfg.numDevices}) * 1024))
+              mem=$(${pkgs.gawk}/bin/awk '/MemTotal: / {
+                  print int($2*${toString cfg.memoryPercent}/100.0/${toString devicesCount}*1024)
+              }' /proc/meminfo)
 
-              echo $mem > /sys/class/block/${dev}/disksize
+              ${pkgs.utillinux}/sbin/zramctl --size $mem --algorithm ${cfg.algorithm} /dev/${dev}
               ${pkgs.utillinux}/sbin/mkswap /dev/${dev}
             '';
             restartIfChanged = false;
@@ -111,6 +157,9 @@ in
       in listToAttrs ((map createZramInitService devices) ++ [(nameValuePair "zram-reloader"
         {
           description = "Reload zram kernel module when number of devices changes";
+          wants = [ "systemd-udevd.service" ];
+          after = [ "systemd-udevd.service" ];
+          unitConfig.DefaultDependencies = false; # needed to prevent a cycle
           serviceConfig = {
             Type = "oneshot";
             RemainAfterExit = true;
@@ -118,7 +167,11 @@ in
             ExecStart = "${modprobe} zram";
             ExecStop = "${modprobe} -r zram";
           };
-          restartTriggers = [ cfg.numDevices ];
+          restartTriggers = [
+            cfg.numDevices
+            cfg.algorithm
+            cfg.memoryPercent
+          ];
           restartIfChanged = true;
         })]);
 
diff --git a/nixos/modules/hardware/all-firmware.nix b/nixos/modules/hardware/all-firmware.nix
index e978ec6b40ada..69cc22aaa34b1 100644
--- a/nixos/modules/hardware/all-firmware.nix
+++ b/nixos/modules/hardware/all-firmware.nix
@@ -38,7 +38,7 @@ in {
         firmwareLinuxNonfree
         intel2200BGFirmware
         rtl8192su-firmware
-      ] ++ optional (pkgs.stdenv.isAarch32 || pkgs.stdenv.isAarch64) raspberrypiWirelessFirmware
+      ] ++ optional (pkgs.stdenv.hostPlatform.isAarch32 || pkgs.stdenv.hostPlatform.isAarch64) raspberrypiWirelessFirmware
         ++ optionals (versionOlder config.boot.kernelPackages.kernel.version "4.13") [
         rtl8723bs-firmware
       ];
diff --git a/nixos/modules/hardware/bladeRF.nix b/nixos/modules/hardware/bladeRF.nix
new file mode 100644
index 0000000000000..9254434771431
--- /dev/null
+++ b/nixos/modules/hardware/bladeRF.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.bladeRF;
+
+in
+
+{
+  options.hardware.bladeRF = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enables udev rules for BladeRF devices. By default grants access
+        to users in the "bladerf" group. You may want to install the
+        libbladeRF package.
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [ pkgs.libbladeRF ];
+    users.groups.bladerf = {};
+  };
+}
\ No newline at end of file
diff --git a/nixos/modules/hardware/ckb-next.nix b/nixos/modules/hardware/ckb-next.nix
new file mode 100644
index 0000000000000..20b2756d8b266
--- /dev/null
+++ b/nixos/modules/hardware/ckb-next.nix
@@ -0,0 +1,49 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.hardware.ckb-next;
+
+in
+  {
+    options.hardware.ckb-next = {
+      enable = mkEnableOption "the Corsair keyboard/mouse driver";
+
+      gid = mkOption {
+        type = types.nullOr types.int;
+        default = null;
+        example = 100;
+        description = ''
+          Limit access to the ckb daemon to a particular group.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.ckb-next;
+        defaultText = "pkgs.ckb-next";
+        description = ''
+          The package implementing the Corsair keyboard/mouse driver.
+        '';
+      };
+    };
+
+    config = mkIf cfg.enable {
+      environment.systemPackages = [ cfg.package ];
+
+      systemd.services.ckb-next = {
+        description = "Corsair Keyboards and Mice Daemon";
+        wantedBy = ["multi-user.target"];
+        serviceConfig = {
+          ExecStart = "${cfg.package}/bin/ckb-next-daemon ${optionalString (cfg.gid != null) "--gid=${builtins.toString cfg.gid}"}";
+          Restart = "on-failure";
+          StandardOutput = "syslog";
+        };
+      };
+    };
+
+    meta = {
+      maintainers = with lib.maintainers; [ kierdavis ];
+    };
+  }
diff --git a/nixos/modules/hardware/ckb.nix b/nixos/modules/hardware/ckb.nix
deleted file mode 100644
index 8429572a88226..0000000000000
--- a/nixos/modules/hardware/ckb.nix
+++ /dev/null
@@ -1,40 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.hardware.ckb;
-
-in
-  {
-    options.hardware.ckb = {
-      enable = mkEnableOption "the Corsair keyboard/mouse driver";
-
-      package = mkOption {
-        type = types.package;
-        default = pkgs.ckb;
-        defaultText = "pkgs.ckb";
-        description = ''
-          The package implementing the Corsair keyboard/mouse driver.
-        '';
-      };
-    };
-
-    config = mkIf cfg.enable {
-      environment.systemPackages = [ cfg.package ];
-
-      systemd.services.ckb = {
-        description = "Corsair Keyboard Daemon";
-        wantedBy = ["multi-user.target"];
-        script = "${cfg.package}/bin/ckb-daemon";
-        serviceConfig = {
-          Restart = "always";
-          StandardOutput = "syslog";
-        };
-      };
-    };
-
-    meta = {
-      maintainers = with lib.maintainers; [ kierdavis ];
-    };
-  }
diff --git a/nixos/modules/hardware/opengl.nix b/nixos/modules/hardware/opengl.nix
index b371af353cf9a..6b7b8069fd44a 100644
--- a/nixos/modules/hardware/opengl.nix
+++ b/nixos/modules/hardware/opengl.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, pkgs_i686, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -124,22 +124,26 @@ in
 
   config = mkIf cfg.enable {
 
-    assertions = lib.singleton {
-      assertion = cfg.driSupport32Bit -> pkgs.stdenv.isx86_64;
-      message = "Option driSupport32Bit only makes sense on a 64-bit system.";
-    };
-
-    system.activationScripts.setup-opengl =
-      ''
-        ln -sfn ${package} /run/opengl-driver
-        ${if pkgs.stdenv.isi686 then ''
-          ln -sfn opengl-driver /run/opengl-driver-32
-        '' else if cfg.driSupport32Bit then ''
-          ln -sfn ${package32} /run/opengl-driver-32
-        '' else ''
-          rm -f /run/opengl-driver-32
-        ''}
-      '';
+    assertions = [
+      { assertion = cfg.driSupport32Bit -> pkgs.stdenv.isx86_64;
+        message = "Option driSupport32Bit only makes sense on a 64-bit system.";
+      }
+      { assertion = cfg.driSupport32Bit -> (config.boot.kernelPackages.kernel.features.ia32Emulation or false);
+        message = "Option driSupport32Bit requires a kernel that supports 32bit emulation";
+      }
+    ];
+
+    systemd.tmpfiles.rules = [
+      "L+ /run/opengl-driver - - - - ${package}"
+      (
+        if pkgs.stdenv.isi686 then
+          "L+ /run/opengl-driver-32 - - - - opengl-driver"
+        else if cfg.driSupport32Bit then
+          "L+ /run/opengl-driver-32 - - - - ${package32}"
+        else
+          "r /run/opengl-driver-32"
+      )
+    ];
 
     environment.sessionVariables.LD_LIBRARY_PATH =
       [ "/run/opengl-driver/lib" ] ++ optional cfg.driSupport32Bit "/run/opengl-driver-32/lib";
@@ -148,7 +152,7 @@ in
       [ "/run/opengl-driver/share" ] ++ optional cfg.driSupport32Bit "/run/opengl-driver-32/share";
 
     hardware.opengl.package = mkDefault (makePackage pkgs);
-    hardware.opengl.package32 = mkDefault (makePackage pkgs_i686);
+    hardware.opengl.package32 = mkDefault (makePackage pkgs.pkgsi686Linux);
 
     boot.extraModulePackages = optional (elem "virtualbox" videoDrivers) kernelPackages.virtualboxGuestAdditions;
   };
diff --git a/nixos/modules/hardware/raid/hpsa.nix b/nixos/modules/hardware/raid/hpsa.nix
index 1b4b1fa1954fb..3a65cb800a98a 100644
--- a/nixos/modules/hardware/raid/hpsa.nix
+++ b/nixos/modules/hardware/raid/hpsa.nix
@@ -8,7 +8,7 @@ let
     version = "2.40-13.0";
 
     src = pkgs.fetchurl {
-      url = "http://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/${name}_amd64.deb";
+      url = "https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/${name}_amd64.deb";
       sha256 = "11w7fwk93lmfw0yya4jpjwdmgjimqxx6412sqa166g1pz4jil4sw";
     };
 
@@ -34,7 +34,7 @@ let
 
     meta = with lib; {
       description = "HP Smart Array CLI";
-      homepage = http://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/;
+      homepage = https://downloads.linux.hpe.com/SDR/downloads/MCP/Ubuntu/pool/non-free/;
       license = licenses.unfreeRedistributable;
       platforms = [ "x86_64-linux" ];
       maintainers = with maintainers; [ volth ];
diff --git a/nixos/modules/hardware/steam-hardware.nix b/nixos/modules/hardware/steam-hardware.nix
new file mode 100644
index 0000000000000..378aeffe71b5e
--- /dev/null
+++ b/nixos/modules/hardware/steam-hardware.nix
@@ -0,0 +1,25 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.hardware.steam-hardware;
+
+in
+
+{
+  options.hardware.steam-hardware = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable udev rules for Steam hardware such as the Steam Controller, other supported controllers and the HTC Vive";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.udev.packages = [
+      pkgs.steamPackages.steam
+    ];
+  };
+}
diff --git a/nixos/modules/hardware/video/amdgpu-pro.nix b/nixos/modules/hardware/video/amdgpu-pro.nix
index 50af022b93c86..ab9e0c92020e0 100644
--- a/nixos/modules/hardware/video/amdgpu-pro.nix
+++ b/nixos/modules/hardware/video/amdgpu-pro.nix
@@ -1,6 +1,6 @@
 # This module provides the proprietary AMDGPU-PRO drivers.
 
-{ config, lib, pkgs, pkgs_i686, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -11,7 +11,7 @@ let
   enabled = elem "amdgpu-pro" drivers;
 
   package = config.boot.kernelPackages.amdgpu-pro;
-  package32 = pkgs_i686.linuxPackages.amdgpu-pro.override { libsOnly = true; kernel = null; };
+  package32 = pkgs.pkgsi686Linux.linuxPackages.amdgpu-pro.override { libsOnly = true; kernel = null; };
 
   opengl = config.hardware.opengl;
 
diff --git a/nixos/modules/hardware/video/ati.nix b/nixos/modules/hardware/video/ati.nix
index 2fa37af6ca588..6102919f0155f 100644
--- a/nixos/modules/hardware/video/ati.nix
+++ b/nixos/modules/hardware/video/ati.nix
@@ -1,6 +1,6 @@
 # This module provides the proprietary ATI X11 / OpenGL drivers.
 
-{ config, lib, pkgs_i686, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -24,7 +24,7 @@ in
       { name = "fglrx"; modules = [ ati_x11 ]; libPath = [ "${ati_x11}/lib" ]; };
 
     hardware.opengl.package = ati_x11;
-    hardware.opengl.package32 = pkgs_i686.linuxPackages.ati_drivers_x11.override { libsOnly = true; kernel = null; };
+    hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.ati_drivers_x11.override { libsOnly = true; kernel = null; };
 
     environment.systemPackages = [ ati_x11 ];
 
diff --git a/nixos/modules/hardware/video/nvidia.nix b/nixos/modules/hardware/video/nvidia.nix
index eb1952280331f..6ba8130af71f6 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.
 
-{ config, lib, pkgs, pkgs_i686, ... }:
+{ stdenv, config, lib, pkgs, ... }:
 
 with lib;
 
@@ -20,38 +20,154 @@ let
       kernelPackages.nvidia_x11_legacy304
     else if elem "nvidiaLegacy340" drivers then
       kernelPackages.nvidia_x11_legacy340
+    else if elem "nvidiaLegacy390" drivers then
+      kernelPackages.nvidia_x11_legacy390
     else null;
 
   nvidia_x11 = nvidiaForKernel config.boot.kernelPackages;
-  nvidia_libs32 = (nvidiaForKernel pkgs_i686.linuxPackages).override { libsOnly = true; kernel = null; };
+  nvidia_libs32 =
+    if versionOlder nvidia_x11.version "391" then
+      ((nvidiaForKernel pkgs.pkgsi686Linux.linuxPackages).override { libsOnly = true; kernel = null; }).out
+    else
+      (nvidiaForKernel config.boot.kernelPackages).lib32;
 
   enabled = nvidia_x11 != null;
+
+  cfg = config.hardware.nvidia;
+  optimusCfg = cfg.optimus_prime;
 in
 
 {
+  options = {
+    hardware.nvidia.modesetting.enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Enable kernel modesetting when using the NVIDIA proprietary driver.
+
+        Enabling this fixes screen tearing when using Optimus via PRIME (see
+        <option>hardware.nvidia.optimus_prime.enable</option>. This is not enabled
+        by default because it is not officially supported by NVIDIA and would not
+        work with SLI.
+      '';
+    };
+
+    hardware.nvidia.optimus_prime.enable = lib.mkOption {
+      type = lib.types.bool;
+      default = false;
+      description = ''
+        Enable NVIDIA Optimus support using the NVIDIA proprietary driver via PRIME.
+        If enabled, the NVIDIA GPU will be always on and used for all rendering,
+        while enabling output to displays attached only to the integrated Intel GPU
+        without a multiplexer.
+
+        Note that this option only has any effect if the "nvidia" driver is specified
+        in <option>services.xserver.videoDrivers</option>, and it should preferably
+        be the only driver there.
+
+        If this is enabled, then the bus IDs of the NVIDIA and Intel GPUs have to be
+        specified (<option>hardware.nvidia.optimus_prime.nvidiaBusId</option> and
+        <option>hardware.nvidia.optimus_prime.intelBusId</option>).
+
+        If you enable this, you may want to also enable kernel modesetting for the
+        NVIDIA driver (<option>hardware.nvidia.modesetting.enable</option>) in order
+        to prevent tearing.
+
+        Note that this configuration will only be successful when a display manager
+        for which the <option>services.xserver.displayManager.setupCommands</option>
+        option is supported is used; notably, SLiM is not supported.
+      '';
+    };
+
+    hardware.nvidia.optimus_prime.nvidiaBusId = lib.mkOption {
+      type = lib.types.string;
+      default = "";
+      example = "PCI:1:0:0";
+      description = ''
+        Bus ID of the NVIDIA GPU. You can find it using lspci; for example if lspci
+        shows the NVIDIA GPU at "01:00.0", set this option to "PCI:1:0:0".
+      '';
+    };
+
+    hardware.nvidia.optimus_prime.intelBusId = lib.mkOption {
+      type = lib.types.string;
+      default = "";
+      example = "PCI:0:2:0";
+      description = ''
+        Bus ID of the Intel GPU. You can find it using lspci; for example if lspci
+        shows the Intel GPU at "00:02.0", set this option to "PCI:0:2:0".
+      '';
+    };
+  };
 
   config = mkIf enabled {
     assertions = [
       {
-        assertion = config.services.xserver.displayManager.gdm.wayland;
-        message = "NVidia drivers don't support wayland";
+        assertion = with config.services.xserver.displayManager; gdm.enable -> !gdm.wayland;
+        message = "NVIDIA drivers don't support wayland, set services.xserver.displayManager.gdm.wayland=false";
+      }
+      {
+        assertion = !optimusCfg.enable ||
+          (optimusCfg.nvidiaBusId != "" && optimusCfg.intelBusId != "");
+        message = ''
+          When NVIDIA Optimus via PRIME is enabled, the GPU bus IDs must configured.
+        '';
       }
     ];
 
-    services.xserver.drivers = singleton
-      { name = "nvidia"; modules = [ nvidia_x11.bin ]; libPath = [ nvidia_x11 ]; };
+    # If Optimus/PRIME is enabled, we:
+    # - Specify the configured NVIDIA GPU bus ID in the Device section for the
+    #   "nvidia" driver.
+    # - Add the AllowEmptyInitialConfiguration option to the Screen section for the
+    #   "nvidia" driver, in order to allow the X server to start without any outputs.
+    # - Add a separate Device section for the Intel GPU, using the "modesetting"
+    #   driver and with the configured BusID.
+    # - Reference that Device section from the ServerLayout section as an inactive
+    #   device.
+    # - Configure the display manager to run specific `xrandr` commands which will
+    #   configure/enable displays connected to the Intel GPU.
+
+    services.xserver.drivers = singleton {
+      name = "nvidia";
+      modules = [ nvidia_x11.bin ];
+      libPath = [ nvidia_x11 ];
+      deviceSection = optionalString optimusCfg.enable
+        ''
+          BusID "${optimusCfg.nvidiaBusId}"
+        '';
+      screenSection =
+        ''
+          Option "RandRRotation" "on"
+          ${optionalString optimusCfg.enable "Option \"AllowEmptyInitialConfiguration\""}
+        '';
+    };
 
-    services.xserver.screenSection =
+    services.xserver.extraConfig = optionalString optimusCfg.enable
+      ''
+        Section "Device"
+          Identifier "nvidia-optimus-intel"
+          Driver "modesetting"
+          BusID  "${optimusCfg.intelBusId}"
+          Option "AccelMethod" "none"
+        EndSection
+      '';
+    services.xserver.serverLayoutSection = optionalString optimusCfg.enable
       ''
-        Option "RandRRotation" "on"
+        Inactive "nvidia-optimus-intel"
       '';
 
+    services.xserver.displayManager.setupCommands = optionalString optimusCfg.enable ''
+      # Added by nvidia configuration module for Optimus/PRIME.
+      ${pkgs.xorg.xrandr}/bin/xrandr --setprovideroutputsource modesetting NVIDIA-0
+      ${pkgs.xorg.xrandr}/bin/xrandr --auto
+    '';
+
     environment.etc."nvidia/nvidia-application-profiles-rc" = mkIf nvidia_x11.useProfiles {
       source = "${nvidia_x11.bin}/share/nvidia/nvidia-application-profiles-rc";
     };
 
     hardware.opengl.package = nvidia_x11.out;
-    hardware.opengl.package32 = nvidia_libs32.out;
+    hardware.opengl.package32 = nvidia_libs32;
 
     environment.systemPackages = [ nvidia_x11.bin nvidia_x11.settings ]
       ++ lib.filter (p: p != null) [ nvidia_x11.persistenced ];
@@ -62,6 +178,8 @@ in
     boot.kernelModules = [ "nvidia-uvm" ] ++
       lib.optionals config.services.xserver.enable [ "nvidia" "nvidia_modeset" "nvidia_drm" ];
 
+    # If requested enable modesetting via kernel parameter.
+    boot.kernelParams = optional cfg.modesetting.enable "nvidia-drm.modeset=1";
 
     # Create /dev/nvidia-uvm when the nvidia-uvm module is loaded.
     services.udev.extraRules =
diff --git a/nixos/modules/i18n/input-method/default.xml b/nixos/modules/i18n/input-method/default.xml
index ab918a9fb23e3..117482fb0d57d 100644
--- a/nixos/modules/i18n/input-method/default.xml
+++ b/nixos/modules/i18n/input-method/default.xml
@@ -3,32 +3,50 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-input-methods">
-
-<title>Input Methods</title>
-
-<para>Input methods are an operating system component that allows any data, such
-  as keyboard strokes or mouse movements, to be received as input. In this way
-  users can enter characters and symbols not found on their input devices. Using
-  an input method is obligatory for any language that has more graphemes than
-  there are keys on the keyboard.</para>
-
-<para>The following input methods are available in NixOS:</para>
-
-<itemizedlist>
-  <listitem><para>IBus: The intelligent input bus.</para></listitem>
-  <listitem><para>Fcitx: A customizable lightweight input
-      method.</para></listitem>
-  <listitem><para>Nabi: A Korean input method based on XIM.</para></listitem>
-  <listitem><para>Uim: The universal input method, is a library with a XIM
-      bridge.</para></listitem>
-</itemizedlist>
-
-<section xml:id="module-services-input-methods-ibus"><title>IBus</title>
-
-<para>IBus is an Intelligent Input Bus. It provides full featured and user
-  friendly input method user interface.</para>
-
-<para>The following snippet can be used to configure IBus:</para>
+ <title>Input Methods</title>
+ <para>
+  Input methods are an operating system component that allows any data, such as
+  keyboard strokes or mouse movements, to be received as input. In this way
+  users can enter characters and symbols not found on their input devices.
+  Using an input method is obligatory for any language that has more graphemes
+  than there are keys on the keyboard.
+ </para>
+ <para>
+  The following input methods are available in NixOS:
+ </para>
+ <itemizedlist>
+  <listitem>
+   <para>
+    IBus: The intelligent input bus.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+    Fcitx: A customizable lightweight input method.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+    Nabi: A Korean input method based on XIM.
+   </para>
+  </listitem>
+  <listitem>
+   <para>
+    Uim: The universal input method, is a library with a XIM bridge.
+   </para>
+  </listitem>
+ </itemizedlist>
+ <section xml:id="module-services-input-methods-ibus">
+  <title>IBus</title>
+
+  <para>
+   IBus is an Intelligent Input Bus. It provides full featured and user
+   friendly input method user interface.
+  </para>
+
+  <para>
+   The following snippet can be used to configure IBus:
+  </para>
 
 <programlisting>
 i18n.inputMethod = {
@@ -37,57 +55,89 @@ i18n.inputMethod = {
 };
 </programlisting>
 
-<para><literal>i18n.inputMethod.ibus.engines</literal> is optional and can be
-  used to add extra IBus engines.</para>
-
-<para>Available extra IBus engines are:</para>
-
-<itemizedlist>
-  <listitem><para>Anthy (<literal>ibus-engines.anthy</literal>): Anthy is a
-      system for Japanese input method. It converts Hiragana text to Kana Kanji
-      mixed text.</para></listitem>
-  <listitem><para>Hangul (<literal>ibus-engines.hangul</literal>): Korean input
-      method.</para></listitem>
-  <listitem><para>m17n (<literal>ibus-engines.m17n</literal>): m17n is an input
-      method that uses input methods and corresponding icons in the m17n
-      database.</para></listitem>
-  <listitem><para>mozc (<literal>ibus-engines.mozc</literal>): A Japanese input
-      method from Google.</para></listitem>
-  <listitem><para>Table (<literal>ibus-engines.table</literal>): An input method
-      that load tables of input methods.</para></listitem>
-  <listitem><para>table-others (<literal>ibus-engines.table-others</literal>):
-      Various table-based input methods. To use this, and any other table-based
-      input methods, it must appear in the list of engines along with
-      <literal>table</literal>. For example:
+  <para>
+   <literal>i18n.inputMethod.ibus.engines</literal> is optional and can be used
+   to add extra IBus engines.
+  </para>
+
+  <para>
+   Available extra IBus engines are:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Anthy (<literal>ibus-engines.anthy</literal>): Anthy is a system for
+     Japanese input method. It converts Hiragana text to Kana Kanji mixed text.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Hangul (<literal>ibus-engines.hangul</literal>): Korean input method.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     m17n (<literal>ibus-engines.m17n</literal>): m17n is an input method that
+     uses input methods and corresponding icons in the m17n database.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     mozc (<literal>ibus-engines.mozc</literal>): A Japanese input method from
+     Google.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Table (<literal>ibus-engines.table</literal>): An input method that load
+     tables of input methods.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     table-others (<literal>ibus-engines.table-others</literal>): Various
+     table-based input methods. To use this, and any other table-based input
+     methods, it must appear in the list of engines along with
+     <literal>table</literal>. For example:
 <programlisting>
 ibus.engines = with pkgs.ibus-engines; [ table table-others ];
 </programlisting>
-  </para></listitem>
-</itemizedlist>
-
-<para>To use any input method, the package must be added in the configuration,
-  as shown above, and also (after running <literal>nixos-rebuild</literal>) the
-  input method must be added from IBus' preference dialog.</para>
-
-<simplesect xml:id="module-services-input-methods-troubleshooting">
-  <title>Troubleshooting</title>
-  <para>If IBus works in some applications but not others, a likely cause of
-  this is that IBus is depending on a different version of
-  <literal>glib</literal> to what the applications are depending on. This can
-  be checked by running <literal>nix-store -q --requisites &lt;path&gt; | grep
-  glib</literal>, where <literal>&lt;path&gt;</literal> is the path of either
-  IBus or an application in the Nix store. The <literal>glib</literal>
-  packages must match exactly. If they do not, uninstalling and reinstalling
-  the application is a likely fix.</para>
-</simplesect>
-</section>
-
-<section xml:id="module-services-input-methods-fcitx"><title>Fcitx</title>
-
-<para>Fcitx is an input method framework with extension support. It has three
-  built-in Input Method Engine, Pinyin, QuWei and Table-based input
-  methods.</para>
-<para>The following snippet can be used to configure Fcitx:</para>
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   To use any input method, the package must be added in the configuration, as
+   shown above, and also (after running <literal>nixos-rebuild</literal>) the
+   input method must be added from IBus' preference dialog.
+  </para>
+
+  <simplesect xml:id="module-services-input-methods-troubleshooting">
+   <title>Troubleshooting</title>
+   <para>
+    If IBus works in some applications but not others, a likely cause of this
+    is that IBus is depending on a different version of <literal>glib</literal>
+    to what the applications are depending on. This can be checked by running
+    <literal>nix-store -q --requisites &lt;path&gt; | grep glib</literal>,
+    where <literal>&lt;path&gt;</literal> is the path of either IBus or an
+    application in the Nix store. The <literal>glib</literal> packages must
+    match exactly. If they do not, uninstalling and reinstalling the
+    application is a likely fix.
+   </para>
+  </simplesect>
+ </section>
+ <section xml:id="module-services-input-methods-fcitx">
+  <title>Fcitx</title>
+
+  <para>
+   Fcitx is an input method framework with extension support. It has three
+   built-in Input Method Engine, Pinyin, QuWei and Table-based input methods.
+  </para>
+
+  <para>
+   The following snippet can be used to configure Fcitx:
+  </para>
 
 <programlisting>
 i18n.inputMethod = {
@@ -96,51 +146,89 @@ i18n.inputMethod = {
 };
 </programlisting>
 
-<para><literal>i18n.inputMethod.fcitx.engines</literal> is optional and can be
-  used to add extra Fcitx engines.</para>
-
-<para>Available extra Fcitx engines are:</para>
-
-<itemizedlist>
-  <listitem><para>Anthy (<literal>fcitx-engines.anthy</literal>): Anthy is a
-      system for Japanese input method. It converts Hiragana text to Kana Kanji
-      mixed text.</para></listitem>
-  <listitem><para>Chewing (<literal>fcitx-engines.chewing</literal>): Chewing is
-      an intelligent Zhuyin input method. It is one of the most popular input
-      methods among Traditional Chinese Unix users.</para></listitem>
-  <listitem><para>Hangul (<literal>fcitx-engines.hangul</literal>): Korean input
-      method.</para></listitem>
-  <listitem><para>Unikey (<literal>fcitx-engines.unikey</literal>): Vietnamese input
-      method.</para></listitem>
-  <listitem><para>m17n (<literal>fcitx-engines.m17n</literal>): m17n is an input
-      method that uses input methods and corresponding icons in the m17n
-      database.</para></listitem>
-  <listitem><para>mozc (<literal>fcitx-engines.mozc</literal>): A Japanese input
-      method from Google.</para></listitem>
-  <listitem><para>table-others (<literal>fcitx-engines.table-others</literal>):
-      Various table-based input methods.</para></listitem>
-</itemizedlist>
-</section>
-
-<section xml:id="module-services-input-methods-nabi"><title>Nabi</title>
-
-<para>Nabi is an easy to use Korean X input method. It allows you to enter
-  phonetic Korean characters (hangul) and pictographic Korean characters
-  (hanja).</para>
-<para>The following snippet can be used to configure Nabi:</para>
+  <para>
+   <literal>i18n.inputMethod.fcitx.engines</literal> is optional and can be
+   used to add extra Fcitx engines.
+  </para>
+
+  <para>
+   Available extra Fcitx engines are:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Anthy (<literal>fcitx-engines.anthy</literal>): Anthy is a system for
+     Japanese input method. It converts Hiragana text to Kana Kanji mixed text.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Chewing (<literal>fcitx-engines.chewing</literal>): Chewing is an
+     intelligent Zhuyin input method. It is one of the most popular input
+     methods among Traditional Chinese Unix users.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Hangul (<literal>fcitx-engines.hangul</literal>): Korean input method.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Unikey (<literal>fcitx-engines.unikey</literal>): Vietnamese input method.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     m17n (<literal>fcitx-engines.m17n</literal>): m17n is an input method that
+     uses input methods and corresponding icons in the m17n database.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     mozc (<literal>fcitx-engines.mozc</literal>): A Japanese input method from
+     Google.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     table-others (<literal>fcitx-engines.table-others</literal>): Various
+     table-based input methods.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+ <section xml:id="module-services-input-methods-nabi">
+  <title>Nabi</title>
+
+  <para>
+   Nabi is an easy to use Korean X input method. It allows you to enter
+   phonetic Korean characters (hangul) and pictographic Korean characters
+   (hanja).
+  </para>
+
+  <para>
+   The following snippet can be used to configure Nabi:
+  </para>
 
 <programlisting>
 i18n.inputMethod = {
   <link linkend="opt-i18n.inputMethod.enabled">enabled</link> = "nabi";
 };
 </programlisting>
-</section>
+ </section>
+ <section xml:id="module-services-input-methods-uim">
+  <title>Uim</title>
 
-<section xml:id="module-services-input-methods-uim"><title>Uim</title>
+  <para>
+   Uim (short for "universal input method") is a multilingual input method
+   framework. Applications can use it through so-called bridges.
+  </para>
 
-<para>Uim (short for "universal input method") is a multilingual input method
-  framework. Applications can use it through so-called bridges.</para>
-<para>The following snippet can be used to configure uim:</para>
+  <para>
+   The following snippet can be used to configure uim:
+  </para>
 
 <programlisting>
 i18n.inputMethod = {
@@ -148,8 +236,9 @@ i18n.inputMethod = {
 };
 </programlisting>
 
-<para>Note: The <xref linkend="opt-i18n.inputMethod.uim.toolbar"/> option can be
-  used to choose uim toolbar.</para>
-
-</section>
+  <para>
+   Note: The <xref linkend="opt-i18n.inputMethod.uim.toolbar"/> option can be
+   used to choose uim toolbar.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/installer/cd-dvd/channel.nix b/nixos/modules/installer/cd-dvd/channel.nix
index 01cfe8a02e10d..e946c4abc5761 100644
--- a/nixos/modules/installer/cd-dvd/channel.nix
+++ b/nixos/modules/installer/cd-dvd/channel.nix
@@ -16,7 +16,7 @@ let
     { }
     ''
       mkdir -p $out
-      cp -prd ${nixpkgs} $out/nixos
+      cp -prd ${nixpkgs.outPath} $out/nixos
       chmod -R u+w $out/nixos
       if [ ! -e $out/nixos/nixpkgs ]; then
         ln -s . $out/nixos/nixpkgs
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
new file mode 100644
index 0000000000000..228ef371d2523
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-base.nix
@@ -0,0 +1,49 @@
+# This module contains the basic configuration for building a graphical NixOS
+# installation CD.
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  imports = [ ./installation-cd-base.nix ];
+
+  services.xserver = {
+    enable = true;
+
+    # Don't start the X server by default.
+    autorun = mkForce false;
+
+    # Automatically login as root.
+    displayManager.slim = {
+      enable = true;
+      defaultUser = "root";
+      autoLogin = true;
+    };
+
+  };
+
+  # Provide networkmanager for easy wireless configuration.
+  networking.networkmanager.enable = true;
+  networking.wireless.enable = mkForce false;
+
+  # KDE complains if power management is disabled (to be precise, if
+  # there is no power management backend such as upower).
+  powerManagement.enable = true;
+
+  environment.systemPackages = [
+    # Include gparted for partitioning disks.
+    pkgs.gparted
+
+    # Include some editors.
+    pkgs.vim
+    pkgs.bvi # binary editor
+    pkgs.joe
+
+    # Firefox for reading the manual.
+    pkgs.firefox
+
+    pkgs.glxinfo
+  ];
+
+}
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 4c4e69d60d9c7..42b5ec8822726 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-gnome.nix
@@ -6,47 +6,11 @@
 with lib;
 
 {
-  imports = [ ./installation-cd-base.nix ];
+  imports = [ ./installation-cd-graphical-base.nix ];
 
-  services.xserver = {
-    enable = true;
-    # GDM doesn't start in virtual machines with ISO
-    displayManager.slim = {
-      enable = true;
-      defaultUser = "root";
-      autoLogin = true;
-    };
-    desktopManager.gnome3 = {
-      enable = true;
-      extraGSettingsOverrides = ''
-        [org.gnome.desktop.background]
-        show-desktop-icons=true
-
-        [org.gnome.nautilus.desktop]
-        trash-icon-visible=false
-        volumes-visible=false
-        home-icon-visible=false
-        network-icon-visible=false
-      '';
-
-      extraGSettingsOverridePackages = [ pkgs.gnome3.nautilus ];
-    };
-  };
-
-  environment.systemPackages =
-    [ # Include gparted for partitioning disks.
-      pkgs.gparted
+  services.xserver.desktopManager.gnome3.enable = true;
 
-      # Include some editors.
-      pkgs.vim
-      pkgs.bvi # binary editor
-      pkgs.joe
-
-      pkgs.glxinfo
-    ];
-
-  # Don't start the X server by default.
-  services.xserver.autorun = mkForce false;
+  services.xserver.displayManager.slim.enable = mkForce false;
 
   # Auto-login as root.
   services.xserver.displayManager.gdm.autoLogin = {
@@ -54,25 +18,4 @@ with lib;
     user = "root";
   };
 
-  system.activationScripts.installerDesktop = let
-    # Must be executable
-    desktopFile = pkgs.writeScript "nixos-manual.desktop" ''
-      [Desktop Entry]
-      Version=1.0
-      Type=Link
-      Name=NixOS Manual
-      URL=${config.system.build.manual.manual}/share/doc/nixos/index.html
-      Icon=system-help
-    '';
-
-  # use cp and chmod +x, we must be sure the apps are in the nix store though
-  in ''
-    mkdir -p /root/Desktop
-    ln -sfT ${desktopFile} /root/Desktop/nixos-manual.desktop
-    cp ${pkgs.gnome3.gnome-terminal}/share/applications/gnome-terminal.desktop /root/Desktop/gnome-terminal.desktop
-    chmod a+rx /root/Desktop/gnome-terminal.desktop
-    cp ${pkgs.gparted}/share/applications/gparted.desktop /root/Desktop/gparted.desktop
-    chmod a+rx /root/Desktop/gparted.desktop
-  '';
-
 }
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix b/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
index 63227d5734954..1c3c9cb30b41b 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-graphical-kde.nix
@@ -1,23 +1,14 @@
 # This module defines a NixOS installation CD that contains X11 and
-# KDE 5.
+# Plasma5.
 
 { config, lib, pkgs, ... }:
 
 with lib;
 
 {
-  imports = [ ./installation-cd-base.nix ];
+  imports = [ ./installation-cd-graphical-base.nix ];
 
   services.xserver = {
-    enable = true;
-
-    # Automatically login as root.
-    displayManager.slim = {
-      enable = true;
-      defaultUser = "root";
-      autoLogin = true;
-    };
-
     desktopManager.plasma5 = {
       enable = true;
       enableQt4Support = false;
@@ -27,45 +18,25 @@ with lib;
     synaptics.enable = true;
   };
 
-  environment.systemPackages =
-    [ pkgs.glxinfo
-
-      # Include gparted for partitioning disks.
-      pkgs.gparted
-
-      # Firefox for reading the manual.
-      pkgs.firefox
-
-      # Include some editors.
-      pkgs.vim
-      pkgs.bvi # binary editor
-      pkgs.joe
-    ];
-
-  # Provide networkmanager for easy wireless configuration.
-  networking.networkmanager.enable = true;
-  networking.wireless.enable = mkForce false;
-
-  # KDE complains if power management is disabled (to be precise, if
-  # there is no power management backend such as upower).
-  powerManagement.enable = true;
-
-  # Don't start the X server by default.
-  services.xserver.autorun = mkForce false;
+  environment.systemPackages = with pkgs; [
+    # Graphical text editor
+    kate
+  ];
 
   system.activationScripts.installerDesktop = let
-    desktopFile = pkgs.writeText "nixos-manual.desktop" ''
+
+    manualDesktopFile = pkgs.writeScript "nixos-manual.desktop" ''
       [Desktop Entry]
       Version=1.0
       Type=Application
       Name=NixOS Manual
-      Exec=firefox ${config.system.build.manual.manual}/share/doc/nixos/index.html
+      Exec=firefox ${config.system.build.manual.manualHTMLIndex}
       Icon=text-html
     '';
 
   in ''
     mkdir -p /root/Desktop
-    ln -sfT ${desktopFile} /root/Desktop/nixos-manual.desktop
+    ln -sfT ${manualDesktopFile} /root/Desktop/nixos-manual.desktop
     ln -sfT ${pkgs.konsole}/share/applications/org.kde.konsole.desktop /root/Desktop/org.kde.konsole.desktop
     ln -sfT ${pkgs.gparted}/share/applications/gparted.desktop /root/Desktop/gparted.desktop
   '';
diff --git a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
index 3dc0f606bf609..bcdbffdc20b7d 100644
--- a/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
+++ b/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix
@@ -7,4 +7,6 @@
   imports =
     [ ./installation-cd-base.nix
     ];
+
+  fonts.fontconfig.enable = false;
 }
diff --git a/nixos/modules/installer/cd-dvd/iso-image.nix b/nixos/modules/installer/cd-dvd/iso-image.nix
index 98712f0759a98..e78e290e74387 100644
--- a/nixos/modules/installer/cd-dvd/iso-image.nix
+++ b/nixos/modules/installer/cd-dvd/iso-image.nix
@@ -50,7 +50,7 @@ let
     finalCfg = {
       name = "NixOS ${config.system.nixos.label}${config.isoImage.appendToMenuLabel}";
       params = "init=${config.system.build.toplevel}/init ${additional} ${toString config.boot.kernelParams}";
-      image = "/boot/bzImage";
+      image = "/boot/${config.system.boot.loader.kernelFile}";
       initrd = "/boot/initrd";
     };
   in
@@ -163,7 +163,7 @@ let
       cp -v ${pkgs.refind}/share/refind/refind_x64.efi $out/EFI/boot/
       ''
     else
-      "# No refind for ia32"
+      "# No refind for ${targetArch}"
   ;
 
   grubMenuCfg = ''
@@ -222,18 +222,34 @@ let
   efiDir = pkgs.runCommand "efi-directory" {} ''
     mkdir -p $out/EFI/boot/
 
+    # ALWAYS required modules.
     MODULES="fat iso9660 part_gpt part_msdos \
              normal boot linux configfile loopback chain halt \
-             efifwsetup efi_gop efi_uga \
+             efifwsetup efi_gop \
              ls search search_label search_fs_uuid search_fs_file \
              gfxmenu gfxterm gfxterm_background gfxterm_menu test all_video loadenv \
              exfat ext2 ntfs btrfs hfsplus udf \
              videoinfo png \
              echo serial \
             "
+
+    echo "Building GRUB with modules:"
+    for mod in $MODULES; do
+      echo " - $mod"
+    done
+
+    # Modules that may or may not be available per-platform.
+    echo "Adding additional modules:"
+    for mod in efi_uga; do
+      if [ -f ${pkgs.grub2_efi}/lib/grub/${pkgs.grub2_efi.grubTarget}/$mod.mod ]; then
+        echo " - $mod"
+        MODULES+=" $mod"
+      fi
+    done
+
     # Make our own efi program, we can't rely on "grub-install" since it seems to
     # probe for devices, even with --skip-fs-probe.
-    ${pkgs.grub2_efi}/bin/grub-mkimage -o $out/EFI/boot/${if targetArch == "x64" then "bootx64" else "bootx32"}.efi -p /EFI/boot -O ${if targetArch == "x64" then "x86_64" else "i386"}-efi \
+    ${pkgs.grub2_efi}/bin/grub-mkimage -o $out/EFI/boot/boot${targetArch}.efi -p /EFI/boot -O ${pkgs.grub2_efi.grubTarget} \
       $MODULES
     cp ${pkgs.grub2_efi}/share/grub/unicode.pf2 $out/EFI/boot/
 
@@ -323,11 +339,11 @@ let
     #   dates (cp -p, touch, mcopy -m, faketime for label), IDs (mkfs.vfat -i)
     ''
       mkdir ./contents && cd ./contents
-      cp -rp "${efiDir}"/* .
+      cp -rp "${efiDir}"/EFI .
       mkdir ./boot
       cp -p "${config.boot.kernelPackages.kernel}/${config.system.boot.loader.kernelFile}" \
         "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}" ./boot/
-      touch --date=@0 ./*
+      touch --date=@0 ./EFI ./boot
 
       usage_size=$(du -sb --apparent-size . | tr -cd '[:digit:]')
       # Make the image 110% as big as the files need to make up for FAT overhead
@@ -339,15 +355,24 @@ let
       echo "Image size: $image_size"
       truncate --size=$image_size "$out"
       ${pkgs.libfaketime}/bin/faketime "2000-01-01 00:00:00" ${pkgs.dosfstools}/sbin/mkfs.vfat -i 12345678 -n EFIBOOT "$out"
-      mcopy -bpsvm -i "$out" ./* ::
+      mcopy -psvm -i "$out" ./EFI ./boot ::
+      # Verify the FAT partition.
+      ${pkgs.dosfstools}/sbin/fsck.vfat -vn "$out"
     ''; # */
 
-  targetArch = if pkgs.stdenv.isi686 then
-    "ia32"
-  else if pkgs.stdenv.isx86_64 then
-    "x64"
-  else
-    throw "Unsupported architecture";
+  # Name used by UEFI for architectures.
+  targetArch =
+    if pkgs.stdenv.isi686 then
+      "ia32"
+    else if pkgs.stdenv.isx86_64 then
+      "x64"
+    else if pkgs.stdenv.isAarch64 then
+      "aa64"
+    else
+      throw "Unsupported architecture";
+
+  # Syslinux (and isolinux) only supports x86-based architectures.
+  canx86BiosBoot = pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64;
 
 in
 
@@ -481,9 +506,9 @@ in
     # here and it causes a cyclic dependency.
     boot.loader.grub.enable = false;
 
-    # !!! Hack - attributes expected by other modules.
-    system.boot.loader.kernelFile = "bzImage";
-    environment.systemPackages = [ pkgs.grub2 pkgs.grub2_efi pkgs.syslinux ];
+    environment.systemPackages = [ pkgs.grub2 pkgs.grub2_efi ]
+      ++ optional canx86BiosBoot pkgs.syslinux
+    ;
 
     # In stage 1 of the boot, mount the CD as the root FS by label so
     # that we don't need to know its device.  We pass the label of the
@@ -554,13 +579,7 @@ in
     # Individual files to be included on the CD, outside of the Nix
     # store on the CD.
     isoImage.contents =
-      [ { source = pkgs.substituteAll  {
-            name = "isolinux.cfg";
-            src = pkgs.writeText "isolinux.cfg-in" isolinuxCfg;
-            bootRoot = "/boot";
-          };
-          target = "/isolinux/isolinux.cfg";
-        }
+      [
         { source = config.boot.kernelPackages.kernel + "/" + config.system.boot.loader.kernelFile;
           target = "/boot/" + config.system.boot.loader.kernelFile;
         }
@@ -570,9 +589,6 @@ in
         { source = config.system.build.squashfsStore;
           target = "/nix-store.squashfs";
         }
-        { source = "${pkgs.syslinux}/share/syslinux";
-          target = "/isolinux";
-        }
         { source = config.isoImage.efiSplashImage;
           target = "/EFI/boot/efi-background.png";
         }
@@ -582,6 +598,17 @@ in
         { source = pkgs.writeText "version" config.system.nixos.label;
           target = "/version.txt";
         }
+      ] ++ optionals canx86BiosBoot [
+        { source = pkgs.substituteAll  {
+            name = "isolinux.cfg";
+            src = pkgs.writeText "isolinux.cfg-in" isolinuxCfg;
+            bootRoot = "/boot";
+          };
+          target = "/isolinux/isolinux.cfg";
+        }
+        { source = "${pkgs.syslinux}/share/syslinux";
+          target = "/isolinux";
+        }
       ] ++ optionals config.isoImage.makeEfiBootable [
         { source = efiImg;
           target = "/boot/efi.img";
@@ -589,7 +616,7 @@ in
         { source = "${efiDir}/EFI";
           target = "/EFI";
         }
-      ] ++ optionals config.boot.loader.grub.memtest86.enable [
+      ] ++ optionals (config.boot.loader.grub.memtest86.enable && canx86BiosBoot) [
         { source = "${pkgs.memtest86plus}/memtest.bin";
           target = "/boot/memtest.bin";
         }
@@ -604,9 +631,10 @@ in
     # Create the ISO image.
     system.build.isoImage = pkgs.callPackage ../../../lib/make-iso9660-image.nix ({
       inherit (config.isoImage) isoName compressImage volumeID contents;
-      bootable = true;
+      bootable = canx86BiosBoot;
       bootImage = "/isolinux/isolinux.bin";
-    } // optionalAttrs config.isoImage.makeUsbBootable {
+      syslinux = if canx86BiosBoot then pkgs.syslinux else null;
+    } // optionalAttrs (config.isoImage.makeUsbBootable && canx86BiosBoot) {
       usbBootable = true;
       isohybridMbrImage = "${pkgs.syslinux}/share/syslinux/isohdpfx.bin";
     } // optionalAttrs config.isoImage.makeEfiBootable {
diff --git a/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix b/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix
new file mode 100644
index 0000000000000..2882fbcc73052
--- /dev/null
+++ b/nixos/modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix
@@ -0,0 +1,7 @@
+{ pkgs, ... }:
+
+{
+  imports = [ ./sd-image-aarch64.nix ];
+
+  boot.kernelPackages = pkgs.linuxPackages_latest;
+}
diff --git a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
index 86e19f3da5629..5f7194e92a364 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-aarch64.nix
@@ -5,7 +5,7 @@
 let
   extlinux-conf-builder =
     import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
-      inherit pkgs;
+      pkgs = pkgs.buildPackages;
     };
 in
 {
@@ -15,18 +15,10 @@ in
     ./sd-image.nix
   ];
 
-  assertions = lib.singleton {
-    assertion = pkgs.stdenv.hostPlatform.system == "aarch64-linux"
-      && pkgs.stdenv.hostPlatform.system == pkgs.stdenv.buildPlatform.system;
-    message = "sd-image-aarch64.nix can be only built natively on Aarch64 / ARM64; " +
-      "it cannot be cross compiled";
-  };
-
   boot.loader.grub.enable = false;
   boot.loader.generic-extlinux-compatible.enable = true;
 
   boot.consoleLogLevel = lib.mkDefault 7;
-  boot.kernelPackages = pkgs.linuxPackages_latest;
 
   # The serial ports listed here are:
   # - ttyS0: for Tegra (Jetson TX1)
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 695c79ca17073..71448f74c3611 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-armv7l-multiplatform.nix
@@ -5,7 +5,7 @@
 let
   extlinux-conf-builder =
     import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
-      inherit pkgs;
+      pkgs = pkgs.buildPackages;
     };
 in
 {
@@ -15,13 +15,6 @@ in
     ./sd-image.nix
   ];
 
-  assertions = lib.singleton {
-    assertion = pkgs.stdenv.hostPlatform.system == "armv7l-linux"
-      && pkgs.stdenv.hostPlatform.system == pkgs.stdenv.buildPlatform.system;
-    message = "sd-image-armv7l-multiplatform.nix can be only built natively on ARMv7; " +
-      "it cannot be cross compiled";
-  };
-
   boot.loader.grub.enable = false;
   boot.loader.generic-extlinux-compatible.enable = true;
 
diff --git a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
index e395b265d15e9..96e06670694e8 100644
--- a/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image-raspberrypi.nix
@@ -5,7 +5,7 @@
 let
   extlinux-conf-builder =
     import ../../system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix {
-      inherit pkgs;
+      pkgs = pkgs.buildPackages;
     };
 in
 {
@@ -15,13 +15,6 @@ in
     ./sd-image.nix
   ];
 
-  assertions = lib.singleton {
-    assertion = pkgs.stdenv.hostPlatform.system == "armv6l-linux"
-      && pkgs.stdenv.hostPlatform.system == pkgs.stdenv.buildPlatform.system;
-    message = "sd-image-raspberrypi.nix can be only built natively on ARMv6; " +
-      "it cannot be cross compiled";
-  };
-
   boot.loader.grub.enable = false;
   boot.loader.generic-extlinux-compatible.enable = true;
 
diff --git a/nixos/modules/installer/cd-dvd/sd-image.nix b/nixos/modules/installer/cd-dvd/sd-image.nix
index b6e1d11c2b547..69746a8e9799e 100644
--- a/nixos/modules/installer/cd-dvd/sd-image.nix
+++ b/nixos/modules/installer/cd-dvd/sd-image.nix
@@ -134,7 +134,9 @@ in
         ${config.sdImage.populateBootCommands}
 
         # Copy the populated /boot into the SD image
-        (cd boot; mcopy -bpsvm -i ../bootpart.img ./* ::)
+        (cd boot; mcopy -psvm -i ../bootpart.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
       '';
     }) {};
diff --git a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
index 7ec09acd5919b..90a5128c02a58 100644
--- a/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
+++ b/nixos/modules/installer/cd-dvd/system-tarball-sheevaplug.nix
@@ -137,7 +137,7 @@ in
   # Setting vesa, we don't get the nvidia driver, which can't work in arm.
   services.xserver.videoDrivers = [ "vesa" ];
 
-  services.nixosManual.enable = false;
+  documentation.nixos.enable = false;
 
   # Include the firmware for various wireless cards.
   networking.enableRalinkFirmware = true;
diff --git a/nixos/modules/installer/tools/nix-fallback-paths.nix b/nixos/modules/installer/tools/nix-fallback-paths.nix
index cb182a08a830c..5d431df4b114c 100644
--- a/nixos/modules/installer/tools/nix-fallback-paths.nix
+++ b/nixos/modules/installer/tools/nix-fallback-paths.nix
@@ -1,6 +1,6 @@
 {
-  x86_64-linux = "/nix/store/h180y3n5k1ypxgm1pcvj243qix5j45zz-nix-2.1.1";
-  i686-linux = "/nix/store/v2y4k4v9ml07jmfq739wyflapg3b7b5k-nix-2.1.1";
-  aarch64-linux = "/nix/store/v485craglq7xm5996ci8qy5dyc17dab0-nix-2.1.1";
-  x86_64-darwin = "/nix/store/lc3ymlix73kaad5srjdgaxp9ngr1sg6g-nix-2.1.1";
+  x86_64-linux = "/nix/store/pid1yakjasch4pwl63nzbj22z9zf0q26-nix-2.2";
+  i686-linux = "/nix/store/qpkl0cxy0xh4h432lv2qsjrmhvx5x2vy-nix-2.2";
+  aarch64-linux = "/nix/store/0jg7h94x986d8cskg6gcfza9x67spdbp-nix-2.2";
+  x86_64-darwin = "/nix/store/a48whqkmxnsfhwbk6nay74iyc1cf0lr2-nix-2.2";
 }
diff --git a/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix b/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
index 4372d196261e9..c1028a0ad7e93 100644
--- a/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
+++ b/nixos/modules/installer/tools/nixos-build-vms/build-vms.nix
@@ -1,9 +1,13 @@
 { system ? builtins.currentSystem
+, config ? {}
 , networkExpr
 }:
 
 let nodes = import networkExpr; in
 
-with import ../../../../lib/testing.nix { inherit system; };
+with import ../../../../lib/testing.nix {
+  inherit system;
+  pkgs = import ../../../../.. { inherit system config; };
+};
 
 (makeTest { inherit nodes; testScript = ""; }).driver
diff --git a/nixos/modules/installer/tools/nixos-generate-config.pl b/nixos/modules/installer/tools/nixos-generate-config.pl
index 359caad89a720..686204ee03425 100644
--- a/nixos/modules/installer/tools/nixos-generate-config.pl
+++ b/nixos/modules/installer/tools/nixos-generate-config.pl
@@ -277,8 +277,7 @@ if ($virt eq "qemu" || $virt eq "kvm" || $virt eq "bochs") {
 
 # Also for Hyper-V.
 if ($virt eq "microsoft") {
-    push @initrdAvailableKernelModules, "hv_storvsc";
-    $videoDriver = "fbdev";
+    push @attrs, "virtualisation.hypervGuest.enable = true;"
 }
 
 
@@ -315,14 +314,16 @@ push @attrs, "services.xserver.videoDrivers = [ \"$videoDriver\" ];" if $videoDr
 
 # Generate the swapDevices option from the currently activated swap
 # devices.
-my @swaps = read_file("/proc/swaps");
-shift @swaps;
+my @swaps = read_file("/proc/swaps", err_mode => 'carp');
 my @swapDevices;
-foreach my $swap (@swaps) {
-    $swap =~ /^(\S+)\s/;
-    next unless -e $1;
-    my $dev = findStableDevPath $1;
-    push @swapDevices, "{ device = \"$dev\"; }";
+if (@swaps) {
+    shift @swaps;
+    foreach my $swap (@swaps) {
+        $swap =~ /^(\S+)\s/;
+        next unless -e $1;
+        my $dev = findStableDevPath $1;
+        push @swapDevices, "{ device = \"$dev\"; }";
+    }
 }
 
 
@@ -339,6 +340,8 @@ foreach my $fs (read_file("/proc/self/mountinfo")) {
     chomp $fs;
     my @fields = split / /, $fs;
     my $mountPoint = $fields[4];
+    $mountPoint =~ s/\\040/ /g; # account for mount points with spaces in the name (\040 is the escape character)
+    $mountPoint =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character)
     next unless -d $mountPoint;
     my @mountOptions = split /,/, $fields[5];
 
@@ -354,6 +357,8 @@ foreach my $fs (read_file("/proc/self/mountinfo")) {
     my $fsType = $fields[$n];
     my $device = $fields[$n + 1];
     my @superOptions = split /,/, $fields[$n + 2];
+    $device =~ s/\\040/ /g; # account for devices with spaces in the name (\040 is the escape character)
+    $device =~ s/\\011/\t/g; # account for mount points with tabs in the name (\011 is the escape character)
 
     # Skip the read-only bind-mount on /nix/store.
     next if $mountPoint eq "/nix/store" && (grep { $_ eq "rw" } @superOptions) && (grep { $_ eq "ro" } @mountOptions);
@@ -448,7 +453,11 @@ EOF
                 if (-e $slave) {
                     my $dmName = read_file("/sys/class/block/$deviceName/dm/name");
                     chomp $dmName;
-                    $fileSystems .= "  boot.initrd.luks.devices.\"$dmName\".device = \"${\(findStableDevPath $slave)}\";\n\n";
+                    # Ensure to add an entry only once
+                    my $luksDevice = "  boot.initrd.luks.devices.\"$dmName\".device";
+                    if ($fileSystems !~ /^\Q$luksDevice\E/m) {
+                        $fileSystems .= "$luksDevice = \"${\(findStableDevPath $slave)}\";\n\n";
+                    }
                 }
             }
         }
@@ -630,9 +639,9 @@ $bootLoaderConfig
   # services.xserver.desktopManager.plasma5.enable = true;
 
   # Define a user account. Don't forget to set a password with ‘passwd’.
-  # users.users.guest = {
+  # users.users.jane = {
   #   isNormalUser = true;
-  #   uid = 1000;
+  #   extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
   # };
 
   # This value determines the NixOS release with which your system is to be
diff --git a/nixos/modules/installer/tools/nixos-install.sh b/nixos/modules/installer/tools/nixos-install.sh
index 22c1e0fe9a34b..defc46ad2a723 100644
--- a/nixos/modules/installer/tools/nixos-install.sh
+++ b/nixos/modules/installer/tools/nixos-install.sh
@@ -13,6 +13,7 @@ extraBuildFlags=()
 
 mountPoint=/mnt
 channelPath=
+system=
 
 while [ "$#" -gt 0 ]; do
     i="$1"; shift 1
diff --git a/nixos/modules/installer/tools/nixos-option.sh b/nixos/modules/installer/tools/nixos-option.sh
index 3f1e591b97b01..4560e9c7403aa 100644
--- a/nixos/modules/installer/tools/nixos-option.sh
+++ b/nixos/modules/installer/tools/nixos-option.sh
@@ -82,7 +82,7 @@ evalNix(){
   set -e
 
   if test $exit_code -eq 0; then
-      cat <<EOF
+      sed '/^warning: Nix search path/d' <<EOF
 $result
 EOF
       return 0;
@@ -90,7 +90,7 @@ EOF
       sed -n '
   /^error/ { s/, at (string):[0-9]*:[0-9]*//; p; };
   /^warning: Nix search path/ { p; };
-' <<EOF
+' >&2 <<EOF
 $result
 EOF
     exit_code=1
@@ -314,13 +314,13 @@ else
   # echo 1>&2 "Warning: This value is not an option."
 
   result=$(evalCfg "")
-  if names=$(attrNames "$result" 2> /dev/null); then
+  if [ ! -z "$result" ]; then
+    names=$(attrNames "$result" 2> /dev/null)
     echo 1>&2 "This attribute set contains:"
     escapeQuotes () { eval echo "$1"; }
     nixMap escapeQuotes "$names"
   else
-    echo 1>&2 "An error occurred while looking for attribute names."
-    echo $result
+    echo 1>&2 "An error occurred while looking for attribute names. Are you sure that '$option' exists?"
   fi
 fi
 
diff --git a/nixos/modules/installer/tools/nixos-rebuild.sh b/nixos/modules/installer/tools/nixos-rebuild.sh
index 2af73519bc524..361c2e49e05c1 100644
--- a/nixos/modules/installer/tools/nixos-rebuild.sh
+++ b/nixos/modules/installer/tools/nixos-rebuild.sh
@@ -53,11 +53,11 @@ while [ "$#" -gt 0 ]; do
         repair=1
         extraBuildFlags+=("$i")
         ;;
-      --max-jobs|-j|--cores|-I)
+      --max-jobs|-j|--cores|-I|--builders)
         j="$1"; shift 1
         extraBuildFlags+=("$i" "$j")
         ;;
-      --show-trace|--no-build-hook|--keep-failed|-K|--keep-going|-k|--verbose|-v|-vv|-vvv|-vvvv|-vvvvv|--fallback|--repair|--no-build-output|-Q|-j*)
+      --show-trace|--keep-failed|-K|--keep-going|-k|--verbose|-v|-vv|-vvv|-vvvv|-vvvvv|--fallback|--repair|--no-build-output|-Q|-j*)
         extraBuildFlags+=("$i")
         ;;
       --option)
diff --git a/nixos/modules/installer/tools/tools.nix b/nixos/modules/installer/tools/tools.nix
index af0a3a2fcc882..00c4d5018bf56 100644
--- a/nixos/modules/installer/tools/tools.nix
+++ b/nixos/modules/installer/tools/tools.nix
@@ -37,7 +37,7 @@ let
     name = "nixos-generate-config";
     src = ./nixos-generate-config.pl;
     path = [ pkgs.btrfs-progs ];
-    perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl";
+    perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix}";
     inherit (config.system.nixos) release;
   };
 
diff --git a/nixos/modules/installer/virtualbox-demo.nix b/nixos/modules/installer/virtualbox-demo.nix
index 8ca3592f38002..2e1b4b3998b55 100644
--- a/nixos/modules/installer/virtualbox-demo.nix
+++ b/nixos/modules/installer/virtualbox-demo.nix
@@ -22,4 +22,42 @@ with lib;
 
   powerManagement.enable = false;
   system.stateVersion = mkDefault "18.03";
+
+  installer.cloneConfigExtra = ''
+  # Let demo build as a trusted user.
+  # nix.trustedUsers = [ "demo" ];
+
+  # Mount a VirtualBox shared folder.
+  # This is configurable in the VirtualBox menu at
+  # Machine / Settings / Shared Folders.
+  # fileSystems."/mnt" = {
+  #   fsType = "vboxsf";
+  #   device = "nameofdevicetomount";
+  #   options = [ "rw" ];
+  # };
+
+  # By default, the NixOS VirtualBox demo image includes SDDM and Plasma.
+  # If you prefer another desktop manager or display manager, you may want
+  # to disable the default.
+  # services.xserver.desktopManager.plasma5.enable = lib.mkForce false;
+  # services.xserver.displayManager.sddm.enable = lib.mkForce false;
+
+  # Enable GDM/GNOME by uncommenting above two lines and two lines below.
+  # services.xserver.displayManager.gdm.enable = true;
+  # services.xserver.desktopManager.gnome3.enable = true;
+
+  # 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
+  # ];
+
+  # Enable the OpenSSH daemon.
+  # services.openssh.enable = true;
+
+  system.stateVersion = mkDefault "18.03";
+  '';
 }
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix
index e6ccda5d7f400..9b2e1235b7481 100644
--- a/nixos/modules/misc/documentation.nix
+++ b/nixos/modules/misc/documentation.nix
@@ -1,8 +1,72 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, baseModules, ... }:
 
 with lib;
 
-let cfg = config.documentation; in
+let
+
+  cfg = config.documentation;
+
+  /* For the purpose of generating docs, evaluate options with each derivation
+    in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
+    It isn't perfect, but it seems to cover a vast majority of use cases.
+    Caveat: even if the package is reached by a different means,
+    the path above will be shown and not e.g. `${config.services.foo.package}`. */
+  manual = import ../../doc/manual rec {
+    inherit pkgs config;
+    version = config.system.nixos.release;
+    revision = "release-${version}";
+    options =
+      let
+        scrubbedEval = evalModules {
+          modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ baseModules;
+          args = (config._module.args) // { modules = [ ]; };
+          specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; };
+        };
+        scrubDerivations = namePrefix: pkgSet: mapAttrs
+          (name: value:
+            let wholeName = "${namePrefix}.${name}"; in
+            if isAttrs value then
+              scrubDerivations wholeName value
+              // (optionalAttrs (isDerivation value) { outPath = "\${${wholeName}}"; })
+            else value
+          )
+          pkgSet;
+      in scrubbedEval.options;
+  };
+
+  helpScript = pkgs.writeScriptBin "nixos-help"
+    ''
+      #! ${pkgs.runtimeShell} -e
+      # Finds first executable browser in a colon-separated list.
+      # (see how xdg-open defines BROWSER)
+      browser="$(
+        IFS=: ; for b in $BROWSER; do
+          [ -n "$(type -P "$b" || true)" ] && echo "$b" && break
+        done
+      )"
+      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
+        fi
+      fi
+      exec "$browser" ${manual.manualHTMLIndex}
+    '';
+
+  desktopItem = pkgs.makeDesktopItem {
+    name = "nixos-manual";
+    desktopName = "NixOS Manual";
+    genericName = "View NixOS documentation in a web browser";
+    icon = "nix-snowflake";
+    exec = "${helpScript}/bin/nixos-help";
+    categories = "System";
+  };
+
+in
 
 {
 
@@ -66,6 +130,22 @@ let cfg = config.documentation; in
         '';
       };
 
+      nixos.enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to install NixOS's own documentation.
+          <itemizedlist>
+          <listitem><para>This includes man pages like
+                    <citerefentry><refentrytitle>configuration.nix</refentrytitle>
+                    <manvolnum>5</manvolnum></citerefentry> if <option>man.enable</option> is
+                    set.</para></listitem>
+          <listitem><para>This includes the HTML manual and the <command>nixos-help</command> command if
+                    <option>doc.enable</option> is set.</para></listitem>
+          </itemizedlist>
+        '';
+      };
+
     };
 
   };
@@ -76,6 +156,7 @@ let cfg = config.documentation; in
       environment.systemPackages = [ pkgs.man-db ];
       environment.pathsToLink = [ "/share/man" ];
       environment.extraOutputsToInstall = [ "man" ] ++ optional cfg.dev.enable "devman";
+      environment.etc."man.conf".source = "${pkgs.man-db}/etc/man_db.conf";
     })
 
     (mkIf cfg.info.enable {
@@ -86,7 +167,7 @@ let cfg = config.documentation; in
         if [ -w $out/share/info ]; then
           shopt -s nullglob
           for i in $out/share/info/*.info $out/share/info/*.info.gz; do
-              ${pkgs.texinfo}/bin/install-info $i $out/share/info/dir
+              ${pkgs.buildPackages.texinfo}/bin/install-info $i $out/share/info/dir
           done
         fi
       '';
@@ -99,6 +180,21 @@ let cfg = config.documentation; in
       environment.extraOutputsToInstall = [ "doc" ] ++ optional cfg.dev.enable "devdoc";
     })
 
+    (mkIf cfg.nixos.enable {
+      system.build.manual = manual;
+
+      environment.systemPackages = []
+        ++ optional cfg.man.enable manual.manpages
+        ++ optionals cfg.doc.enable ([ manual.manualHTML helpScript ]
+           ++ optionals config.services.xserver.enable [ desktopItem pkgs.nixos-icons ]);
+
+      services.mingetty.helpLine = mkIf cfg.doc.enable (
+          "\nRun `nixos-help` "
+        + optionalString config.services.nixosManual.showManual "or press <Alt-F${toString config.services.nixosManual.ttyNumber}> "
+        + "for the NixOS manual."
+      );
+    })
+
   ]);
 
 }
diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index c65291cf97e8b..5f134b51939c3 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -101,7 +101,7 @@
       iodined = 66;
       #libvirtd = 67; # unused
       graphite = 68;
-      statsd = 69;
+      #statsd = 69; # removed 2018-11-14
       transmission = 70;
       postgres = 71;
       #vboxusers = 72; # unused
@@ -175,7 +175,7 @@
       dnsmasq = 141;
       uhub = 142;
       yandexdisk = 143;
-      #collectd = 144; #unused
+      mxisd = 144; # was once collectd
       consul = 145;
       mailpile = 146;
       redmine = 147;
@@ -289,7 +289,7 @@
       stanchion = 262;
       riak-cs = 263;
       infinoted = 264;
-      # keystone = 265; # unused, removed 2017-12-13
+      sickbeard = 265;
       headphones = 266;
       couchpotato = 267;
       gogs = 268;
@@ -306,7 +306,7 @@
       rslsync = 279;
       minio = 280;
       kanboard = 281;
-      pykms = 282;
+      # pykms = 282; # DynamicUser = true
       kodi = 283;
       restya-board = 284;
       mighttpd2 = 285;
@@ -329,6 +329,16 @@
       # kvm = 302; # unused
       # render = 303; # unused
       zeronet = 304;
+      lirc = 305;
+      lidarr = 306;
+      slurm = 307;
+      kapacitor = 308;
+      solr = 309;
+      alerta = 310;
+      minetest = 311;
+      rss2email = 312;
+      cockroachdb = 313;
+      zoneminder = 314;
 
       # When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
 
@@ -383,7 +393,7 @@
       virtuoso = 44;
       #rtkit = 45; # unused
       dovecot2 = 46;
-      #dovenull = 47; # unused
+      dovenull2 = 47;
       prayer = 49;
       mpd = 50;
       clamav = 51;
@@ -404,7 +414,7 @@
       iodined = 66;
       libvirtd = 67;
       graphite = 68;
-      #statsd = 69; # unused
+      #statsd = 69; # removed 2018-11-14
       transmission = 70;
       postgres = 71;
       vboxusers = 72;
@@ -476,7 +486,7 @@
       #dnsmasq = 141; # unused
       uhub = 142;
       #yandexdisk = 143; # unused
-      #collectd = 144; # unused
+      mxisd = 144; # was once collectd
       #consul = 145; # unused
       mailpile = 146;
       redmine = 147;
@@ -579,7 +589,7 @@
       stanchion = 262;
       riak-cs = 263;
       infinoted = 264;
-      # keystone = 265; # unused, removed 2017-12-13
+      sickbeard = 265;
       headphones = 266;
       couchpotato = 267;
       gogs = 268;
@@ -595,7 +605,7 @@
       rslsync = 279;
       minio = 280;
       kanboard = 281;
-      pykms = 282;
+      # pykms = 282; # DynamicUser = true
       kodi = 283;
       restya-board = 284;
       mighttpd2 = 285;
@@ -618,6 +628,16 @@
       kvm = 302; # default udev rules from systemd requires these
       render = 303; # default udev rules from systemd requires these
       zeronet = 304;
+      lirc = 305;
+      lidarr = 306;
+      slurm = 307;
+      kapacitor = 308;
+      solr = 309;
+      alerta = 310;
+      minetest = 311;
+      rss2email = 312;
+      cockroachdb = 313;
+      zoneminder = 314;
 
       # When adding a gid, make sure it doesn't match an existing
       # uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/misc/nixpkgs.nix b/nixos/modules/misc/nixpkgs.nix
index 7f9833e184ab9..3a717fddaba2b 100644
--- a/nixos/modules/misc/nixpkgs.nix
+++ b/nixos/modules/misc/nixpkgs.nix
@@ -1,9 +1,10 @@
-{ config, lib, pkgs, ... }:
+{ config, options, lib, pkgs, ... }:
 
 with lib;
 
 let
   cfg = config.nixpkgs;
+  opt = options.nixpkgs;
 
   isConfig = x:
     builtins.isAttrs x || lib.isFunction x;
@@ -54,6 +55,12 @@ let
     check = builtins.isAttrs;
   };
 
+  defaultPkgs = import ../../.. {
+    inherit (cfg) config overlays localSystem crossSystem;
+  };
+
+  finalPkgs = if opt.pkgs.isDefined then cfg.pkgs.appendOverlays cfg.overlays else defaultPkgs;
+
 in
 
 {
@@ -65,17 +72,21 @@ in
             inherit (cfg) config overlays localSystem crossSystem;
           }
         '';
-      default = import ../../.. {
-        inherit (cfg) config overlays localSystem crossSystem;
-      };
       type = pkgsType;
       example = literalExample ''import <nixpkgs> {}'';
       description = ''
-        This is the evaluation of Nixpkgs that will be provided to
-        all NixOS modules. Defining this option has the effect of
-        ignoring the other options that would otherwise be used to
-        evaluate Nixpkgs, because those are arguments to the default
-        value. The default value imports the Nixpkgs source files
+        If set, the pkgs argument to all NixOS modules is the value of
+        this option, extended with <code>nixpkgs.overlays</code>, if
+        that is also set. Either <code>nixpkgs.crossSystem</code> or
+        <code>nixpkgs.localSystem</code> will be used in an assertion
+        to check that the NixOS and Nixpkgs architectures match. Any
+        other options in <code>nixpkgs.*</code>, notably <code>config</code>,
+        will be ignored.
+
+        If unset, the pkgs argument to all NixOS modules is determined
+        as shown in the default value for this option.
+
+        The default value imports the Nixpkgs source files
         relative to the location of this NixOS module, because
         NixOS and Nixpkgs are distributed together for consistency,
         so the <code>nixos</code> in the default value is in fact a
@@ -128,12 +139,14 @@ in
       description = ''
         List of overlays to use with the Nix Packages collection.
         (For details, see the Nixpkgs documentation.)  It allows
-        you to override packages globally. This is a function that
+        you to override packages globally. Each function in the list
         takes as an argument the <emphasis>original</emphasis> Nixpkgs.
         The first argument should be used for finding dependencies, and
         the second should be used for overriding recipes.
 
-        Ignored when <code>nixpkgs.pkgs</code> is set.
+        If <code>nixpkgs.pkgs</code> is set, overlays specified here
+        will be applied after the overlays that were already present
+        in <code>nixpkgs.pkgs</code>.
       '';
     };
 
@@ -207,8 +220,26 @@ in
 
   config = {
     _module.args = {
-      pkgs = cfg.pkgs;
-      pkgs_i686 = cfg.pkgs.pkgsi686Linux;
+      pkgs = finalPkgs;
     };
+
+    assertions = [
+      (
+        let
+          nixosExpectedSystem =
+            if config.nixpkgs.crossSystem != null
+            then config.nixpkgs.crossSystem.system
+            else config.nixpkgs.localSystem.system;
+          nixosOption =
+            if config.nixpkgs.crossSystem != null
+            then "nixpkgs.crossSystem"
+            else "nixpkgs.localSystem";
+          pkgsSystem = finalPkgs.stdenv.targetPlatform.system;
+        in {
+          assertion = nixosExpectedSystem == pkgsSystem;
+          message = "The NixOS nixpkgs.pkgs option was set to a Nixpkgs invocation that compiles to target system ${pkgsSystem} but NixOS was configured for system ${nixosExpectedSystem} via NixOS option ${nixosOption}. The NixOS system settings must match the Nixpkgs target system.";
+        }
+      )
+    ];
   };
 }
diff --git a/nixos/modules/misc/version.nix b/nixos/modules/misc/version.nix
index c593adcdae6e0..c576cf4cb925e 100644
--- a/nixos/modules/misc/version.nix
+++ b/nixos/modules/misc/version.nix
@@ -5,7 +5,6 @@ with lib;
 let
   cfg = config.system.nixos;
 
-  revisionFile = "${toString pkgs.path}/.git-revision";
   gitRepo      = "${toString pkgs.path}/.git";
   gitCommitId  = lib.substring 0 7 (commitIdFromGitRepo gitRepo);
 in
@@ -37,15 +36,14 @@ in
     nixos.revision = mkOption {
       internal = true;
       type = types.str;
-      default = if pathIsDirectory gitRepo then commitIdFromGitRepo gitRepo
-                else if pathExists revisionFile then fileContents revisionFile
-                else "master";
+      default = trivial.revisionWithDefault "master";
       description = "The Git revision from which this NixOS configuration was built.";
     };
 
     nixos.codeName = mkOption {
       readOnly = true;
       type = types.str;
+      default = trivial.codeName;
       description = "The NixOS release code name (e.g. <literal>Emu</literal>).";
     };
 
@@ -82,9 +80,6 @@ in
       version = mkDefault (cfg.release + cfg.versionSuffix);
       revision      = mkIf (pathIsDirectory gitRepo) (mkDefault            gitCommitId);
       versionSuffix = mkIf (pathIsDirectory gitRepo) (mkDefault (".git." + gitCommitId));
-
-      # Note: the first letter is bumped on every release.  It's an animal.
-      codeName = "Koi";
     };
 
     # Generate /etc/os-release.  See
@@ -98,6 +93,7 @@ in
         VERSION_CODENAME=${toLower cfg.codeName}
         VERSION_ID="${cfg.version}"
         PRETTY_NAME="NixOS ${cfg.version} (${cfg.codeName})"
+        LOGO="nix-snowflake"
         HOME_URL="https://nixos.org/"
         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 b5afb5b1553e5..6219abda7dc18 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -11,6 +11,9 @@
   ./config/xdg/icons.nix
   ./config/xdg/menus.nix
   ./config/xdg/mime.nix
+  ./config/appstream.nix
+  ./config/xdg/sounds.nix
+  ./config/gtk/gtk-icon-cache.nix
   ./config/gnu.nix
   ./config/i18n.nix
   ./config/iproute2.nix
@@ -33,8 +36,9 @@
   ./config/vpnc.nix
   ./config/zram.nix
   ./hardware/all-firmware.nix
+  ./hardware/bladeRF.nix
   ./hardware/brightnessctl.nix
-  ./hardware/ckb.nix
+  ./hardware/ckb-next.nix
   ./hardware/cpu/amd-microcode.nix
   ./hardware/cpu/intel-microcode.nix
   ./hardware/digitalbitbox.nix
@@ -46,6 +50,7 @@
   ./hardware/opengl.nix
   ./hardware/pcmcia.nix
   ./hardware/raid/hpsa.nix
+  ./hardware/steam-hardware.nix
   ./hardware/usb-wwan.nix
   ./hardware/onlykey.nix
   ./hardware/video/amdgpu.nix
@@ -62,7 +67,6 @@
   ./i18n/input-method/ibus.nix
   ./i18n/input-method/nabi.nix
   ./i18n/input-method/uim.nix
-  ./installer/tools/auto-upgrade.nix
   ./installer/tools/tools.nix
   ./misc/assertions.nix
   ./misc/crashdump.nix
@@ -89,6 +93,7 @@
   ./programs/criu.nix
   ./programs/dconf.nix
   ./programs/digitalbitbox/default.nix
+  ./programs/dmrconfig.nix
   ./programs/environment.nix
   ./programs/firejail.nix
   ./programs/fish.nix
@@ -96,18 +101,20 @@
   ./programs/gnupg.nix
   ./programs/gphoto2.nix
   ./programs/iftop.nix
+  ./programs/iotop.nix
   ./programs/java.nix
   ./programs/kbdlight.nix
   ./programs/less.nix
   ./programs/light.nix
   ./programs/mosh.nix
+  ./programs/mininet.nix
   ./programs/mtr.nix
   ./programs/nano.nix
+  ./programs/nm-applet.nix
   ./programs/npm.nix
   ./programs/oblogout.nix
   ./programs/plotinus.nix
   ./programs/qt5ct.nix
-  ./programs/rootston.nix
   ./programs/screen.nix
   ./programs/sedutil.nix
   ./programs/slock.nix
@@ -120,11 +127,13 @@
   ./programs/sysdig.nix
   ./programs/systemtap.nix
   ./programs/sway.nix
+  ./programs/sway-beta.nix
   ./programs/thefuck.nix
   ./programs/tmux.nix
   ./programs/udevil.nix
   ./programs/venus.nix
   ./programs/vim.nix
+  ./programs/wavemon.nix
   ./programs/way-cooler.nix
   ./programs/wireshark.nix
   ./programs/xfs_quota.nix
@@ -146,8 +155,10 @@
   ./security/chromium-suid-sandbox.nix
   ./security/dhparams.nix
   ./security/duosec.nix
+  ./security/google_oslogin.nix
   ./security/hidepid.nix
   ./security/lock-kernel-modules.nix
+  ./security/misc.nix
   ./security/oath.nix
   ./security/pam.nix
   ./security/pam_usb.nix
@@ -207,6 +218,7 @@
   ./services/databases/aerospike.nix
   ./services/databases/cassandra.nix
   ./services/databases/clickhouse.nix
+  ./services/databases/cockroachdb.nix
   ./services/databases/couchdb.nix
   ./services/databases/firebird.nix
   ./services/databases/foundationdb.nix
@@ -232,14 +244,17 @@
   ./services/desktops/dleyna-server.nix
   ./services/desktops/flatpak.nix
   ./services/desktops/geoclue2.nix
+  ./services/desktops/gsignond.nix
   ./services/desktops/pipewire.nix
   ./services/desktops/gnome3/at-spi2-core.nix
   ./services/desktops/gnome3/chrome-gnome-shell.nix
   ./services/desktops/gnome3/evolution-data-server.nix
+  ./services/desktops/gnome3/file-roller.nix
   ./services/desktops/gnome3/gnome-disks.nix
   ./services/desktops/gnome3/gnome-documents.nix
   ./services/desktops/gnome3/gnome-keyring.nix
   ./services/desktops/gnome3/gnome-online-accounts.nix
+  ./services/desktops/gnome3/gnome-remote-desktop.nix
   ./services/desktops/gnome3/gnome-online-miners.nix
   ./services/desktops/gnome3/gnome-terminal-server.nix
   ./services/desktops/gnome3/gnome-user-share.nix
@@ -272,15 +287,18 @@
   ./services/hardware/interception-tools.nix
   ./services/hardware/irqbalance.nix
   ./services/hardware/lcd.nix
+  ./services/hardware/lirc.nix
   ./services/hardware/nvidia-optimus.nix
   ./services/hardware/pcscd.nix
   ./services/hardware/pommed.nix
+  ./services/hardware/ratbagd.nix
   ./services/hardware/sane.nix
   ./services/hardware/sane_extra_backends/brscan4.nix
   ./services/hardware/tcsd.nix
   ./services/hardware/tlp.nix
   ./services/hardware/thinkfan.nix
   ./services/hardware/trezord.nix
+  ./services/hardware/triggerhappy.nix
   ./services/hardware/u2f.nix
   ./services/hardware/udev.nix
   ./services/hardware/udisks2.nix
@@ -288,12 +306,14 @@
   ./services/hardware/usbmuxd.nix
   ./services/hardware/thermald.nix
   ./services/hardware/undervolt.nix
+  ./services/hardware/vdr.nix
   ./services/logging/SystemdJournal2Gelf.nix
   ./services/logging/awstats.nix
   ./services/logging/fluentd.nix
   ./services/logging/graylog.nix
   ./services/logging/heartbeat.nix
   ./services/logging/journalbeat.nix
+  ./services/logging/journaldriver.nix
   ./services/logging/journalwatch.nix
   ./services/logging/klogd.nix
   ./services/logging/logcheck.nix
@@ -320,12 +340,15 @@
   ./services/mail/postgrey.nix
   ./services/mail/spamassassin.nix
   ./services/mail/rspamd.nix
+  ./services/mail/rss2email.nix
   ./services/mail/rmilter.nix
+  ./services/mail/roundcube.nix
   ./services/mail/nullmailer.nix
   ./services/misc/airsonic.nix
   ./services/misc/apache-kafka.nix
   ./services/misc/autofs.nix
   ./services/misc/autorandr.nix
+  ./services/misc/bees.nix
   ./services/misc/bepasty.nix
   ./services/misc/canto-daemon.nix
   ./services/misc/calibre-server.nix
@@ -364,6 +387,7 @@
   ./services/misc/jackett.nix
   ./services/misc/logkeys.nix
   ./services/misc/leaps.nix
+  ./services/misc/lidarr.nix
   ./services/misc/mantisbt.nix
   ./services/misc/mathics.nix
   ./services/misc/matrix-synapse.nix
@@ -394,6 +418,7 @@
   ./services/misc/rogue.nix
   ./services/misc/serviio.nix
   ./services/misc/safeeyes.nix
+  ./services/misc/sickbeard.nix
   ./services/misc/siproxd.nix
   ./services/misc/snapper.nix
   ./services/misc/sonarr.nix
@@ -410,7 +435,9 @@
   ./services/misc/uhub.nix
   ./services/misc/weechat.nix
   ./services/misc/xmr-stak.nix
+  ./services/misc/zoneminder.nix
   ./services/misc/zookeeper.nix
+  ./services/monitoring/alerta.nix
   ./services/monitoring/apcupsd.nix
   ./services/monitoring/arbtt.nix
   ./services/monitoring/bosun.nix
@@ -421,10 +448,12 @@
   ./services/monitoring/dd-agent/dd-agent.nix
   ./services/monitoring/fusion-inventory.nix
   ./services/monitoring/grafana.nix
+  ./services/monitoring/grafana-reporter.nix
   ./services/monitoring/graphite.nix
   ./services/monitoring/hdaps.nix
   ./services/monitoring/heapster.nix
   ./services/monitoring/incron.nix
+  ./services/monitoring/kapacitor.nix
   ./services/monitoring/longview.nix
   ./services/monitoring/monit.nix
   ./services/monitoring/munin.nix
@@ -439,7 +468,6 @@
   ./services/monitoring/riemann-tools.nix
   ./services/monitoring/scollector.nix
   ./services/monitoring/smartd.nix
-  ./services/monitoring/statsd.nix
   ./services/monitoring/sysstat.nix
   ./services/monitoring/systemhealth.nix
   ./services/monitoring/teamviewer.nix
@@ -497,6 +525,8 @@
   ./services/networking/dnsdist.nix
   ./services/networking/dnsmasq.nix
   ./services/networking/ejabberd.nix
+  ./services/networking/epmd.nix
+  ./services/networking/eternal-terminal.nix
   ./services/networking/fakeroute.nix
   ./services/networking/ferm.nix
   ./services/networking/firefox/sync-server.nix
@@ -544,6 +574,7 @@
   ./services/networking/miredo.nix
   ./services/networking/mstpd.nix
   ./services/networking/murmur.nix
+  ./services/networking/mxisd.nix
   ./services/networking/namecoind.nix
   ./services/networking/nat.nix
   ./services/networking/ndppd.nix
@@ -557,6 +588,7 @@
   ./services/networking/nsd.nix
   ./services/networking/ntopng.nix
   ./services/networking/ntpd.nix
+  ./services/networking/nullidentdmod.nix
   ./services/networking/nylon.nix
   ./services/networking/ocserv.nix
   ./services/networking/oidentd.nix
@@ -605,6 +637,7 @@
   ./services/networking/supplicant.nix
   ./services/networking/supybot.nix
   ./services/networking/syncthing.nix
+  ./services/networking/syncthing-relay.nix
   ./services/networking/tcpcrypt.nix
   ./services/networking/teamspeak3.nix
   ./services/networking/tinc.nix
@@ -627,7 +660,7 @@
   ./services/networking/zerobin.nix
   ./services/networking/zeronet.nix
   ./services/networking/zerotierone.nix
-  ./services/networking/znc.nix
+  ./services/networking/znc/default.nix
   ./services/printing/cupsd.nix
   ./services/scheduling/atd.nix
   ./services/scheduling/chronos.nix
@@ -650,6 +683,7 @@
   ./services/security/hologram-server.nix
   ./services/security/hologram-agent.nix
   ./services/security/munge.nix
+  ./services/security/nginx-sso.nix
   ./services/security/oauth2_proxy.nix
   ./services/security/oauth2_proxy_nginx.nix
   ./services/security/physlock.nix
@@ -666,7 +700,7 @@
   ./services/system/dbus.nix
   ./services/system/earlyoom.nix
   ./services/system/localtime.nix
-  ./services/system/kerberos.nix
+  ./services/system/kerberos/default.nix
   ./services/system/nscd.nix
   ./services/system/saslauthd.nix
   ./services/system/uptimed.nix
@@ -681,15 +715,18 @@
   ./services/web-apps/atlassian/confluence.nix
   ./services/web-apps/atlassian/crowd.nix
   ./services/web-apps/atlassian/jira.nix
+  ./services/web-apps/codimd.nix
   ./services/web-apps/frab.nix
+  ./services/web-apps/icingaweb2/icingaweb2.nix
+  ./services/web-apps/icingaweb2/module-monitoring.nix
   ./services/web-apps/mattermost.nix
+  ./services/web-apps/nextcloud.nix
   ./services/web-apps/nexus.nix
   ./services/web-apps/pgpkeyserver-lite.nix
   ./services/web-apps/matomo.nix
   ./services/web-apps/restya-board.nix
   ./services/web-apps/tt-rss.nix
   ./services/web-apps/selfoss.nix
-  ./services/web-apps/quassel-webserver.nix
   ./services/web-apps/virtlyst.nix
   ./services/web-apps/youtrack.nix
   ./services/web-servers/apache-httpd/default.nix
@@ -726,12 +763,14 @@
   ./services/x11/display-managers/lightdm.nix
   ./services/x11/display-managers/sddm.nix
   ./services/x11/display-managers/slim.nix
+  ./services/x11/display-managers/startx.nix
   ./services/x11/display-managers/xpra.nix
   ./services/x11/fractalart.nix
   ./services/x11/hardware/libinput.nix
   ./services/x11/hardware/multitouch.nix
   ./services/x11/hardware/synaptics.nix
   ./services/x11/hardware/wacom.nix
+  ./services/x11/gdk-pixbuf.nix
   ./services/x11/redshift.nix
   ./services/x11/urxvtd.nix
   ./services/x11/window-managers/awesome.nix
@@ -782,6 +821,7 @@
   ./system/boot/timesyncd.nix
   ./system/boot/tmp.nix
   ./system/etc/etc.nix
+  ./tasks/auto-upgrade.nix
   ./tasks/bcache.nix
   ./tasks/cpu-freq.nix
   ./tasks/encrypted-devices.nix
diff --git a/nixos/modules/profiles/base.nix b/nixos/modules/profiles/base.nix
index 5aaffa4f1f2a1..2a2fe119d30cb 100644
--- a/nixos/modules/profiles/base.nix
+++ b/nixos/modules/profiles/base.nix
@@ -7,7 +7,7 @@
   # Include some utilities that are useful for installing or repairing
   # the system.
   environment.systemPackages = [
-    pkgs.w3m-nox # needed for the manual anyway
+    pkgs.w3m-nographics # needed for the manual anyway
     pkgs.testdisk # useful for repairing boot problems
     pkgs.ms-sys # for writing Microsoft boot sectors / MBRs
     pkgs.efibootmgr
@@ -19,6 +19,9 @@
     pkgs.cryptsetup # needed for dm-crypt volumes
     pkgs.mkpasswd # for generating password files
 
+    # Some text editors.
+    pkgs.vim
+
     # Some networking tools.
     pkgs.fuse
     pkgs.fuse3
@@ -46,7 +49,7 @@
   ];
 
   # Include support for various filesystems.
-  boot.supportedFilesystems = [ "btrfs" "reiserfs" "vfat" "f2fs" "xfs" "ntfs" "cifs" ];
+  boot.supportedFilesystems = [ "btrfs" "reiserfs" "vfat" "f2fs" "xfs" "zfs" "ntfs" "cifs" ];
 
   # Configure host id for ZFS to work
   networking.hostId = lib.mkDefault "8425e349";
diff --git a/nixos/modules/profiles/clone-config.nix b/nixos/modules/profiles/clone-config.nix
index 99d4774584f1e..3f669ba7d2e16 100644
--- a/nixos/modules/profiles/clone-config.nix
+++ b/nixos/modules/profiles/clone-config.nix
@@ -48,6 +48,8 @@ let
 
       {
         imports = [ ${toString config.installer.cloneConfigIncludes} ];
+
+        ${config.installer.cloneConfigExtra}
       }
     '';
 
@@ -73,6 +75,13 @@ in
       '';
     };
 
+    installer.cloneConfigExtra = mkOption {
+      default = "";
+      description = ''
+        Extra text to include in the cloned configuration.nix included in this
+        installer.
+      '';
+    };
   };
 
   config = {
diff --git a/nixos/modules/profiles/docker-container.nix b/nixos/modules/profiles/docker-container.nix
index 7031d7d1d5931..5d6b11498b522 100644
--- a/nixos/modules/profiles/docker-container.nix
+++ b/nixos/modules/profiles/docker-container.nix
@@ -15,15 +15,19 @@ in {
 
   # Create the tarball
   system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
-    contents = [];
+    contents = [
+      {
+        source = "${config.system.build.toplevel}/.";
+        target = "./";
+      }
+    ];
     extraArgs = "--owner=0";
 
     # Add init script to image
-    storeContents = [
-      { object = config.system.build.toplevel + "/init";
-        symlink = "/init";
-      }
-    ] ++ (pkgs2storeContents [ pkgs.stdenv ]);
+    storeContents = pkgs2storeContents [
+      config.system.build.toplevel
+      pkgs.stdenv
+    ];
 
     # Some container managers like lxc need these
     extraCommands = "mkdir -p proc sys dev";
diff --git a/nixos/modules/profiles/graphical.nix b/nixos/modules/profiles/graphical.nix
index 332cf58aa5382..fba756391b118 100644
--- a/nixos/modules/profiles/graphical.nix
+++ b/nixos/modules/profiles/graphical.nix
@@ -7,9 +7,12 @@
   services.xserver = {
     enable = true;
     displayManager.sddm.enable = true;
-    desktopManager.plasma5.enable = true;
+    desktopManager.plasma5 = {
+      enable = true;
+      enableQt4Support = false;
+    };
     libinput.enable = true; # for touchpad support on many laptops
   };
 
-  environment.systemPackages = [ pkgs.glxinfo ];
+  environment.systemPackages = [ pkgs.glxinfo pkgs.firefox ];
 }
diff --git a/nixos/modules/profiles/hardened.nix b/nixos/modules/profiles/hardened.nix
index 2af8bf1f8e30c..9ab2ee87a19e5 100644
--- a/nixos/modules/profiles/hardened.nix
+++ b/nixos/modules/profiles/hardened.nix
@@ -6,23 +6,43 @@
 with lib;
 
 {
+  meta = {
+    maintainers = [ maintainers.joachifm ];
+  };
+
   boot.kernelPackages = mkDefault pkgs.linuxPackages_hardened;
 
+  nix.allowedUsers = mkDefault [ "@users" ];
+
   security.hideProcessInformation = mkDefault true;
 
   security.lockKernelModules = mkDefault true;
 
+  security.allowUserNamespaces = mkDefault false;
+
+  security.protectKernelImage = mkDefault true;
+
+  security.allowSimultaneousMultithreading = mkDefault false;
+
+  security.virtualization.flushL1DataCache = mkDefault "always";
+
   security.apparmor.enable = mkDefault true;
 
   boot.kernelParams = [
+    # Slab/slub sanity checks, redzoning, and poisoning
+    "slub_debug=FZP"
+
+    # Disable slab merging to make certain heap overflow attacks harder
+    "slab_nomerge"
+
     # Overwrite free'd memory
     "page_poison=1"
 
     # Disable legacy virtual syscalls
     "vsyscall=none"
 
-    # Disable hibernation (allows replacing the running kernel)
-    "nohibernate"
+    # Enable PTI even if CPU claims to be safe from meltdown
+    "pti=on"
   ];
 
   boot.blacklistedKernelModules = [
@@ -36,9 +56,6 @@ with lib;
   # (e.g., parent/child)
   boot.kernel.sysctl."kernel.yama.ptrace_scope" = mkOverride 500 1;
 
-  # Prevent replacing the running kernel image w/o reboot
-  boot.kernel.sysctl."kernel.kexec_load_disabled" = mkDefault true;
-
   # Restrict access to kernel ring buffer (information leaks)
   boot.kernel.sysctl."kernel.dmesg_restrict" = mkDefault true;
 
@@ -55,18 +72,6 @@ with lib;
   # ... or at least apply some hardening to it
   boot.kernel.sysctl."net.core.bpf_jit_harden" = mkDefault true;
 
-  # A recurring problem with user namespaces is that there are
-  # still code paths where the kernel's permission checking logic
-  # fails to account for namespacing, instead permitting a
-  # namespaced process to act outside the namespace with the
-  # same privileges as it would have inside it.  This is particularly
-  # bad in the common case of running as root within the namespace.
-  #
-  # Setting the number of allowed user namespaces to 0 effectively disables
-  # the feature at runtime.  Attempting to create a user namespace
-  # with unshare will then fail with "no space left on device".
-  boot.kernel.sysctl."user.max_user_namespaces" = mkDefault 0;
-
   # Raise ASLR entropy for 64bit & 32bit, respectively.
   #
   # Note: mmap_rnd_compat_bits may not exist on 64bit.
diff --git a/nixos/modules/profiles/headless.nix b/nixos/modules/profiles/headless.nix
index 131ee272859a9..46a9b6a7d8d5c 100644
--- a/nixos/modules/profiles/headless.nix
+++ b/nixos/modules/profiles/headless.nix
@@ -6,7 +6,6 @@
 with lib;
 
 {
-  sound.enable = false;
   boot.vesa = false;
 
   # Don't start a tty on the serial consoles.
diff --git a/nixos/modules/profiles/installation-device.nix b/nixos/modules/profiles/installation-device.nix
index ff4a23a18d069..580ea4a58e5b4 100644
--- a/nixos/modules/profiles/installation-device.nix
+++ b/nixos/modules/profiles/installation-device.nix
@@ -22,9 +22,10 @@ with lib;
   config = {
 
     # Enable in installer, even if the minimal profile disables it.
-    services.nixosManual.enable = mkForce true;
+    documentation.enable = mkForce true;
 
     # Show the manual.
+    documentation.nixos.enable = mkForce true;
     services.nixosManual.showManual = true;
 
     # Let the user play Rogue on TTY 8 during the installation.
@@ -62,7 +63,7 @@ with lib;
     # Tell the Nix evaluator to garbage collect more aggressively.
     # This is desirable in memory-constrained environments that don't
     # (yet) have swap set up.
-    environment.variables.GC_INITIAL_HEAP_SIZE = "100000";
+    environment.variables.GC_INITIAL_HEAP_SIZE = "1M";
 
     # Make the installer more likely to succeed in low memory
     # environments.  The kernel's overcommit heustistics bite us
@@ -86,9 +87,6 @@ with lib;
     # console less cumbersome if the machine has a public IP.
     networking.firewall.logRefusedConnections = mkDefault false;
 
-    environment.systemPackages = [ pkgs.vim ];
-
-
     # Allow the user to log in as root without a password.
     users.users.root.initialHashedPassword = "";
   };
diff --git a/nixos/modules/profiles/minimal.nix b/nixos/modules/profiles/minimal.nix
index ed04e46c77d1d..f044e6f39ea5a 100644
--- a/nixos/modules/profiles/minimal.nix
+++ b/nixos/modules/profiles/minimal.nix
@@ -12,7 +12,6 @@ with lib;
   i18n.supportedLocales = [ (config.i18n.defaultLocale + "/UTF-8") ];
 
   documentation.enable = mkDefault false;
-  services.nixosManual.enable = mkDefault false;
 
-  sound.enable = mkDefault false;
+  documentation.nixos.enable = mkDefault false;
 }
diff --git a/nixos/modules/programs/adb.nix b/nixos/modules/programs/adb.nix
index 942572cef9d51..250d8c252a3bc 100644
--- a/nixos/modules/programs/adb.nix
+++ b/nixos/modules/programs/adb.nix
@@ -16,7 +16,6 @@ with lib;
           To grant access to a user, it must be part of adbusers group:
           <code>users.users.alice.extraGroups = ["adbusers"];</code>
         '';
-        relatedPackages = [ ["androidenv" "platformTools"] ];
       };
     };
   };
@@ -24,7 +23,7 @@ with lib;
   ###### implementation
   config = mkIf config.programs.adb.enable {
     services.udev.packages = [ pkgs.android-udev-rules ];
-    environment.systemPackages = [ pkgs.androidenv.platformTools ];
+    environment.systemPackages = [ pkgs.androidenv.androidPkgs_9_0.platform-tools ];
     users.groups.adbusers = {};
   };
 }
diff --git a/nixos/modules/programs/bash/bash.nix b/nixos/modules/programs/bash/bash.nix
index 424e1506b4c5d..d22f9dfa3199b 100644
--- a/nixos/modules/programs/bash/bash.nix
+++ b/nixos/modules/programs/bash/bash.nix
@@ -16,7 +16,7 @@ let
     # programmable completion. If we do, enable all modules installed in
     # the system and user profile in obsolete /etc/bash_completion.d/
     # directories. Bash loads completions in all
-    # $XDG_DATA_DIRS/share/bash-completion/completions/
+    # $XDG_DATA_DIRS/bash-completion/completions/
     # on demand, so they do not need to be sourced here.
     if shopt -q progcomp &>/dev/null; then
       . "${pkgs.bash-completion}/etc/profile.d/bash_completion.sh"
@@ -33,7 +33,8 @@ let
   '';
 
   bashAliases = concatStringsSep "\n" (
-    mapAttrsFlatten (k: v: "alias ${k}='${v}'") cfg.shellAliases
+    mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
+      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
   );
 
 in
@@ -59,12 +60,12 @@ in
       */
 
       shellAliases = mkOption {
-        default = config.environment.shellAliases;
+        default = {};
         description = ''
-          Set of aliases for bash shell. See <option>environment.shellAliases</option>
-          for an option format description.
+          Set of aliases for bash shell, which overrides <option>environment.shellAliases</option>.
+          See <option>environment.shellAliases</option> for an option format description.
         '';
-        type = types.attrs; # types.attrsOf types.stringOrPath;
+        type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
@@ -97,7 +98,12 @@ in
           if [ "$TERM" != "dumb" -o -n "$INSIDE_EMACS" ]; then
             PROMPT_COLOR="1;31m"
             let $UID && PROMPT_COLOR="1;32m"
-            PS1="\n\[\033[$PROMPT_COLOR\][\u@\h:\w]\\$\[\033[0m\] "
+            if [ -n "$INSIDE_EMACS" ]; then
+              # Emacs term mode doesn't support xterm title escape sequence (\e]0;)
+              PS1="\n\[\033[$PROMPT_COLOR\][\u@\h:\w]\\$\[\033[0m\] "
+            else
+              PS1="\n\[\033[$PROMPT_COLOR\][\[\e]0;\u@\h: \w\a\]\u@\h:\w]\$\[\033[0m\] "
+            fi
             if test "$TERM" = "xterm"; then
               PS1="\[\033]2;\h:\u:\w\007\]$PS1"
             fi
@@ -125,6 +131,8 @@ in
 
     programs.bash = {
 
+      shellAliases = mapAttrs (name: mkDefault) cfge.shellAliases;
+
       shellInit = ''
         if [ -z "$__NIXOS_SET_ENVIRONMENT_DONE" ]; then
             . ${config.system.build.setEnvironment}
diff --git a/nixos/modules/programs/command-not-found/command-not-found.nix b/nixos/modules/programs/command-not-found/command-not-found.nix
index bbe7165c62fbd..656c255fcb185 100644
--- a/nixos/modules/programs/command-not-found/command-not-found.nix
+++ b/nixos/modules/programs/command-not-found/command-not-found.nix
@@ -16,7 +16,7 @@ let
     isExecutable = true;
     inherit (pkgs) perl;
     inherit (cfg) dbPath;
-    perlFlags = concatStrings (map (path: "-I ${path}/lib/perl5/site_perl ")
+    perlFlags = concatStrings (map (path: "-I ${path}/${pkgs.perl.libPrefix} ")
       [ pkgs.perlPackages.DBI pkgs.perlPackages.DBDSQLite pkgs.perlPackages.StringShellQuote ]);
   };
 
diff --git a/nixos/modules/programs/digitalbitbox/doc.xml b/nixos/modules/programs/digitalbitbox/doc.xml
index a26653dda535f..c63201628dbd7 100644
--- a/nixos/modules/programs/digitalbitbox/doc.xml
+++ b/nixos/modules/programs/digitalbitbox/doc.xml
@@ -3,75 +3,64 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-programs-digitalbitbox">
-
-  <title>Digital Bitbox</title>
-
-  <para>
-    Digital Bitbox is a hardware wallet and second-factor authenticator.
-  </para>
-
-  <para>
-    The <literal>digitalbitbox</literal> programs module may be
-    installed by setting <literal>programs.digitalbitbox</literal>
-    to <literal>true</literal> in a manner similar to
-
+ <title>Digital Bitbox</title>
+ <para>
+  Digital Bitbox is a hardware wallet and second-factor authenticator.
+ </para>
+ <para>
+  The <literal>digitalbitbox</literal> programs module may be installed by
+  setting <literal>programs.digitalbitbox</literal> to <literal>true</literal>
+  in a manner similar to
 <programlisting>
 <xref linkend="opt-programs.digitalbitbox.enable"/> = true;
 </programlisting>
-
-    and bundles the <literal>digitalbitbox</literal> package (see <xref
+  and bundles the <literal>digitalbitbox</literal> package (see
+  <xref
       linkend="sec-digitalbitbox-package" />), which contains the
-    <literal>dbb-app</literal> and <literal>dbb-cli</literal> binaries,
-    along with the hardware module (see <xref
+  <literal>dbb-app</literal> and <literal>dbb-cli</literal> binaries, along
+  with the hardware module (see
+  <xref
       linkend="sec-digitalbitbox-hardware-module" />) which sets up the
-    necessary udev rules to access the device.
-  </para>
-
-  <para>
-    Enabling the digitalbitbox module is pretty much the easiest way to
-    get a Digital Bitbox device working on your system.
-  </para>
+  necessary udev rules to access the device.
+ </para>
+ <para>
+  Enabling the digitalbitbox module is pretty much the easiest way to get a
+  Digital Bitbox device working on your system.
+ </para>
+ <para>
+  For more information, see
+  <link xlink:href="https://digitalbitbox.com/start_linux" />.
+ </para>
+ <section xml:id="sec-digitalbitbox-package">
+  <title>Package</title>
 
   <para>
-    For more information, see
-    <link xlink:href="https://digitalbitbox.com/start_linux" />.
-  </para>
-
-  <section xml:id="sec-digitalbitbox-package">
-    <title>Package</title>
-
-    <para>
-      The binaries, <literal>dbb-app</literal> (a GUI tool) and
-      <literal>dbb-cli</literal> (a CLI tool), are available through the
-      <literal>digitalbitbox</literal> package which could be installed
-      as follows:
-
+   The binaries, <literal>dbb-app</literal> (a GUI tool) and
+   <literal>dbb-cli</literal> (a CLI tool), are available through the
+   <literal>digitalbitbox</literal> package which could be installed as
+   follows:
 <programlisting>
 <xref linkend="opt-environment.systemPackages"/> = [
   pkgs.digitalbitbox
 ];
 </programlisting>
-    </para>
-  </section>
-
-
-  <section xml:id="sec-digitalbitbox-hardware-module">
-    <title>Hardware</title>
-
-    <para>
-      The digitalbitbox hardware package enables the udev rules for
-      Digital Bitbox devices and may be installed as follows:
+  </para>
+ </section>
+ <section xml:id="sec-digitalbitbox-hardware-module">
+  <title>Hardware</title>
 
+  <para>
+   The digitalbitbox hardware package enables the udev rules for Digital Bitbox
+   devices and may be installed as follows:
 <programlisting>
 <xref linkend="opt-hardware.digitalbitbox.enable"/> = true;
 </programlisting>
-    </para>
-
-    <para>
-      In order to alter the udev rules, one may provide different values for
-      the <literal>udevRule51</literal> and <literal>udevRule52</literal>
-      attributes by means of overriding as follows:
+  </para>
 
+  <para>
+   In order to alter the udev rules, one may provide different values for the
+   <literal>udevRule51</literal> and <literal>udevRule52</literal> attributes
+   by means of overriding as follows:
 <programlisting>
 programs.digitalbitbox = {
   <link linkend="opt-programs.digitalbitbox.enable">enable</link> = true;
@@ -80,6 +69,6 @@ programs.digitalbitbox = {
   };
 };
 </programlisting>
-    </para>
-  </section>
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/programs/dmrconfig.nix b/nixos/modules/programs/dmrconfig.nix
new file mode 100644
index 0000000000000..e48a4f3183708
--- /dev/null
+++ b/nixos/modules/programs/dmrconfig.nix
@@ -0,0 +1,38 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.dmrconfig;
+
+in {
+  meta.maintainers = [ maintainers.etu ];
+
+  ###### interface
+  options = {
+    programs.dmrconfig = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Whether to configure system to enable use of dmrconfig. This
+          enables the required udev rules and installs the program.
+        '';
+        relatedPackages = [ "dmrconfig" ];
+      };
+
+      package = mkOption {
+        default = pkgs.dmrconfig;
+        type = types.package;
+        defaultText = "pkgs.dmrconfig";
+        description = "dmrconfig derivation to use";
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ cfg.package ];
+    services.udev.packages = [ cfg.package ];
+  };
+}
diff --git a/nixos/modules/programs/fish.nix b/nixos/modules/programs/fish.nix
index c3f742acde2e6..b38af07b92c37 100644
--- a/nixos/modules/programs/fish.nix
+++ b/nixos/modules/programs/fish.nix
@@ -9,7 +9,8 @@ let
   cfg = config.programs.fish;
 
   fishAliases = concatStringsSep "\n" (
-    mapAttrsFlatten (k: v: "alias ${k} '${v}'") cfg.shellAliases
+    mapAttrsFlatten (k: v: "alias ${k} ${escapeShellArg v}")
+      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
   );
 
 in
@@ -53,12 +54,12 @@ in
       };
 
       shellAliases = mkOption {
-        default = config.environment.shellAliases;
+        default = {};
         description = ''
-          Set of aliases for fish shell. See <option>environment.shellAliases</option>
-          for an option format description.
+          Set of aliases for fish shell, which overrides <option>environment.shellAliases</option>.
+          See <option>environment.shellAliases</option> for an option format description.
         '';
-        type = types.attrs;
+        type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
@@ -99,6 +100,8 @@ in
 
   config = mkIf cfg.enable {
 
+    programs.fish.shellAliases = mapAttrs (name: mkDefault) cfge.shellAliases;
+
     environment.etc."fish/foreign-env/shellInit".text = cfge.shellInit;
     environment.etc."fish/foreign-env/loginShellInit".text = cfge.loginShellInit;
     environment.etc."fish/foreign-env/interactiveShellInit".text = cfge.interactiveShellInit;
diff --git a/nixos/modules/programs/gnupg.nix b/nixos/modules/programs/gnupg.nix
index addc9dcca87ed..b01de9efaa5eb 100644
--- a/nixos/modules/programs/gnupg.nix
+++ b/nixos/modules/programs/gnupg.nix
@@ -77,7 +77,7 @@ in
 
     systemd.packages = [ pkgs.gnupg ];
 
-    environment.extraInit = ''
+    environment.interactiveShellInit = ''
       # Bind gpg-agent to this TTY if gpg commands are used.
       export GPG_TTY=$(tty)
 
diff --git a/nixos/modules/programs/iotop.nix b/nixos/modules/programs/iotop.nix
new file mode 100644
index 0000000000000..5512dbc62f72b
--- /dev/null
+++ b/nixos/modules/programs/iotop.nix
@@ -0,0 +1,17 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.iotop;
+in {
+  options = {
+    programs.iotop.enable = mkEnableOption "iotop + setcap wrapper";
+  };
+  config = mkIf cfg.enable {
+    security.wrappers.iotop = {
+      source = "${pkgs.iotop}/bin/iotop";
+      capabilities = "cap_net_admin+p";
+    };
+  };
+}
diff --git a/nixos/modules/programs/light.nix b/nixos/modules/programs/light.nix
index 6f8c389acc97e..9f2a03e7e7639 100644
--- a/nixos/modules/programs/light.nix
+++ b/nixos/modules/programs/light.nix
@@ -13,7 +13,8 @@ in
         default = false;
         type = types.bool;
         description = ''
-          Whether to install Light backlight control with setuid wrapper.
+          Whether to install Light backlight control command
+          and udev rules granting access to members of the "video" group.
         '';
       };
     };
@@ -21,6 +22,6 @@ in
 
   config = mkIf cfg.enable {
     environment.systemPackages = [ pkgs.light ];
-    security.wrappers.light.source = "${pkgs.light.out}/bin/light";
+    services.udev.packages = [ pkgs.light ];
   };
 }
diff --git a/nixos/modules/programs/mininet.nix b/nixos/modules/programs/mininet.nix
new file mode 100644
index 0000000000000..ecc924325e6b0
--- /dev/null
+++ b/nixos/modules/programs/mininet.nix
@@ -0,0 +1,39 @@
+# Global configuration for mininet
+# kernel must have NETNS/VETH/SCHED
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg  = config.programs.mininet;
+
+  generatedPath = with pkgs; makeSearchPath "bin"  [
+    iperf ethtool iproute socat
+  ];
+
+  pyEnv = pkgs.python.withPackages(ps: [ ps.mininet-python ]);
+
+  mnexecWrapped = pkgs.runCommand "mnexec-wrapper"
+    { buildInputs = [ pkgs.makeWrapper pkgs.pythonPackages.wrapPython ]; }
+    ''
+      makeWrapper ${pkgs.mininet}/bin/mnexec \
+        $out/bin/mnexec \
+        --prefix PATH : "${generatedPath}"
+
+      ln -s ${pyEnv}/bin/mn $out/bin/mn
+
+      # mn errors out without a telnet binary
+      # pkgs.telnet brings an undesired ifconfig into PATH see #43105
+      ln -s ${pkgs.telnet}/bin/telnet $out/bin/telnet
+    '';
+in
+{
+  options.programs.mininet.enable = mkEnableOption "Mininet";
+
+  config = mkIf cfg.enable {
+
+    virtualisation.vswitch.enable = true;
+
+    environment.systemPackages = [ mnexecWrapped ];
+  };
+}
diff --git a/nixos/modules/programs/nano.nix b/nixos/modules/programs/nano.nix
index 27b6d446c75d7..6a4d46338e19b 100644
--- a/nixos/modules/programs/nano.nix
+++ b/nixos/modules/programs/nano.nix
@@ -2,6 +2,7 @@
 
 let
   cfg = config.programs.nano;
+  LF = "\n";
 in
 
 {
@@ -33,9 +34,9 @@ in
 
   ###### implementation
 
-  config = lib.mkIf (cfg.nanorc != "") {
+  config = lib.mkIf (cfg.nanorc != "" || cfg.syntaxHighlight) {
     environment.etc."nanorc".text = lib.concatStrings [ cfg.nanorc
-      (lib.optionalString cfg.syntaxHighlight ''include "${pkgs.nano}/share/nano/*.nanorc"'') ];
+      (lib.optionalString cfg.syntaxHighlight ''${LF}include "${pkgs.nano}/share/nano/*.nanorc"'') ];
   };
 
 }
diff --git a/nixos/modules/programs/nm-applet.nix b/nixos/modules/programs/nm-applet.nix
new file mode 100644
index 0000000000000..e42219e9638c9
--- /dev/null
+++ b/nixos/modules/programs/nm-applet.nix
@@ -0,0 +1,14 @@
+{ config, lib, pkgs, ... }:
+
+{
+  options.programs.nm-applet.enable = lib.mkEnableOption "nm-applet";
+
+  config = lib.mkIf config.programs.nm-applet.enable {
+    systemd.user.services.nm-applet = {
+      description = "Network manager applet";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+      serviceConfig.ExecStart = "${pkgs.networkmanagerapplet}/bin/nm-applet";
+    };
+  };
+}
diff --git a/nixos/modules/programs/plotinus.xml b/nixos/modules/programs/plotinus.xml
index 91740ee16ec2f..902cd89e0c492 100644
--- a/nixos/modules/programs/plotinus.xml
+++ b/nixos/modules/programs/plotinus.xml
@@ -3,23 +3,28 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-program-plotinus">
-
-<title>Plotinus</title>
-
-<para><emphasis>Source:</emphasis> <filename>modules/programs/plotinus.nix</filename></para>
-
-<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="https://github.com/p-e-w/plotinus"/></para>
-
-<para>Plotinus is a searchable command palette in every modern GTK+ application.</para>
-
-<para>When in a GTK+3 application and Plotinus is enabled, you can press <literal>Ctrl+Shift+P</literal> to open the command palette.  The command palette provides a searchable list of of all menu items in the application.</para>
-
-<para>To enable Plotinus, add the following to your <filename>configuration.nix</filename>:
-
+ <title>Plotinus</title>
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/programs/plotinus.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://github.com/p-e-w/plotinus"/>
+ </para>
+ <para>
+  Plotinus is a searchable command palette in every modern GTK+ application.
+ </para>
+ <para>
+  When in a GTK+3 application and Plotinus is enabled, you can press
+  <literal>Ctrl+Shift+P</literal> to open the command palette. The command
+  palette provides a searchable list of of all menu items in the application.
+ </para>
+ <para>
+  To enable Plotinus, add the following to your
+  <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-programs.plotinus.enable"/> = true;
 </programlisting>
-
-</para>
-
+ </para>
 </chapter>
diff --git a/nixos/modules/programs/rootston.nix b/nixos/modules/programs/rootston.nix
deleted file mode 100644
index 842d9e6cfb48f..0000000000000
--- a/nixos/modules/programs/rootston.nix
+++ /dev/null
@@ -1,103 +0,0 @@
-{ config, pkgs, lib, ... }:
-
-with lib;
-
-let
-  cfg = config.programs.rootston;
-
-  rootstonWrapped = pkgs.writeScriptBin "rootston" ''
-    #! ${pkgs.runtimeShell}
-    if [[ "$#" -ge 1 ]]; then
-      exec ${pkgs.rootston}/bin/rootston "$@"
-    else
-      ${cfg.extraSessionCommands}
-      exec ${pkgs.rootston}/bin/rootston -C ${cfg.configFile}
-    fi
-  '';
-in {
-  options.programs.rootston = {
-    enable = mkEnableOption ''
-      rootston, the reference compositor for wlroots. The purpose of rootston
-      is to test and demonstrate the features of wlroots (if you want a real
-      Wayland compositor you should e.g. use Sway instead). You can manually
-      start the compositor by running "rootston" from a terminal'';
-
-    extraSessionCommands = mkOption {
-      type = types.lines;
-      default = "";
-      example = ''
-        # Define a keymap (US QWERTY is the default)
-        export XKB_DEFAULT_LAYOUT=de,us
-        export XKB_DEFAULT_VARIANT=nodeadkeys
-        export XKB_DEFAULT_OPTIONS=grp:alt_shift_toggle,caps:escape
-      '';
-      description = ''
-        Shell commands executed just before rootston is started.
-      '';
-    };
-
-    extraPackages = mkOption {
-      type = with types; listOf package;
-      default = with pkgs; [
-        westonLite xwayland rofi
-      ];
-      defaultText = literalExample ''
-        with pkgs; [
-          westonLite xwayland rofi
-        ]
-      '';
-      example = literalExample "[ ]";
-      description = ''
-        Extra packages to be installed system wide.
-      '';
-    };
-
-    config = mkOption {
-      type = types.str;
-      default = ''
-        [keyboard]
-        meta-key = Logo
-
-        # Sway/i3 like Keybindings
-        # Maps key combinations with commands to execute
-        # Commands include:
-        # - "exit" to stop the compositor
-        # - "exec" to execute a shell command
-        # - "close" to close the current view
-        # - "next_window" to cycle through windows
-        [bindings]
-        Logo+Shift+e = exit
-        Logo+q = close
-        Logo+m = maximize
-        Alt+Tab = next_window
-        Logo+Return = exec weston-terminal
-        Logo+d = exec rofi -show run
-      '';
-      description = ''
-        Default configuration for rootston (used when called without any
-        parameters).
-      '';
-    };
-
-    configFile = mkOption {
-      type = types.path;
-      default = "/etc/rootston.ini";
-      example = literalExample "${pkgs.rootston}/etc/rootston.ini";
-      description = ''
-        Path to the default rootston configuration file (the "config" option
-        will have no effect if you change the path).
-      '';
-    };
-  };
-
-  config = mkIf cfg.enable {
-    environment.etc."rootston.ini".text = cfg.config;
-    environment.systemPackages = [ rootstonWrapped ] ++ cfg.extraPackages;
-
-    hardware.opengl.enable = mkDefault true;
-    fonts.enableDefaultFonts = mkDefault true;
-    programs.dconf.enable = mkDefault true;
-  };
-
-  meta.maintainers = with lib.maintainers; [ primeos gnidorah ];
-}
diff --git a/nixos/modules/programs/shell.nix b/nixos/modules/programs/shell.nix
index 944a8bdf16fc4..9842e2bef643d 100644
--- a/nixos/modules/programs/shell.nix
+++ b/nixos/modules/programs/shell.nix
@@ -8,18 +8,12 @@ with lib;
 
   config = {
 
-    environment.shellAliases =
-      { ls = "ls --color=tty";
-        ll = "ls -l";
-        l  = "ls -alh";
-      };
-
     environment.shellInit =
       ''
         # Set up the per-user profile.
         mkdir -m 0755 -p "$NIX_USER_PROFILE_DIR"
         if [ "$(stat --printf '%u' "$NIX_USER_PROFILE_DIR")" != "$(id -u)" ]; then
-            echo "WARNING: bad ownership on $NIX_USER_PROFILE_DIR, should be $(id -u)" >&2
+            echo "WARNING: the per-user profile dir $NIX_USER_PROFILE_DIR should belong to user id $(id -u)" >&2
         fi
 
         if [ -w "$HOME" ]; then
@@ -41,7 +35,7 @@ with lib;
           NIX_USER_GCROOTS_DIR="/nix/var/nix/gcroots/per-user/$USER"
           mkdir -m 0755 -p "$NIX_USER_GCROOTS_DIR"
           if [ "$(stat --printf '%u' "$NIX_USER_GCROOTS_DIR")" != "$(id -u)" ]; then
-              echo "WARNING: bad ownership on $NIX_USER_GCROOTS_DIR, should be $(id -u)" >&2
+              echo "WARNING: the per-user gcroots dir $NIX_USER_GCROOTS_DIR should belong to user id $(id -u)" >&2
           fi
 
           # Set up a default Nix expression from which to install stuff.
diff --git a/nixos/modules/programs/singularity.nix b/nixos/modules/programs/singularity.nix
index 86153d933855d..b27e122bd1d9e 100644
--- a/nixos/modules/programs/singularity.nix
+++ b/nixos/modules/programs/singularity.nix
@@ -3,18 +3,27 @@
 with lib;
 let
   cfg = config.programs.singularity;
+  singularity = pkgs.singularity.overrideAttrs (attrs : {
+    installPhase = attrs.installPhase + ''
+      mv $bin/libexec/singularity/bin/starter-suid $bin/libexec/singularity/bin/starter-suid.orig
+      ln -s /run/wrappers/bin/singularity-suid $bin/libexec/singularity/bin/starter-suid
+    '';
+  });
 in {
   options.programs.singularity = {
     enable = mkEnableOption "Singularity";
   };
 
   config = mkIf cfg.enable {
-      environment.systemPackages = [ pkgs.singularity ];
-      systemd.tmpfiles.rules = [ "d /var/singularity/mnt/session 0770 root root -"
-                                 "d /var/singularity/mnt/final 0770 root root -"
-                                 "d /var/singularity/mnt/overlay 0770 root root -"
-                                 "d /var/singularity/mnt/container 0770 root root -"
-                                 "d /var/singularity/mnt/source 0770 root root -"];
+      environment.systemPackages = [ singularity ];
+      security.wrappers.singularity-suid.source = "${singularity}/libexec/singularity/bin/starter-suid.orig";
+      systemd.tmpfiles.rules = [
+        "d /var/singularity/mnt/session 0770 root root -"
+        "d /var/singularity/mnt/final 0770 root root -"
+        "d /var/singularity/mnt/overlay 0770 root root -"
+        "d /var/singularity/mnt/container 0770 root root -"
+        "d /var/singularity/mnt/source 0770 root root -"
+      ];
   };
 
 }
diff --git a/nixos/modules/programs/ssh.nix b/nixos/modules/programs/ssh.nix
index cc398174e6ce0..4640c1d78d20e 100644
--- a/nixos/modules/programs/ssh.nix
+++ b/nixos/modules/programs/ssh.nix
@@ -167,16 +167,16 @@ in
           The set of system-wide known SSH hosts.
         '';
         example = literalExample ''
-          [
-            {
+          {
+            myhost = {
               hostNames = [ "myhost" "myhost.mydomain.com" "10.10.1.4" ];
               publicKeyFile = ./pubkeys/myhost_ssh_host_dsa_key.pub;
-            }
-            {
+            };
+            myhost2 = {
               hostNames = [ "myhost2" ];
               publicKeyFile = ./pubkeys/myhost2_ssh_host_dsa_key.pub;
-            }
-          ]
+            };
+          }
         '';
       };
 
diff --git a/nixos/modules/programs/sway-beta.nix b/nixos/modules/programs/sway-beta.nix
new file mode 100644
index 0000000000000..3c235de0ce63f
--- /dev/null
+++ b/nixos/modules/programs/sway-beta.nix
@@ -0,0 +1,91 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.sway-beta;
+  swayPackage = cfg.package;
+
+  swayWrapped = pkgs.writeShellScriptBin "sway" ''
+    set -o errexit
+
+    if [ ! "$_SWAY_WRAPPER_ALREADY_EXECUTED" ]; then
+      export _SWAY_WRAPPER_ALREADY_EXECUTED=1
+      ${cfg.extraSessionCommands}
+    fi
+
+    if [ "$DBUS_SESSION_BUS_ADDRESS" ]; then
+      export DBUS_SESSION_BUS_ADDRESS
+      exec ${swayPackage}/bin/sway "$@"
+    else
+      exec ${pkgs.dbus}/bin/dbus-run-session ${swayPackage}/bin/sway "$@"
+    fi
+  '';
+  swayJoined = pkgs.symlinkJoin {
+    name = "sway-joined";
+    paths = [ swayWrapped swayPackage ];
+  };
+in {
+  options.programs.sway-beta = {
+    enable = mkEnableOption ''
+      Sway, the i3-compatible tiling Wayland compositor. This module will be removed after the final release of Sway 1.0
+    '';
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.sway-beta;
+      defaultText = "pkgs.sway-beta";
+      description = ''
+        The package to be used for `sway`.
+      '';
+    };
+
+    extraSessionCommands = mkOption {
+      type = types.lines;
+      default = "";
+      example = ''
+        export SDL_VIDEODRIVER=wayland
+        # needs qt5.qtwayland in systemPackages
+        export QT_QPA_PLATFORM=wayland
+        export QT_WAYLAND_DISABLE_WINDOWDECORATION="1"
+        # Fix for some Java AWT applications (e.g. Android Studio),
+        # use this if they aren't displayed properly:
+        export _JAVA_AWT_WM_NONREPARENTING=1
+      '';
+      description = ''
+        Shell commands executed just before Sway is started.
+      '';
+    };
+
+    extraPackages = mkOption {
+      type = with types; listOf package;
+      default = with pkgs; [
+        swaylock swayidle
+        xwayland rxvt_unicode dmenu
+      ];
+      defaultText = literalExample ''
+        with pkgs; [ swaylock swayidle xwayland rxvt_unicode dmenu ];
+      '';
+      example = literalExample ''
+        with pkgs; [
+          xwayland
+          i3status i3status-rust
+          termite rofi light
+        ]
+      '';
+      description = ''
+        Extra packages to be installed system wide.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ swayJoined ] ++ cfg.extraPackages;
+    security.pam.services.swaylock = {};
+    hardware.opengl.enable = mkDefault true;
+    fonts.enableDefaultFonts = mkDefault true;
+    programs.dconf.enable = mkDefault true;
+  };
+
+  meta.maintainers = with lib.maintainers; [ gnidorah primeos colemickens ];
+}
diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix
index 0eaaf6b85b99a..b3847db8cd9cc 100644
--- a/nixos/modules/programs/sway.nix
+++ b/nixos/modules/programs/sway.nix
@@ -7,11 +7,18 @@ let
   swayPackage = pkgs.sway;
 
   swayWrapped = pkgs.writeShellScriptBin "sway" ''
-    if [[ "$#" -ge 1 ]]; then
+    set -o errexit
+
+    if [ ! "$_SWAY_WRAPPER_ALREADY_EXECUTED" ]; then
+      export _SWAY_WRAPPER_ALREADY_EXECUTED=1
+      ${cfg.extraSessionCommands}
+    fi
+
+    if [ "$DBUS_SESSION_BUS_ADDRESS" ]; then
+      export DBUS_SESSION_BUS_ADDRESS
       exec sway-setcap "$@"
     else
-      ${cfg.extraSessionCommands}
-      exec ${pkgs.dbus.dbus-launch} --exit-with-session sway-setcap
+      exec ${pkgs.dbus}/bin/dbus-run-session sway-setcap "$@"
     fi
   '';
   swayJoined = pkgs.symlinkJoin {
diff --git a/nixos/modules/programs/thefuck.nix b/nixos/modules/programs/thefuck.nix
index f4ae52934760d..21ed6603c1bdd 100644
--- a/nixos/modules/programs/thefuck.nix
+++ b/nixos/modules/programs/thefuck.nix
@@ -29,8 +29,8 @@ in
 
     config = mkIf cfg.enable {
       environment.systemPackages = with pkgs; [ thefuck ];
-      environment.shellInit = initScript;
 
+      programs.bash.interactiveShellInit = initScript;
       programs.zsh.interactiveShellInit = mkIf prg.zsh.enable initScript;
       programs.fish.interactiveShellInit = mkIf prg.fish.enable ''
         ${pkgs.thefuck}/bin/thefuck --alias | source
diff --git a/nixos/modules/programs/wavemon.nix b/nixos/modules/programs/wavemon.nix
new file mode 100644
index 0000000000000..ac665fe4a0236
--- /dev/null
+++ b/nixos/modules/programs/wavemon.nix
@@ -0,0 +1,28 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.programs.wavemon;
+in {
+  options = {
+    programs.wavemon = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to add wavemon to the global environment and configure a
+          setcap wrapper for it.
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = with pkgs; [ wavemon ];
+    security.wrappers.wavemon = {
+      source = "${pkgs.wavemon}/bin/wavemon";
+      capabilities = "cap_net_admin+ep";
+    };
+  };
+}
diff --git a/nixos/modules/programs/way-cooler.nix b/nixos/modules/programs/way-cooler.nix
index 633e959be9f32..f27bd42bd764c 100644
--- a/nixos/modules/programs/way-cooler.nix
+++ b/nixos/modules/programs/way-cooler.nix
@@ -8,7 +8,7 @@ let
 
   wcWrapped = pkgs.writeShellScriptBin "way-cooler" ''
     ${cfg.extraSessionCommands}
-    exec ${pkgs.dbus.dbus-launch} --exit-with-session ${way-cooler}/bin/way-cooler
+    exec ${pkgs.dbus}/bin/dbus-run-session ${way-cooler}/bin/way-cooler
   '';
   wcJoined = pkgs.symlinkJoin {
     name = "way-cooler-wrapped";
diff --git a/nixos/modules/programs/xss-lock.nix b/nixos/modules/programs/xss-lock.nix
index 49d522c604f5f..c290df01b9604 100644
--- a/nixos/modules/programs/xss-lock.nix
+++ b/nixos/modules/programs/xss-lock.nix
@@ -9,7 +9,8 @@ in
   options.programs.xss-lock = {
     enable = mkEnableOption "xss-lock";
     lockerCommand = mkOption {
-      example = "xlock";
+      default = "${pkgs.i3lock}/bin/i3lock";
+      example = literalExample ''''${pkgs.i3lock-fancy}/bin/i3lock-fancy'';
       type = types.string;
       description = "Locker to be used with xsslock";
     };
diff --git a/nixos/modules/programs/yabar.nix b/nixos/modules/programs/yabar.nix
index a01083c3ace98..db085211366ee 100644
--- a/nixos/modules/programs/yabar.nix
+++ b/nixos/modules/programs/yabar.nix
@@ -44,10 +44,23 @@ in
       enable = mkEnableOption "yabar";
 
       package = mkOption {
-        default = pkgs.yabar;
-        example = literalExample "pkgs.yabar-unstable";
+        default = pkgs.yabar-unstable;
+        example = literalExample "pkgs.yabar";
         type = types.package;
 
+        # `yabar-stable` segfaults under certain conditions.
+        apply = x: if x == pkgs.yabar-unstable then x else flip warn x ''
+          It's not recommended to use `yabar' with `programs.yabar', the (old) stable release
+          tends to segfault under certain circumstances:
+
+          * https://github.com/geommer/yabar/issues/86
+          * https://github.com/geommer/yabar/issues/68
+          * https://github.com/geommer/yabar/issues/143
+
+          Most of them don't occur on master anymore, until a new release is published, it's recommended
+          to use `yabar-unstable'.
+        '';
+
         description = ''
           The package which contains the `yabar` binary.
 
diff --git a/nixos/modules/programs/zsh/oh-my-zsh.xml b/nixos/modules/programs/zsh/oh-my-zsh.xml
index 6567d4a42facd..b26980daf067d 100644
--- a/nixos/modules/programs/zsh/oh-my-zsh.xml
+++ b/nixos/modules/programs/zsh/oh-my-zsh.xml
@@ -3,105 +3,135 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-programs-zsh-ohmyzsh">
-
-<title>Oh my ZSH</title>
-
-<para><literal><link xlink:href="https://ohmyz.sh/">oh-my-zsh</link></literal> is a framework
-to manage your <link xlink:href="https://www.zsh.org/">ZSH</link> configuration
-including completion scripts for several CLI tools or custom prompt themes.</para>
-
-<section xml:id="module-programs-oh-my-zsh-usage"><title>Basic usage</title>
-<para>The module uses the <literal>oh-my-zsh</literal> package with all available features.  The
-initial setup using Nix expressions is fairly similar to the configuration format
-of <literal>oh-my-zsh</literal>.
-
+ <title>Oh my ZSH</title>
+ <para>
+  <literal><link xlink:href="https://ohmyz.sh/">oh-my-zsh</link></literal> is a
+  framework to manage your <link xlink:href="https://www.zsh.org/">ZSH</link>
+  configuration including completion scripts for several CLI tools or custom
+  prompt themes.
+ </para>
+ <section xml:id="module-programs-oh-my-zsh-usage">
+  <title>Basic usage</title>
+
+  <para>
+   The module uses the <literal>oh-my-zsh</literal> package with all available
+   features. The initial setup using Nix expressions is fairly similar to the
+   configuration format of <literal>oh-my-zsh</literal>.
 <programlisting>
 {
-  programs.ohMyZsh = {
+  programs.zsh.ohMyZsh = {
     enable = true;
     plugins = [ "git" "python" "man" ];
     theme = "agnoster";
   };
 }
 </programlisting>
-
-For a detailed explanation of these arguments please refer to the
-<link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki"><literal>oh-my-zsh</literal> docs</link>.
-</para>
-<para>The expression generates the needed
-configuration and writes it into your <literal>/etc/zshrc</literal>.
-</para></section>
-
-<section xml:id="module-programs-oh-my-zsh-additions"><title>Custom additions</title>
-
-<para>Sometimes third-party or custom scripts such as a modified theme may be needed.
-<literal>oh-my-zsh</literal> provides the
-<link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals"><literal>ZSH_CUSTOM</literal></link> 
-environment variable for this which points to a directory with additional scripts.</para>
-
-<para>The module can do this as well:
-
+   For a detailed explanation of these arguments please refer to the
+   <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki"><literal>oh-my-zsh</literal>
+   docs</link>.
+  </para>
+
+  <para>
+   The expression generates the needed configuration and writes it into your
+   <literal>/etc/zshrc</literal>.
+  </para>
+ </section>
+ <section xml:id="module-programs-oh-my-zsh-additions">
+  <title>Custom additions</title>
+
+  <para>
+   Sometimes third-party or custom scripts such as a modified theme may be
+   needed. <literal>oh-my-zsh</literal> provides the
+   <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/wiki/Customization#overriding-internals"><literal>ZSH_CUSTOM</literal></link>
+   environment variable for this which points to a directory with additional
+   scripts.
+  </para>
+
+  <para>
+   The module can do this as well:
 <programlisting>
 {
-  programs.ohMyZsh.custom = "~/path/to/custom/scripts";
+  programs.zsh.ohMyZsh.custom = "~/path/to/custom/scripts";
 }
 </programlisting>
-</para></section>
-
-<section xml:id="module-programs-oh-my-zsh-environments"><title>Custom environments</title>
-
-<para>There are several extensions for <literal>oh-my-zsh</literal> packaged in <literal>nixpkgs</literal>.
-One of them is <link xlink:href="https://github.com/spwhitt/nix-zsh-completions">nix-zsh-completions</link>
-which bundles completion scripts and a plugin for <literal>oh-my-zsh</literal>.</para>
-
-<para>Rather than using a single mutable path for <literal>ZSH_CUSTOM</literal>, it's also possible to
-generate this path from a list of Nix packages:
-
+  </para>
+ </section>
+ <section xml:id="module-programs-oh-my-zsh-environments">
+  <title>Custom environments</title>
+
+  <para>
+   There are several extensions for <literal>oh-my-zsh</literal> packaged in
+   <literal>nixpkgs</literal>. One of them is
+   <link xlink:href="https://github.com/spwhitt/nix-zsh-completions">nix-zsh-completions</link>
+   which bundles completion scripts and a plugin for
+   <literal>oh-my-zsh</literal>.
+  </para>
+
+  <para>
+   Rather than using a single mutable path for <literal>ZSH_CUSTOM</literal>,
+   it's also possible to generate this path from a list of Nix packages:
 <programlisting>
 { pkgs, ... }:
 {
-  programs.ohMyZsh.customPkgs = with pkgs; [
+  programs.zsh.ohMyZsh.customPkgs = with pkgs; [
     pkgs.nix-zsh-completions
     # and even more...
   ];
 }
 </programlisting>
-
-Internally a single store path will be created using <literal>buildEnv</literal>.
-Please refer to the docs of
-<link xlink:href="https://nixos.org/nixpkgs/manual/#sec-building-environment"><literal>buildEnv</literal></link>
-for further reference.</para>
-
-<para><emphasis>Please keep in mind that this is not compatible with <literal>programs.ohMyZsh.custom</literal>
-as it requires an immutable store path while <literal>custom</literal> shall remain mutable! An evaluation failure
-will be thrown if both <literal>custom</literal> and <literal>customPkgs</literal> are set.</emphasis>
-</para></section>
-
-<section xml:id="module-programs-oh-my-zsh-packaging-customizations"><title>Package your own customizations</title>
-
-<para>If third-party customizations (e.g. new themes) are supposed to be added to <literal>oh-my-zsh</literal>
-there are several pitfalls to keep in mind:</para>
-
-<itemizedlist>
-  <listitem>
-    <para>To comply with the default structure of <literal>ZSH</literal> the entire output needs to be written to
-    <literal>$out/share/zsh.</literal></para>
-  </listitem>
-  <listitem>
-    <para>Completion scripts are supposed to be stored at <literal>$out/share/zsh/site-functions</literal>. This directory
-    is part of the <literal><link xlink:href="http://zsh.sourceforge.net/Doc/Release/Functions.html">fpath</link></literal>
-    and the package should be compatible with pure <literal>ZSH</literal> setups. The module will automatically link
-    the contents of <literal>site-functions</literal> to completions directory in the proper store path.</para>
-  </listitem>
-  <listitem>
-    <para>The <literal>plugins</literal> directory needs the structure <literal>pluginname/pluginname.plugin.zsh</literal>
-    as structured in the <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/tree/91b771914bc7c43dd7c7a43b586c5de2c225ceb7/plugins">upstream repo.</link>
+   Internally a single store path will be created using
+   <literal>buildEnv</literal>. Please refer to the docs of
+   <link xlink:href="https://nixos.org/nixpkgs/manual/#sec-building-environment"><literal>buildEnv</literal></link>
+   for further reference.
+  </para>
+
+  <para>
+   <emphasis>Please keep in mind that this is not compatible with
+   <literal>programs.zsh.ohMyZsh.custom</literal> as it requires an immutable store
+   path while <literal>custom</literal> shall remain mutable! An evaluation
+   failure will be thrown if both <literal>custom</literal> and
+   <literal>customPkgs</literal> are set.</emphasis>
+  </para>
+ </section>
+ <section xml:id="module-programs-oh-my-zsh-packaging-customizations">
+  <title>Package your own customizations</title>
+
+  <para>
+   If third-party customizations (e.g. new themes) are supposed to be added to
+   <literal>oh-my-zsh</literal> there are several pitfalls to keep in mind:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     To comply with the default structure of <literal>ZSH</literal> the entire
+     output needs to be written to <literal>$out/share/zsh.</literal>
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Completion scripts are supposed to be stored at
+     <literal>$out/share/zsh/site-functions</literal>. This directory is part
+     of the
+     <literal><link xlink:href="http://zsh.sourceforge.net/Doc/Release/Functions.html">fpath</link></literal>
+     and the package should be compatible with pure <literal>ZSH</literal>
+     setups. The module will automatically link the contents of
+     <literal>site-functions</literal> to completions directory in the proper
+     store path.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     The <literal>plugins</literal> directory needs the structure
+     <literal>pluginname/pluginname.plugin.zsh</literal> as structured in the
+     <link xlink:href="https://github.com/robbyrussell/oh-my-zsh/tree/91b771914bc7c43dd7c7a43b586c5de2c225ceb7/plugins">upstream
+     repo.</link>
     </para>
-  </listitem>
-</itemizedlist>
+   </listitem>
+  </itemizedlist>
 
-<para>
-A derivation for <literal>oh-my-zsh</literal> may look like this:
+  <para>
+   A derivation for <literal>oh-my-zsh</literal> may look like this:
 <programlisting>
 { stdenv, fetchFromGitHub }:
 
@@ -120,6 +150,6 @@ stdenv.mkDerivation rec {
   '';
 }
 </programlisting>
-</para>
-</section>
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/programs/zsh/zsh-autosuggestions.nix b/nixos/modules/programs/zsh/zsh-autosuggestions.nix
index 416f4c9c67511..ded17f38a618e 100644
--- a/nixos/modules/programs/zsh/zsh-autosuggestions.nix
+++ b/nixos/modules/programs/zsh/zsh-autosuggestions.nix
@@ -18,13 +18,13 @@ in
     };
 
     strategy = mkOption {
-      type = types.enum [ "default" "match_prev_cmd" ];
-      default = "default";
+      type = types.enum [ "history" "match_prev_cmd" ];
+      default = "history";
       description = ''
         Set ZSH_AUTOSUGGEST_STRATEGY to choose the strategy for generating suggestions.
         There are currently two to choose from:
 
-          * default: Chooses the most recent match.
+          * history: Chooses the most recent match.
           * match_prev_cmd: Chooses the most recent match whose preceding history item matches
             the most recently executed command (more info). Note that this strategy won't work as
             expected with ZSH options that don't preserve the history order such as
@@ -51,7 +51,7 @@ in
       source ${pkgs.zsh-autosuggestions}/share/zsh-autosuggestions/zsh-autosuggestions.zsh
 
       export ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE="${cfg.highlightStyle}"
-      export ZSH_AUTOSUGGEST_STRATEGY="${cfg.strategy}"
+      export ZSH_AUTOSUGGEST_STRATEGY=("${cfg.strategy}")
 
       ${concatStringsSep "\n" (mapAttrsToList (key: value: ''export ${key}="${value}"'') cfg.extraConfig)}
     '';
diff --git a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
index e7cf17c2c00cb..89087a229eb73 100644
--- a/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
+++ b/nixos/modules/programs/zsh/zsh-syntax-highlighting.nix
@@ -48,6 +48,23 @@ in
           https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/docs/highlighters/pattern.md
         '';
       };
+      styles = mkOption {
+        default = {};
+        type = types.attrsOf types.string;
+
+        example = literalExample ''
+          {
+            "alias" = "fg=magenta,bold";
+          }
+        '';
+
+        description = ''
+          Specifies custom styles to be highlighted by zsh-syntax-highlighting.
+
+          Please refer to the docs for more information about the usage:
+          https://github.com/zsh-users/zsh-syntax-highlighting/blob/master/docs/highlighters/main.md
+        '';
+      };
     };
   };
 
@@ -73,6 +90,11 @@ in
             pattern: design:
             "ZSH_HIGHLIGHT_PATTERNS+=('${pattern}' '${design}')"
           ) cfg.patterns)
+        ++ optionals (length(attrNames cfg.styles) > 0)
+          (mapAttrsToList (
+            styles: design:
+            "ZSH_HIGHLIGHT_STYLES[${styles}]='${design}'"
+          ) cfg.styles)
       );
   };
 }
diff --git a/nixos/modules/programs/zsh/zsh.nix b/nixos/modules/programs/zsh/zsh.nix
index b4ca8730958c2..164d8db5859a8 100644
--- a/nixos/modules/programs/zsh/zsh.nix
+++ b/nixos/modules/programs/zsh/zsh.nix
@@ -11,7 +11,8 @@ let
   cfg = config.programs.zsh;
 
   zshAliases = concatStringsSep "\n" (
-    mapAttrsFlatten (k: v: "alias ${k}='${v}'") cfg.shellAliases
+    mapAttrsFlatten (k: v: "alias ${k}=${escapeShellArg v}")
+      (filterAttrs (k: v: !isNull v) cfg.shellAliases)
   );
 
 in
@@ -34,13 +35,12 @@ in
       };
 
       shellAliases = mkOption {
-        default = config.environment.shellAliases;
+        default = {};
         description = ''
-          Set of aliases for zsh shell. Overrides the default value taken from
-           <option>environment.shellAliases</option>.
+          Set of aliases for zsh shell, which overrides <option>environment.shellAliases</option>.
           See <option>environment.shellAliases</option> for an option format description.
         '';
-        type = types.attrs; # types.attrsOf types.stringOrPath;
+        type = with types; attrsOf (nullOr (either str path));
       };
 
       shellInit = mkOption {
@@ -106,6 +106,8 @@ in
 
   config = mkIf cfg.enable {
 
+    programs.zsh.shellAliases = mapAttrs (name: mkDefault) cfge.shellAliases;
+
     environment.etc."zshenv".text =
       ''
         # /etc/zshenv: DO NOT EDIT -- this file has been generated automatically.
diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix
index 4a6bdfe83dd28..24ab963f718a1 100644
--- a/nixos/modules/rename.nix
+++ b/nixos/modules/rename.nix
@@ -28,7 +28,10 @@ with lib;
       (config:
         let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config;
         in if enabled then [ pkgs.gutenprint ] else [ ]))
-    (mkRenamedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ])
+    (mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]
+      (config:
+        let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
+        in if value != "" then [ value ] else []))
     (mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
     (mkRenamedOptionModule [ "services" "elasticsearch" "host" ] [ "services" "elasticsearch" "listenAddress" ])
     (mkRenamedOptionModule [ "services" "graphite" "api" "host" ] [ "services" "graphite" "api" "listenAddress" ])
@@ -39,6 +42,7 @@ with lib;
     (mkRenamedOptionModule [ "services" "kubernetes" "apiserver" "address" ] ["services" "kubernetes" "apiserver" "bindAddress"])
     (mkRemovedOptionModule [ "services" "kubernetes" "apiserver" "publicAddress" ] "")
     (mkRenamedOptionModule [ "services" "kubernetes" "addons" "dashboard" "enableRBAC" ] [ "services" "kubernetes" "addons" "dashboard" "rbac" "enable" ])
+    (mkRemovedOptionModule [ "services" "kubernetes" "kubelet" "cadvisorPort" ] "")
     (mkRenamedOptionModule [ "services" "logstash" "address" ] [ "services" "logstash" "listenAddress" ])
     (mkRenamedOptionModule [ "services" "mpd" "network" "host" ] [ "services" "mpd" "network" "listenAddress" ])
     (mkRenamedOptionModule [ "services" "neo4j" "host" ] [ "services" "neo4j" "defaultListenAddress" ])
@@ -65,6 +69,9 @@ with lib;
     (mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead")
     (mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead")
 
+    # PAM
+    (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
+
     (mkRemovedOptionModule [ "services" "rmilter" "bindInetSockets" ] "Use services.rmilter.bindSocket.* instead")
     (mkRemovedOptionModule [ "services" "rmilter" "bindUnixSockets" ] "Use services.rmilter.bindSocket.* instead")
 
@@ -276,6 +283,11 @@ with lib;
 
     (mkRenamedOptionModule [ "programs" "info" "enable" ] [ "documentation" "info" "enable" ])
     (mkRenamedOptionModule [ "programs" "man"  "enable" ] [ "documentation" "man"  "enable" ])
+    (mkRenamedOptionModule [ "services" "nixosManual" "enable" ] [ "documentation" "nixos" "enable" ])
+
+    # ckb
+    (mkRenamedOptionModule [ "hardware" "ckb" "enable" ] [ "hardware" "ckb-next" "enable" ])
+    (mkRenamedOptionModule [ "hardware" "ckb" "package" ] [ "hardware" "ckb-next" "package" ])
 
   ] ++ (flip map [ "blackboxExporter" "collectdExporter" "fritzboxExporter"
                    "jsonExporter" "minioExporter" "nginxExporter" "nodeExporter"
diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml
index b4cd83f6632c7..ef71fe53d0c7e 100644
--- a/nixos/modules/security/acme.xml
+++ b/nixos/modules/security/acme.xml
@@ -3,23 +3,25 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-security-acme">
-
-<title>SSL/TLS Certificates with ACME</title>
-
-<para>NixOS supports automatic domain validation &amp; certificate
-retrieval and renewal using the ACME protocol. This is currently only
-implemented by and for Let's Encrypt. The alternative ACME client
-<literal>simp_le</literal> is used under the hood.</para>
-
-<section xml:id="module-security-acme-prerequisites"><title>Prerequisites</title>
-
-<para>You need to have a running HTTP server for verification. The server must
-have a webroot defined that can serve
-<filename>.well-known/acme-challenge</filename>. This directory must be
-writeable by the user that will run the ACME client.</para>
-
-<para>For instance, this generic snippet could be used for Nginx:
-
+ <title>SSL/TLS Certificates with ACME</title>
+ <para>
+  NixOS supports automatic domain validation &amp; certificate retrieval and
+  renewal using the ACME protocol. This is currently only implemented by and
+  for Let's Encrypt. The alternative ACME client <literal>simp_le</literal> is
+  used under the hood.
+ </para>
+ <section xml:id="module-security-acme-prerequisites">
+  <title>Prerequisites</title>
+
+  <para>
+   You need to have a running HTTP server for verification. The server must
+   have a webroot defined that can serve
+   <filename>.well-known/acme-challenge</filename>. This directory must be
+   writeable by the user that will run the ACME client.
+  </para>
+
+  <para>
+   For instance, this generic snippet could be used for Nginx:
 <programlisting>
 http {
   server {
@@ -37,43 +39,47 @@ http {
   }
 }
 </programlisting>
-</para>
-
-</section>
-
-<section xml:id="module-security-acme-configuring"><title>Configuring</title>
-
-<para>To enable ACME certificate retrieval &amp; renewal for a certificate for
-<literal>foo.example.com</literal>, add the following in your
-<filename>configuration.nix</filename>:
-
+  </para>
+ </section>
+ <section xml:id="module-security-acme-configuring">
+  <title>Configuring</title>
+
+  <para>
+   To enable ACME certificate retrieval &amp; renewal for a certificate for
+   <literal>foo.example.com</literal>, add the following in your
+   <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-security.acme.certs"/>."foo.example.com" = {
   <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/www/challenges";
   <link linkend="opt-security.acme.certs._name_.email">email</link> = "foo@example.com";
 };
 </programlisting>
-</para>
-
-<para>The private key <filename>key.pem</filename> and certificate
-<filename>fullchain.pem</filename> will be put into
-<filename>/var/lib/acme/foo.example.com</filename>. The target directory can
-be configured with the option <xref linkend="opt-security.acme.directory"/>.
-</para>
-
-<para>Refer to <xref linkend="ch-options" /> for all available configuration
-options for the <link linkend="opt-security.acme.certs">security.acme</link> module.</para>
-
-</section>
-
-<section xml:id="module-security-acme-nginx"><title>Using ACME certificates in Nginx</title>
-<para>NixOS supports fetching ACME certificates for you by setting
-  <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;</literal> in a virtualHost config. We
-first create self-signed placeholder certificates in place of the
-real ACME certs. The placeholder certs are overwritten when the ACME
-certs arrive. For <literal>foo.example.com</literal> the config would
-look like.
-</para>
+  </para>
+
+  <para>
+   The private key <filename>key.pem</filename> and certificate
+   <filename>fullchain.pem</filename> will be put into
+   <filename>/var/lib/acme/foo.example.com</filename>. The target directory can
+   be configured with the option <xref linkend="opt-security.acme.directory"/>.
+  </para>
+
+  <para>
+   Refer to <xref linkend="ch-options" /> for all available configuration
+   options for the <link linkend="opt-security.acme.certs">security.acme</link>
+   module.
+  </para>
+ </section>
+ <section xml:id="module-security-acme-nginx">
+  <title>Using ACME certificates in Nginx</title>
+
+  <para>
+   NixOS supports fetching ACME certificates for you by setting
+   <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link>
+   = true;</literal> in a virtualHost config. We first create self-signed
+   placeholder certificates in place of the real ACME certs. The placeholder
+   certs are overwritten when the ACME certs arrive. For
+   <literal>foo.example.com</literal> the config would look like.
+  </para>
 
 <programlisting>
 services.nginx = {
@@ -89,5 +95,5 @@ services.nginx = {
   };
 }
 </programlisting>
-</section>
+ </section>
 </chapter>
diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix
index dfbf5d859ba9e..498c2f25d1c0d 100644
--- a/nixos/modules/security/apparmor-suid.nix
+++ b/nixos/modules/security/apparmor-suid.nix
@@ -28,7 +28,7 @@ with lib;
         capability setuid,
         network inet raw,
 
-        ${pkgs.glibc.out}/lib/*.so mr,
+        ${pkgs.stdenv.cc.libc.out}/lib/*.so mr,
         ${pkgs.libcap.lib}/lib/libcap.so* mr,
         ${pkgs.attr.out}/lib/libattr.so* mr,
 
diff --git a/nixos/modules/security/dhparams.nix b/nixos/modules/security/dhparams.nix
index e2b84c3e3b380..62a499ea624de 100644
--- a/nixos/modules/security/dhparams.nix
+++ b/nixos/modules/security/dhparams.nix
@@ -170,4 +170,6 @@ in {
       '';
     }) cfg.params;
   };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
 }
diff --git a/nixos/modules/security/google_oslogin.nix b/nixos/modules/security/google_oslogin.nix
new file mode 100644
index 0000000000000..246419b681af0
--- /dev/null
+++ b/nixos/modules/security/google_oslogin.nix
@@ -0,0 +1,68 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.security.googleOsLogin;
+  package = pkgs.google-compute-engine-oslogin;
+
+in
+
+{
+
+  options = {
+
+    security.googleOsLogin.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable Google OS Login
+
+        The OS Login package enables the following components:
+        AuthorizedKeysCommand to query valid SSH keys from the user's OS Login
+        profile during ssh authentication phase.
+        NSS Module to provide user and group information
+        PAM Module for the sshd service, providing authorization and
+        authentication support, allowing the system to use data stored in
+        Google Cloud IAM permissions to control both, the ability to log into
+        an instance, and to perform operations as root (sudo).
+      '';
+    };
+
+  };
+
+  config = mkIf cfg.enable {
+    security.pam.services.sshd = {
+      makeHomeDir = true;
+      googleOsLoginAccountVerification = true;
+      # disabled for now: googleOsLoginAuthentication = true;
+    };
+
+    security.sudo.extraConfig = ''
+      #includedir /run/google-sudoers.d
+    '';
+    systemd.tmpfiles.rules = [
+      "d /run/google-sudoers.d 750 root root -"
+      "d /var/google-users.d 750 root root -"
+    ];
+
+    # enable the nss module, so user lookups etc. work
+    system.nssModules = [ package ];
+
+    # Ugly: sshd refuses to start if a store path is given because /nix/store is group-writable.
+    # So indirect by a symlink.
+    environment.etc."ssh/authorized_keys_command_google_oslogin" = {
+      mode = "0755";
+      text = ''
+        #!/bin/sh
+        exec ${package}/bin/google_authorized_keys "$@"
+      '';
+    };
+    services.openssh.extraConfig = ''
+      AuthorizedKeysCommand /etc/ssh/authorized_keys_command_google_oslogin %u
+      AuthorizedKeysCommandUser nobody
+    '';
+  };
+
+}
diff --git a/nixos/modules/security/hidepid.xml b/nixos/modules/security/hidepid.xml
index d69341eb3cde1..5a17cb1da412c 100644
--- a/nixos/modules/security/hidepid.xml
+++ b/nixos/modules/security/hidepid.xml
@@ -3,31 +3,26 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="sec-hidepid">
-
-  <title>Hiding process information</title>
-
-  <para>
-    Setting
+ <title>Hiding process information</title>
+ <para>
+  Setting
 <programlisting>
 <xref linkend="opt-security.hideProcessInformation"/> = true;
 </programlisting>
-    ensures that access to process information is restricted to the
-    owning user.  This implies, among other things, that command-line
-    arguments remain private.  Unless your deployment relies on unprivileged
-    users being able to inspect the process information of other users, this
-    option should be safe to enable.
-  </para>
-
-  <para>
-    Members of the <literal>proc</literal> group are exempt from process
-    information hiding.
-  </para>
-
-  <para>
-    To allow a service <replaceable>foo</replaceable> to run without process information hiding, set
+  ensures that access to process information is restricted to the owning user.
+  This implies, among other things, that command-line arguments remain private.
+  Unless your deployment relies on unprivileged users being able to inspect the
+  process information of other users, this option should be safe to enable.
+ </para>
+ <para>
+  Members of the <literal>proc</literal> group are exempt from process
+  information hiding.
+ </para>
+ <para>
+  To allow a service <replaceable>foo</replaceable> to run without process
+  information hiding, set
 <programlisting>
 <link linkend="opt-systemd.services._name_.serviceConfig">systemd.services.<replaceable>foo</replaceable>.serviceConfig</link>.SupplementaryGroups = [ "proc" ];
 </programlisting>
-  </para>
-
+ </para>
 </chapter>
diff --git a/nixos/modules/security/lock-kernel-modules.nix b/nixos/modules/security/lock-kernel-modules.nix
index c81521ed9b085..fc9e7939d8148 100644
--- a/nixos/modules/security/lock-kernel-modules.nix
+++ b/nixos/modules/security/lock-kernel-modules.nix
@@ -3,6 +3,10 @@
 with lib;
 
 {
+  meta = {
+    maintainers = [ maintainers.joachifm ];
+  };
+
   options = {
     security.lockKernelModules = mkOption {
       type = types.bool;
diff --git a/nixos/modules/security/misc.nix b/nixos/modules/security/misc.nix
new file mode 100644
index 0000000000000..4506a67487d41
--- /dev/null
+++ b/nixos/modules/security/misc.nix
@@ -0,0 +1,125 @@
+{ config, lib, ... }:
+
+with lib;
+
+{
+  meta = {
+    maintainers = [ maintainers.joachifm ];
+  };
+
+  options = {
+    security.allowUserNamespaces = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to allow creation of user namespaces.  A recurring problem
+        with user namespaces is the presence of code paths where the kernel's
+        permission checking logic fails to account for namespacing, instead
+        permitting a namespaced process to act outside the namespace with the
+        same privileges as it would have inside it.  This is particularly
+        damaging in the common case of running as root within the namespace.
+        When user namespace creation is disallowed, attempting to create
+        a user namespace fails with "no space left on device" (ENOSPC).
+      '';
+    };
+
+    security.protectKernelImage = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to prevent replacing the running kernel image.
+      '';
+    };
+
+    security.allowSimultaneousMultithreading = mkOption {
+      type = types.bool;
+      default = true;
+      description = ''
+        Whether to allow SMT/hyperthreading.  Disabling SMT means that only
+        physical CPU cores will be usable at runtime, potentially at
+        significant performance cost.
+        </para>
+
+        <para>
+        The primary motivation for disabling SMT is to mitigate the risk of
+        leaking data between threads running on the same CPU core (due to
+        e.g., shared caches).  This attack vector is unproven.
+        </para>
+
+        <para>
+        Disabling SMT is a supplement to the L1 data cache flushing mitigation
+        (see <xref linkend="opt-security.virtualization.flushL1DataCache"/>)
+        versus malicious VM guests (SMT could "bring back" previously flushed
+        data).
+        </para>
+        <para>
+      '';
+    };
+
+    security.virtualization.flushL1DataCache = mkOption {
+      type = types.nullOr (types.enum [ "never" "cond" "always" ]);
+      default = null;
+      description = ''
+        Whether the hypervisor should flush the L1 data cache before
+        entering guests.
+        See also <xref linkend="opt-security.allowSimultaneousMultithreading"/>.
+        </para>
+
+        <para>
+          <variablelist>
+          <varlistentry>
+            <term><literal>null</literal></term>
+            <listitem><para>uses the kernel default</para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term><literal>"never"</literal></term>
+            <listitem><para>disables L1 data cache flushing entirely.
+            May be appropriate if all guests are trusted.</para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term><literal>"cond"</literal></term>
+            <listitem><para>flushes L1 data cache only for pre-determined
+            code paths.  May leak information about the host address space
+            layout.</para></listitem>
+          </varlistentry>
+          <varlistentry>
+            <term><literal>"always"</literal></term>
+            <listitem><para>flushes L1 data cache every time the hypervisor
+            enters the guest.  May incur significant performance cost.
+            </para></listitem>
+          </varlistentry>
+          </variablelist>
+      '';
+    };
+  };
+
+  config = mkMerge [
+    (mkIf (!config.security.allowUserNamespaces) {
+      # Setting the number of allowed user namespaces to 0 effectively disables
+      # the feature at runtime.  Note that root may raise the limit again
+      # at any time.
+      boot.kernel.sysctl."user.max_user_namespaces" = 0;
+
+      assertions = [
+        { assertion = config.nix.useSandbox -> config.security.allowUserNamespaces;
+          message = "`nix.useSandbox = true` conflicts with `!security.allowUserNamespaces`.";
+        }
+      ];
+    })
+
+    (mkIf config.security.protectKernelImage {
+      # Disable hibernation (allows replacing the running kernel)
+      boot.kernelParams = [ "nohibernate" ];
+      # Prevent replacing the running kernel image w/o reboot
+      boot.kernel.sysctl."kernel.kexec_load_disabled" = mkDefault true;
+    })
+
+    (mkIf (!config.security.allowSimultaneousMultithreading) {
+      boot.kernelParams = [ "nosmt" ];
+    })
+
+    (mkIf (config.security.virtualization.flushL1DataCache != null) {
+      boot.kernelParams = [ "kvm-intel.vmentry_l1d_flush=${config.security.virtualization.flushL1DataCache}" ];
+    })
+  ];
+}
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index bef10b4fe614c..206b529ed6807 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -37,12 +37,14 @@ let
       };
 
       u2fAuth = mkOption {
-        default = config.security.pam.enableU2F;
+        default = config.security.pam.u2f.enable;
         type = types.bool;
         description = ''
           If set, users listed in
-          <filename>~/.config/Yubico/u2f_keys</filename> are able to log in
-          with the associated U2F key.
+          <filename>$XDG_CONFIG_HOME/Yubico/u2f_keys</filename> (or
+          <filename>$HOME/.config/Yubico/u2f_keys</filename> if XDG variable is
+          not set) are able to log in with the associated U2F key. Path can be
+          changed using <option>security.pam.u2f.authFile</option> option.
         '';
       };
 
@@ -77,6 +79,30 @@ let
         '';
       };
 
+      googleOsLoginAccountVerification = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If set, will use the Google OS Login PAM modules
+          (<literal>pam_oslogin_login</literal>,
+          <literal>pam_oslogin_admin</literal>) to verify possible OS Login
+          users and set sudoers configuration accordingly.
+          This only makes sense to enable for the <literal>sshd</literal> PAM
+          service.
+        '';
+      };
+
+      googleOsLoginAuthentication = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If set, will use the <literal>pam_oslogin_login</literal>'s user
+          authentication methods to authenticate users using 2FA.
+          This only makes sense to enable for the <literal>sshd</literal> PAM
+          service.
+        '';
+      };
+
       fprintAuth = mkOption {
         default = config.services.fprintd.enable;
         type = types.bool;
@@ -269,7 +295,7 @@ let
       text = mkDefault
         (''
           # Account management.
-          account ${if cfg.sssdStrictAccess then "required" else "sufficient"} pam_unix.so
+          account required pam_unix.so
           ${optionalString use_ldap
               "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"}
           ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false)
@@ -278,8 +304,14 @@ let
               "account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so"}
           ${optionalString config.krb5.enable
               "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"}
+          ${optionalString cfg.googleOsLoginAccountVerification ''
+            account [success=ok ignore=ignore default=die] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so
+            account [success=ok default=ignore] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_admin.so
+          ''}
 
           # Authentication management.
+          ${optionalString cfg.googleOsLoginAuthentication
+              "auth [success=done perm_denied=bad default=ignore] ${pkgs.google-compute-engine-oslogin}/lib/pam_oslogin_login.so"}
           ${optionalString cfg.rootOK
               "auth sufficient pam_rootok.so"}
           ${optionalString cfg.requireWheel
@@ -290,8 +322,8 @@ let
               "auth sufficient ${pkgs.pam_ssh_agent_auth}/libexec/pam_ssh_agent_auth.so file=~/.ssh/authorized_keys:~/.ssh/authorized_keys2:/etc/ssh/authorized_keys.d/%u"}
           ${optionalString cfg.fprintAuth
               "auth sufficient ${pkgs.fprintd}/lib/security/pam_fprintd.so"}
-          ${optionalString cfg.u2fAuth
-              "auth sufficient ${pkgs.pam_u2f}/lib/security/pam_u2f.so"}
+          ${let u2f = config.security.pam.u2f; in optionalString cfg.u2fAuth
+              "auth ${u2f.control} ${pkgs.pam_u2f}/lib/security/pam_u2f.so ${optionalString u2f.debug "debug"} ${optionalString (u2f.authFile != null) "authfile=${u2f.authFile}"} ${optionalString u2f.interactive "interactive"} ${optionalString u2f.cue "cue"}"}
           ${optionalString cfg.usbAuth
               "auth sufficient ${pkgs.pam_usb}/lib/security/pam_usb.so"}
           ${let oath = config.security.pam.oath; in optionalString cfg.oathAuth
@@ -338,7 +370,7 @@ let
           auth required pam_deny.so
 
           # Password management.
-          password requisite pam_unix.so nullok sha512
+          password sufficient pam_unix.so nullok sha512
           ${optionalString config.security.pam.enableEcryptfs
               "password optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so"}
           ${optionalString cfg.pamMount
@@ -497,11 +529,96 @@ in
       '';
     };
 
-    security.pam.enableU2F = mkOption {
-      default = false;
-      description = ''
-        Enable the U2F PAM module.
-      '';
+    security.pam.u2f = {
+      enable = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Enables U2F PAM (<literal>pam-u2f</literal>) module.
+
+          If set, users listed in
+          <filename>$XDG_CONFIG_HOME/Yubico/u2f_keys</filename> (or
+          <filename>$HOME/.config/Yubico/u2f_keys</filename> if XDG variable is
+          not set) are able to log in with the associated U2F key. The path can
+          be changed using <option>security.pam.u2f.authFile</option> option.
+
+          File format is:
+          <literal>username:first_keyHandle,first_public_key: second_keyHandle,second_public_key</literal>
+          This file can be generated using <command>pamu2fcfg</command> command.
+
+          More information can be found <link
+          xlink:href="https://developers.yubico.com/pam-u2f/">here</link>.
+        '';
+      };
+
+      authFile = mkOption {
+        default = null;
+        type = with types; nullOr path;
+        description = ''
+          By default <literal>pam-u2f</literal> module reads the keys from
+          <filename>$XDG_CONFIG_HOME/Yubico/u2f_keys</filename> (or
+          <filename>$HOME/.config/Yubico/u2f_keys</filename> if XDG variable is
+          not set).
+
+          If you want to change auth file locations or centralize database (for
+          example use <filename>/etc/u2f-mappings</filename>) you can set this
+          option.
+
+          File format is:
+          <literal>username:first_keyHandle,first_public_key: second_keyHandle,second_public_key</literal>
+          This file can be generated using <command>pamu2fcfg</command> command.
+
+          More information can be found <link
+          xlink:href="https://developers.yubico.com/pam-u2f/">here</link>.
+        '';
+      };
+
+      control = mkOption {
+        default = "sufficient";
+        type = types.enum [ "required" "requisite" "sufficient" "optional" ];
+        description = ''
+          This option sets pam "control".
+          If you want to have multi factor authentication, use "required".
+          If you want to use U2F device instead of regular password, use "sufficient".
+
+          Read
+          <citerefentry>
+            <refentrytitle>pam.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for better understanding of this option.
+        '';
+      };
+
+      debug = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Debug output to stderr.
+        '';
+      };
+
+      interactive = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          Set to prompt a message and wait before testing the presence of a U2F device.
+          Recommended if your device doesn’t have a tactile trigger.
+        '';
+      };
+
+      cue = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          By default <literal>pam-u2f</literal> module does not inform user
+          that he needs to use the u2f device, it just waits without a prompt.
+
+          If you set this option to <literal>true</literal>,
+          <literal>cue</literal> option is added to <literal>pam-u2f</literal>
+          module and reminder message will be displayed.
+        '';
+      };
     };
 
     security.pam.enableEcryptfs = mkOption {
@@ -533,7 +650,7 @@ in
       ++ optionals config.krb5.enable [pam_krb5 pam_ccreds]
       ++ optionals config.security.pam.enableOTPW [ pkgs.otpw ]
       ++ optionals config.security.pam.oath.enable [ pkgs.oathToolkit ]
-      ++ optionals config.security.pam.enableU2F [ pkgs.pam_u2f ];
+      ++ optionals config.security.pam.u2f.enable [ pkgs.pam_u2f ];
 
     boot.supportedFilesystems = optionals config.security.pam.enableEcryptfs [ "ecryptfs" ];
 
@@ -548,6 +665,13 @@ in
     environment.etc =
       mapAttrsToList (n: v: makePAMService v) config.security.pam.services;
 
+    systemd.tmpfiles.rules = optionals
+      (any (s: s.updateWtmp) (attrValues config.security.pam.services))
+      [
+        "f /var/log/wtmp"
+        "f /var/log/lastlog"
+      ];
+
     security.pam.services =
       { other.text =
           ''
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index 04685f2c9ea15..7f1de81d5b70d 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -88,11 +88,11 @@ in
       "polkit-agent-helper-1".source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1";
     };
 
-    system.activationScripts.polkit =
-      ''
-        # Probably no more needed, clean up
-        rm -rf /var/lib/{polkit-1,PolicyKit}
-      '';
+    systemd.tmpfiles.rules = [
+      # Probably no more needed, clean up
+      "R /var/lib/polkit-1"
+      "R /var/lib/PolicyKit"
+    ];
 
     users.users.polkituser = {
       description = "PolKit daemon";
diff --git a/nixos/modules/security/rngd.nix b/nixos/modules/security/rngd.nix
index 3a1ffc55e5fe9..a54ef2e6fcad1 100644
--- a/nixos/modules/security/rngd.nix
+++ b/nixos/modules/security/rngd.nix
@@ -20,7 +20,6 @@ with lib;
       KERNEL=="random", TAG+="systemd"
       SUBSYSTEM=="cpu", ENV{MODALIAS}=="cpu:type:x86,*feature:*009E*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"
       KERNEL=="hw_random", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"
-      ${if config.services.tcsd.enable then "" else ''KERNEL=="tpm0", TAG+="systemd", ENV{SYSTEMD_WANTS}+="rngd.service"''}
     '';
 
     systemd.services.rngd = {
@@ -30,8 +29,7 @@ with lib;
 
       description = "Hardware RNG Entropy Gatherer Daemon";
 
-      serviceConfig.ExecStart = "${pkgs.rng_tools}/sbin/rngd -f -v" +
-        (if config.services.tcsd.enable then " --no-tpm=1" else "");
+      serviceConfig.ExecStart = "${pkgs.rng-tools}/sbin/rngd -f";
     };
   };
 }
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 77e4b2a616d8e..dcb9c8d4ed5f3 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -180,35 +180,6 @@ in
           # programs to be wrapped.
           WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin
 
-          # Remove the old /var/setuid-wrappers path from the system...
-          #
-          # TODO: this is only necessary for upgrades 16.09 => 17.x;
-          # this conditional removal block needs to be removed after
-          # the release.
-          if [ -d /var/setuid-wrappers ]; then
-            rm -rf /var/setuid-wrappers
-            ln -s /run/wrappers/bin /var/setuid-wrappers
-          fi
-
-          # Remove the old /run/setuid-wrappers-dir path from the
-          # system as well...
-          #
-          # TODO: this is only necessary for upgrades 16.09 => 17.x;
-          # this conditional removal block needs to be removed after
-          # the release.
-          if [ -d /run/setuid-wrapper-dirs ]; then
-            rm -rf /run/setuid-wrapper-dirs
-            ln -s /run/wrappers/bin /run/setuid-wrapper-dirs
-          fi
-
-          # TODO: this is only necessary for upgrades 16.09 => 17.x;
-          # this conditional removal block needs to be removed after
-          # the release.
-          if readlink -f /run/booted-system | grep nixos-17 > /dev/null; then
-            rm -rf /run/setuid-wrapper-dirs
-            rm -rf /var/setuid-wrappers
-          fi
-
           # We want to place the tmpdirs for the wrappers to the parent dir.
           wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
           chmod a+rx $wrapperDir
diff --git a/nixos/modules/services/admin/salt/master.nix b/nixos/modules/services/admin/salt/master.nix
index 165580b978374..c6b1b0cc0bd8e 100644
--- a/nixos/modules/services/admin/salt/master.nix
+++ b/nixos/modules/services/admin/salt/master.nix
@@ -53,6 +53,9 @@ in
         Type = "notify";
         NotifyAccess = "all";
       };
+      restartTriggers = [
+        config.environment.etc."salt/master".source
+      ];
     };
   };
 
diff --git a/nixos/modules/services/admin/salt/minion.nix b/nixos/modules/services/admin/salt/minion.nix
index 9ecefb32cfa8f..c8fa9461a2094 100644
--- a/nixos/modules/services/admin/salt/minion.nix
+++ b/nixos/modules/services/admin/salt/minion.nix
@@ -15,7 +15,6 @@ let
     # Default is in /etc/salt/pki/minion
     pki_dir = "/var/lib/salt/pki/minion";
   } cfg.configuration;
-  configDir = pkgs.writeTextDir "minion" (builtins.toJSON fullConfig);
 
 in
 
@@ -28,15 +27,24 @@ in
         default = {};
         description = ''
           Salt minion configuration as Nix attribute set.
-          See <link xlink:href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html"/>                                                                                                 
-          for details.          
+          See <link xlink:href="https://docs.saltstack.com/en/latest/ref/configuration/minion.html"/>
+          for details.
         '';
       };
     };
   };
 
   config = mkIf cfg.enable {
-    environment.systemPackages = with pkgs; [ salt ];
+    environment = {
+      # Set this up in /etc/salt/minion so `salt-call`, etc. work.
+      # The alternatives are
+      # - passing --config-dir to all salt commands, not just the minion unit,
+      # - setting aglobal environment variable.
+      etc."salt/minion".source = pkgs.writeText "minion" (
+        builtins.toJSON fullConfig
+      );
+      systemPackages = with pkgs; [ salt ];
+    };
     systemd.services.salt-minion = {
       description = "Salt Minion";
       wantedBy = [ "multi-user.target" ];
@@ -45,11 +53,14 @@ in
         utillinux
       ];
       serviceConfig = {
-        ExecStart = "${pkgs.salt}/bin/salt-minion --config-dir=${configDir}";
+        ExecStart = "${pkgs.salt}/bin/salt-minion";
         LimitNOFILE = 8192;
         Type = "notify";
         NotifyAccess = "all";
       };
+      restartTriggers = [
+        config.environment.etc."salt/minion".source
+      ];
     };
   };
 }
diff --git a/nixos/modules/services/amqp/rabbitmq.nix b/nixos/modules/services/amqp/rabbitmq.nix
index bb6fc0a104dff..7373be2a9b0bb 100644
--- a/nixos/modules/services/amqp/rabbitmq.nix
+++ b/nixos/modules/services/amqp/rabbitmq.nix
@@ -4,14 +4,18 @@ with lib;
 
 let
   cfg = config.services.rabbitmq;
-  config_file = pkgs.writeText "rabbitmq.config" cfg.config;
-  config_file_wo_suffix = builtins.substring 0 ((builtins.stringLength config_file) - 7) config_file;
+
+  inherit (builtins) concatStringsSep;
+
+  config_file_content = lib.generators.toKeyValue {} cfg.configItems;
+  config_file = pkgs.writeText "rabbitmq.conf" config_file_content;
+
+  advanced_config_file = pkgs.writeText "advanced.config" cfg.config;
 
 in {
   ###### interface
   options = {
     services.rabbitmq = {
-
       enable = mkOption {
         default = false;
         description = ''
@@ -20,6 +24,15 @@ in {
         '';
       };
 
+      package = mkOption {
+        default = pkgs.rabbitmq-server;
+        type = types.package;
+        defaultText = "pkgs.rabbitmq-server";
+        description = ''
+          Which rabbitmq package to use.
+        '';
+      };
+
       listenAddress = mkOption {
         default = "127.0.0.1";
         example = "";
@@ -30,6 +43,10 @@ in {
           <literal>guest</literal> with password
           <literal>guest</literal> by default, so you should delete
           this user if you intend to allow external access.
+
+          Together with 'port' setting it's mostly an alias for
+          configItems."listeners.tcp.1" and it's left for backwards
+          compatibility with previous version of this module.
         '';
         type = types.str;
       };
@@ -60,12 +77,47 @@ in {
         '';
       };
 
+      configItems = mkOption {
+        default = {};
+        type = types.attrsOf types.str;
+        example = ''
+          {
+            "auth_backends.1.authn" = "rabbit_auth_backend_ldap";
+            "auth_backends.1.authz" = "rabbit_auth_backend_internal";
+          }
+        '';
+        description = ''
+          Configuration options in RabbitMQ's new config file format,
+          which is a simple key-value format that can not express nested
+          data structures. This is known as the <literal>rabbitmq.conf</literal> file,
+          although outside NixOS that filename may have Erlang syntax, particularly
+          prior to RabbitMQ 3.7.0.
+
+          If you do need to express nested data structures, you can use
+          <literal>config</literal> option. Configuration from <literal>config</literal>
+          will be merged into these options by RabbitMQ at runtime to
+          form the final configuration.
+
+          See http://www.rabbitmq.com/configure.html#config-items
+          For the distinct formats, see http://www.rabbitmq.com/configure.html#config-file-formats
+        '';
+      };
+
       config = mkOption {
         default = "";
         type = types.str;
         description = ''
-          Verbatim configuration file contents.
-          See http://www.rabbitmq.com/configure.html
+          Verbatim advanced configuration file contents using the Erlang syntax.
+          This is also known as the <literal>advanced.config</literal> file or the old config format.
+
+          <literal>configItems</literal> is preferred whenever possible. However, nested
+          data structures can only be expressed properly using the <literal>config</literal> option.
+
+          The contents of this option will be merged into the <literal>configItems</literal>
+          by RabbitMQ at runtime to form the final configuration.
+
+          See the second table on http://www.rabbitmq.com/configure.html#config-items
+          For the distinct formats, see http://www.rabbitmq.com/configure.html#config-file-formats
         '';
       };
 
@@ -74,6 +126,12 @@ in {
         type = types.listOf types.str;
         description = "The names of plugins to enable";
       };
+
+      pluginDirs = mkOption {
+        default = [];
+        type = types.listOf types.path;
+        description = "The list of directories containing external plugins";
+      };
     };
   };
 
@@ -81,7 +139,10 @@ in {
   ###### implementation
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ pkgs.rabbitmq_server ];
+    # This is needed so we will have 'rabbitmqctl' in our PATH
+    environment.systemPackages = [ cfg.package ];
+
+    services.epmd.enable = true;
 
     users.users.rabbitmq = {
       description = "RabbitMQ server user";
@@ -93,44 +154,54 @@ in {
 
     users.groups.rabbitmq.gid = config.ids.gids.rabbitmq;
 
+    services.rabbitmq.configItems = {
+      "listeners.tcp.1" = mkDefault "${cfg.listenAddress}:${toString cfg.port}";
+    };
+
     systemd.services.rabbitmq = {
       description = "RabbitMQ Server";
 
       wantedBy = [ "multi-user.target" ];
-      after = [ "network.target" ];
+      after = [ "network.target" "epmd.socket" ];
+      wants = [ "network.target" "epmd.socket" ];
 
-      path = [ pkgs.rabbitmq_server pkgs.procps ];
+      path = [ cfg.package pkgs.procps ];
 
       environment = {
         RABBITMQ_MNESIA_BASE = "${cfg.dataDir}/mnesia";
-        RABBITMQ_NODE_IP_ADDRESS = cfg.listenAddress;
-        RABBITMQ_NODE_PORT = toString cfg.port;
         RABBITMQ_LOGS = "-";
-        RABBITMQ_SASL_LOGS = "-";
-        RABBITMQ_PID_FILE = "${cfg.dataDir}/pid";
         SYS_PREFIX = "";
+        RABBITMQ_CONFIG_FILE = config_file;
+        RABBITMQ_PLUGINS_DIR = concatStringsSep ":" cfg.pluginDirs;
         RABBITMQ_ENABLED_PLUGINS_FILE = pkgs.writeText "enabled_plugins" ''
           [ ${concatStringsSep "," cfg.plugins} ].
         '';
-      } //  optionalAttrs (cfg.config != "") { RABBITMQ_CONFIG_FILE = config_file_wo_suffix; };
+      } //  optionalAttrs (cfg.config != "") { RABBITMQ_ADVANCED_CONFIG_FILE = advanced_config_file; };
 
       serviceConfig = {
-        ExecStart = "${pkgs.rabbitmq_server}/sbin/rabbitmq-server";
-        ExecStop = "${pkgs.rabbitmq_server}/sbin/rabbitmqctl stop";
+        PermissionsStartOnly = true; # preStart must be run as root
+        ExecStart = "${cfg.package}/sbin/rabbitmq-server";
+        ExecStop = "${cfg.package}/sbin/rabbitmqctl shutdown";
         User = "rabbitmq";
         Group = "rabbitmq";
         WorkingDirectory = cfg.dataDir;
+        Type = "notify";
+        NotifyAccess = "all";
+        UMask = "0027";
+        LimitNOFILE = "100000";
+        Restart = "on-failure";
+        RestartSec = "10";
+        TimeoutStartSec = "3600";
       };
 
-      postStart = ''
-        rabbitmqctl wait ${cfg.dataDir}/pid
-      '';
-
       preStart = ''
         ${optionalString (cfg.cookie != "") ''
             echo -n ${cfg.cookie} > ${cfg.dataDir}/.erlang.cookie
+            chown rabbitmq:rabbitmq ${cfg.dataDir}/.erlang.cookie
             chmod 600 ${cfg.dataDir}/.erlang.cookie
         ''}
+        mkdir -p /var/log/rabbitmq
+        chown rabbitmq:rabbitmq /var/log/rabbitmq
       '';
     };
 
diff --git a/nixos/modules/services/backup/bacula.nix b/nixos/modules/services/backup/bacula.nix
index a0565ca26204b..24cad61282606 100644
--- a/nixos/modules/services/backup/bacula.nix
+++ b/nixos/modules/services/backup/bacula.nix
@@ -346,8 +346,12 @@ in {
       description = "Bacula File Daemon";
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.bacula ];
-      serviceConfig.ExecStart = "${pkgs.bacula}/sbin/bacula-fd -f -u root -g bacula -c ${fd_conf}";
-      serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      serviceConfig = {
+        ExecStart = "${pkgs.bacula}/sbin/bacula-fd -f -u root -g bacula -c ${fd_conf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LogsDirectory = "bacula";
+        StateDirectory = "bacula";
+      };
     };
 
     systemd.services.bacula-sd = mkIf sd_cfg.enable {
@@ -355,8 +359,12 @@ in {
       description = "Bacula Storage Daemon";
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.bacula ];
-      serviceConfig.ExecStart = "${pkgs.bacula}/sbin/bacula-sd -f -u bacula -g bacula -c ${sd_conf}";
-      serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      serviceConfig = {
+        ExecStart = "${pkgs.bacula}/sbin/bacula-sd -f -u bacula -g bacula -c ${sd_conf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LogsDirectory = "bacula";
+        StateDirectory = "bacula";
+      };
     };
 
     services.postgresql.enable = dir_cfg.enable == true;
@@ -366,8 +374,12 @@ in {
       description = "Bacula Director Daemon";
       wantedBy = [ "multi-user.target" ];
       path = [ pkgs.bacula ];
-      serviceConfig.ExecStart = "${pkgs.bacula}/sbin/bacula-dir -f -u bacula -g bacula -c ${dir_conf}";
-      serviceConfig.ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      serviceConfig = {
+        ExecStart = "${pkgs.bacula}/sbin/bacula-dir -f -u bacula -g bacula -c ${dir_conf}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        LogsDirectory = "bacula";
+        StateDirectory = "bacula";
+      };
       preStart = ''
         if ! test -e "${libDir}/db-created"; then
             ${pkgs.postgresql}/bin/createuser --no-superuser --no-createdb --no-createrole bacula
diff --git a/nixos/modules/services/backup/borgbackup.nix b/nixos/modules/services/backup/borgbackup.nix
index 415a70ea5ad41..2ad116a7872ad 100644
--- a/nixos/modules/services/backup/borgbackup.nix
+++ b/nixos/modules/services/backup/borgbackup.nix
@@ -191,10 +191,9 @@ in {
         options = {
 
           paths = mkOption {
-            type = with types; either path (nonEmptyListOf path);
+            type = with types; coercedTo str lib.singleton (listOf str);
             description = "Path(s) to back up.";
             example = "/home/user";
-            apply = x: if isList x then x else [ x ];
           };
 
           repo = mkOption {
diff --git a/nixos/modules/services/backup/postgresql-backup.nix b/nixos/modules/services/backup/postgresql-backup.nix
index 2ec78ce6f2cfa..11efa47ec5b2e 100644
--- a/nixos/modules/services/backup/postgresql-backup.nix
+++ b/nixos/modules/services/backup/postgresql-backup.nix
@@ -6,11 +6,11 @@ let
 
   cfg = config.services.postgresqlBackup;
 
-  postgresqlBackupService = db :
+  postgresqlBackupService = db: dumpCmd:
     {
       enable = true;
 
-      description = "Backup of database ${db}";
+      description = "Backup of ${db} database(s)";
 
       requires = [ "postgresql.service" ];
 
@@ -20,11 +20,13 @@ let
       '';
 
       script = ''
+        umask 0077 # ensure backup is only readable by postgres user
+
         if [ -e ${cfg.location}/${db}.sql.gz ]; then
           ${pkgs.coreutils}/bin/mv ${cfg.location}/${db}.sql.gz ${cfg.location}/${db}.prev.sql.gz
         fi
 
-        ${config.services.postgresql.package}/bin/pg_dump ${cfg.pgdumpOptions} ${db} | \
+        ${dumpCmd} | \
           ${pkgs.gzip}/bin/gzip -c > ${cfg.location}/${db}.sql.gz
       '';
 
@@ -40,9 +42,7 @@ let
 in {
 
   options = {
-
     services.postgresqlBackup = {
-
       enable = mkOption {
         default = false;
         description = ''
@@ -59,6 +59,19 @@ in {
         '';
       };
 
+      backupAll = mkOption {
+        default = cfg.databases == [];
+        defaultText = "services.postgresqlBackup.databases == []";
+        type = lib.types.bool;
+        description = ''
+          Backup all databases using pg_dumpall.
+          This option is mutual exclusive to
+          <literal>services.postgresqlBackup.databases</literal>.
+          The resulting backup dump will have the name all.sql.gz.
+          This option is the default if no databases are specified.
+        '';
+      };
+
       databases = mkOption {
         default = [];
         description = ''
@@ -77,18 +90,36 @@ in {
         type = types.string;
         default = "-Cbo";
         description = ''
-          Command line options for pg_dump.
+          Command line options for pg_dump. This options is not used
+          if <literal>config.services.postgresqlBackup.backupAll</literal> is enabled.
+          Note that config.services.postgresqlBackup.backupAll is also active,
+          when no databases where specified.
         '';
       };
     };
 
   };
 
-  config = mkIf config.services.postgresqlBackup.enable {
-
-    systemd.services = listToAttrs (map (db : {
+  config = mkMerge [
+    {
+      assertions = [{
+        assertion = cfg.backupAll -> cfg.databases == [];
+        message = "config.services.postgresqlBackup.backupAll cannot be used together with config.services.postgresqlBackup.databases";
+      }];
+    }
+    (mkIf (cfg.enable && cfg.backupAll) {
+      systemd.services.postgresqlBackup =
+        postgresqlBackupService "all" "${config.services.postgresql.package}/bin/pg_dumpall";
+    })
+    (mkIf (cfg.enable && !cfg.backupAll) {
+      systemd.services = listToAttrs (map (db:
+        let
+          cmd = "${config.services.postgresql.package}/bin/pg_dump ${cfg.pgdumpOptions} ${db}";
+        in {
           name = "postgresqlBackup-${db}";
-          value = postgresqlBackupService db; } ) cfg.databases);
-  };
+          value = postgresqlBackupService db cmd;
+        }) cfg.databases);
+    })
+  ];
 
 }
diff --git a/nixos/modules/services/backup/restic.nix b/nixos/modules/services/backup/restic.nix
index 6ece5a9b5ad63..7e8e91e4b9c38 100644
--- a/nixos/modules/services/backup/restic.nix
+++ b/nixos/modules/services/backup/restic.nix
@@ -1,6 +1,11 @@
 { config, lib, pkgs, ... }:
 
 with lib;
+
+let
+  # Type for a valid systemd unit option. Needed for correctly passing "timerConfig" to "systemd.timers"
+  unitOption = (import ../../system/boot/systemd-unit-options.nix { inherit config lib; }).unitOption;
+in
 {
   options.services.restic.backups = mkOption {
     description = ''
@@ -47,7 +52,7 @@ with lib;
         };
 
         timerConfig = mkOption {
-          type = types.attrsOf types.str;
+          type = types.attrsOf unitOption;
           default = {
             OnCalendar = "daily";
           };
diff --git a/nixos/modules/services/cluster/kubernetes/default.nix b/nixos/modules/services/cluster/kubernetes/default.nix
index aeaa6bd66c99f..6f3c45b29bf24 100644
--- a/nixos/modules/services/cluster/kubernetes/default.nix
+++ b/nixos/modules/services/cluster/kubernetes/default.nix
@@ -622,13 +622,6 @@ in {
         type = types.bool;
       };
 
-      # TODO: remove this deprecated flag
-      cadvisorPort = mkOption {
-        description = "Kubernetes kubelet local cadvisor port.";
-        default = 4194;
-        type = types.int;
-      };
-
       clusterDns = mkOption {
         description = "Use alternative DNS.";
         default = "10.1.0.1";
@@ -791,7 +784,7 @@ in {
     clusterCidr = mkOption {
       description = "Kubernetes controller manager and proxy CIDR Range for Pods in cluster.";
       default = "10.1.0.0/16";
-      type = types.str;
+      type = types.nullOr types.str;
     };
 
     flannel.enable = mkOption {
@@ -862,7 +855,6 @@ in {
             --hostname-override=${cfg.kubelet.hostname} \
             --allow-privileged=${boolToString cfg.kubelet.allowPrivileged} \
             --root-dir=${cfg.dataDir} \
-            --cadvisor_port=${toString cfg.kubelet.cadvisorPort} \
             ${optionalString (cfg.kubelet.clusterDns != "")
               "--cluster-dns=${cfg.kubelet.clusterDns}"} \
             ${optionalString (cfg.kubelet.clusterDomain != "")
@@ -1026,9 +1018,9 @@ in {
             ${if (cfg.controllerManager.rootCaFile!=null)
               then "--root-ca-file=${cfg.controllerManager.rootCaFile}"
               else "--root-ca-file=/var/run/kubernetes/apiserver.crt"} \
-            ${optionalString (cfg.clusterCidr!=null)
-              "--cluster-cidr=${cfg.clusterCidr}"} \
-            --allocate-node-cidrs=true \
+            ${if (cfg.clusterCidr!=null)
+              then "--cluster-cidr=${cfg.clusterCidr} --allocate-node-cidrs=true"
+              else "--allocate-node-cidrs=false"} \
             ${optionalString (cfg.controllerManager.featureGates != [])
               "--feature-gates=${concatMapStringsSep "," (feature: "${feature}=true") cfg.controllerManager.featureGates}"} \
             ${optionalString cfg.verbose "--v=6"} \
diff --git a/nixos/modules/services/cluster/kubernetes/dns.nix b/nixos/modules/services/cluster/kubernetes/dns.nix
index 43bbb50a48d4d..5a3e281ea6948 100644
--- a/nixos/modules/services/cluster/kubernetes/dns.nix
+++ b/nixos/modules/services/cluster/kubernetes/dns.nix
@@ -3,8 +3,13 @@
 with lib;
 
 let
-  version = "1.14.10";
+  version = "1.2.5";
   cfg = config.services.kubernetes.addons.dns;
+  ports = {
+    dns = 10053;
+    health = 10054;
+    metrics = 10055;
+  };
 in {
   options.services.kubernetes.addons.dns = {
     enable = mkEnableOption "kubernetes dns addon";
@@ -27,49 +32,130 @@ in {
       type = types.str;
     };
 
-    kube-dns = mkOption {
-      description = "Docker image to seed for the kube-dns main container.";
-      type = types.attrs;
-      default = {
-        imageName = "k8s.gcr.io/k8s-dns-kube-dns-amd64";
-        imageDigest = "sha256:b99fc3eee2a9f052f7eb4cc00f15eb12fc405fa41019baa2d6b79847ae7284a8";
-        finalImageTag = version;
-        sha256 = "0x583znk9smqn0fix7ld8sm5jgaxhqhx3fq97b1wkqm7iwhvl3pj";
-      };
-    };
-
-    dnsmasq-nanny = mkOption {
-      description = "Docker image to seed for the kube-dns dnsmasq container.";
-      type = types.attrs;
-      default = {
-        imageName = "k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64";
-        imageDigest = "sha256:bbb2a290a568125b3b996028958eb773f33b5b87a6b37bf38a28f8b62dddb3c8";
-        finalImageTag = version;
-        sha256 = "1fihml7s2mfwgac51cbqpylkwbivc8nyhgi4vb820s83zvl8a6y1";
-      };
+    replicas = mkOption {
+      description = "Number of DNS pod replicas to deploy in the cluster.";
+      default = 2;
+      type = types.int;
     };
 
-    sidecar = mkOption {
-      description = "Docker image to seed for the kube-dns sidecar container.";
+    coredns = mkOption {
+      description = "Docker image to seed for the CoreDNS container.";
       type = types.attrs;
       default = {
-        imageName = "k8s.gcr.io/k8s-dns-sidecar-amd64";
-        imageDigest = "sha256:4f1ab957f87b94a5ec1edc26fae50da2175461f00afecf68940c4aa079bd08a4";
+        imageName = "coredns/coredns";
+        imageDigest = "sha256:33c8da20b887ae12433ec5c40bfddefbbfa233d5ce11fb067122e68af30291d6";
         finalImageTag = version;
-        sha256 = "08l1bv5jgrhvjzpqpbinrkgvv52snc4fzyd8ya9v18ns2klyz7m0";
+        sha256 = "13q19rgwapv27xcs664dw502254yw4zw63insf6g2danidv2mg6i";
       };
     };
   };
 
   config = mkIf cfg.enable {
-    services.kubernetes.kubelet.seedDockerImages = with pkgs.dockerTools; [
-      (pullImage cfg.kube-dns)
-      (pullImage cfg.dnsmasq-nanny)
-      (pullImage cfg.sidecar)
-    ];
+    services.kubernetes.kubelet.seedDockerImages =
+      singleton (pkgs.dockerTools.pullImage cfg.coredns);
 
     services.kubernetes.addonManager.addons = {
-      kubedns-deployment = {
+      coredns-sa = {
+        apiVersion = "v1";
+        kind = "ServiceAccount";
+        metadata = {
+          labels = {
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+            "k8s-app" = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+          };
+          name = "coredns";
+          namespace = "kube-system";
+        };
+      };
+
+      coredns-cr = {
+        apiVersion = "rbac.authorization.k8s.io/v1beta1";
+        kind = "ClusterRole";
+        metadata = {
+          labels = {
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+            "k8s-app" = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+            "kubernetes.io/bootstrapping" = "rbac-defaults";
+          };
+          name = "system:coredns";
+        };
+        rules = [
+          {
+            apiGroups = [ "" ];
+            resources = [ "endpoints" "services" "pods" "namespaces" ];
+            verbs = [ "list" "watch" ];
+          }
+          {
+            apiGroups = [ "" ];
+            resources = [ "nodes" ];
+            verbs = [ "get" ];
+          }
+        ];
+      };
+
+      coredns-crb = {
+        apiVersion = "rbac.authorization.k8s.io/v1beta1";
+        kind = "ClusterRoleBinding";
+        metadata = {
+          annotations = {
+            "rbac.authorization.kubernetes.io/autoupdate" = "true";
+          };
+          labels = {
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+            "k8s-app" = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+            "kubernetes.io/bootstrapping" = "rbac-defaults";
+          };
+          name = "system:coredns";
+        };
+        roleRef = {
+          apiGroup = "rbac.authorization.k8s.io";
+          kind = "ClusterRole";
+          name = "system:coredns";
+        };
+        subjects = [
+          {
+            kind = "ServiceAccount";
+            name = "coredns";
+            namespace = "kube-system";
+          }
+        ];
+      };
+
+      coredns-cm = {
+        apiVersion = "v1";
+        kind = "ConfigMap";
+        metadata = {
+          labels = {
+            "addonmanager.kubernetes.io/mode" = "Reconcile";
+            "k8s-app" = "kube-dns";
+            "kubernetes.io/cluster-service" = "true";
+          };
+          name = "coredns";
+          namespace = "kube-system";
+        };
+        data = {
+          Corefile = ".:${toString ports.dns} {
+            errors
+            health :${toString ports.health}
+            kubernetes ${cfg.clusterDomain} in-addr.arpa ip6.arpa {
+              pods insecure
+              upstream
+              fallthrough in-addr.arpa ip6.arpa
+            }
+            prometheus :${toString ports.metrics}
+            proxy . /etc/resolv.conf
+            cache 30
+            loop
+            reload
+            loadbalance
+          }";
+        };
+      };
+
+      coredns-deploy = {
         apiVersion = "extensions/v1beta1";
         kind = "Deployment";
         metadata = {
@@ -77,183 +163,97 @@ in {
             "addonmanager.kubernetes.io/mode" = "Reconcile";
             "k8s-app" = "kube-dns";
             "kubernetes.io/cluster-service" = "true";
+            "kubernetes.io/name" = "CoreDNS";
           };
-          name = "kube-dns";
+          name = "coredns";
           namespace = "kube-system";
         };
         spec = {
-          selector.matchLabels."k8s-app" = "kube-dns";
+          replicas = cfg.replicas;
+          selector = {
+            matchLabels = { k8s-app = "kube-dns"; };
+          };
           strategy = {
-            rollingUpdate = {
-              maxSurge = "10%";
-              maxUnavailable = 0;
-            };
+            rollingUpdate = { maxUnavailable = 1; };
+            type = "RollingUpdate";
           };
           template = {
             metadata = {
-              annotations."scheduler.alpha.kubernetes.io/critical-pod" = "";
-              labels.k8s-app = "kube-dns";
+              labels = {
+                k8s-app = "kube-dns";
+              };
             };
             spec = {
-              priorityClassName = "system-cluster-critical";
               containers = [
                 {
-                  name = "kubedns";
-                  image = with cfg.kube-dns; "${imageName}:${finalImageTag}";
-                  resources = {
-                    limits.memory = "170Mi";
-                    requests = {
-                      cpu = "100m";
-                      memory = "70Mi";
-                    };
-                  };
+                  args = [ "-conf" "/etc/coredns/Corefile" ];
+                  image = with cfg.coredns; "${imageName}:${finalImageTag}";
+                  imagePullPolicy = "Never";
                   livenessProbe = {
                     failureThreshold = 5;
                     httpGet = {
-                      path = "/healthcheck/kubedns";
-                      port = 10054;
+                      path = "/health";
+                      port = ports.health;
                       scheme = "HTTP";
                     };
                     initialDelaySeconds = 60;
                     successThreshold = 1;
                     timeoutSeconds = 5;
                   };
-                  readinessProbe = {
-                    httpGet = {
-                      path = "/readiness";
-                      port = 8081;
-                      scheme = "HTTP";
-                    };
-                    initialDelaySeconds = 3;
-                    timeoutSeconds = 5;
-                  };
-                  args = [
-                    "--domain=${cfg.clusterDomain}"
-                    "--dns-port=10053"
-                    "--config-dir=/kube-dns-config"
-                    "--v=2"
-                  ];
-                  env = [
-                    {
-                      name = "PROMETHEUS_PORT";
-                      value = "10055";
-                    }
-                  ];
+                  name = "coredns";
                   ports = [
                     {
-                      containerPort = 10053;
-                      name = "dns-local";
+                      containerPort = ports.dns;
+                      name = "dns";
                       protocol = "UDP";
                     }
                     {
-                      containerPort = 10053;
-                      name = "dns-tcp-local";
+                      containerPort = ports.dns;
+                      name = "dns-tcp";
                       protocol = "TCP";
                     }
                     {
-                      containerPort = 10055;
+                      containerPort = ports.metrics;
                       name = "metrics";
                       protocol = "TCP";
                     }
                   ];
-                  volumeMounts = [
-                    {
-                      mountPath = "/kube-dns-config";
-                      name = "kube-dns-config";
-                    }
-                  ];
-                }
-                {
-                  name = "dnsmasq";
-                  image = with cfg.dnsmasq-nanny; "${imageName}:${finalImageTag}";
-                  livenessProbe = {
-                    httpGet = {
-                      path = "/healthcheck/dnsmasq";
-                      port = 10054;
-                      scheme = "HTTP";
-                    };
-                    initialDelaySeconds = 60;
-                    timeoutSeconds = 5;
-                    successThreshold = 1;
-                    failureThreshold = 5;
-                  };
-                  args = [
-                    "-v=2"
-                    "-logtostderr"
-                    "-configDir=/etc/k8s/dns/dnsmasq-nanny"
-                    "-restartDnsmasq=true"
-                    "--"
-                    "-k"
-                    "--cache-size=1000"
-                    "--log-facility=-"
-                    "--server=/${cfg.clusterDomain}/127.0.0.1#10053"
-                    "--server=/in-addr.arpa/127.0.0.1#10053"
-                    "--server=/ip6.arpa/127.0.0.1#10053"
-                  ];
-                  ports = [
-                    {
-                      containerPort = 53;
-                      name = "dns";
-                      protocol = "UDP";
-                    }
-                    {
-                      containerPort = 53;
-                      name = "dns-tcp";
-                      protocol = "TCP";
-                    }
-                  ];
                   resources = {
+                    limits = {
+                      memory = "170Mi";
+                    };
                     requests = {
-                      cpu = "150m";
-                      memory = "20Mi";
+                      cpu = "100m";
+                      memory = "70Mi";
                     };
                   };
-                  volumeMounts = [
-                    {
-                      mountPath = "/etc/k8s/dns/dnsmasq-nanny";
-                      name = "kube-dns-config";
-                    }
-                  ];
-                }
-                {
-                  name = "sidecar";
-                  image = with cfg.sidecar; "${imageName}:${finalImageTag}";
-                  livenessProbe = {
-                    httpGet = {
-                      path = "/metrics";
-                      port = 10054;
-                      scheme = "HTTP";
+                  securityContext = {
+                    allowPrivilegeEscalation = false;
+                    capabilities = {
+                      drop = [ "all" ];
                     };
-                    initialDelaySeconds = 60;
-                    timeoutSeconds = 5;
-                    successThreshold = 1;
-                    failureThreshold = 5;
+                    readOnlyRootFilesystem = true;
                   };
-                  args = [
-                    "--v=2"
-                    "--logtostderr"
-                    "--probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.${cfg.clusterDomain},5,A"
-                    "--probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.${cfg.clusterDomain},5,A"
-                  ];
-                  ports = [
+                  volumeMounts = [
                     {
-                      containerPort = 10054;
-                      name = "metrics";
-                      protocol = "TCP";
+                      mountPath = "/etc/coredns";
+                      name = "config-volume";
+                      readOnly = true;
                     }
                   ];
-                  resources = {
-                    requests = {
-                      cpu = "10m";
-                      memory = "20Mi";
-                    };
-                  };
                 }
               ];
               dnsPolicy = "Default";
-              serviceAccountName = "kube-dns";
+              nodeSelector = {
+                "beta.kubernetes.io/os" = "linux";
+              };
+              serviceAccountName = "coredns";
               tolerations = [
                 {
+                  effect = "NoSchedule";
+                  key = "node-role.kubernetes.io/master";
+                }
+                {
                   key = "CriticalAddonsOnly";
                   operator = "Exists";
                 }
@@ -261,10 +261,15 @@ in {
               volumes = [
                 {
                   configMap = {
-                    name = "kube-dns";
-                    optional = true;
+                    items = [
+                      {
+                        key = "Corefile";
+                        path = "Corefile";
+                      }
+                    ];
+                    name = "coredns";
                   };
-                  name = "kube-dns-config";
+                  name = "config-volume";
                 }
               ];
             };
@@ -272,51 +277,40 @@ in {
         };
       };
 
-      kubedns-svc = {
+      coredns-svc = {
         apiVersion = "v1";
         kind = "Service";
         metadata = {
+          annotations = {
+            "prometheus.io/port" = toString ports.metrics;
+            "prometheus.io/scrape" = "true";
+          };
           labels = {
             "addonmanager.kubernetes.io/mode" = "Reconcile";
             "k8s-app" = "kube-dns";
             "kubernetes.io/cluster-service" = "true";
-            "kubernetes.io/name" = "KubeDNS";
+            "kubernetes.io/name" = "CoreDNS";
           };
           name = "kube-dns";
-          namespace  = "kube-system";
+          namespace = "kube-system";
         };
         spec = {
           clusterIP = cfg.clusterIp;
           ports = [
-            {name = "dns"; port = 53; protocol = "UDP";}
-            {name = "dns-tcp"; port = 53; protocol = "TCP";}
+            {
+              name = "dns";
+              port = 53;
+              targetPort = ports.dns;
+              protocol = "UDP";
+            }
+            {
+              name = "dns-tcp";
+              port = 53;
+              targetPort = ports.dns;
+              protocol = "TCP";
+            }
           ];
-          selector.k8s-app = "kube-dns";
-        };
-      };
-
-      kubedns-sa = {
-        apiVersion = "v1";
-        kind = "ServiceAccount";
-        metadata = {
-          name = "kube-dns";
-          namespace = "kube-system";
-          labels = {
-            "kubernetes.io/cluster-service" = "true";
-            "addonmanager.kubernetes.io/mode" = "Reconcile";
-          };
-        };
-      };
-
-      kubedns-cm = {
-        apiVersion = "v1";
-        kind = "ConfigMap";
-        metadata = {
-          name = "kube-dns";
-          namespace = "kube-system";
-          labels = {
-            "addonmanager.kubernetes.io/mode" = "EnsureExists";
-          };
+          selector = { k8s-app = "kube-dns"; };
         };
       };
     };
diff --git a/nixos/modules/services/computing/slurm/slurm.nix b/nixos/modules/services/computing/slurm/slurm.nix
index 09174ed39f5e8..d1a1383e45b0b 100644
--- a/nixos/modules/services/computing/slurm/slurm.nix
+++ b/nixos/modules/services/computing/slurm/slurm.nix
@@ -6,13 +6,18 @@ let
 
   cfg = config.services.slurm;
   # configuration file can be generated by http://slurm.schedmd.com/configurator.html
+
+  defaultUser = "slurm";
+
   configFile = pkgs.writeTextDir "slurm.conf"
     ''
       ClusterName=${cfg.clusterName}
+      StateSaveLocation=${cfg.stateSaveLocation}
+      SlurmUser=${cfg.user}
       ${optionalString (cfg.controlMachine != null) ''controlMachine=${cfg.controlMachine}''}
       ${optionalString (cfg.controlAddr != null) ''controlAddr=${cfg.controlAddr}''}
-      ${optionalString (cfg.nodeName != null) ''nodeName=${cfg.nodeName}''}
-      ${optionalString (cfg.partitionName != null) ''partitionName=${cfg.partitionName}''}
+      ${toString (map (x: "NodeName=${x}\n") cfg.nodeName)}
+      ${toString (map (x: "PartitionName=${x}\n") cfg.partitionName)}
       PlugStackConfig=${plugStackConfig}
       ProctrackType=${cfg.procTrackType}
       ${cfg.extraConfig}
@@ -24,17 +29,24 @@ let
       ${cfg.extraPlugstackConfig}
     '';
 
-
   cgroupConfig = pkgs.writeTextDir "cgroup.conf"
    ''
      ${cfg.extraCgroupConfig}
    '';
 
+  slurmdbdConf = pkgs.writeTextDir "slurmdbd.conf"
+   ''
+     DbdHost=${cfg.dbdserver.dbdHost}
+     SlurmUser=${cfg.user}
+     StorageType=accounting_storage/mysql
+     ${cfg.dbdserver.extraConfig}
+   '';
+
   # slurm expects some additional config files to be
   # in the same directory as slurm.conf
   etcSlurm = pkgs.symlinkJoin {
     name = "etc-slurm";
-    paths = [ configFile cgroupConfig plugStackConfig ];
+    paths = [ configFile cgroupConfig plugStackConfig ] ++ cfg.extraConfigPaths;
   };
 
 in
@@ -43,6 +55,8 @@ in
 
   ###### interface
 
+  meta.maintainers = [ maintainers.markuskowa ];
+
   options = {
 
     services.slurm = {
@@ -60,6 +74,27 @@ in
         };
       };
 
+      dbdserver = {
+        enable = mkEnableOption "SlurmDBD service";
+
+        dbdHost = mkOption {
+          type = types.str;
+          default = config.networking.hostName;
+          description = ''
+            Hostname of the machine where <literal>slurmdbd</literal>
+            is running (i.e. name returned by <literal>hostname -s</literal>).
+          '';
+        };
+
+        extraConfig = mkOption {
+          type = types.lines;
+          default = "";
+          description = ''
+            Extra configuration for <literal>slurmdbd.conf</literal>
+          '';
+        };
+      };
+
       client = {
         enable = mkEnableOption "slurm client daemon";
       };
@@ -116,9 +151,9 @@ in
       };
 
       nodeName = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "linux[1-32] CPUs=1 State=UNKNOWN";
+        type = types.listOf types.str;
+        default = [];
+        example = literalExample ''[ "linux[1-32] CPUs=1 State=UNKNOWN" ];'';
         description = ''
           Name that SLURM uses to refer to a node (or base partition for BlueGene
           systems). Typically this would be the string that "/bin/hostname -s"
@@ -127,9 +162,9 @@ in
       };
 
       partitionName = mkOption {
-        type = types.nullOr types.str;
-        default = null;
-        example = "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP";
+        type = types.listOf types.str;
+        default = [];
+        example = literalExample ''[ "debug Nodes=linux[1-32] Default=YES MaxTime=INFINITE State=UP" ];'';
         description = ''
           Name by which the partition may be referenced. Note that now you have
           to write the partition's parameters after the name.
@@ -150,7 +185,7 @@ in
       };
 
       procTrackType = mkOption {
-        type = types.string;
+        type = types.str;
         default = "proctrack/linuxproc";
         description = ''
           Plugin to be used for process tracking on a job step basis.
@@ -159,6 +194,25 @@ in
         '';
       };
 
+      stateSaveLocation = mkOption {
+        type = types.str;
+        default = "/var/spool/slurmctld";
+        description = ''
+          Directory into which the Slurm controller, slurmctld, saves its state.
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = defaultUser;
+        description = ''
+          Set this option when you want to run the slurmctld daemon
+          as something else than the default slurm user "slurm".
+          Note that the UID of this user needs to be the same
+          on all nodes.
+        '';
+      };
+
       extraConfig = mkOption {
         default = "";
         type = types.lines;
@@ -184,6 +238,19 @@ in
           used when <literal>procTrackType=proctrack/cgroup</literal>.
         '';
       };
+
+      extraConfigPaths = mkOption {
+        type = with types; listOf path;
+        default = [];
+        description = ''
+          Slurm expects config files for plugins in the same path
+          as <literal>slurm.conf</literal>. Add extra nix store
+          paths that should be merged into same directory as
+          <literal>slurm.conf</literal>.
+        '';
+      };
+
+
     };
 
   };
@@ -220,12 +287,24 @@ in
         '';
       };
 
-  in mkIf (cfg.enableStools || cfg.client.enable || cfg.server.enable) {
+  in mkIf ( cfg.enableStools ||
+            cfg.client.enable ||
+            cfg.server.enable ||
+            cfg.dbdserver.enable ) {
 
     environment.systemPackages = [ wrappedSlurm ];
 
     services.munge.enable = mkDefault true;
 
+    # use a static uid as default to ensure it is the same on all nodes
+    users.users.slurm = mkIf (cfg.user == defaultUser) {
+      name = defaultUser;
+      group = "slurm";
+      uid = config.ids.uids.slurm;
+    };
+
+    users.groups.slurm.gid = config.ids.uids.slurm;
+
     systemd.services.slurmd = mkIf (cfg.client.enable) {
       path = with pkgs; [ wrappedSlurm coreutils ]
         ++ lib.optional cfg.enableSrunX11 slurm-spank-x11;
@@ -235,6 +314,7 @@ in
 
       serviceConfig = {
         Type = "forking";
+        KillMode = "process";
         ExecStart = "${wrappedSlurm}/bin/slurmd";
         PIDFile = "/run/slurmd.pid";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
@@ -261,6 +341,29 @@ in
         PIDFile = "/run/slurmctld.pid";
         ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
       };
+
+      preStart = ''
+        mkdir -p ${cfg.stateSaveLocation}
+        chown -R ${cfg.user}:slurm ${cfg.stateSaveLocation}
+      '';
+    };
+
+    systemd.services.slurmdbd = mkIf (cfg.dbdserver.enable) {
+      path = with pkgs; [ wrappedSlurm munge coreutils ];
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" "munged.service" "mysql.service" ];
+      requires = [ "munged.service" "mysql.service" ];
+
+      # slurm strips the last component off the path
+      environment.SLURM_CONF = "${slurmdbdConf}/slurm.conf";
+
+      serviceConfig = {
+        Type = "forking";
+        ExecStart = "${cfg.package}/bin/slurmdbd";
+        PIDFile = "/run/slurmdbd.pid";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+      };
     };
 
   };
diff --git a/nixos/modules/services/continuous-integration/buildbot/master.nix b/nixos/modules/services/continuous-integration/buildbot/master.nix
index 8d767de37f00a..0f07e6133bb5c 100644
--- a/nixos/modules/services/continuous-integration/buildbot/master.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/master.nix
@@ -6,8 +6,12 @@ with lib;
 
 let
   cfg = config.services.buildbot-master;
+
+  python = cfg.package.pythonModule;
+
   escapeStr = s: escape ["'"] s;
-  masterCfg = if cfg.masterCfg == null then pkgs.writeText "master.cfg" ''
+
+  defaultMasterCfg = pkgs.writeText "master.cfg" ''
     from buildbot.plugins import *
     factory = util.BuildFactory()
     c = BuildmasterConfig = dict(
@@ -27,8 +31,28 @@ let
       factory.addStep(step)
 
     ${cfg.extraConfig}
-  ''
-  else cfg.masterCfg;
+  '';
+
+  tacFile = pkgs.writeText "buildbot-master.tac" ''
+    import os
+
+    from twisted.application import service
+    from buildbot.master import BuildMaster
+
+    basedir = '${cfg.buildbotDir}'
+
+    configfile = '${cfg.masterCfg}'
+
+    # Default umask for server
+    umask = None
+
+    # note: this line is matched against to check that this is a buildmaster
+    # directory; do not edit it.
+    application = service.Application('buildmaster')
+
+    m = BuildMaster(basedir, configfile, umask)
+    m.setServiceParent(application)
+  '';
 
 in {
   options = {
@@ -66,9 +90,9 @@ in {
       };
 
       masterCfg = mkOption {
-        type = types.nullOr types.path;
+        type = types.path;
         description = "Optionally pass master.cfg path. Other options in this configuration will be ignored.";
-        default = null;
+        default = defaultMasterCfg;
         example = "/etc/nixos/buildbot/master.cfg";
       };
 
@@ -175,18 +199,25 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.buildbot-full;
-        defaultText = "pkgs.buildbot-full";
+        default = pkgs.pythonPackages.buildbot-full;
+        defaultText = "pkgs.pythonPackages.buildbot-full";
         description = "Package to use for buildbot.";
-        example = literalExample "pkgs.buildbot-full";
+        example = literalExample "pkgs.python3Packages.buildbot-full";
       };
 
       packages = mkOption {
-        default = with pkgs; [ python27Packages.twisted git ];
+        default = [ pkgs.git ];
         example = literalExample "[ pkgs.git ]";
         type = types.listOf types.package;
         description = "Packages to add to PATH for the buildbot process.";
       };
+
+      pythonPackages = mkOption {
+        default = pythonPackages: with pythonPackages; [ ];
+        defaultText = "pythonPackages: with pythonPackages; [ ]";
+        description = "Packages to add the to the PYTHONPATH of the buildbot process.";
+        example = literalExample "pythonPackages: with pythonPackages; [ requests ]";
+      };
     };
   };
 
@@ -210,14 +241,15 @@ in {
       description = "Buildbot Continuous Integration Server.";
       after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
-      path = cfg.packages;
+      path = cfg.packages ++ cfg.pythonPackages python.pkgs;
+      environment.PYTHONPATH = "${python.withPackages (self: cfg.pythonPackages self ++ [ cfg.package ])}/${python.sitePackages}";
 
       preStart = ''
-        env > envvars
-        mkdir -vp ${cfg.buildbotDir}
-        ln -sfv ${masterCfg} ${cfg.buildbotDir}/master.cfg
-        rm -fv $cfg.buildbotDir}/buildbot.tac
-        ${cfg.package}/bin/buildbot create-master ${cfg.buildbotDir}
+        mkdir -vp "${cfg.buildbotDir}"
+        # Link the tac file so buildbot command line tools recognize the directory
+        ln -sf "${tacFile}" "${cfg.buildbotDir}/buildbot.tac"
+        ${cfg.package}/bin/buildbot create-master --db "${cfg.dbUrl}" "${cfg.buildbotDir}"
+        rm -f buildbot.tac.new master.cfg.sample
       '';
 
       serviceConfig = {
@@ -225,12 +257,11 @@ in {
         User = cfg.user;
         Group = cfg.group;
         WorkingDirectory = cfg.home;
-        ExecStart = "${cfg.package}/bin/buildbot start --nodaemon ${cfg.buildbotDir}";
+        # NOTE: call twistd directly with stdout logging for systemd
+        ExecStart = "${python.pkgs.twisted}/bin/twistd -o --nodaemon --pidfile= --logfile - --python ${tacFile}";
       };
-
     };
   };
 
   meta.maintainers = with lib.maintainers; [ nand0p mic92 ];
-
 }
diff --git a/nixos/modules/services/continuous-integration/buildbot/worker.nix b/nixos/modules/services/continuous-integration/buildbot/worker.nix
index 67c541570b97c..4130ec918a70a 100644
--- a/nixos/modules/services/continuous-integration/buildbot/worker.nix
+++ b/nixos/modules/services/continuous-integration/buildbot/worker.nix
@@ -7,6 +7,40 @@ with lib;
 let
   cfg = config.services.buildbot-worker;
 
+  python = cfg.package.pythonModule;
+
+  tacFile = pkgs.writeText "aur-buildbot-worker.tac" ''
+    import os
+    from io import open
+
+    from buildbot_worker.bot import Worker
+    from twisted.application import service
+
+    basedir = '${cfg.buildbotDir}'
+
+    # note: this line is matched against to check that this is a worker
+    # directory; do not edit it.
+    application = service.Application('buildbot-worker')
+
+    master_url_split = '${cfg.masterUrl}'.split(':')
+    buildmaster_host = master_url_split[0]
+    port = int(master_url_split[1])
+    workername = '${cfg.workerUser}'
+
+    with open('${cfg.workerPassFile}', 'r', encoding='utf-8') as passwd_file:
+        passwd = passwd_file.read().strip('\r\n')
+    keepalive = 600
+    umask = None
+    maxdelay = 300
+    numcpus = None
+    allow_shutdown = None
+
+    s = Worker(buildmaster_host, port, workername, passwd, basedir,
+               keepalive, umask=umask, maxdelay=maxdelay,
+               numcpus=numcpus, allow_shutdown=allow_shutdown)
+    s.setServiceParent(application)
+  '';
+
 in {
   options = {
     services.buildbot-worker = {
@@ -59,6 +93,23 @@ in {
         description = "Specifies the Buildbot Worker password.";
       };
 
+      workerPassFile = mkOption {
+        type = types.path;
+        description = "File used to store the Buildbot Worker password";
+      };
+
+      hostMessage = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = "Description of this worker";
+      };
+
+      adminMessage = mkOption {
+        default = null;
+        type = types.nullOr types.str;
+        description = "Name of the administrator of this worker";
+      };
+
       masterUrl = mkOption {
         default = "localhost:9989";
         type = types.str;
@@ -67,23 +118,24 @@ in {
 
       package = mkOption {
         type = types.package;
-        default = pkgs.buildbot-worker;
-        defaultText = "pkgs.buildbot-worker";
+        default = pkgs.pythonPackages.buildbot-worker;
+        defaultText = "pkgs.pythonPackages.buildbot-worker";
         description = "Package to use for buildbot worker.";
-        example = literalExample "pkgs.buildbot-worker";
+        example = literalExample "pkgs.python3Packages.buildbot-worker";
       };
 
       packages = mkOption {
-        default = with pkgs; [ python27Packages.twisted git ];
+        default = with pkgs; [ git ];
         example = literalExample "[ pkgs.git ]";
         type = types.listOf types.package;
         description = "Packages to add to PATH for the buildbot process.";
       };
-
     };
   };
 
   config = mkIf cfg.enable {
+    services.buildbot-worker.workerPassFile = mkDefault (pkgs.writeText "buildbot-worker-password" cfg.workerPass);
+
     users.groups = optional (cfg.group == "bbworker") {
       name = "bbworker";
     };
@@ -104,11 +156,16 @@ in {
       after = [ "network.target" "buildbot-master.service" ];
       wantedBy = [ "multi-user.target" ];
       path = cfg.packages;
+      environment.PYTHONPATH = "${python.withPackages (p: [ cfg.package ])}/${python.sitePackages}";
 
       preStart = ''
-        mkdir -vp ${cfg.buildbotDir}
-        rm -fv $cfg.buildbotDir}/buildbot.tac
-        ${cfg.package}/bin/buildbot-worker create-worker ${cfg.buildbotDir} ${cfg.masterUrl} ${cfg.workerUser} ${cfg.workerPass}
+        mkdir -vp "${cfg.buildbotDir}/info"
+        ${optionalString (cfg.hostMessage != null) ''
+          ln -sf "${pkgs.writeText "buildbot-worker-host" cfg.hostMessage}" "${cfg.buildbotDir}/info/host"
+        ''}
+        ${optionalString (cfg.adminMessage != null) ''
+          ln -sf "${pkgs.writeText "buildbot-worker-admin" cfg.adminMessage}" "${cfg.buildbotDir}/info/admin"
+        ''}
       '';
 
       serviceConfig = {
@@ -116,11 +173,9 @@ in {
         User = cfg.user;
         Group = cfg.group;
         WorkingDirectory = cfg.home;
-        Environment = "PYTHONPATH=${cfg.package}/lib/python2.7/site-packages:${pkgs.python27Packages.future}/lib/python2.7/site-packages";
 
         # NOTE: call twistd directly with stdout logging for systemd
-        #ExecStart = "${cfg.package}/bin/buildbot-worker start --nodaemon ${cfg.buildbotDir}";
-        ExecStart = "${pkgs.python27Packages.twisted}/bin/twistd -n -l - -y ${cfg.buildbotDir}/buildbot.tac";
+        ExecStart = "${python.pkgs.twisted}/bin/twistd --nodaemon --pidfile= --logfile - --python ${tacFile}";
       };
 
     };
diff --git a/nixos/modules/services/continuous-integration/jenkins/job-builder.nix b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
index 861b46a2d642f..5d1bfe4ec407e 100644
--- a/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
+++ b/nixos/modules/services/continuous-integration/jenkins/job-builder.nix
@@ -42,6 +42,18 @@ in {
         type = types.str;
         description = ''
           User token in Jenkins used to reload config.
+          WARNING: This token will be world readable in the Nix store. To keep
+          it secret, use the <option>accessTokenFile</option> option instead.
+        '';
+      };
+
+      accessTokenFile = mkOption {
+        default = "";
+        type = types.str;
+        example = "/run/keys/jenkins-job-builder-access-token";
+        description = ''
+          File containing the API token for the <option>accessUser</option>
+          user.
         '';
       };
 
@@ -103,6 +115,21 @@ in {
   };
 
   config = mkIf (jenkinsCfg.enable && cfg.enable) {
+    assertions = [
+      { assertion =
+          if cfg.accessUser != ""
+          then (cfg.accessToken != "" && cfg.accessTokenFile == "") ||
+               (cfg.accessToken == "" && cfg.accessTokenFile != "")
+          else true;
+        message = ''
+          One of accessToken and accessTokenFile options must be non-empty
+          strings, but not both. Current values:
+            services.jenkins.jobBuilder.accessToken = "${cfg.accessToken}"
+            services.jenkins.jobBuilder.accessTokenFile = "${cfg.accessTokenFile}"
+        '';
+      }
+    ];
+
     systemd.services.jenkins-job-builder = {
       description = "Jenkins Job Builder Service";
       # JJB can run either before or after jenkins. We chose after, so we can
@@ -128,8 +155,13 @@ in {
           ownerStamp = ".config-xml-managed-by-nixos-jenkins-job-builder";
           reloadScript = ''
             echo "Asking Jenkins to reload config"
-            CRUMB=$(curl -s 'http://${cfg.accessUser}:${cfg.accessToken}@${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')
-            curl --silent -X POST -H "$CRUMB" http://${cfg.accessUser}:${cfg.accessToken}@${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}/reload
+            curl_opts="--silent --fail --show-error"
+            access_token=${if cfg.accessTokenFile != ""
+                           then "$(cat '${cfg.accessTokenFile}')"
+                           else cfg.accessToken}
+            jenkins_url="http://${cfg.accessUser}:$access_token@${jenkinsCfg.listenAddress}:${toString jenkinsCfg.port}${jenkinsCfg.prefix}"
+            crumb=$(curl $curl_opts "$jenkins_url"'/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,":",//crumb)')
+            curl $curl_opts -X POST -H "$crumb" "$jenkins_url"/reload
           '';
         in
           ''
diff --git a/nixos/modules/services/databases/aerospike.nix b/nixos/modules/services/databases/aerospike.nix
index 5f33164998beb..4b905f90529d2 100644
--- a/nixos/modules/services/databases/aerospike.nix
+++ b/nixos/modules/services/databases/aerospike.nix
@@ -43,6 +43,7 @@ in
 
       package = mkOption {
         default = pkgs.aerospike;
+        defaultText = "pkgs.aerospike";
         type = types.package;
         description = "Which Aerospike derivation to use";
       };
diff --git a/nixos/modules/services/databases/cassandra.nix b/nixos/modules/services/databases/cassandra.nix
index 86e74d5d5ab42..d741ee48c48f0 100644
--- a/nixos/modules/services/databases/cassandra.nix
+++ b/nixos/modules/services/databases/cassandra.nix
@@ -34,11 +34,13 @@ let
     { name = "cassandra-etc";
       cassandraYaml = builtins.toJSON cassandraConfigWithAddresses;
       cassandraEnvPkg = "${cfg.package}/conf/cassandra-env.sh";
+      cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig;
       buildCommand = ''
         mkdir -p "$out"
 
         echo "$cassandraYaml" > "$out/cassandra.yaml"
         ln -s "$cassandraEnvPkg" "$out/cassandra-env.sh"
+        ln -s "$cassandraLogbackConfig" "$out/logback.xml"
       '';
     };
 in {
@@ -139,7 +141,27 @@ in {
         correspond to a single address, IP aliasing is not supported.
       '';
     };
+    logbackConfig = mkOption {
+      type = types.lines;
+      default = ''
+        <configuration scan="false">
+          <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+            <encoder>
+              <pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern>
+            </encoder>
+          </appender>
 
+          <root level="INFO">
+            <appender-ref ref="STDOUT" />
+          </root>
+
+          <logger name="com.thinkaurelius.thrift" level="ERROR"/>
+        </configuration>
+      '';
+      description = ''
+        XML logback configuration for cassandra
+      '';
+    };
     extraConfig = mkOption {
       type = types.attrs;
       default = {};
diff --git a/nixos/modules/services/databases/clickhouse.nix b/nixos/modules/services/databases/clickhouse.nix
index 1b8771cec3915..21e0cee341515 100644
--- a/nixos/modules/services/databases/clickhouse.nix
+++ b/nixos/modules/services/databases/clickhouse.nix
@@ -70,6 +70,11 @@ with lib;
       };
     };
 
+    environment.systemPackages = [ pkgs.clickhouse ];
+
+    # startup requires a `/etc/localtime` which only if exists if `time.timeZone != null`
+    time.timeZone = mkDefault "UTC";
+
   };
 
 }
diff --git a/nixos/modules/services/databases/cockroachdb.nix b/nixos/modules/services/databases/cockroachdb.nix
new file mode 100644
index 0000000000000..e977751b21efd
--- /dev/null
+++ b/nixos/modules/services/databases/cockroachdb.nix
@@ -0,0 +1,217 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.cockroachdb;
+  crdb = cfg.package;
+
+  escape    = builtins.replaceStrings ["%"] ["%%"];
+  ifNotNull = v: s: optionalString (!isNull v) s;
+
+  startupCommand = lib.concatStringsSep " "
+    [ # Basic startup
+      "${crdb}/bin/cockroach start"
+      "--logtostderr"
+      "--store=/var/lib/cockroachdb"
+      (ifNotNull cfg.locality "--locality='${cfg.locality}'")
+
+      # WebUI settings
+      "--http-addr='${cfg.http.address}:${toString cfg.http.port}'"
+
+      # Cluster listen address
+      "--listen-addr='${cfg.listen.address}:${toString cfg.listen.port}'"
+
+      # Cluster configuration
+      (ifNotNull cfg.join "--join=${cfg.join}")
+
+      # Cache and memory settings. Must be escaped.
+      "--cache='${escape cfg.cache}'"
+      "--max-sql-memory='${escape cfg.maxSqlMemory}'"
+
+      # Certificate/security settings.
+      (if cfg.insecure then "--insecure" else "--certs-dir=${cfg.certsDir}")
+    ];
+
+    addressOption = descr: defaultPort: {
+      address = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = "Address to bind to for ${descr}";
+      };
+
+      port = mkOption {
+        type = types.port;
+        default = defaultPort;
+        description = "Port to bind to for ${descr}";
+      };
+    };
+in
+
+{
+  options = {
+    services.cockroachdb = {
+      enable = mkEnableOption "CockroachDB Server";
+
+      listen = addressOption "intra-cluster communication" 26257;
+
+      http = addressOption "http-based Admin UI" 8080;
+
+      locality = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          An ordered, comma-separated list of key-value pairs that describe the
+          topography of the machine. Topography might include country,
+          datacenter or rack designations. Data is automatically replicated to
+          maximize diversities of each tier. The order of tiers is used to
+          determine the priority of the diversity, so the more inclusive
+          localities like country should come before less inclusive localities
+          like datacenter.  The tiers and order must be the same on all nodes.
+          Including more tiers is better than including fewer. For example:
+
+          <literal>
+              country=us,region=us-west,datacenter=us-west-1b,rack=12
+              country=ca,region=ca-east,datacenter=ca-east-2,rack=4
+
+              planet=earth,province=manitoba,colo=secondary,power=3
+          </literal>
+        '';
+      };
+
+      join = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "The addresses for connecting the node to a cluster.";
+      };
+
+      insecure = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Run in insecure mode.";
+      };
+
+      certsDir = mkOption {
+        type = types.nullOr types.path;
+        default = null;
+        description = "The path to the certificate directory.";
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "cockroachdb";
+        description = "User account under which CockroachDB runs";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "cockroachdb";
+        description = "User account under which CockroachDB runs";
+      };
+
+      openPorts = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Open firewall ports for cluster communication by default";
+      };
+
+      cache = mkOption {
+        type = types.str;
+        default = "25%";
+        description = ''
+          The total size for caches.
+
+          This can be a percentage, expressed with a fraction sign or as a
+          decimal-point number, or any bytes-based unit. For example,
+          <literal>"25%"</literal>, <literal>"0.25"</literal> both represent
+          25% of the available system memory. The values
+          <literal>"1000000000"</literal> and <literal>"1GB"</literal> both
+          represent 1 gigabyte of memory.
+
+        '';
+      };
+
+      maxSqlMemory = mkOption {
+        type = types.str;
+        default = "25%";
+        description = ''
+          The maximum in-memory storage capacity available to store temporary
+          data for SQL queries.
+
+          This can be a percentage, expressed with a fraction sign or as a
+          decimal-point number, or any bytes-based unit. For example,
+          <literal>"25%"</literal>, <literal>"0.25"</literal> both represent
+          25% of the available system memory. The values
+          <literal>"1000000000"</literal> and <literal>"1GB"</literal> both
+          represent 1 gigabyte of memory.
+        '';
+      };
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.cockroachdb;
+        defaultText = "pkgs.cockroachdb";
+        description = ''
+          The CockroachDB derivation to use for running the service.
+          
+          This would primarily be useful to enable Enterprise Edition features
+          in your own custom CockroachDB build (Nixpkgs CockroachDB binaries
+          only contain open source features and open source code).
+        '';
+      };
+    };
+  };
+
+  config = mkIf config.services.cockroachdb.enable {
+    assertions = [
+      { assertion = !cfg.insecure -> !(isNull cfg.certsDir);
+        message = "CockroachDB must have a set of SSL certificates (.certsDir), or run in Insecure Mode (.insecure = true)";
+      }
+    ];
+
+    environment.systemPackages = [ crdb ];
+
+    users.users = optionalAttrs (cfg.user == "cockroachdb") (singleton
+      { name        = "cockroachdb";
+        description = "CockroachDB Server User";
+        uid         = config.ids.uids.cockroachdb;
+        group       = cfg.group;
+      });
+
+    users.groups = optionalAttrs (cfg.group == "cockroachdb") (singleton
+      { name = "cockroachdb";
+        gid  = config.ids.gids.cockroachdb;
+      });
+
+    networking.firewall.allowedTCPPorts = lib.optionals cfg.openPorts
+      [ cfg.http.port cfg.listen.port ];
+
+    systemd.services.cockroachdb =
+      { description   = "CockroachDB Server";
+        documentation = [ "man:cockroach(1)" "https://www.cockroachlabs.com" ];
+
+        after    = [ "network.target" "time-sync.target" ];
+        requires = [ "time-sync.target" ];
+        wantedBy = [ "multi-user.target" ];
+
+        unitConfig.RequiresMountsFor = "/var/lib/cockroachdb";
+
+        serviceConfig =
+          { ExecStart = startupCommand;
+            Type = "notify";
+            User = cfg.user;
+            StateDirectory = "cockroachdb";
+            StateDirectoryMode = "0700";
+
+            Restart = "always";
+
+            # A conservative-ish timeout is alright here, because for Type=notify
+            # cockroach will send systemd pings during startup to keep it alive
+            TimeoutStopSec = 60;
+            RestartSec = 10;
+          };
+      };
+  };
+
+  meta.maintainers = with lib.maintainers; [ thoughtpolice ];
+}
diff --git a/nixos/modules/services/databases/foundationdb.xml b/nixos/modules/services/databases/foundationdb.xml
index 7883680d46cc3..bf4b644c9b86a 100644
--- a/nixos/modules/services/databases/foundationdb.xml
+++ b/nixos/modules/services/databases/foundationdb.xml
@@ -3,42 +3,50 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-foundationdb">
-
-<title>FoundationDB</title>
-
-<para><emphasis>Source:</emphasis> <filename>modules/services/databases/foundationdb.nix</filename></para>
-
-<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="https://apple.github.io/foundationdb/"/></para>
-
-<para><emphasis>Maintainer:</emphasis> Austin Seipp</para>
-
-<para><emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x</para>
-
-<para>FoundationDB (or "FDB") is an open source, distributed, transactional
-key-value store.</para>
-
-<section xml:id="module-services-foundationdb-configuring"><title>Configuring and basic setup</title>
-
-<para>To enable FoundationDB, add the following to your
-<filename>configuration.nix</filename>:
-
+ <title>FoundationDB</title>
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/services/databases/foundationdb.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://apple.github.io/foundationdb/"/>
+ </para>
+ <para>
+  <emphasis>Maintainer:</emphasis> Austin Seipp
+ </para>
+ <para>
+  <emphasis>Available version(s):</emphasis> 5.1.x, 5.2.x, 6.0.x
+ </para>
+ <para>
+  FoundationDB (or "FDB") is an open source, distributed, transactional
+  key-value store.
+ </para>
+ <section xml:id="module-services-foundationdb-configuring">
+  <title>Configuring and basic setup</title>
+
+  <para>
+   To enable FoundationDB, add the following to your
+   <filename>configuration.nix</filename>:
 <programlisting>
 services.foundationdb.enable = true;
 services.foundationdb.package = pkgs.foundationdb52; # FoundationDB 5.2.x
 </programlisting>
-</para>
-
-<para>The <option>services.foundationdb.package</option> option is required,
-and must always be specified. Due to the fact FoundationDB network protocols and
-on-disk storage formats may change between (major) versions, and upgrades must
-be explicitly handled by the user, you must always manually specify this
-yourself so that the NixOS module will use the proper version. Note that minor,
-bugfix releases are always compatible.</para>
-
-<para>After running <command>nixos-rebuild</command>, you can verify whether
-FoundationDB is running by executing <command>fdbcli</command> (which is added
-to <option>environment.systemPackages</option>):
-
+  </para>
+
+  <para>
+   The <option>services.foundationdb.package</option> option is required, and
+   must always be specified. Due to the fact FoundationDB network protocols and
+   on-disk storage formats may change between (major) versions, and upgrades
+   must be explicitly handled by the user, you must always manually specify
+   this yourself so that the NixOS module will use the proper version. Note
+   that minor, bugfix releases are always compatible.
+  </para>
+
+  <para>
+   After running <command>nixos-rebuild</command>, you can verify whether
+   FoundationDB is running by executing <command>fdbcli</command> (which is
+   added to <option>environment.systemPackages</option>):
 <programlisting>
 $ sudo -u foundationdb fdbcli
 Using cluster file `/etc/foundationdb/fdb.cluster'.
@@ -66,14 +74,14 @@ Cluster:
 
 fdb>
 </programlisting>
-</para>
-
-<para>You can also write programs using the available client libraries.
-For example, the following Python program can be run in order to grab the
-cluster status, as a quick example. (This example uses
-<command>nix-shell</command> shebang support to automatically supply the
-necessary Python modules).
-
+  </para>
+
+  <para>
+   You can also write programs using the available client libraries. For
+   example, the following Python program can be run in order to grab the
+   cluster status, as a quick example. (This example uses
+   <command>nix-shell</command> shebang support to automatically supply the
+   necessary Python modules).
 <programlisting>
 a@link> cat fdb-status.py
 #! /usr/bin/env nix-shell
@@ -100,255 +108,336 @@ a@link> ./fdb-status.py
 FoundationDB available: True
 a@link>
 </programlisting>
-</para>
-
-<para>FoundationDB is run under the <command>foundationdb</command> user and
-group by default, but this may be changed in the NixOS configuration. The
-systemd unit <command>foundationdb.service</command> controls the
-<command>fdbmonitor</command> process.</para>
-
-<para>By default, the NixOS module for FoundationDB creates a single
-SSD-storage based database for development and basic usage. This storage engine
-is designed for SSDs and will perform poorly on HDDs; however it can handle far
-more data than the alternative "memory" engine and is a better default choice
-for most deployments. (Note that you can change the storage backend on-the-fly
-for a given FoundationDB cluster using <command>fdbcli</command>.)</para>
-
-<para>Furthermore, only 1 server process and 1 backup agent are started in the
-default configuration. See below for more on scaling to increase this.</para>
-
-<para>FoundationDB stores all data for all server processes under
-<filename>/var/lib/foundationdb</filename>. You can override this using
-<option>services.foundationdb.dataDir</option>, e.g.
-
+  </para>
+
+  <para>
+   FoundationDB is run under the <command>foundationdb</command> user and group
+   by default, but this may be changed in the NixOS configuration. The systemd
+   unit <command>foundationdb.service</command> controls the
+   <command>fdbmonitor</command> process.
+  </para>
+
+  <para>
+   By default, the NixOS module for FoundationDB creates a single SSD-storage
+   based database for development and basic usage. This storage engine is
+   designed for SSDs and will perform poorly on HDDs; however it can handle far
+   more data than the alternative "memory" engine and is a better default
+   choice for most deployments. (Note that you can change the storage backend
+   on-the-fly for a given FoundationDB cluster using
+   <command>fdbcli</command>.)
+  </para>
+
+  <para>
+   Furthermore, only 1 server process and 1 backup agent are started in the
+   default configuration. See below for more on scaling to increase this.
+  </para>
+
+  <para>
+   FoundationDB stores all data for all server processes under
+   <filename>/var/lib/foundationdb</filename>. You can override this using
+   <option>services.foundationdb.dataDir</option>, e.g.
 <programlisting>
 services.foundationdb.dataDir = "/data/fdb";
 </programlisting>
-
-</para>
-
-<para>Similarly, logs are stored under
-<filename>/var/log/foundationdb</filename> by default, and there is a
-corresponding <option>services.foundationdb.logDir</option> as well.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-scaling"><title>Scaling processes and backup agents</title>
-
-<para>Scaling the number of server processes is quite easy; simply specify
-<option>services.foundationdb.serverProcesses</option> to be the number of
-FoundationDB worker processes that should be started on the machine.</para>
-
-<para>FoundationDB worker processes typically require 4GB of RAM per-process at
-minimum for good performance, so this option is set to 1 by default since the
-maximum amount of RAM is unknown. You're advised to abide by this restriction,
-so pick a number of processes so that each has 4GB or more.</para>
-
-<para>A similar option exists in order to scale backup agent processes,
-<option>services.foundationdb.backupProcesses</option>. Backup agents are not
-as performance/RAM sensitive, so feel free to experiment with the number of
-available backup processes.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-clustering"><title>Clustering</title>
-
-<para>FoundationDB on NixOS works similarly to other Linux systems, so this
-section will be brief. Please refer to the full FoundationDB documentation for
-more on clustering.</para>
-
-<para>FoundationDB organizes clusters using a set of
-<emphasis>coordinators</emphasis>, which are just specially-designated worker
-processes. By default, every installation of FoundationDB on NixOS will start
-as its own individual cluster, with a single coordinator: the first worker
-process on <command>localhost</command>.</para>
-
-<para>Coordinators are specified globally using the
-<command>/etc/foundationdb/fdb.cluster</command> file, which all servers and
-client applications will use to find and join coordinators. Note that this file
-<emphasis>can not</emphasis> be managed by NixOS so easily: FoundationDB is
-designed so that it will rewrite the file at runtime for all clients and nodes
-when cluster coordinators change, with clients transparently handling this
-without intervention. It is fundamentally a mutable file, and you should not
-try to manage it in any way in NixOS.</para>
-
-<para>When dealing with a cluster, there are two main things you want to
-do:</para>
-
-<itemizedlist>
-  <listitem><para>Add a node to the cluster for storage/compute.</para></listitem>
-  <listitem><para>Promote an ordinary worker to a coordinator.</para></listitem>
-</itemizedlist>
-
-<para>A node must already be a member of the cluster in order to properly be
-promoted to a coordinator, so you must always add it first if you wish to
-promote it.</para>
-
-<para>To add a machine to a FoundationDB cluster:</para>
-
-<itemizedlist>
-  <listitem><para>Choose one of the servers to start as the initial coordinator.
-      </para></listitem>
-  <listitem><para>Copy the <command>/etc/foundationdb/fdb.cluster</command> file
-      from this server to all the other servers. Restart FoundationDB on all of
-      these other servers, so they join the cluster.</para></listitem>
-  <listitem><para>All of these servers are now connected and working together
-      in the cluster, under the chosen coordinator.</para></listitem>
-</itemizedlist>
-
-<para>At this point, you can add as many nodes as you want by just repeating
-the above steps. By default there will still be a single coordinator: you can
-use <command>fdbcli</command> to change this and add new coordinators.</para>
-
-<para>As a convenience, FoundationDB can automatically assign coordinators
-based on the redundancy mode you wish to achieve for the cluster. Once all the
-nodes have been joined, simply set the replication policy, and then issue the
-<command>coordinators auto</command> command</para>
-
-<para>For example, assuming we have 3 nodes available, we can enable double
-redundancy mode, then auto-select coordinators. For double redundancy, 3
-coordinators is ideal: therefore FoundationDB will make
-<emphasis>every</emphasis> node a coordinator automatically:</para>
+  </para>
+
+  <para>
+   Similarly, logs are stored under <filename>/var/log/foundationdb</filename>
+   by default, and there is a corresponding
+   <option>services.foundationdb.logDir</option> as well.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-scaling">
+  <title>Scaling processes and backup agents</title>
+
+  <para>
+   Scaling the number of server processes is quite easy; simply specify
+   <option>services.foundationdb.serverProcesses</option> to be the number of
+   FoundationDB worker processes that should be started on the machine.
+  </para>
+
+  <para>
+   FoundationDB worker processes typically require 4GB of RAM per-process at
+   minimum for good performance, so this option is set to 1 by default since
+   the maximum amount of RAM is unknown. You're advised to abide by this
+   restriction, so pick a number of processes so that each has 4GB or more.
+  </para>
+
+  <para>
+   A similar option exists in order to scale backup agent processes,
+   <option>services.foundationdb.backupProcesses</option>. Backup agents are
+   not as performance/RAM sensitive, so feel free to experiment with the number
+   of available backup processes.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-clustering">
+  <title>Clustering</title>
+
+  <para>
+   FoundationDB on NixOS works similarly to other Linux systems, so this
+   section will be brief. Please refer to the full FoundationDB documentation
+   for more on clustering.
+  </para>
+
+  <para>
+   FoundationDB organizes clusters using a set of
+   <emphasis>coordinators</emphasis>, which are just specially-designated
+   worker processes. By default, every installation of FoundationDB on NixOS
+   will start as its own individual cluster, with a single coordinator: the
+   first worker process on <command>localhost</command>.
+  </para>
+
+  <para>
+   Coordinators are specified globally using the
+   <command>/etc/foundationdb/fdb.cluster</command> file, which all servers and
+   client applications will use to find and join coordinators. Note that this
+   file <emphasis>can not</emphasis> be managed by NixOS so easily:
+   FoundationDB is designed so that it will rewrite the file at runtime for all
+   clients and nodes when cluster coordinators change, with clients
+   transparently handling this without intervention. It is fundamentally a
+   mutable file, and you should not try to manage it in any way in NixOS.
+  </para>
+
+  <para>
+   When dealing with a cluster, there are two main things you want to do:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Add a node to the cluster for storage/compute.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Promote an ordinary worker to a coordinator.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   A node must already be a member of the cluster in order to properly be
+   promoted to a coordinator, so you must always add it first if you wish to
+   promote it.
+  </para>
+
+  <para>
+   To add a machine to a FoundationDB cluster:
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     Choose one of the servers to start as the initial coordinator.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Copy the <command>/etc/foundationdb/fdb.cluster</command> file from this
+     server to all the other servers. Restart FoundationDB on all of these
+     other servers, so they join the cluster.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     All of these servers are now connected and working together in the
+     cluster, under the chosen coordinator.
+    </para>
+   </listitem>
+  </itemizedlist>
+
+  <para>
+   At this point, you can add as many nodes as you want by just repeating the
+   above steps. By default there will still be a single coordinator: you can
+   use <command>fdbcli</command> to change this and add new coordinators.
+  </para>
+
+  <para>
+   As a convenience, FoundationDB can automatically assign coordinators based
+   on the redundancy mode you wish to achieve for the cluster. Once all the
+   nodes have been joined, simply set the replication policy, and then issue
+   the <command>coordinators auto</command> command
+  </para>
+
+  <para>
+   For example, assuming we have 3 nodes available, we can enable double
+   redundancy mode, then auto-select coordinators. For double redundancy, 3
+   coordinators is ideal: therefore FoundationDB will make
+   <emphasis>every</emphasis> node a coordinator automatically:
+  </para>
 
 <programlisting>
 fdbcli> configure double ssd
 fdbcli> coordinators auto
 </programlisting>
 
-<para>This will transparently update all the servers within seconds, and
-appropriately rewrite the <command>fdb.cluster</command> file, as well as
-informing all client processes to do the same.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-connectivity"><title>Client connectivity</title>
-
-<para>By default, all clients must use the current
-<command>fdb.cluster</command> file to access a given FoundationDB cluster.
-This file is located by default in
-<command>/etc/foundationdb/fdb.cluster</command> on all machines with the
-FoundationDB service enabled, so you may copy the active one from your cluster
-to a new node in order to connect, if it is not part of the cluster.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-authorization"><title>Client authorization and TLS</title>
-
-<para>By default, any user who can connect to a FoundationDB process with the
-correct cluster configuration can access anything. FoundationDB uses a
-pluggable design to transport security, and out of the box it supports a
-LibreSSL-based plugin for TLS support. This plugin not only does in-flight
-encryption, but also performs client authorization based on the given
-endpoint's certificate chain. For example, a FoundationDB server may be
-configured to only accept client connections over TLS, where the client TLS
-certificate is from organization <emphasis>Acme Co</emphasis> in the
-<emphasis>Research and Development</emphasis> unit.</para>
-
-<para>Configuring TLS with FoundationDB is done using the
-<option>services.foundationdb.tls</option> options in order to control the peer
-verification string, as well as the certificate and its private key.</para>
-
-<para>Note that the certificate and its private key must be accessible to the
-FoundationDB user account that the server runs under. These files are also NOT
-managed by NixOS, as putting them into the store may reveal private
-information.</para>
-
-<para>After you have a key and certificate file in place, it is not enough to
-simply set the NixOS module options -- you must also configure the
-<command>fdb.cluster</command> file to specify that a given set of coordinators
-use TLS. This is as simple as adding the suffix <command>:tls</command> to your
-cluster coordinator configuration, after the port number. For example, assuming
-you have a coordinator on localhost with the default configuration, simply
-specifying:</para>
+  <para>
+   This will transparently update all the servers within seconds, and
+   appropriately rewrite the <command>fdb.cluster</command> file, as well as
+   informing all client processes to do the same.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-connectivity">
+  <title>Client connectivity</title>
+
+  <para>
+   By default, all clients must use the current <command>fdb.cluster</command>
+   file to access a given FoundationDB cluster. This file is located by default
+   in <command>/etc/foundationdb/fdb.cluster</command> on all machines with the
+   FoundationDB service enabled, so you may copy the active one from your
+   cluster to a new node in order to connect, if it is not part of the cluster.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-authorization">
+  <title>Client authorization and TLS</title>
+
+  <para>
+   By default, any user who can connect to a FoundationDB process with the
+   correct cluster configuration can access anything. FoundationDB uses a
+   pluggable design to transport security, and out of the box it supports a
+   LibreSSL-based plugin for TLS support. This plugin not only does in-flight
+   encryption, but also performs client authorization based on the given
+   endpoint's certificate chain. For example, a FoundationDB server may be
+   configured to only accept client connections over TLS, where the client TLS
+   certificate is from organization <emphasis>Acme Co</emphasis> in the
+   <emphasis>Research and Development</emphasis> unit.
+  </para>
+
+  <para>
+   Configuring TLS with FoundationDB is done using the
+   <option>services.foundationdb.tls</option> options in order to control the
+   peer verification string, as well as the certificate and its private key.
+  </para>
+
+  <para>
+   Note that the certificate and its private key must be accessible to the
+   FoundationDB user account that the server runs under. These files are also
+   NOT managed by NixOS, as putting them into the store may reveal private
+   information.
+  </para>
+
+  <para>
+   After you have a key and certificate file in place, it is not enough to
+   simply set the NixOS module options -- you must also configure the
+   <command>fdb.cluster</command> file to specify that a given set of
+   coordinators use TLS. This is as simple as adding the suffix
+   <command>:tls</command> to your cluster coordinator configuration, after the
+   port number. For example, assuming you have a coordinator on localhost with
+   the default configuration, simply specifying:
+  </para>
 
 <programlisting>
 XXXXXX:XXXXXX@127.0.0.1:4500:tls
 </programlisting>
 
-<para>will configure all clients and server processes to use TLS from now
-on.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-disaster-recovery"><title>Backups and Disaster Recovery</title>
-
-<para>The usual rules for doing FoundationDB backups apply on NixOS as written
-in the FoundationDB manual. However, one important difference is the security
-profile for NixOS: by default, the <command>foundationdb</command> systemd unit
-uses <emphasis>Linux namespaces</emphasis> to restrict write access to the
-system, except for the log directory, data directory, and the
-<command>/etc/foundationdb/</command> directory. This is enforced by default
-and cannot be disabled.</para>
-
-<para>However, a side effect of this is that the <command>fdbbackup</command>
-command doesn't work properly for local filesystem backups: FoundationDB uses a
-server process alongside the database processes to perform backups and copy the
-backups to the filesystem. As a result, this process is put under the
-restricted namespaces above: the backup process can only write to a limited
-number of paths.</para>
-
-<para>In order to allow flexible backup locations on local disks, the
-FoundationDB NixOS module supports a
-<option>services.foundationdb.extraReadWritePaths</option> option. This option
-takes a list of paths, and adds them to the systemd unit, allowing the
-processes inside the service to write (and read) the specified
-directories.</para>
-
-<para>For example, to create backups in <command>/opt/fdb-backups</command>,
-first set up the paths in the module options:</para>
+  <para>
+   will configure all clients and server processes to use TLS from now on.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-disaster-recovery">
+  <title>Backups and Disaster Recovery</title>
+
+  <para>
+   The usual rules for doing FoundationDB backups apply on NixOS as written in
+   the FoundationDB manual. However, one important difference is the security
+   profile for NixOS: by default, the <command>foundationdb</command> systemd
+   unit uses <emphasis>Linux namespaces</emphasis> to restrict write access to
+   the system, except for the log directory, data directory, and the
+   <command>/etc/foundationdb/</command> directory. This is enforced by default
+   and cannot be disabled.
+  </para>
+
+  <para>
+   However, a side effect of this is that the <command>fdbbackup</command>
+   command doesn't work properly for local filesystem backups: FoundationDB
+   uses a server process alongside the database processes to perform backups
+   and copy the backups to the filesystem. As a result, this process is put
+   under the restricted namespaces above: the backup process can only write to
+   a limited number of paths.
+  </para>
+
+  <para>
+   In order to allow flexible backup locations on local disks, the FoundationDB
+   NixOS module supports a
+   <option>services.foundationdb.extraReadWritePaths</option> option. This
+   option takes a list of paths, and adds them to the systemd unit, allowing
+   the processes inside the service to write (and read) the specified
+   directories.
+  </para>
+
+  <para>
+   For example, to create backups in <command>/opt/fdb-backups</command>, first
+   set up the paths in the module options:
+  </para>
 
 <programlisting>
 services.foundationdb.extraReadWritePaths = [ "/opt/fdb-backups" ];
 </programlisting>
 
-<para>Restart the FoundationDB service, and it will now be able to write to
-this directory (even if it does not yet exist.) Note: this path
-<emphasis>must</emphasis> exist before restarting the unit. Otherwise, systemd
-will not include it in the private FoundationDB namespace (and it will not add
-it dynamically at runtime).</para>
+  <para>
+   Restart the FoundationDB service, and it will now be able to write to this
+   directory (even if it does not yet exist.) Note: this path
+   <emphasis>must</emphasis> exist before restarting the unit. Otherwise,
+   systemd will not include it in the private FoundationDB namespace (and it
+   will not add it dynamically at runtime).
+  </para>
 
-<para>You can now perform a backup:</para>
+  <para>
+   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>
-
-</section>
-
-<section xml:id="module-services-foundationdb-limitations"><title>Known limitations</title>
-
-<para>The FoundationDB setup for NixOS should currently be considered beta.
-FoundationDB is not new software, but the NixOS compilation and integration has
-only undergone fairly basic testing of all the available functionality.</para>
-
-<itemizedlist>
-  <listitem><para>There is no way to specify individual parameters for
-      individual <command>fdbserver</command> processes. Currently, all server
-      processes inherit all the global <command>fdbmonitor</command> settings.
-      </para></listitem>
-  <listitem><para>Ruby bindings are not currently installed.</para></listitem>
-  <listitem><para>Go bindings are not currently installed.</para></listitem>
-</itemizedlist>
-
-</section>
-
-<section xml:id="module-services-foundationdb-options"><title>Options</title>
-
-<para>NixOS's FoundationDB module allows you to configure all of the most
-relevant configuration options for <command>fdbmonitor</command>, matching it
-quite closely. A complete list of options for the FoundationDB module may be
-found <link linkend="opt-services.foundationdb.enable">here</link>. You should
-also read the FoundationDB documentation as well.</para>
-
-</section>
-
-<section xml:id="module-services-foundationdb-full-docs"><title>Full documentation</title>
-
-<para>FoundationDB is a complex piece of software, and requires careful
-administration to properly use. Full documentation for administration can be
-found here: <link xlink:href="https://apple.github.io/foundationdb/"/>.</para>
-
-</section>
-
+ </section>
+ <section xml:id="module-services-foundationdb-limitations">
+  <title>Known limitations</title>
+
+  <para>
+   The FoundationDB setup for NixOS should currently be considered beta.
+   FoundationDB is not new software, but the NixOS compilation and integration
+   has only undergone fairly basic testing of all the available functionality.
+  </para>
+
+  <itemizedlist>
+   <listitem>
+    <para>
+     There is no way to specify individual parameters for individual
+     <command>fdbserver</command> processes. Currently, all server processes
+     inherit all the global <command>fdbmonitor</command> settings.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Ruby bindings are not currently installed.
+    </para>
+   </listitem>
+   <listitem>
+    <para>
+     Go bindings are not currently installed.
+    </para>
+   </listitem>
+  </itemizedlist>
+ </section>
+ <section xml:id="module-services-foundationdb-options">
+  <title>Options</title>
+
+  <para>
+   NixOS's FoundationDB module allows you to configure all of the most relevant
+   configuration options for <command>fdbmonitor</command>, matching it quite
+   closely. A complete list of options for the FoundationDB module may be found
+   <link linkend="opt-services.foundationdb.enable">here</link>. You should
+   also read the FoundationDB documentation as well.
+  </para>
+ </section>
+ <section xml:id="module-services-foundationdb-full-docs">
+  <title>Full documentation</title>
+
+  <para>
+   FoundationDB is a complex piece of software, and requires careful
+   administration to properly use. Full documentation for administration can be
+   found here: <link xlink:href="https://apple.github.io/foundationdb/"/>.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/databases/mysql.nix b/nixos/modules/services/databases/mysql.nix
index 0dde9ee6e2e5f..467feb09b3a35 100644
--- a/nixos/modules/services/databases/mysql.nix
+++ b/nixos/modules/services/databases/mysql.nix
@@ -12,26 +12,22 @@ let
     let
       pName = _p: (builtins.parseDrvName (_p.name)).name;
     in pName mysql == pName pkgs.mariadb;
+  isMysqlAtLeast57 =
+    let
+      pName = _p: (builtins.parseDrvName (_p.name)).name;
+    in (pName mysql == pName pkgs.mysql57)
+       && ((builtins.compareVersions mysql.version "5.7") >= 0);
 
   pidFile = "${cfg.pidDir}/mysqld.pid";
 
+  mysqldAndInstallOptions =
+    "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql}";
   mysqldOptions =
-    "--user=${cfg.user} --datadir=${cfg.dataDir} --basedir=${mysql} " +
-    "--pid-file=${pidFile}";
-
-  myCnf = pkgs.writeText "my.cnf"
-  ''
-    [mysqld]
-    port = ${toString cfg.port}
-    ${optionalString (cfg.bind != null) "bind-address = ${cfg.bind}" }
-    ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "log-bin=mysql-bin"}
-    ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "server-id = ${toString cfg.replication.serverId}"}
-    ${optionalString (cfg.ensureUsers != [])
-    ''
-      plugin-load-add = auth_socket.so
-    ''}
-    ${cfg.extraOptions}
-  '';
+    "${mysqldAndInstallOptions} --pid-file=${pidFile}";
+  # For MySQL 5.7+, --insecure creates the root user without password
+  # (earlier versions and MariaDB do this by default).
+  installOptions =
+    "${mysqldAndInstallOptions} ${lib.optionalString isMysqlAtLeast57 "--insecure"}";
 
 in
 
@@ -147,7 +143,7 @@ in
           option is changed. This means that users created and permissions assigned once through this option or
           otherwise have to be removed manually.
         '';
-        example = [
+        example = literalExample ''[
           {
             name = "nextcloud";
             ensurePermissions = {
@@ -160,7 +156,7 @@ in
               "*.*" = "SELECT, LOCK TABLES";
             };
           }
-        ];
+        ]'';
       };
 
       # FIXME: remove this option; it's a really bad idea.
@@ -231,6 +227,21 @@ in
 
     environment.systemPackages = [mysql];
 
+    environment.etc."my.cnf".text =
+    ''
+      [mysqld]
+      port = ${toString cfg.port}
+      datadir = ${cfg.dataDir}
+      ${optionalString (cfg.bind != null) "bind-address = ${cfg.bind}" }
+      ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "log-bin=mysql-bin"}
+      ${optionalString (cfg.replication.role == "master" || cfg.replication.role == "slave") "server-id = ${toString cfg.replication.serverId}"}
+      ${optionalString (cfg.ensureUsers != [])
+      ''
+        plugin-load-add = auth_socket.so
+      ''}
+      ${cfg.extraOptions}
+    '';
+
     systemd.services.mysql = let
       hasNotify = (cfg.package == pkgs.mariadb);
     in {
@@ -238,6 +249,7 @@ in
 
         after = [ "network.target" ];
         wantedBy = [ "multi-user.target" ];
+        restartTriggers = [ config.environment.etc."my.cnf".source ];
 
         unitConfig.RequiresMountsFor = "${cfg.dataDir}";
 
@@ -252,7 +264,7 @@ in
             if ! test -e ${cfg.dataDir}/mysql; then
                 mkdir -m 0700 -p ${cfg.dataDir}
                 chown -R ${cfg.user} ${cfg.dataDir}
-                ${mysql}/bin/mysql_install_db ${mysqldOptions}
+                ${mysql}/bin/mysql_install_db --defaults-file=/etc/my.cnf ${installOptions}
                 touch /tmp/mysql_init
             fi
 
@@ -263,7 +275,8 @@ in
         serviceConfig = {
           Type = if hasNotify then "notify" else "simple";
           RuntimeDirectory = "mysqld";
-          ExecStart = "${mysql}/bin/mysqld --defaults-extra-file=${myCnf} ${mysqldOptions}";
+          # 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 = ''
@@ -351,7 +364,7 @@ in
             ${optionalString (cfg.ensureDatabases != []) ''
               (
               ${concatMapStrings (database: ''
-                echo "CREATE DATABASE IF NOT EXISTS ${database};"
+                echo "CREATE DATABASE IF NOT EXISTS \`${database}\`;"
               '') cfg.ensureDatabases}
               ) | ${mysql}/bin/mysql -u root -N
             ''}
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index 9f2bf5ef8a9cc..e996211be7da2 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -54,6 +54,13 @@ in
         description = "The database directory.";
       };
 
+      logLevel = mkOption {
+        type = types.str;
+        default = "0";
+        example = "acl trace";
+        description = "The log level selector of slapd.";
+      };
+
       configDir = mkOption {
         type = types.nullOr types.path;
         default = null;
@@ -139,7 +146,7 @@ in
         chown -R "${cfg.user}:${cfg.group}" "${cfg.dataDir}"
       '';
       serviceConfig.ExecStart =
-        "${openldap.out}/libexec/slapd -d 0 " +
+        "${openldap.out}/libexec/slapd -d ${cfg.logLevel} " +
           "-u '${cfg.user}' -g '${cfg.group}' " +
           "-h '${concatStringsSep " " cfg.urlList}' " +
           "${configOpts}";
diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index f59fb1c817726..aeab445a99831 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -55,7 +55,7 @@ in
 
       package = mkOption {
         type = types.package;
-        example = literalExample "pkgs.postgresql96";
+        example = literalExample "pkgs.postgresql_9_6";
         description = ''
           PostgreSQL package to use.
         '';
@@ -118,7 +118,7 @@ in
       extraPlugins = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ (pkgs.postgis.override { postgresql = pkgs.postgresql94; }) ]";
+        example = literalExample "[ (pkgs.postgis.override { postgresql = pkgs.postgresql_9_4; }) ]";
         description = ''
           When this list contains elements a new store path is created.
           PostgreSQL and the elements are symlinked into it. Then pg_config,
@@ -167,9 +167,9 @@ in
       # Note: when changing the default, make it conditional on
       # ‘system.stateVersion’ to maintain compatibility with existing
       # systems!
-      mkDefault (if versionAtLeast config.system.stateVersion "17.09" then pkgs.postgresql96
-            else if versionAtLeast config.system.stateVersion "16.03" then pkgs.postgresql95
-            else pkgs.postgresql94);
+      mkDefault (if versionAtLeast config.system.stateVersion "17.09" then pkgs.postgresql_9_6
+            else if versionAtLeast config.system.stateVersion "16.03" then pkgs.postgresql_9_5
+            else pkgs.postgresql_9_4);
 
     services.postgresql.dataDir =
       mkDefault (if versionAtLeast config.system.stateVersion "17.09" then "/var/lib/postgresql/${config.services.postgresql.package.psqlSchema}"
@@ -188,6 +188,8 @@ in
         uid = config.ids.uids.postgres;
         group = "postgres";
         description = "PostgreSQL server user";
+        home = "${cfg.dataDir}";
+        useDefaultShell = true;
       };
 
     users.groups.postgres.gid = config.ids.gids.postgres;
@@ -236,6 +238,9 @@ in
             User = "postgres";
             Group = "postgres";
             PermissionsStartOnly = true;
+            Type = if lib.versionAtLeast cfg.package.version "9.6"
+                   then "notify"
+                   else "simple";
 
             # Shut down Postgres using SIGINT ("Fast Shutdown mode").  See
             # http://www.postgresql.org/docs/current/static/server-shutdown.html
@@ -269,5 +274,5 @@ in
   };
 
   meta.doc = ./postgresql.xml;
-
+  meta.maintainers = with lib.maintainers; [ thoughtpolice ];
 }
diff --git a/nixos/modules/services/databases/postgresql.xml b/nixos/modules/services/databases/postgresql.xml
index 1aaf339632454..14f4d4909bc0c 100644
--- a/nixos/modules/services/databases/postgresql.xml
+++ b/nixos/modules/services/databases/postgresql.xml
@@ -3,36 +3,39 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-postgresql">
-
-<title>PostgreSQL</title>
-
+ <title>PostgreSQL</title>
 <!-- FIXME: render nicely -->
-
 <!-- FIXME: source can be added automatically -->
-<para><emphasis>Source:</emphasis> <filename>modules/services/databases/postgresql.nix</filename></para>
-
-<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="http://www.postgresql.org/docs/"/></para>
-
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/services/databases/postgresql.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="http://www.postgresql.org/docs/"/>
+ </para>
 <!-- FIXME: more stuff, like maintainer? -->
-
-<para>PostgreSQL is an advanced, free relational database.<!-- MORE --></para>
-
-<section xml:id="module-services-postgres-configuring"><title>Configuring</title>
-
-<para>To enable PostgreSQL, add the following to your
-<filename>configuration.nix</filename>:
-
+ <para>
+  PostgreSQL is an advanced, free relational database.
+<!-- MORE -->
+ </para>
+ <section xml:id="module-services-postgres-configuring">
+  <title>Configuring</title>
+
+  <para>
+   To enable PostgreSQL, add the following to your
+   <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-services.postgresql.enable"/> = true;
-<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql94;
+<xref linkend="opt-services.postgresql.package"/> = pkgs.postgresql_9_4;
 </programlisting>
-
-Note that you are required to specify the desired version of
-PostgreSQL (e.g. <literal>pkgs.postgresql94</literal>). Since
-upgrading your PostgreSQL version requires a database dump and reload
-(see below), NixOS cannot provide a default value for
-<xref linkend="opt-services.postgresql.package"/> such as the most recent
-release of PostgreSQL.</para>
+   Note that you are required to specify the desired version of PostgreSQL
+   (e.g. <literal>pkgs.postgresql_9_4</literal>). Since upgrading your
+   PostgreSQL version requires a database dump and reload (see below), NixOS
+   cannot provide a default value for
+   <xref linkend="opt-services.postgresql.package"/> such as the most recent
+   release of PostgreSQL.
+  </para>
 
 <!--
 <para>After running <command>nixos-rebuild</command>, you can verify
@@ -47,31 +50,28 @@ alice=>
 </screen>
 -->
 
-<para>By default, PostgreSQL stores its databases in
-<filename>/var/db/postgresql</filename>. You can override this using
-<xref linkend="opt-services.postgresql.dataDir"/>, e.g.
-
+  <para>
+   By default, PostgreSQL stores its databases in
+   <filename>/var/db/postgresql</filename>. You can override this using
+   <xref linkend="opt-services.postgresql.dataDir"/>, e.g.
 <programlisting>
 <xref linkend="opt-services.postgresql.dataDir"/> = "/data/postgresql";
 </programlisting>
-
-</para>
-
-</section>
-
-
-<section xml:id="module-services-postgres-upgrading"><title>Upgrading</title>
-
-<para>FIXME: document dump/upgrade/load cycle.</para>
-
-</section>
-
-
-<section xml:id="module-services-postgres-options"><title>Options</title>
-
-  <para>A complete list of options for the PostgreSQL module may be found <link linkend="opt-services.postgresql.enable">here</link>.</para>
-
-</section>
-
-
+  </para>
+ </section>
+ <section xml:id="module-services-postgres-upgrading">
+  <title>Upgrading</title>
+
+  <para>
+   FIXME: document dump/upgrade/load cycle.
+  </para>
+ </section>
+ <section xml:id="module-services-postgres-options">
+  <title>Options</title>
+
+  <para>
+   A complete list of options for the PostgreSQL module may be found
+   <link linkend="opt-services.postgresql.enable">here</link>.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/desktops/accountsservice.nix b/nixos/modules/services/desktops/accountsservice.nix
index 933b9da2c83c1..c48036a99e8fb 100644
--- a/nixos/modules/services/desktops/accountsservice.nix
+++ b/nixos/modules/services/desktops/accountsservice.nix
@@ -39,14 +39,14 @@ with lib;
 
     systemd.packages = [ pkgs.accountsservice ];
 
-    systemd.services.accounts-daemon = {
+    systemd.services.accounts-daemon = recursiveUpdate {
 
       wantedBy = [ "graphical.target" ];
 
       # Accounts daemon looks for dbus interfaces in $XDG_DATA_DIRS/accountsservice
       environment.XDG_DATA_DIRS = "${config.system.path}/share";
 
-    } // (optionalAttrs (!config.users.mutableUsers) {
+    } (optionalAttrs (!config.users.mutableUsers) {
       environment.NIXOS_USERS_PURE = "true";
     });
   };
diff --git a/nixos/modules/services/desktops/flatpak.xml b/nixos/modules/services/desktops/flatpak.xml
index d9c8b711c450b..8045d5fa14f8b 100644
--- a/nixos/modules/services/desktops/flatpak.xml
+++ b/nixos/modules/services/desktops/flatpak.xml
@@ -3,51 +3,54 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-flatpak">
-
-<title>Flatpak</title>
-
-<para><emphasis>Source:</emphasis> <filename>modules/services/desktop/flatpak.nix</filename></para>
-
-<para><emphasis>Upstream documentation:</emphasis> <link xlink:href="https://github.com/flatpak/flatpak/wiki"/></para>
-
-<para>Flatpak is a system for building, distributing, and running sandboxed desktop applications on Linux.</para>
-
-<para>
-  To enable Flatpak, add the following to your <filename>configuration.nix</filename>:
-
-  <programlisting>
+ <title>Flatpak</title>
+ <para>
+  <emphasis>Source:</emphasis>
+  <filename>modules/services/desktop/flatpak.nix</filename>
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://github.com/flatpak/flatpak/wiki"/>
+ </para>
+ <para>
+  Flatpak is a system for building, distributing, and running sandboxed desktop
+  applications on Linux.
+ </para>
+ <para>
+  To enable Flatpak, add the following to your
+  <filename>configuration.nix</filename>:
+<programlisting>
   <xref linkend="opt-services.flatpak.enable"/> = true;
   </programlisting>
-</para>
-
-<para>
-  For the sandboxed apps to work correctly, desktop integration portals need to be installed. If you run GNOME, this will be handled automatically for you; in other cases, you will need to add something like the following to your <filename>configuration.nix</filename>:
-
-  <programlisting>
+ </para>
+ <para>
+  For the sandboxed apps to work correctly, desktop integration portals need to
+  be installed. If you run GNOME, this will be handled automatically for you;
+  in other cases, you will need to add something like the following to your
+  <filename>configuration.nix</filename>:
+<programlisting>
   <xref linkend="opt-services.flatpak.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>
+ </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>
-
-  or by opening the <link xlink:href="https://flathub.org/repo/flathub.flatpakrepo">repository file</link> in GNOME Software.
-</para>
-
-<para>
+  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>
+<programlisting>
   flatpak search bustle
   flatpak install flathub org.freedesktop.Bustle
   flatpak run org.freedesktop.Bustle
   </programlisting>
-
   Again, GNOME Software offers graphical interface for these tasks.
-</para>
+ </para>
 </chapter>
diff --git a/nixos/modules/services/desktops/geoclue2.nix b/nixos/modules/services/desktops/geoclue2.nix
index dafb0af207567..840aa5294ed0d 100644
--- a/nixos/modules/services/desktops/geoclue2.nix
+++ b/nixos/modules/services/desktops/geoclue2.nix
@@ -61,6 +61,8 @@ in
         wantedBy = [ "default.target" ];
       };
     };
+
+    environment.etc."geoclue/geoclue.conf".source = "${package}/etc/geoclue/geoclue.conf";
   };
 
 }
diff --git a/nixos/modules/services/desktops/gnome3/file-roller.nix b/nixos/modules/services/desktops/gnome3/file-roller.nix
new file mode 100644
index 0000000000000..7fb558a98953f
--- /dev/null
+++ b/nixos/modules/services/desktops/gnome3/file-roller.nix
@@ -0,0 +1,32 @@
+# File Roller.
+
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.gnome3.file-roller = {
+
+      enable = mkEnableOption "File Roller, an archive manager for GNOME";
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf config.services.gnome3.file-roller.enable {
+
+    environment.systemPackages = [ pkgs.gnome3.file-roller ];
+
+    services.dbus.packages = [ pkgs.gnome3.file-roller ];
+
+  };
+
+}
diff --git a/nixos/modules/services/desktops/gnome3/gnome-keyring.nix b/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
index aa1165ab3bba6..5ea4350be5b42 100644
--- a/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
+++ b/nixos/modules/services/desktops/gnome3/gnome-keyring.nix
@@ -33,7 +33,7 @@ with lib;
 
     environment.systemPackages = [ pkgs.gnome3.gnome-keyring ];
 
-    services.dbus.packages = [ pkgs.gnome3.gnome-keyring pkgs.gnome3.gcr ];
+    services.dbus.packages = [ pkgs.gnome3.gnome-keyring pkgs.gcr ];
 
   };
 
diff --git a/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix b/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix
new file mode 100644
index 0000000000000..021f4f9534b42
--- /dev/null
+++ b/nixos/modules/services/desktops/gnome3/gnome-remote-desktop.nix
@@ -0,0 +1,18 @@
+# Remote desktop daemon using Pipewire.
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+  ###### interface
+  options = {
+    services.gnome3.gnome-remote-desktop = {
+      enable = mkEnableOption "Remote Desktop support using Pipewire";
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.services.gnome3.gnome-remote-desktop.enable {
+    systemd.packages = [ pkgs.gnome3.gnome-remote-desktop ];
+  };
+}
diff --git a/nixos/modules/services/desktops/gnome3/seahorse.nix b/nixos/modules/services/desktops/gnome3/seahorse.nix
index e9ad738269e43..9631157934f97 100644
--- a/nixos/modules/services/desktops/gnome3/seahorse.nix
+++ b/nixos/modules/services/desktops/gnome3/seahorse.nix
@@ -29,7 +29,7 @@ with lib;
 
   config = mkIf config.services.gnome3.seahorse.enable {
 
-    environment.systemPackages = [ pkgs.gnome3.seahorse ];
+    environment.systemPackages = [ pkgs.gnome3.seahorse pkgs.gnome3.dconf ];
 
     services.dbus.packages = [ pkgs.gnome3.seahorse ];
 
diff --git a/nixos/modules/services/desktops/gsignond.nix b/nixos/modules/services/desktops/gsignond.nix
new file mode 100644
index 0000000000000..cf26e05d5c18b
--- /dev/null
+++ b/nixos/modules/services/desktops/gsignond.nix
@@ -0,0 +1,43 @@
+# Accounts-SSO gSignOn daemon
+
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  package = pkgs.gsignond.override { plugins = config.services.gsignond.plugins; };
+in
+{
+
+  ###### interface
+
+  options = {
+
+    services.gsignond = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable gSignOn daemon, a DBus service
+          which performs user authentication on behalf of its clients.
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.listOf types.package;
+        default = [];
+        description = ''
+          What plugins to use with the gSignOn daemon.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+  config = mkIf config.services.gsignond.enable {
+    environment.etc."gsignond.conf".source = "${package}/etc/gsignond.conf";
+    services.dbus.packages = [ package ];
+  };
+
+}
diff --git a/nixos/modules/services/desktops/profile-sync-daemon.nix b/nixos/modules/services/desktops/profile-sync-daemon.nix
index e3f74df3e573e..e4e47cfbd4383 100644
--- a/nixos/modules/services/desktops/profile-sync-daemon.nix
+++ b/nixos/modules/services/desktops/profile-sync-daemon.nix
@@ -4,22 +4,7 @@ with lib;
 
 let
   cfg = config.services.psd;
-
-  configFile = ''
-    ${optionalString (cfg.users != [ ]) ''
-      USERS="${concatStringsSep " " cfg.users}"
-    ''}
-
-    ${optionalString (cfg.browsers != [ ]) ''
-      BROWSERS="${concatStringsSep " " cfg.browsers}"
-    ''}
-
-    ${optionalString (cfg.volatile != "") "VOLATILE=${cfg.volatile}"}
-    ${optionalString (cfg.daemonFile != "") "DAEMON_FILE=${cfg.daemonFile}"}
-  '';
-
 in {
-
   options.services.psd = with types; {
     enable = mkOption {
       type = bool;
@@ -28,32 +13,6 @@ in {
         Whether to enable the Profile Sync daemon.
       '';
     };
-
-    users = mkOption {
-      type = listOf str;
-      default = [ ];
-      example = [ "demo" ];
-      description = ''
-        A list of users whose browser profiles should be sync'd to tmpfs.
-      '';
-    };
-
-    browsers = mkOption {
-      type = listOf str;
-      default = [ ];
-      example = [ "chromium" "firefox" ];
-      description = ''
-        A list of browsers to sync. Available choices are:
-
-        chromium chromium-dev conkeror.mozdev.org epiphany firefox
-        firefox-trunk google-chrome google-chrome-beta google-chrome-unstable
-        heftig-aurora icecat luakit midori opera opera-developer opera-beta
-        qupzilla palemoon rekonq seamonkey
-
-        An empty list will enable all browsers.
-      '';
-    };
-
     resyncTimer = mkOption {
       type = str;
       default = "1h";
@@ -66,80 +25,53 @@ in {
         omitted.
       '';
     };
-
-    volatile = mkOption {
-      type = str;
-      default = "/run/psd-profiles";
-      description = ''
-        The directory where browser profiles should reside(this should be
-        mounted as a tmpfs). Do not include a trailing backslash.
-      '';
-    };
-
-    daemonFile = mkOption {
-      type = str;
-      default = "/run/psd";
-      description = ''
-        Where the pid and backup configuration files will be stored.
-      '';
-    };
   };
 
   config = mkIf cfg.enable {
-    assertions = [
-      { assertion = cfg.users != [];
-        message = "services.psd.users must contain at least one user";
-      }
-    ];
-
     systemd = {
-      services = {
-        psd = {
-          description = "Profile Sync daemon";
-          wants = [ "psd-resync.service" "local-fs.target" ];
-          wantedBy = [ "multi-user.target" ];
-          preStart = "mkdir -p ${cfg.volatile}";
-
-          path = with pkgs; [ glibc rsync gawk ];
-
-          unitConfig = {
-            RequiresMountsFor = [ "/home/" ];
+      user = {
+        services = {
+          psd = {
+            enable = true;
+            description = "Profile Sync daemon";
+            wants = [ "psd-resync.service" "local-fs.target" ];
+            wantedBy = [ "default.target" ];
+            path = with pkgs; [ rsync kmod gawk nettools utillinux profile-sync-daemon ];
+            unitConfig = {
+              RequiresMountsFor = [ "/home/" ];
+            };
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = "yes";
+              ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon sync";
+              ExecStop = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon unsync";
+            };
           };
 
-          serviceConfig = {
-            Type = "oneshot";
-            RemainAfterExit = "yes";
-            ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon sync";
-            ExecStop = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon unsync";
+          psd-resync = {
+            enable = true;
+            description = "Timed profile resync";
+            after = [ "psd.service" ];
+            wants = [ "psd-resync.timer" ];
+            partOf = [ "psd.service" ];
+            wantedBy = [ "default.target" ];
+            path = with pkgs; [ rsync kmod gawk nettools utillinux profile-sync-daemon ];
+            serviceConfig = {
+              Type = "oneshot";
+              ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon resync";
+            };
           };
         };
 
-        psd-resync = {
-          description = "Timed profile resync";
-          after = [ "psd.service" ];
-          wants = [ "psd-resync.timer" ];
-          partOf = [ "psd.service" ];
-
-          path = with pkgs; [ glibc rsync gawk ];
+        timers.psd-resync = {
+          description = "Timer for profile sync daemon - ${cfg.resyncTimer}";
+          partOf = [ "psd-resync.service" "psd.service" ];
 
-          serviceConfig = {
-            Type = "oneshot";
-            ExecStart = "${pkgs.profile-sync-daemon}/bin/profile-sync-daemon resync";
+          timerConfig = {
+            OnUnitActiveSec = "${cfg.resyncTimer}";
           };
         };
       };
-
-      timers.psd-resync = {
-        description = "Timer for profile sync daemon - ${cfg.resyncTimer}";
-        partOf = [ "psd-resync.service" "psd.service" ];
-
-        timerConfig = {
-          OnUnitActiveSec = "${cfg.resyncTimer}";
-        };
-      };
     };
-
-    environment.etc."psd.conf".text = configFile;
-
   };
 }
diff --git a/nixos/modules/services/development/jupyter/default.nix b/nixos/modules/services/development/jupyter/default.nix
index 9fcc004318654..f20860af6e128 100644
--- a/nixos/modules/services/development/jupyter/default.nix
+++ b/nixos/modules/services/development/jupyter/default.nix
@@ -145,6 +145,7 @@ in {
       systemd.services.jupyter = {
         description = "Jupyter development server";
 
+        after = [ "network.target" ];
         wantedBy = [ "multi-user.target" ];
 
         # TODO: Patch notebook so we can explicitly pass in a shell
diff --git a/nixos/modules/services/editors/emacs.xml b/nixos/modules/services/editors/emacs.xml
index 94eb2e6a77bf5..1ac53c818a7f7 100644
--- a/nixos/modules/services/editors/emacs.xml
+++ b/nixos/modules/services/editors/emacs.xml
@@ -3,150 +3,148 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-emacs">
-
-  <title>Emacs</title>
-
-  <!--
+ <title>Emacs</title>
+<!--
     Documentation contributors:
       Damien Cassou @DamienCassou
       Thomas Tuegel @ttuegel
       Rodney Lorrimar @rvl
   -->
+ <para>
+  <link xlink:href="https://www.gnu.org/software/emacs/">Emacs</link> is an
+  extensible, customizable, self-documenting real-time display editor — and
+  more. At its core is an interpreter for Emacs Lisp, a dialect of the Lisp
+  programming language with extensions to support text editing.
+ </para>
+ <para>
+  Emacs runs within a graphical desktop environment using the X Window System,
+  but works equally well on a text terminal. Under
+  <productname>macOS</productname>, a "Mac port" edition is available, which
+  uses Apple's native GUI frameworks.
+ </para>
+ <para>
+  <productname>Nixpkgs</productname> provides a superior environment for
+  running <application>Emacs</application>. It's simple to create custom builds
+  by overriding the default packages. Chaotic collections of Emacs Lisp code
+  and extensions can be brought under control using declarative package
+  management. <productname>NixOS</productname> even provides a
+  <command>systemd</command> user service for automatically starting the Emacs
+  daemon.
+ </para>
+ <section xml:id="module-services-emacs-installing">
+  <title>Installing <application>Emacs</application></title>
 
   <para>
-    <link xlink:href="http://www.gnu.org/software/emacs/">Emacs</link>
-    is an extensible, customizable, self-documenting real-time display
-    editor — and more. At its core is an interpreter for Emacs Lisp, a
-    dialect of the Lisp programming language with extensions to
-    support text editing.
+   Emacs can be installed in the normal way for Nix (see
+   <xref linkend="sec-package-management" />). In addition, a NixOS
+   <emphasis>service</emphasis> can be enabled.
   </para>
 
-  <para>
-    Emacs runs within a graphical desktop environment using the X
-    Window System, but works equally well on a text terminal. Under
-    <productname>macOS</productname>, a "Mac port" edition is
-    available, which uses Apple's native GUI frameworks.
-  </para>
+  <section xml:id="module-services-emacs-releases">
+   <title>The Different Releases of Emacs</title>
+
+   <para>
+    <productname>Nixpkgs</productname> defines several basic Emacs packages.
+    The following are attributes belonging to the <varname>pkgs</varname> set:
+    <variablelist>
+     <varlistentry>
+      <term>
+       <varname>emacs</varname>
+      </term>
+      <term>
+       <varname>emacs25</varname>
+      </term>
+      <listitem>
+       <para>
+        The latest stable version of Emacs 25 using the
+        <link
+                xlink:href="http://www.gtk.org">GTK+ 2</link>
+        widget toolkit.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term>
+       <varname>emacs25-nox</varname>
+      </term>
+      <listitem>
+       <para>
+        Emacs 25 built without any dependency on X11 libraries.
+       </para>
+      </listitem>
+     </varlistentry>
+     <varlistentry>
+      <term>
+       <varname>emacsMacport</varname>
+      </term>
+      <term>
+       <varname>emacs25Macport</varname>
+      </term>
+      <listitem>
+       <para>
+        Emacs 25 with the "Mac port" patches, providing a more native look and
+        feel under macOS.
+       </para>
+      </listitem>
+     </varlistentry>
+    </variablelist>
+   </para>
+
+   <para>
+    If those aren't suitable, then the following imitation Emacs editors are
+    also available in Nixpkgs:
+    <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>,
+    <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>,
+    <link xlink:href="http://yi-editor.github.io/">Yi</link>.
+   </para>
+  </section>
 
-  <para>
-    <productname>Nixpkgs</productname> provides a superior environment
-    for running <application>Emacs</application>. It's simple to
-    create custom builds by overriding the default packages. Chaotic
-    collections of Emacs Lisp code and extensions can be brought under
-    control using declarative package
-    management. <productname>NixOS</productname> even provides a
-    <command>systemd</command> user service for automatically
-    starting the Emacs daemon.
-  </para>
+  <section xml:id="module-services-emacs-adding-packages">
+   <title>Adding Packages to Emacs</title>
 
-  <section xml:id="module-services-emacs-installing">
-    <title>Installing <application>Emacs</application></title>
+   <para>
+    Emacs includes an entire ecosystem of functionality beyond text editing,
+    including a project planner, mail and news reader, debugger interface,
+    calendar, and more.
+   </para>
 
+   <para>
+    Most extensions are gotten with the Emacs packaging system
+    (<filename>package.el</filename>) from
+    <link
+        xlink:href="https://elpa.gnu.org/">Emacs Lisp Package Archive
+    (<acronym>ELPA</acronym>)</link>,
+    <link xlink:href="https://melpa.org/"><acronym>MELPA</acronym></link>,
+    <link xlink:href="https://stable.melpa.org/">MELPA Stable</link>, and
+    <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>. Nixpkgs is
+    regularly updated to mirror all these archives.
+   </para>
+
+   <para>
+    Under NixOS, you can continue to use
+    <function>package-list-packages</function> and
+    <function>package-install</function> to install packages. You can also
+    declare the set of Emacs packages you need using the derivations from
+    Nixpkgs. The rest of this section discusses declarative installation of
+    Emacs packages through nixpkgs.
+   </para>
+
+   <note>
     <para>
-      Emacs can be installed in the normal way for Nix (see
-      <xref linkend="sec-package-management" />).
-      In addition, a NixOS <emphasis>service</emphasis>
-      can be enabled.
+     This documentation describes the new Emacs packages framework in NixOS
+     16.03 (<varname>emacsPackagesNg</varname>) which should not be confused
+     with the previous and deprecated framework
+     (<varname>emacs24Packages</varname>).
     </para>
-
-    <section xml:id="module-services-emacs-releases">
-      <title>The Different Releases of Emacs</title>
-
-      <para>
-        <productname>Nixpkgs</productname> defines several basic Emacs
-        packages. The following are attributes belonging to the
-        <varname>pkgs</varname> set:
-
-        <variablelist>
-          <varlistentry>
-            <term><varname>emacs</varname></term>
-            <term><varname>emacs25</varname></term>
-            <listitem>
-              <para>
-                The latest stable version of Emacs 25 using the <link
-                xlink:href="http://www.gtk.org">GTK+ 2</link> widget
-                toolkit.
-              </para>
-            </listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><varname>emacs25-nox</varname></term>
-            <listitem>
-              <para>
-                Emacs 25 built without any dependency on X11
-                libraries.
-              </para>
-            </listitem>
-          </varlistentry>
-          <varlistentry>
-            <term><varname>emacsMacport</varname></term>
-            <term><varname>emacs25Macport</varname></term>
-            <listitem>
-              <para>
-                Emacs 25 with the "Mac port" patches, providing a more
-                native look and feel under macOS.
-              </para>
-            </listitem>
-          </varlistentry>
-        </variablelist>
-      </para>
-
-      <para>
-        If those aren't suitable, then the following imitation Emacs
-        editors are also available in Nixpkgs:
-        <link xlink:href="https://www.gnu.org/software/zile/">Zile</link>,
-        <link xlink:href="http://homepage.boetes.org/software/mg/">mg</link>,
-        <link xlink:href="http://yi-editor.github.io/">Yi</link>.
-      </para>
-
-    </section>
-    <section xml:id="module-services-emacs-adding-packages">
-      <title>Adding Packages to Emacs</title>
-      <para>
-        Emacs includes an entire ecosystem of functionality beyond
-        text editing, including a project planner, mail and news
-        reader, debugger interface, calendar, and more.
-      </para>
-
-      <para>
-        Most extensions are gotten with the Emacs packaging system
-        (<filename>package.el</filename>) from <link
-        xlink:href="https://elpa.gnu.org/">Emacs Lisp Package Archive
-        (<acronym>ELPA</acronym>)</link>,
-        <link xlink:href="https://melpa.org/"><acronym>MELPA</acronym></link>,
-        <link xlink:href="https://stable.melpa.org/">MELPA Stable</link>,
-        and <link xlink:href="http://orgmode.org/elpa.html">Org ELPA</link>.
-        Nixpkgs is regularly updated to mirror all these archives.
-      </para>
-
-      <para>
-        Under NixOS, you can continue to use
-        <function>package-list-packages</function> and
-        <function>package-install</function> to install packages. You
-        can also declare the set of Emacs packages you need using the
-        derivations from Nixpkgs. The rest of this section discusses
-        declarative installation of Emacs packages through nixpkgs.
-      </para>
-
-      <note>
-        <para>
-          This documentation describes the new Emacs packages
-          framework in NixOS 16.03
-          (<varname>emacsPackagesNg</varname>) which should not be
-          confused with the previous and deprecated framework
-          (<varname>emacs24Packages</varname>).
-        </para>
-      </note>
-
-      <para>
-        The first step to declare the list of packages you want in
-        your Emacs installation is to create a dedicated
-        derivation. This can be done in a dedicated
-        <filename>emacs.nix</filename> file such as:
-
-      <example xml:id="ex-emacsNix">
-        <title>Nix expression to build Emacs with packages (<filename>emacs.nix</filename>)</title>
-        <programlisting language="nix">
+   </note>
+
+   <para>
+    The first step to declare the list of packages you want in your Emacs
+    installation is to create a dedicated derivation. This can be done in a
+    dedicated <filename>emacs.nix</filename> file such as:
+    <example xml:id="ex-emacsNix">
+     <title>Nix expression to build Emacs with packages (<filename>emacs.nix</filename>)</title>
+<programlisting language="nix">
 /*
 This is a nix expression to build Emacs and some Emacs packages I like
 from source on any distribution where Nix is installed. This will install
@@ -181,119 +179,104 @@ in
     pkgs.notmuch   # From main packages set <co xml:id="ex-emacsNix-7" />
   ])
 </programlisting>
-      </example>
-
-      <calloutlist>
-        <callout arearefs="ex-emacsNix-1">
-          <para>
-            The first non-comment line in this file
-            (<literal>{ pkgs ? ... }</literal>)
-            indicates that the whole file represents a function.
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-2">
-          <para>
-            The <varname>let</varname> expression below defines a
-            <varname>myEmacs</varname> binding pointing to the current
-            stable version of Emacs. This binding is here to separate the
-            choice of the Emacs binary from the specification of the
-            required packages.
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-3">
-          <para>
-            This generates an <varname>emacsWithPackages</varname>
-            function. It takes a single argument: a function from a
-            package set to a list of packages (the packages that will
-            be available in Emacs).
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-4">
-          <para>
-            The rest of the file specifies the list of packages to
-            install. In the example, two packages
-            (<varname>magit</varname> and
-            <varname>zerodark-theme</varname>) are taken from MELPA
-            stable.
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-5">
-          <para>
-            Two packages (<varname>undo-tree</varname> and
-            <varname>zoom-frm</varname>) are taken from MELPA.
-          </para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-6">
-          <para>Three packages are taken from GNU ELPA.</para>
-        </callout>
-
-        <callout arearefs="ex-emacsNix-7">
-          <para>
-            <varname>notmuch</varname> is taken from a nixpkgs derivation
-            which contains an Emacs mode.
-          </para>
-        </callout>
-
-      </calloutlist>
+    </example>
+    <calloutlist>
+     <callout arearefs="ex-emacsNix-1">
+      <para>
+       The first non-comment line in this file (<literal>{ pkgs ? ...
+       }</literal>) indicates that the whole file represents a function.
       </para>
-
+     </callout>
+     <callout arearefs="ex-emacsNix-2">
       <para>
-        The result of this configuration will be an
-        <command>emacs</command> command which launches Emacs with all
-        of your chosen packages in the <varname>load-path</varname>.
+       The <varname>let</varname> expression below defines a
+       <varname>myEmacs</varname> binding pointing to the current stable
+       version of Emacs. This binding is here to separate the choice of the
+       Emacs binary from the specification of the required packages.
       </para>
-
+     </callout>
+     <callout arearefs="ex-emacsNix-3">
       <para>
-        You can check that it works by executing this in a terminal:
-
+       This generates an <varname>emacsWithPackages</varname> function. It
+       takes a single argument: a function from a package set to a list of
+       packages (the packages that will be available in Emacs).
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-4">
+      <para>
+       The rest of the file specifies the list of packages to install. In the
+       example, two packages (<varname>magit</varname> and
+       <varname>zerodark-theme</varname>) are taken from MELPA stable.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-5">
+      <para>
+       Two packages (<varname>undo-tree</varname> and
+       <varname>zoom-frm</varname>) are taken from MELPA.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-6">
+      <para>
+       Three packages are taken from GNU ELPA.
+      </para>
+     </callout>
+     <callout arearefs="ex-emacsNix-7">
+      <para>
+       <varname>notmuch</varname> is taken from a nixpkgs derivation which
+       contains an Emacs mode.
+      </para>
+     </callout>
+    </calloutlist>
+   </para>
+
+   <para>
+    The result of this configuration will be an <command>emacs</command>
+    command which launches Emacs with all of your chosen packages in the
+    <varname>load-path</varname>.
+   </para>
+
+   <para>
+    You can check that it works by executing this in a terminal:
 <screen>
 $ nix-build emacs.nix
 $ ./result/bin/emacs -q
 </screen>
+    and then typing <literal>M-x package-initialize</literal>. Check that you
+    can use all the packages you want in this Emacs instance. For example, try
+    switching to the zerodark theme through <literal>M-x load-theme &lt;RET&gt;
+    zerodark &lt;RET&gt; y</literal>.
+   </para>
 
-        and then typing <literal>M-x package-initialize</literal>.
-        Check that you can use all the packages you want in this
-        Emacs instance. For example, try switching to the zerodark
-        theme through
-        <literal>M-x load-theme &lt;RET&gt; zerodark &lt;RET&gt; y</literal>.
-      </para>
-
-      <tip>
-        <para>
-          A few popular extensions worth checking out are: auctex,
-          company, edit-server, flycheck, helm, iedit, magit,
-          multiple-cursors, projectile, and yasnippet.
-        </para>
-      </tip>
-
-      <para>
-        The list of available packages in the various ELPA
-        repositories can be seen with the following commands:
-        <example xml:id="module-services-emacs-querying-packages">
-          <title>Querying Emacs packages</title>
-          <programlisting><![CDATA[
+   <tip>
+    <para>
+     A few popular extensions worth checking out are: auctex, company,
+     edit-server, flycheck, helm, iedit, magit, multiple-cursors, projectile,
+     and yasnippet.
+    </para>
+   </tip>
+
+   <para>
+    The list of available packages in the various ELPA repositories can be seen
+    with the following commands:
+    <example xml:id="module-services-emacs-querying-packages">
+     <title>Querying Emacs packages</title>
+<programlisting><![CDATA[
 nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.elpaPackages
 nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.melpaPackages
 nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.melpaStablePackages
 nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.orgPackages
 ]]></programlisting>
-        </example>
-      </para>
-
-      <para>
-        If you are on NixOS, you can install this particular Emacs for
-        all users by adding it to the list of system packages
-        (see <xref linkend="sec-declarative-package-mgmt" />). Simply
-        modify your file <filename>configuration.nix</filename> to
-        make it contain:
-        <example xml:id="module-services-emacs-configuration-nix">
-          <title>Custom Emacs in <filename>configuration.nix</filename></title>
-          <programlisting><![CDATA[
+    </example>
+   </para>
+
+   <para>
+    If you are on NixOS, you can install this particular Emacs for all users by
+    adding it to the list of system packages (see
+    <xref linkend="sec-declarative-package-mgmt" />). Simply modify your file
+    <filename>configuration.nix</filename> to make it contain:
+    <example xml:id="module-services-emacs-configuration-nix">
+     <title>Custom Emacs in <filename>configuration.nix</filename></title>
+<programlisting><![CDATA[
 {
  environment.systemPackages = [
    # [...]
@@ -301,60 +284,59 @@ nix-env -f "<nixpkgs>" -qaP -A emacsPackagesNg.orgPackages
   ];
 }
 ]]></programlisting>
-        </example>
-      </para>
+    </example>
+   </para>
 
-      <para>
-        In this case, the next <command>nixos-rebuild switch</command>
-        will take care of adding your <command>emacs</command> to the
-        <varname>PATH</varname> environment variable
-        (see <xref linkend="sec-changing-config" />).
-      </para>
+   <para>
+    In this case, the next <command>nixos-rebuild switch</command> will take
+    care of adding your <command>emacs</command> to the <varname>PATH</varname>
+    environment variable (see <xref linkend="sec-changing-config" />).
+   </para>
 
 <!-- fixme: i think the following is better done with config.nix
 https://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides
 -->
-      <para>
-        If you are not on NixOS or want to install this particular
-        Emacs only for yourself, you can do so by adding it to your
-        <filename>~/.config/nixpkgs/config.nix</filename>
-        (see <link xlink:href="http://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs manual</link>):
-        <example xml:id="module-services-emacs-config-nix">
-          <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title>
-          <programlisting><![CDATA[
+
+   <para>
+    If you are not on NixOS or want to install this particular Emacs only for
+    yourself, you can do so by adding it to your
+    <filename>~/.config/nixpkgs/config.nix</filename> (see
+    <link xlink:href="http://nixos.org/nixpkgs/manual/#sec-modify-via-packageOverrides">Nixpkgs
+    manual</link>):
+    <example xml:id="module-services-emacs-config-nix">
+     <title>Custom Emacs in <filename>~/.config/nixpkgs/config.nix</filename></title>
+<programlisting><![CDATA[
 {
   packageOverrides = super: let self = super.pkgs; in {
     myemacs = import /path/to/emacs.nix { pkgs = self; };
   };
 }
 ]]></programlisting>
-        </example>
-      </para>
-
-      <para>
-        In this case, the next
-        <literal>nix-env -f '&lt;nixpkgs&gt;' -iA myemacs</literal>
-        will take care of adding your emacs to the
-        <varname>PATH</varname> environment variable.
-      </para>
-    </section>
-
-    <section xml:id="module-services-emacs-advanced">
-      <title>Advanced Emacs Configuration</title>
+    </example>
+   </para>
 
-      <para>
-        If you want, you can tweak the Emacs package itself from your
-        <filename>emacs.nix</filename>. For example, if you want to
-        have a GTK+3-based Emacs instead of the default GTK+2-based
-        binary and remove the automatically generated
-        <filename>emacs.desktop</filename> (useful is you only use
-        <command>emacsclient</command>), you can change your file
-        <filename>emacs.nix</filename> in this way:
-      </para>
+   <para>
+    In this case, the next <literal>nix-env -f '&lt;nixpkgs&gt;' -iA
+    myemacs</literal> will take care of adding your emacs to the
+    <varname>PATH</varname> environment variable.
+   </para>
+  </section>
 
-      <example xml:id="ex-emacsGtk3Nix">
-        <title>Custom Emacs build</title>
-        <programlisting><![CDATA[
+  <section xml:id="module-services-emacs-advanced">
+   <title>Advanced Emacs Configuration</title>
+
+   <para>
+    If you want, you can tweak the Emacs package itself from your
+    <filename>emacs.nix</filename>. For example, if you want to have a
+    GTK+3-based Emacs instead of the default GTK+2-based binary and remove the
+    automatically generated <filename>emacs.desktop</filename> (useful is you
+    only use <command>emacsclient</command>), you can change your file
+    <filename>emacs.nix</filename> in this way:
+   </para>
+
+   <example xml:id="ex-emacsGtk3Nix">
+    <title>Custom Emacs build</title>
+<programlisting><![CDATA[
 { pkgs ? import <nixpkgs> {} }:
 let
   myEmacs = (pkgs.emacs.override {
@@ -370,161 +352,143 @@ let
   });
 in [...]
 ]]></programlisting>
-      </example>
+   </example>
 
-      <para>
-        After building this file as shown in <xref linkend="ex-emacsNix" />,
-        you will get an GTK3-based Emacs binary pre-loaded with your
-        favorite packages.
-      </para>
-    </section>
+   <para>
+    After building this file as shown in <xref linkend="ex-emacsNix" />, you
+    will get an GTK3-based Emacs binary pre-loaded with your favorite packages.
+   </para>
   </section>
-
-<section xml:id="module-services-emacs-running">
+ </section>
+ <section xml:id="module-services-emacs-running">
   <title>Running Emacs as a Service</title>
+
   <para>
-    <productname>NixOS</productname> provides an optional
-    <command>systemd</command> service which launches
-    <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html">
-      Emacs daemon
-    </link>
-    with the user's login session.
+   <productname>NixOS</productname> provides an optional
+   <command>systemd</command> service which launches
+   <link xlink:href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Server.html">
+   Emacs daemon </link> with the user's login session.
   </para>
 
   <para>
-    <emphasis>Source:</emphasis>
-    <filename>modules/services/editors/emacs.nix</filename>
+   <emphasis>Source:</emphasis>
+   <filename>modules/services/editors/emacs.nix</filename>
   </para>
 
   <section xml:id="module-services-emacs-enabling">
-    <title>Enabling the Service</title>
-
-    <para>
-      To install and enable the <command>systemd</command>
-      user service for Emacs daemon, add the following to your
-      <filename>configuration.nix</filename>:
+   <title>Enabling the Service</title>
 
+   <para>
+    To install and enable the <command>systemd</command> user service for Emacs
+    daemon, add the following to your <filename>configuration.nix</filename>:
 <programlisting>
 <xref linkend="opt-services.emacs.enable"/> = true;
 <xref linkend="opt-services.emacs.package"/> = import /home/cassou/.emacs.d { pkgs = pkgs; };
 </programlisting>
-    </para>
-
-    <para>
-      The <varname>services.emacs.package</varname> option allows a
-      custom derivation to be used, for example, one created by
-      <function>emacsWithPackages</function>.
-    </para>
-
-    <para>
-      Ensure that the Emacs server is enabled for your user's Emacs
-      configuration, either by customizing the
-      <varname>server-mode</varname> variable, or by adding
-      <literal>(server-start)</literal> to
-      <filename>~/.emacs.d/init.el</filename>.
-    </para>
-
-    <para>
-      To start the daemon, execute the following:
-
+   </para>
+
+   <para>
+    The <varname>services.emacs.package</varname> option allows a custom
+    derivation to be used, for example, one created by
+    <function>emacsWithPackages</function>.
+   </para>
+
+   <para>
+    Ensure that the Emacs server is enabled for your user's Emacs
+    configuration, either by customizing the <varname>server-mode</varname>
+    variable, or by adding <literal>(server-start)</literal> to
+    <filename>~/.emacs.d/init.el</filename>.
+   </para>
+
+   <para>
+    To start the daemon, execute the following:
 <screen>
 $ 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
 </screen>
-
-      The server should now be ready to serve Emacs clients.
-    </para>
-
+    The server should now be ready to serve Emacs clients.
+   </para>
   </section>
 
   <section xml:id="module-services-emacs-starting-client">
-    <title>Starting the client</title>
-    <para>
-      Ensure that the emacs server is enabled, either by customizing
-      the <varname>server-mode</varname> variable, or by adding
-      <literal>(server-start)</literal> to
-      <filename>~/.emacs</filename>.
-    </para>
+   <title>Starting the client</title>
 
-    <para>
-      To connect to the emacs daemon, run one of the following:
-      <programlisting><![CDATA[
+   <para>
+    Ensure that the emacs server is enabled, either by customizing the
+    <varname>server-mode</varname> variable, or by adding
+    <literal>(server-start)</literal> to <filename>~/.emacs</filename>.
+   </para>
+
+   <para>
+    To connect to the emacs daemon, run one of the following:
+<programlisting><![CDATA[
 emacsclient FILENAME
 emacsclient --create-frame  # opens a new frame (window)
 emacsclient --create-frame --tty  # opens a new frame on the current terminal
 ]]></programlisting>
-    </para>
+   </para>
   </section>
 
   <section xml:id="module-services-emacs-editor-variable">
-    <title>Configuring the <varname>EDITOR</varname> variable</title>
-    <!--<title><command>emacsclient</command> as the Default Editor</title>-->
-
-    <para>
-      If <xref linkend="opt-services.emacs.defaultEditor"/> is
-      <literal>true</literal>, the <varname>EDITOR</varname> variable
-      will be set to a wrapper script which launches
-      <command>emacsclient</command>.
-    </para>
-
-    <para>
-      Any setting of <varname>EDITOR</varname> in the shell config
-      files will override
-      <varname>services.emacs.defaultEditor</varname>.
-      To make sure <varname>EDITOR</varname> refers to the Emacs
-      wrapper script, remove any existing <varname>EDITOR</varname>
-      assignment from <filename>.profile</filename>,
-      <filename>.bashrc</filename>, <filename>.zshenv</filename> or
-      any other shell config file.
-    </para>
-
-    <para>
-      If you have formed certain bad habits when editing files,
-      these can be corrected with a shell alias to the wrapper
-      script:
-      <programlisting>alias vi=$EDITOR</programlisting>
-    </para>
+   <title>Configuring the <varname>EDITOR</varname> variable</title>
+
+<!--<title><command>emacsclient</command> as the Default Editor</title>-->
+
+   <para>
+    If <xref linkend="opt-services.emacs.defaultEditor"/> is
+    <literal>true</literal>, the <varname>EDITOR</varname> variable will be set
+    to a wrapper script which launches <command>emacsclient</command>.
+   </para>
+
+   <para>
+    Any setting of <varname>EDITOR</varname> in the shell config files will
+    override <varname>services.emacs.defaultEditor</varname>. To make sure
+    <varname>EDITOR</varname> refers to the Emacs wrapper script, remove any
+    existing <varname>EDITOR</varname> assignment from
+    <filename>.profile</filename>, <filename>.bashrc</filename>,
+    <filename>.zshenv</filename> or any other shell config file.
+   </para>
+
+   <para>
+    If you have formed certain bad habits when editing files, these can be
+    corrected with a shell alias to the wrapper script:
+<programlisting>alias vi=$EDITOR</programlisting>
+   </para>
   </section>
 
   <section xml:id="module-services-emacs-per-user">
-    <title>Per-User Enabling of the Service</title>
-
-    <para>
-      In general, <command>systemd</command> user services
-      are globally enabled by symlinks in
-      <filename>/etc/systemd/user</filename>. In the case where
-      Emacs daemon is not wanted for all users, it is possible to
-      install the service but not globally enable it:
+   <title>Per-User Enabling of the Service</title>
 
+   <para>
+    In general, <command>systemd</command> user services are globally enabled
+    by symlinks in <filename>/etc/systemd/user</filename>. In the case where
+    Emacs daemon is not wanted for all users, it is possible to install the
+    service but not globally enable it:
 <programlisting>
 <xref linkend="opt-services.emacs.enable"/> = false;
 <xref linkend="opt-services.emacs.install"/> = true;
 </programlisting>
-    </para>
-
-    <para>
-      To enable the <command>systemd</command> user service for just
-      the currently logged in user, run:
-
-      <programlisting>systemctl --user enable emacs</programlisting>
-
-      This will add the symlink
-      <filename>~/.config/systemd/user/emacs.service</filename>.
-    </para>
+   </para>
+
+   <para>
+    To enable the <command>systemd</command> user service for just the
+    currently logged in user, run:
+<programlisting>systemctl --user enable emacs</programlisting>
+    This will add the symlink
+    <filename>~/.config/systemd/user/emacs.service</filename>.
+   </para>
   </section>
-</section>
-
-<section xml:id="module-services-emacs-configuring">
+ </section>
+ <section xml:id="module-services-emacs-configuring">
   <title>Configuring Emacs</title>
 
   <para>
-    The Emacs init file should be changed to load the extension
-    packages at startup:
-
-    <example xml:id="module-services-emacs-package-initialisation">
-      <title>Package initialization in <filename>.emacs</filename></title>
-      <programlisting><![CDATA[
+   The Emacs init file should be changed to load the extension packages at
+   startup:
+   <example xml:id="module-services-emacs-package-initialisation">
+    <title>Package initialization in <filename>.emacs</filename></title>
+<programlisting><![CDATA[
 (require 'package)
 
 ;; optional. makes unpure packages archives unavailable
@@ -533,66 +497,71 @@ emacsclient --create-frame --tty  # opens a new frame on the current terminal
 (setq package-enable-at-startup nil)
 (package-initialize)
 ]]></programlisting>
-    </example>
+   </example>
   </para>
 
   <para>
-    After the declarative emacs package configuration has been
-    tested, previously downloaded packages can be cleaned up by
-    removing <filename>~/.emacs.d/elpa</filename> (do make a backup
-    first, in case you forgot a package).
+   After the declarative emacs package configuration has been tested,
+   previously downloaded packages can be cleaned up by removing
+   <filename>~/.emacs.d/elpa</filename> (do make a backup first, in case you
+   forgot a package).
   </para>
 
-  <!--
+<!--
       todo: is it worth documenting customizations for
       server-switch-hook, server-done-hook?
   -->
 
   <section xml:id="module-services-emacs-major-mode">
-    <title>A Major Mode for Nix Expressions</title>
+   <title>A Major Mode for Nix Expressions</title>
 
-    <para>
-      Of interest may be <varname>melpaPackages.nix-mode</varname>,
-      which provides syntax highlighting for the Nix language. This is
-      particularly convenient if you regularly edit Nix files.
-    </para>
+   <para>
+    Of interest may be <varname>melpaPackages.nix-mode</varname>, which
+    provides syntax highlighting for the Nix language. This is particularly
+    convenient if you regularly edit Nix files.
+   </para>
   </section>
 
   <section xml:id="module-services-emacs-man-pages">
-    <title>Accessing man pages</title>
-    <para>
-      You can use <function>woman</function> to get completion of all
-      available man pages. For example, type <literal>M-x woman
-      &lt;RET&gt; nixos-rebuild &lt;RET&gt;.</literal>
-    </para>
+   <title>Accessing man pages</title>
+
+   <para>
+    You can use <function>woman</function> to get completion of all available
+    man pages. For example, type <literal>M-x woman &lt;RET&gt; nixos-rebuild
+    &lt;RET&gt;.</literal>
+   </para>
   </section>
 
   <section xml:id="sec-emacs-docbook-xml">
-    <title>Editing DocBook 5 XML Documents</title>
-    <para>
-      Emacs includes <link
-      xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>,
-      a major-mode for validating and editing XML documents.
-      When editing DocBook 5.0 documents, such as
-      <link linkend="book-nixos-manual">this one</link>,
-      nXML needs to be configured with the relevant schema, which is
-      not included.
-    </para>
+   <title>Editing DocBook 5 XML Documents</title>
 
-    <para>
-      To install the DocBook 5.0 schemas, either add
-      <varname>pkgs.docbook5</varname> to
-      <xref linkend="opt-environment.systemPackages"/> (<link
+   <para>
+    Emacs includes
+    <link
+      xlink:href="https://www.gnu.org/software/emacs/manual/html_node/nxml-mode/Introduction.html">nXML</link>,
+    a major-mode for validating and editing XML documents. When editing DocBook
+    5.0 documents, such as <link linkend="book-nixos-manual">this one</link>,
+    nXML needs to be configured with the relevant schema, which is not
+    included.
+   </para>
+
+   <para>
+    To install the DocBook 5.0 schemas, either add
+    <varname>pkgs.docbook5</varname> to
+    <xref linkend="opt-environment.systemPackages"/>
+    (<link
       linkend="sec-declarative-package-mgmt">NixOS</link>), or run
-      <literal>nix-env -i pkgs.docbook5</literal>
-      (<link linkend="sec-ad-hoc-packages">Nix</link>).
-    </para>
-
-    <para>
-      Then customize the variable <varname>rng-schema-locating-files</varname> to include <filename>~/.emacs.d/schemas.xml</filename> and put the following text into that file:
-      <example xml:id="ex-emacs-docbook-xml">
-        <title>nXML Schema Configuration (<filename>~/.emacs.d/schemas.xml</filename>)</title>
-        <programlisting language="xml"><![CDATA[
+    <literal>nix-env -i pkgs.docbook5</literal>
+    (<link linkend="sec-ad-hoc-packages">Nix</link>).
+   </para>
+
+   <para>
+    Then customize the variable <varname>rng-schema-locating-files</varname> to
+    include <filename>~/.emacs.d/schemas.xml</filename> and put the following
+    text into that file:
+    <example xml:id="ex-emacs-docbook-xml">
+     <title>nXML Schema Configuration (<filename>~/.emacs.d/schemas.xml</filename>)</title>
+<programlisting language="xml"><![CDATA[
 <?xml version="1.0"?>
 <!--
   To let emacs find this file, evaluate:
@@ -612,9 +581,7 @@ emacsclient --create-frame --tty  # opens a new frame on the current terminal
 </locatingRules>
 ]]></programlisting>
     </example>
-  </para>
-
+   </para>
   </section>
-</section>
-
+ </section>
 </chapter>
diff --git a/nixos/modules/services/games/minecraft-server.nix b/nixos/modules/services/games/minecraft-server.nix
index f50d2897843a2..7d26d15016503 100644
--- a/nixos/modules/services/games/minecraft-server.nix
+++ b/nixos/modules/services/games/minecraft-server.nix
@@ -4,8 +4,41 @@ with lib;
 
 let
   cfg = config.services.minecraft-server;
-in
-{
+
+  # We don't allow eula=false anyways
+  eulaFile = builtins.toFile "eula.txt" ''
+    # eula.txt managed by NixOS Configuration
+    eula=true
+  '';
+
+  whitelistFile = pkgs.writeText "whitelist.json"
+    (builtins.toJSON
+      (mapAttrsToList (n: v: { name = n; uuid = v; }) cfg.whitelist));
+
+  cfgToString = v: if builtins.isBool v then boolToString v else toString v;
+
+  serverPropertiesFile = pkgs.writeText "server.properties" (''
+    # server.properties managed by NixOS configuration
+  '' + concatStringsSep "\n" (mapAttrsToList
+    (n: v: "${n}=${cfgToString v}") cfg.serverProperties));
+
+
+  # To be able to open the firewall, we need to read out port values in the
+  # server properties, but fall back to the defaults when those don't exist.
+  # These defaults are from https://minecraft.gamepedia.com/Server.properties#Java_Edition_3
+  defaultServerPort = 25565;
+
+  serverPort = cfg.serverProperties.server-port or defaultServerPort;
+
+  rconPort = if cfg.serverProperties.enable-rcon or false
+    then cfg.serverProperties."rcon.port" or 25575
+    else null;
+
+  queryPort = if cfg.serverProperties.enable-query or false
+    then cfg.serverProperties."query.port" or 25565
+    else null;
+
+in {
   options = {
     services.minecraft-server = {
 
@@ -13,10 +46,32 @@ in
         type = types.bool;
         default = false;
         description = ''
-          If enabled, start a Minecraft Server. The listening port for
-          the server is always <literal>25565</literal>. The server
+          If enabled, start a Minecraft Server. The server
           data will be loaded from and saved to
-          <literal>${cfg.dataDir}</literal>.
+          <option>services.minecraft-server.dataDir</option>.
+        '';
+      };
+
+      declarative = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to use a declarative Minecraft server configuration.
+          Only if set to <literal>true</literal>, the options
+          <option>services.minecraft-server.whitelist</option> and
+          <option>services.minecraft-server.serverProperties</option> will be
+          applied.
+        '';
+      };
+
+      eula = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether you agree to
+          <link xlink:href="https://account.mojang.com/documents/minecraft_eula">
+          Mojangs EULA</link>. This option must be set to
+          <literal>true</literal> to run Minecraft server.
         '';
       };
 
@@ -24,7 +79,7 @@ in
         type = types.path;
         default = "/var/lib/minecraft";
         description = ''
-          Directory to store minecraft database and other state/data files.
+          Directory to store Minecraft database and other state/data files.
         '';
       };
 
@@ -32,21 +87,84 @@ in
         type = types.bool;
         default = false;
         description = ''
-          Whether to open ports in the firewall (if enabled) for the server.
+          Whether to open ports in the firewall for the server.
+        '';
+      };
+
+      whitelist = mkOption {
+        type = let
+          minecraftUUID = types.strMatching
+            "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" // {
+              description = "Minecraft UUID";
+            };
+          in types.attrsOf minecraftUUID;
+        default = {};
+        description = ''
+          Whitelisted players, only has an effect when
+          <option>services.minecraft-server.declarative</option> is
+          <literal>true</literal> and the whitelist is enabled
+          via <option>services.minecraft-server.serverProperties</option> by
+          setting <literal>white-list</literal> to <literal>true</literal>.
+          This is a mapping from Minecraft usernames to UUIDs.
+          You can use <link xlink:href="https://mcuuid.net/"/> to get a
+          Minecraft UUID for a username.
+        '';
+        example = literalExample ''
+          {
+            username1 = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
+            username2 = "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy";
+          };
+        '';
+      };
+
+      serverProperties = mkOption {
+        type = with types; attrsOf (either bool (either int str));
+        default = {};
+        example = literalExample ''
+          {
+            server-port = 43000;
+            difficulty = 3;
+            gamemode = 1;
+            max-players = 5;
+            motd = "NixOS Minecraft server!";
+            white-list = true;
+            enable-rcon = true;
+            "rcon.password" = "hunter2";
+          }
+        '';
+        description = ''
+          Minecraft server properties for the server.properties file. Only has
+          an effect when <option>services.minecraft-server.declarative</option>
+          is set to <literal>true</literal>. See
+          <link xlink:href="https://minecraft.gamepedia.com/Server.properties#Java_Edition_3"/>
+          for documentation on these values.
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.minecraft-server;
+        defaultText = "pkgs.minecraft-server";
+        example = literalExample "pkgs.minecraft-server_1_12_2";
+        description = "Version of minecraft-server to run.";
+      };
+
       jvmOpts = mkOption {
-        type = types.str;
+        type = types.separatedString " ";
         default = "-Xmx2048M -Xms2048M";
-        description = "JVM options for the Minecraft Service.";
+        # Example options from https://minecraft.gamepedia.com/Tutorials/Server_startup_script
+        example = "-Xmx2048M -Xms4092M -XX:+UseG1GC -XX:+CMSIncrementalPacing "
+          + "-XX:+CMSClassUnloadingEnabled -XX:ParallelGCThreads=2 "
+          + "-XX:MinHeapFreeRatio=5 -XX:MaxHeapFreeRatio=10";
+        description = "JVM options for the Minecraft server.";
       };
     };
   };
 
   config = mkIf cfg.enable {
+
     users.users.minecraft = {
-      description     = "Minecraft Server Service user";
+      description     = "Minecraft server service user";
       home            = cfg.dataDir;
       createHome      = true;
       uid             = config.ids.uids.minecraft;
@@ -57,17 +175,60 @@ in
       wantedBy      = [ "multi-user.target" ];
       after         = [ "network.target" ];
 
-      serviceConfig.Restart = "always";
-      serviceConfig.User    = "minecraft";
-      script = ''
-        cd ${cfg.dataDir}
-        exec ${pkgs.minecraft-server}/bin/minecraft-server ${cfg.jvmOpts}
-      '';
-    };
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/minecraft-server ${cfg.jvmOpts}";
+        Restart = "always";
+        User = "minecraft";
+        WorkingDirectory = cfg.dataDir;
+      };
+
+      preStart = ''
+        ln -sf ${eulaFile} eula.txt
+      '' + (if cfg.declarative then ''
+
+        if [ -e .declarative ]; then
+
+          # Was declarative before, no need to back up anything
+          ln -sf ${whitelistFile} whitelist.json
+          cp -f ${serverPropertiesFile} server.properties
+
+        else
 
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedUDPPorts = [ 25565 ];
-      allowedTCPPorts = [ 25565 ];
+          # Declarative for the first time, backup stateful files
+          ln -sb --suffix=.stateful ${whitelistFile} whitelist.json
+          cp -b --suffix=.stateful ${serverPropertiesFile} server.properties
+
+          # server.properties must have write permissions, because every time
+          # the server starts it first parses the file and then regenerates it..
+          chmod +w server.properties
+          echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
+            > .declarative
+
+        fi
+      '' else ''
+        if [ -e .declarative ]; then
+          rm .declarative
+        fi
+      '');
     };
+
+    networking.firewall = mkIf cfg.openFirewall (if cfg.declarative then {
+      allowedUDPPorts = [ serverPort ];
+      allowedTCPPorts = [ serverPort ]
+        ++ optional (! isNull queryPort) queryPort
+        ++ optional (! isNull rconPort) rconPort;
+    } else {
+      allowedUDPPorts = [ defaultServerPort ];
+      allowedTCPPorts = [ defaultServerPort ];
+    });
+
+    assertions = [
+      { assertion = cfg.eula;
+        message = "You must agree to Mojangs EULA to run minecraft-server."
+          + " Read https://account.mojang.com/documents/minecraft_eula and"
+          + " set `services.minecraft-server.eula` to `true` if you agree.";
+      }
+    ];
+
   };
 }
diff --git a/nixos/modules/services/games/minetest-server.nix b/nixos/modules/services/games/minetest-server.nix
index 2de42f20f6ccd..98e69c6dc0ea6 100644
--- a/nixos/modules/services/games/minetest-server.nix
+++ b/nixos/modules/services/games/minetest-server.nix
@@ -84,7 +84,9 @@ in
       home            = "/var/lib/minetest";
       createHome      = true;
       uid             = config.ids.uids.minetest;
+      group           = "minetest";
     };
+    users.groups.minetest.gid = config.ids.gids.minetest;
 
     systemd.services.minetest-server = {
       description   = "Minetest Server Service";
@@ -93,6 +95,7 @@ in
 
       serviceConfig.Restart = "always";
       serviceConfig.User    = "minetest";
+      serviceConfig.Group   = "minetest";
 
       script = ''
         cd /var/lib/minetest
diff --git a/nixos/modules/services/hardware/bluetooth.nix b/nixos/modules/services/hardware/bluetooth.nix
index d7ca8a4317946..2a8dfe4a66c92 100644
--- a/nixos/modules/services/hardware/bluetooth.nix
+++ b/nixos/modules/services/hardware/bluetooth.nix
@@ -25,9 +25,14 @@ in {
         type = types.package;
         default = pkgs.bluez;
         defaultText = "pkgs.bluez";
-        example = "pkgs.bluez.override { enableMidi = true; }";
+        example = "pkgs.bluezFull";
         description = ''
           Which BlueZ package to use.
+
+          <note><para>
+            Use the <literal>pkgs.bluezFull</literal> package to enable all
+            bluez plugins.
+          </para></note>
         '';
       };
 
diff --git a/nixos/modules/services/hardware/fwupd.nix b/nixos/modules/services/hardware/fwupd.nix
index 7743f81fd622c..cad9fa20de0f4 100644
--- a/nixos/modules/services/hardware/fwupd.nix
+++ b/nixos/modules/services/hardware/fwupd.nix
@@ -15,6 +15,19 @@ let
       mkName = p: "pki/fwupd/${baseNameOf (toString p)}";
       mkEtcFile = p: nameValuePair (mkName p) { source = p; };
     in listToAttrs (map mkEtcFile cfg.extraTrustedKeys);
+
+  # We cannot include the file in $out and rely on filesInstalledToEtc
+  # to install it because it would create a cyclic dependency between
+  # the outputs. We also need to enable the remote,
+  # which should not be done by default.
+  testRemote = if cfg.enableTestRemote then {
+    "fwupd/remotes.d/fwupd-tests.conf" = {
+      source = pkgs.runCommand "fwupd-tests-enabled.conf" {} ''
+        sed "s,^Enabled=false,Enabled=true," \
+        "${pkgs.fwupd.installedTests}/etc/fwupd/remotes.d/fwupd-tests.conf" > "$out"
+      '';
+    };
+  } else {};
 in {
 
   ###### interface
@@ -40,7 +53,7 @@ in {
 
       blacklistPlugins = mkOption {
         type = types.listOf types.string;
-        default = [];
+        default = [ "test" ];
         example = [ "udev" ];
         description = ''
           Allow blacklisting specific plugins
@@ -55,6 +68,15 @@ in {
           Installing a public key allows firmware signed with a matching private key to be recognized as trusted, which may require less authentication to install than for untrusted files. By default trusted firmware can be upgraded (but not downgraded) without the user or administrator password. Only very few keys are installed by default.
         '';
       };
+
+      enableTestRemote = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable test remote. This is used by
+          <link xlink:href="https://github.com/hughsie/fwupd/blob/master/data/installed-tests/README.md">installed tests</link>.
+        '';
+      };
     };
   };
 
@@ -78,7 +100,7 @@ in {
         '';
       };
 
-    } // originalEtc // extraTrustedKeys;
+    } // originalEtc // extraTrustedKeys // testRemote;
 
     services.dbus.packages = [ pkgs.fwupd ];
 
diff --git a/nixos/modules/services/hardware/lirc.nix b/nixos/modules/services/hardware/lirc.nix
new file mode 100644
index 0000000000000..826e512c75d17
--- /dev/null
+++ b/nixos/modules/services/hardware/lirc.nix
@@ -0,0 +1,100 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lirc;
+in {
+
+  ###### interface
+
+  options = {
+    services.lirc = {
+
+      enable = mkEnableOption "LIRC daemon";
+
+      options = mkOption {
+        type = types.lines;
+        example = ''
+          [lircd]
+          nodaemon = False
+        '';
+        description = "LIRC default options descriped in man:lircd(8) (<filename>lirc_options.conf</filename>)";
+      };
+
+      configs = mkOption {
+        type = types.listOf types.lines;
+        description = "Configurations for lircd to load, see man:lircd.conf(5) for details (<filename>lircd.conf</filename>)";
+      };
+
+      extraArguments = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Extra arguments to lircd.";
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    # Note: LIRC executables raises a warning, if lirc_options.conf do not exists
+    environment.etc."lirc/lirc_options.conf".text = cfg.options;
+
+    passthru.lirc.socket = "/run/lirc/lircd";
+
+    environment.systemPackages = [ pkgs.lirc ];
+
+    systemd.sockets.lircd = {
+      description = "LIRC daemon socket";
+      wantedBy = [ "sockets.target" ];
+      socketConfig = {
+        ListenStream = config.passthru.lirc.socket;
+        SocketUser = "lirc";
+        SocketMode = "0660";
+      };
+    };
+
+    systemd.services.lircd = let
+      configFile = pkgs.writeText "lircd.conf" (builtins.concatStringsSep "\n" cfg.configs);
+    in {
+      description = "LIRC daemon service";
+      after = [ "network.target" ];
+
+      unitConfig.Documentation = [ "man:lircd(8)" ];
+
+      serviceConfig = {
+        RuntimeDirectory = "lirc";
+
+        # Service runtime directory and socket share same folder.
+        # Following hacks are necessary to get everything right:
+
+        # 1. prevent socket deletion during stop and restart
+        RuntimeDirectoryPreserve = true;
+
+        # 2. fix runtime folder owner-ship, happens when socket activation
+        #    creates the folder
+        PermissionsStartOnly = true;
+        ExecStartPre = [
+          "${pkgs.coreutils}/bin/chown lirc /run/lirc/"
+        ];
+
+        ExecStart = ''
+          ${pkgs.lirc}/bin/lircd --nodaemon \
+            ${escapeShellArgs cfg.extraArguments} \
+            ${configFile}
+        '';
+        User = "lirc";
+      };
+    };
+
+    users.users.lirc = {
+      uid = config.ids.uids.lirc;
+      group = "lirc";
+      description = "LIRC user for lircd";
+    };
+
+    users.groups.lirc.gid = config.ids.gids.lirc;
+  };
+}
diff --git a/nixos/modules/services/hardware/ratbagd.nix b/nixos/modules/services/hardware/ratbagd.nix
new file mode 100644
index 0000000000000..103e1d2315aea
--- /dev/null
+++ b/nixos/modules/services/hardware/ratbagd.nix
@@ -0,0 +1,32 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.ratbagd;
+in
+{
+  ###### interface
+
+  options = {
+    services.ratbagd = {
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable ratbagd for configuring gaming mice.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    # Give users access to the "ratbagctl" tool
+    environment.systemPackages = [ pkgs.libratbag ];
+
+    services.dbus.packages = [ pkgs.libratbag ];
+
+    systemd.packages = [ pkgs.libratbag ];
+  };
+}
diff --git a/nixos/modules/services/hardware/tlp.nix b/nixos/modules/services/hardware/tlp.nix
index 68425822a8844..b894025c0fd59 100644
--- a/nixos/modules/services/hardware/tlp.nix
+++ b/nixos/modules/services/hardware/tlp.nix
@@ -56,6 +56,8 @@ in
 
     powerManagement.scsiLinkPolicy = null;
     powerManagement.cpuFreqGovernor = null;
+    powerManagement.cpufreq.max = null;
+    powerManagement.cpufreq.min = null;
 
     systemd.sockets."systemd-rfkill".enable = false;
 
diff --git a/nixos/modules/services/hardware/trezord.nix b/nixos/modules/services/hardware/trezord.nix
index f2ec00a7d3e18..c06a0665d02ff 100644
--- a/nixos/modules/services/hardware/trezord.nix
+++ b/nixos/modules/services/hardware/trezord.nix
@@ -26,15 +26,14 @@ in {
       name = "trezord-udev-rules";
       destination = "/etc/udev/rules.d/51-trezor.rules";
       text = ''
-        # Trezor 1
-        SUBSYSTEM=="usb",  ATTR{idVendor}=="534c",  ATTR{idProduct}=="0001",  MODE="0666", GROUP="dialout", SYMLINK+="trezor%n"
-        KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0666", GROUP="dialout"
-
-        # Trezor 2 (Model-T)
-        SUBSYSTEM=="usb",  ATTR{idVendor}=="1209",  ATTR{idProduct}=="53c0",  MODE="0661", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
-        SUBSYSTEM=="usb",  ATTR{idVendor}=="1209",  ATTR{idProduct}=="53c1",  MODE="0660", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl", SYMLINK+="trezor%n"
-        KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="dialout", TAG+="uaccess", TAG+="udev-acl"
-  ];
+        # TREZOR v1 (One)
+        SUBSYSTEM=="usb", ATTR{idVendor}=="534c", ATTR{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
+        KERNEL=="hidraw*", ATTRS{idVendor}=="534c", ATTRS{idProduct}=="0001", MODE="0660", GROUP="trezord", TAG+="uaccess"
+
+        # TREZOR v2 (T)
+        SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c0", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
+        SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess", SYMLINK+="trezor%n"
+        KERNEL=="hidraw*", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="53c1", MODE="0660", GROUP="trezord", TAG+="uaccess"
       '';
     });
 
diff --git a/nixos/modules/services/hardware/triggerhappy.nix b/nixos/modules/services/hardware/triggerhappy.nix
new file mode 100644
index 0000000000000..81d4a1ae65bf1
--- /dev/null
+++ b/nixos/modules/services/hardware/triggerhappy.nix
@@ -0,0 +1,114 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.triggerhappy;
+
+  socket = "/run/thd.socket";
+
+  configFile = pkgs.writeText "triggerhappy.conf" ''
+    ${concatMapStringsSep "\n"
+      ({ keys, event, cmd, ... }:
+        ''${concatMapStringsSep "+" (x: "KEY_" + x) keys} ${toString { press = 1; hold = 2; release = 0; }.${event}} ${cmd}''
+      )
+      cfg.bindings}
+    ${cfg.extraConfig}
+  '';
+
+  bindingCfg = { config, ... }: {
+    options = {
+
+      keys = mkOption {
+        type = types.listOf types.str;
+        description = "List of keys to match.  Key names as defined in linux/input-event-codes.h";
+      };
+
+      event = mkOption {
+        type = types.enum ["press" "hold" "release"];
+        default = "press";
+        description = "Event to match.";
+      };
+
+      cmd = mkOption {
+        type = types.str;
+        description = "What to run.";
+      };
+
+    };
+  };
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.triggerhappy = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable the <command>triggerhappy</command> hotkey daemon.
+        '';
+      };
+
+      bindings = mkOption {
+        type = types.listOf (types.submodule bindingCfg);
+        default = [];
+        example = lib.literalExample ''
+          [ { keys = ["PLAYPAUSE"];  cmd = "''${pkgs.mpc_cli}/bin/mpc -q toggle"; } ]
+        '';
+        description = ''
+          Key bindings for <command>triggerhappy</command>.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Literal contents to append to the end of <command>triggerhappy</command> configuration file.
+        '';
+      };
+
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    systemd.sockets.triggerhappy = {
+      description = "Triggerhappy Socket";
+      wantedBy = [ "sockets.target" ];
+      socketConfig.ListenDatagram = socket;
+    };
+
+    systemd.services.triggerhappy = {
+      wantedBy = [ "multi-user.target" ];
+      after = [ "local-fs.target" ];
+      description = "Global hotkey daemon";
+      serviceConfig = {
+        ExecStart = "${pkgs.triggerhappy}/bin/thd --user nobody --socket ${socket} --triggers ${configFile} --deviceglob /dev/input/event*";
+      };
+    };
+
+    services.udev.packages = lib.singleton (pkgs.writeTextFile {
+      name = "triggerhappy-udev-rules";
+      destination = "/etc/udev/rules.d/61-triggerhappy.rules";
+      text = ''
+        ACTION=="add", SUBSYSTEM=="input", KERNEL=="event[0-9]*", ATTRS{name}!="triggerhappy", \
+          RUN+="${pkgs.triggerhappy}/bin/th-cmd --socket ${socket} --passfd --udev"
+      '';
+    });
+
+  };
+
+}
diff --git a/nixos/modules/services/hardware/upower.nix b/nixos/modules/services/hardware/upower.nix
index 2198842a45116..1da47349c0771 100644
--- a/nixos/modules/services/hardware/upower.nix
+++ b/nixos/modules/services/hardware/upower.nix
@@ -56,6 +56,32 @@ in
           { Type = "dbus";
             BusName = "org.freedesktop.UPower";
             ExecStart = "@${cfg.package}/libexec/upowerd upowerd";
+            Restart = "on-failure";
+            # Upstream lockdown:
+            # Filesystem lockdown
+            ProtectSystem = "strict";
+            # Needed by keyboard backlight support
+            ProtectKernelTunables = false;
+            ProtectControlGroups = true;
+            ReadWritePaths = "/var/lib/upower";
+            ProtectHome = true;
+            PrivateTmp = true;
+
+            # Network
+            # PrivateNetwork=true would block udev's netlink socket
+            RestrictAddressFamilies = "AF_UNIX AF_NETLINK";
+
+            # Execute Mappings
+            MemoryDenyWriteExecute = true;
+
+            # Modules
+            ProtectKernelModules = true;
+
+            # Real-time
+            RestrictRealtime = true;
+
+            # Privilege escalation
+            NoNewPrivileges = true;
           };
       };
 
diff --git a/nixos/modules/services/hardware/vdr.nix b/nixos/modules/services/hardware/vdr.nix
new file mode 100644
index 0000000000000..4822506a899ba
--- /dev/null
+++ b/nixos/modules/services/hardware/vdr.nix
@@ -0,0 +1,81 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.vdr;
+  libDir = "/var/lib/vdr";
+in {
+
+  ###### interface
+
+  options = {
+
+    services.vdr = {
+      enable = mkEnableOption "enable VDR. Please put config into ${libDir}.";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.vdr;
+        defaultText = "pkgs.vdr";
+        example = literalExample "pkgs.wrapVdr.override { plugins = with pkgs.vdrPlugins; [ hello ]; }";
+        description = "Package to use.";
+      };
+
+      videoDir = mkOption {
+        type = types.path;
+        default = "/srv/vdr/video";
+        description = "Recording directory";
+      };
+
+      extraArguments = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Additional command line arguments to pass to VDR.";
+      };
+
+      enableLirc = mkEnableOption "enable LIRC";
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable (mkMerge [{
+    systemd.tmpfiles.rules = [
+      "d ${cfg.videoDir} 0755 vdr vdr -"
+      "Z ${cfg.videoDir} - vdr vdr -"
+    ];
+
+    systemd.services.vdr = {
+      description = "VDR";
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${cfg.package}/bin/vdr \
+            --video="${cfg.videoDir}" \
+            --config="${libDir}" \
+            ${escapeShellArgs cfg.extraArguments}
+        '';
+        User = "vdr";
+        CacheDirectory = "vdr";
+        StateDirectory = "vdr";
+        Restart = "on-failure";
+      };
+    };
+
+    users.users.vdr = {
+      group = "vdr";
+      home = libDir;
+    };
+
+    users.groups.vdr = {};
+  }
+
+  (mkIf cfg.enableLirc {
+    services.lirc.enable = true;
+    users.users.vdr.extraGroups = [ "lirc" ];
+    services.vdr.extraArguments = [
+      "--lirc=${config.passthru.lirc.socket}"
+    ];
+  })]);
+}
diff --git a/nixos/modules/services/logging/journaldriver.nix b/nixos/modules/services/logging/journaldriver.nix
index 74ac3d4c23659..9bd581e9ec0e5 100644
--- a/nixos/modules/services/logging/journaldriver.nix
+++ b/nixos/modules/services/logging/journaldriver.nix
@@ -7,7 +7,7 @@
 # to be set.
 #
 # For further information please consult the documentation in the
-# upstream repository at: https://github.com/aprilabank/journaldriver/
+# upstream repository at: https://github.com/tazjin/journaldriver/
 
 { config, lib, pkgs, ...}:
 
diff --git a/nixos/modules/services/mail/clamsmtp.nix b/nixos/modules/services/mail/clamsmtp.nix
index 8f4f39aa72889..fc1267c5d2809 100644
--- a/nixos/modules/services/mail/clamsmtp.nix
+++ b/nixos/modules/services/mail/clamsmtp.nix
@@ -176,4 +176,6 @@ in
         }
       ) cfg.instances);
     };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
 }
diff --git a/nixos/modules/services/mail/dkimproxy-out.nix b/nixos/modules/services/mail/dkimproxy-out.nix
index 894b88e25c1b2..f4ac9e47007af 100644
--- a/nixos/modules/services/mail/dkimproxy-out.nix
+++ b/nixos/modules/services/mail/dkimproxy-out.nix
@@ -115,4 +115,6 @@ in
         };
       };
     };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
 }
diff --git a/nixos/modules/services/mail/dovecot.nix b/nixos/modules/services/mail/dovecot.nix
index e6091182b2abc..30ad7d82fb801 100644
--- a/nixos/modules/services/mail/dovecot.nix
+++ b/nixos/modules/services/mail/dovecot.nix
@@ -311,7 +311,7 @@ in
       { name = "dovenull";
         uid = config.ids.uids.dovenull2;
         description = "Dovecot user for untrusted logins";
-        group = cfg.group;
+        group = "dovenull";
       }
     ] ++ optional (cfg.user == "dovecot2")
          { name = "dovecot2";
@@ -332,6 +332,10 @@ in
       }
     ++ optional (cfg.createMailUser && cfg.mailGroup != null)
       { name = cfg.mailGroup;
+      }
+    ++ singleton
+      { name = "dovenull";
+        gid = config.ids.gids.dovenull2;
       };
 
     environment.etc."dovecot/modules".source = modulesDir;
diff --git a/nixos/modules/services/mail/exim.nix b/nixos/modules/services/mail/exim.nix
index 06c4b2811b3fc..c05811291359e 100644
--- a/nixos/modules/services/mail/exim.nix
+++ b/nixos/modules/services/mail/exim.nix
@@ -2,7 +2,7 @@
 
 let
   inherit (lib) mkIf mkOption singleton types;
-  inherit (pkgs) coreutils exim;
+  inherit (pkgs) coreutils;
   cfg = config.services.exim;
 in
 
@@ -57,6 +57,16 @@ in
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.exim;
+        defaultText = "pkgs.exim";
+        description = ''
+          The Exim derivation to use.
+          This can be used to enable features such as LDAP or PAM support.
+        '';
+      };
+
     };
 
   };
@@ -74,7 +84,7 @@ in
         spool_directory = ${cfg.spoolDir}
         ${cfg.config}
       '';
-      systemPackages = [ exim ];
+      systemPackages = [ cfg.package ];
     };
 
     users.users = singleton {
@@ -89,14 +99,14 @@ in
       gid = config.ids.gids.exim;
     };
 
-    security.wrappers.exim.source = "${exim}/bin/exim";
+    security.wrappers.exim.source = "${cfg.package}/bin/exim";
 
     systemd.services.exim = {
       description = "Exim Mail Daemon";
       wantedBy = [ "multi-user.target" ];
       restartTriggers = [ config.environment.etc."exim.conf".source ];
       serviceConfig = {
-        ExecStart   = "${exim}/bin/exim -bdf -q30m";
+        ExecStart   = "${cfg.package}/bin/exim -bdf -q30m";
         ExecReload  = "${coreutils}/bin/kill -HUP $MAINPID";
       };
       preStart = ''
diff --git a/nixos/modules/services/mail/postfix.nix b/nixos/modules/services/mail/postfix.nix
index 33249aa3e554c..d43733484ffa9 100644
--- a/nixos/modules/services/mail/postfix.nix
+++ b/nixos/modules/services/mail/postfix.nix
@@ -602,7 +602,7 @@ in
             target = "postfix";
           };
 
-        # This makes comfortable for root to run 'postqueue' for example.
+        # This makes it comfortable to run 'postqueue/postdrop' for example.
         systemPackages = [ pkgs.postfix ];
       };
 
@@ -616,6 +616,22 @@ in
         setgid = true;
       };
 
+      security.wrappers.postqueue = {
+        program = "postqueue";
+        source = "${pkgs.postfix}/bin/postqueue";
+        group = setgidGroup;
+        setuid = false;
+        setgid = true;
+      };
+
+      security.wrappers.postdrop = {
+        program = "postdrop";
+        source = "${pkgs.postfix}/bin/postdrop";
+        group = setgidGroup;
+        setuid = false;
+        setgid = true;
+      };
+
       users.users = optional (user == "postfix")
         { name = "postfix";
           description = "Postfix mail server user";
diff --git a/nixos/modules/services/mail/rmilter.nix b/nixos/modules/services/mail/rmilter.nix
index 7f38d75701329..492c645832190 100644
--- a/nixos/modules/services/mail/rmilter.nix
+++ b/nixos/modules/services/mail/rmilter.nix
@@ -52,7 +52,7 @@ in
 
       enable = mkOption {
         type = types.bool;
-        default = cfg.rspamd.enable;
+        default = false;
         description = "Whether to run the rmilter daemon.";
       };
 
@@ -89,7 +89,7 @@ in
 
       bindSocket.path = mkOption {
        type = types.str;
-       default = "/run/rmilter/rmilter.sock";
+       default = "/run/rmilter.sock";
        description = ''
           Path to Unix domain socket to listen on.
         '';
@@ -193,6 +193,9 @@ in
   config = mkMerge [
 
     (mkIf cfg.enable {
+      warnings = [
+        ''`config.services.rmilter' is deprecated, `rmilter' deprecated and unsupported by upstream, and will be removed from next releases. Use built-in rspamd milter instead.''
+      ];
 
       users.users = singleton {
         name = cfg.user;
diff --git a/nixos/modules/services/mail/roundcube.nix b/nixos/modules/services/mail/roundcube.nix
new file mode 100644
index 0000000000000..66b1c1e3e6f9b
--- /dev/null
+++ b/nixos/modules/services/mail/roundcube.nix
@@ -0,0 +1,167 @@
+{ lib, config, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.roundcube;
+in
+{
+  options.services.roundcube = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable roundcube.
+
+        Also enables nginx virtual host management.
+        Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
+        See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+      '';
+    };
+
+    hostName = mkOption {
+      type = types.str;
+      example = "webmail.example.com";
+      description = "Hostname to use for the nginx vhost";
+    };
+
+    package = mkOption {
+      type = types.package;
+      default = pkgs.roundcube;
+
+      example = literalExample ''
+        roundcube.withPlugins (plugins: [ plugins.persistent_login ])
+      '';
+
+      description = ''
+        The package which contains roundcube's sources. Can be overriden to create
+        an environment which contains roundcube and third-party plugins.
+      '';
+    };
+
+    database = {
+      username = mkOption {
+        type = types.str;
+        default = "roundcube";
+        description = "Username for the postgresql connection";
+      };
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          Host of the postgresql server. If this is not set to
+          <literal>localhost</literal>, you have to create the
+          postgresql user and database yourself, with appropriate
+          permissions.
+        '';
+      };
+      password = mkOption {
+        type = types.str;
+        description = "Password for the postgresql connection";
+      };
+      dbname = mkOption {
+        type = types.str;
+        default = "roundcube";
+        description = "Name of the postgresql database";
+      };
+    };
+
+    plugins = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        List of roundcube plugins to enable. Currently, only those directly shipped with Roundcube are supported.
+      '';
+    };
+
+    extraConfig = mkOption {
+      type = types.lines;
+      default = "";
+      description = "Extra configuration for roundcube webmail instance";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.etc."roundcube/config.inc.php".text = ''
+      <?php
+
+      $config = array();
+      $config['db_dsnw'] = 'pgsql://${cfg.database.username}:${cfg.database.password}@${cfg.database.host}/${cfg.database.dbname}';
+      $config['log_driver'] = 'syslog';
+      $config['max_message_size'] = '25M';
+      $config['plugins'] = [${concatMapStringsSep "," (p: "'${p}'") cfg.plugins}];
+      ${cfg.extraConfig}
+    '';
+
+    services.nginx = {
+      enable = true;
+      virtualHosts = {
+        ${cfg.hostName} = {
+          forceSSL = mkDefault true;
+          enableACME = mkDefault true;
+          locations."/" = {
+            root = cfg.package;
+            index = "index.php";
+            extraConfig = ''
+              location ~* \.php$ {
+                fastcgi_split_path_info ^(.+\.php)(/.+)$;
+                fastcgi_pass unix:/run/phpfpm/roundcube;
+                include ${pkgs.nginx}/conf/fastcgi_params;
+                include ${pkgs.nginx}/conf/fastcgi.conf;
+              }
+            '';
+          };
+        };
+      };
+    };
+
+    services.postgresql = mkIf (cfg.database.host == "localhost") {
+      enable = true;
+    };
+
+    services.phpfpm.poolConfigs.roundcube = ''
+      listen = /run/phpfpm/roundcube
+      listen.owner = nginx
+      listen.group = nginx
+      listen.mode = 0660
+      user = nginx
+      pm = dynamic
+      pm.max_children = 75
+      pm.start_servers = 2
+      pm.min_spare_servers = 1
+      pm.max_spare_servers = 20
+      pm.max_requests = 500
+      php_admin_value[error_log] = 'stderr'
+      php_admin_flag[log_errors] = on
+      php_admin_value[post_max_size] = 25M
+      php_admin_value[upload_max_filesize] = 25M
+      catch_workers_output = yes
+    '';
+    systemd.services.phpfpm-roundcube.after = [ "roundcube-setup.service" ];
+
+    systemd.services.roundcube-setup = let
+      pgSuperUser = config.services.postgresql.superUser;
+    in {
+      requires = [ "postgresql.service" ];
+      after = [ "postgresql.service" ];
+      wantedBy = [ "multi-user.target" ];
+      path = [ config.services.postgresql.package ];
+      script = ''
+        mkdir -p /var/lib/roundcube
+        if [ ! -f /var/lib/roundcube/db-created ]; then
+          if [ "${cfg.database.host}" = "localhost" ]; then
+            ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create role ${cfg.database.username} with login password '${cfg.database.password}'";
+            ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "create database ${cfg.database.dbname} with owner ${cfg.database.username}";
+          fi
+          PGPASSWORD=${cfg.database.password} ${pkgs.postgresql}/bin/psql -U ${cfg.database.username} \
+            -f ${cfg.package}/SQL/postgres.initial.sql \
+            -h ${cfg.database.host} ${cfg.database.dbname}
+          touch /var/lib/roundcube/db-created
+        fi
+
+        ${pkgs.php}/bin/php ${cfg.package}/bin/update.sh
+      '';
+      serviceConfig.Type = "oneshot";
+    };
+  };
+}
diff --git a/nixos/modules/services/mail/rspamd.nix b/nixos/modules/services/mail/rspamd.nix
index b3dae60c2c7e0..c9ba867802130 100644
--- a/nixos/modules/services/mail/rspamd.nix
+++ b/nixos/modules/services/mail/rspamd.nix
@@ -6,6 +6,7 @@ let
 
   cfg = config.services.rspamd;
   opts = options.services.rspamd;
+  postfixCfg = config.services.postfix;
 
   bindSocketOpts = {options, config, ... }: {
     options = {
@@ -44,7 +45,9 @@ let
       else "${config.socket}${maybeOption "mode"}${maybeOption "owner"}${maybeOption "group"}";
   };
 
-  workerOpts = { name, ... }: {
+  traceWarning = w: x: builtins.trace "warning: ${w}" x;
+
+  workerOpts = { name, options, ... }: {
     options = {
       enable = mkOption {
         type = types.nullOr types.bool;
@@ -58,9 +61,18 @@ let
       };
       type = mkOption {
         type = types.nullOr (types.enum [
-          "normal" "controller" "fuzzy_storage" "proxy" "lua"
+          "normal" "controller" "fuzzy_storage" "rspamd_proxy" "lua" "proxy"
         ]);
-        description = "The type of this worker";
+        description = ''
+          The type of this worker. The type <literal>proxy</literal> is
+          deprecated and only kept for backwards compatibility and should be
+          replaced with <literal>rspamd_proxy</literal>.
+        '';
+        apply = let
+            from = "services.rspamd.workers.\”${name}\".type";
+            files = options.type.files;
+            warning = "The option `${from}` defined in ${showFiles files} has enum value `proxy` which has been renamed to `rspamd_proxy`";
+          in x: if x == "proxy" then traceWarning warning "rspamd_proxy" else x;
       };
       bindSockets = mkOption {
         type = types.listOf (types.either types.str (types.submodule bindSocketOpts));
@@ -99,52 +111,28 @@ let
         description = "Additional entries to put verbatim into worker section of rspamd config file.";
       };
     };
-    config = mkIf (name == "normal" || name == "controller" || name == "fuzzy") {
+    config = mkIf (name == "normal" || name == "controller" || name == "fuzzy" || name == "rspamd_proxy") {
       type = mkDefault name;
-      includes = mkDefault [ "$CONFDIR/worker-${name}.inc" ];
-      bindSockets = mkDefault (if name == "normal"
-        then [{
-              socket = "/run/rspamd/rspamd.sock";
-              mode = "0660";
-              owner = cfg.user;
-              group = cfg.group;
-            }]
-        else if name == "controller"
-        then [ "localhost:11334" ]
-        else [] );
+      includes = mkDefault [ "$CONFDIR/worker-${if name == "rspamd_proxy" then "proxy" else name}.inc" ];
+      bindSockets =
+        let
+          unixSocket = name: {
+            mode = "0660";
+            socket = "/run/rspamd/${name}.sock";
+            owner = cfg.user;
+            group = cfg.group;
+          };
+        in mkDefault (if name == "normal" then [(unixSocket "rspamd")]
+          else if name == "controller" then [ "localhost:11334" ]
+          else if name == "rspamd_proxy" then [ (unixSocket "proxy") ]
+          else [] );
     };
   };
 
-  indexOf = default: start: list: e:
-    if list == []
-    then default
-    else if (head list) == e then start
-    else (indexOf default (start + (length (listenStreams (head list).socket))) (tail list) e);
-
-  systemdSocket = indexOf (abort "Socket not found") 0 allSockets;
-
   isUnixSocket = socket: hasPrefix "/" (if (isString socket) then socket else socket.socket);
-  isPort = hasPrefix "*:";
-  isIPv4Socket = hasPrefix "*v4:";
-  isIPv6Socket = hasPrefix "*v6:";
-  isLocalHost = hasPrefix "localhost:";
-  listenStreams = socket:
-    if (isLocalHost socket) then
-      let port = (removePrefix "localhost:" socket);
-      in [ "127.0.0.1:${port}" ] ++ (if config.networking.enableIPv6 then ["[::1]:${port}"] else [])
-    else if (isIPv6Socket socket) then [removePrefix "*v6:" socket]
-    else if (isPort socket) then [removePrefix "*:" socket]
-    else if (isIPv4Socket socket) then
-      throw "error: IPv4 only socket not supported in rspamd with socket activation"
-    else if (length (splitString " " socket)) != 1 then
-      throw "error: string options not supported in rspamd with socket activation"
-    else [socket];
-
-  mkBindSockets = enabled: socks: concatStringsSep "\n  " (flatten (map (each:
-    if cfg.socketActivation && enabled != false then
-      let systemd = (systemdSocket each);
-      in (imap (idx: e: "bind_socket = \"systemd:${toString (systemd + idx - 1)}\";") (listenStreams each.socket))
-    else "bind_socket = \"${each.rawEntry}\";") socks));
+
+  mkBindSockets = enabled: socks: concatStringsSep "\n  "
+    (flatten (map (each: "bind_socket = \"${each.rawEntry}\";") socks));
 
   rspamdConfFile = pkgs.writeText "rspamd.conf"
     ''
@@ -153,40 +141,83 @@ let
       options {
         pidfile = "$RUNDIR/rspamd.pid";
         .include "$CONFDIR/options.inc"
+        .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/options.inc"
+        .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/options.inc"
       }
 
       logging {
         type = "syslog";
         .include "$CONFDIR/logging.inc"
+        .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/logging.inc"
+        .include(try=true; priority=10) "$LOCAL_CONFDIR/override.d/logging.inc"
       }
 
-      ${concatStringsSep "\n" (mapAttrsToList (name: value: ''
-        worker ${optionalString (value.name != "normal" && value.name != "controller") "${value.name}"} {
+      ${concatStringsSep "\n" (mapAttrsToList (name: value: let
+          includeName = if name == "rspamd_proxy" then "proxy" else name;
+          tryOverride = if value.extraConfig == "" then "true" else "false";
+        in ''
+        worker "${value.type}" {
           type = "${value.type}";
           ${optionalString (value.enable != null)
             "enabled = ${if value.enable != false then "yes" else "no"};"}
           ${mkBindSockets value.enable value.bindSockets}
           ${optionalString (value.count != null) "count = ${toString value.count};"}
           ${concatStringsSep "\n  " (map (each: ".include \"${each}\"") value.includes)}
-          ${value.extraConfig}
+          .include(try=true; priority=1,duplicate=merge) "$LOCAL_CONFDIR/local.d/worker-${includeName}.inc"
+          .include(try=${tryOverride}; priority=10) "$LOCAL_CONFDIR/override.d/worker-${includeName}.inc"
         }
       '') cfg.workers)}
 
-      ${cfg.extraConfig}
+      ${optionalString (cfg.extraConfig != "") ''
+        .include(priority=10) "$LOCAL_CONFDIR/override.d/extra-config.inc"
+      ''}
    '';
 
-  allMappedSockets = flatten (mapAttrsToList (name: value:
-    if value.enable != false
-    then imap (idx: each: {
-        name = "${name}";
-        index = idx;
-        value = each;
-      }) value.bindSockets
-    else []) cfg.workers);
-  allSockets = map (e: e.value) allMappedSockets;
+  filterFiles = files: filterAttrs (n: v: v.enable) files;
+  rspamdDir = pkgs.linkFarm "etc-rspamd-dir" (
+    (mapAttrsToList (name: file: { name = "local.d/${name}"; path = file.source; }) (filterFiles cfg.locals)) ++
+    (mapAttrsToList (name: file: { name = "override.d/${name}"; path = file.source; }) (filterFiles cfg.overrides)) ++
+    (optional (cfg.localLuaRules != null) { name = "rspamd.local.lua"; path = cfg.localLuaRules; }) ++
+    [ { name = "rspamd.conf"; path = rspamdConfFile; } ]
+  );
 
-  allSocketNames = map (each: "rspamd-${each.name}-${toString each.index}.socket") allMappedSockets;
+  configFileModule = prefix: { name, config, ... }: {
+    options = {
+      enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether this file ${prefix} should be generated.  This
+          option allows specific ${prefix} files to be disabled.
+        '';
+      };
 
+      text = mkOption {
+        default = null;
+        type = types.nullOr types.lines;
+        description = "Text of the file.";
+      };
+
+      source = mkOption {
+        type = types.path;
+        description = "Path of the source file.";
+      };
+    };
+    config = {
+      source = mkIf (config.text != null) (
+        let name' = "rspamd-${prefix}-" + baseNameOf name;
+        in mkDefault (pkgs.writeText name' config.text));
+    };
+  };
+
+  configOverrides =
+    (mapAttrs' (n: v: nameValuePair "worker-${if n == "rspamd_proxy" then "proxy" else n}.inc" {
+      text = v.extraConfig;
+    })
+    (filterAttrs (n: v: v.extraConfig != "") cfg.workers))
+    // (if cfg.extraConfig == "" then {} else {
+      "extra-config.inc".text = cfg.extraConfig;
+    });
 in
 
 {
@@ -197,7 +228,7 @@ in
 
     services.rspamd = {
 
-      enable = mkEnableOption "Whether to run the rspamd daemon.";
+      enable = mkEnableOption "rspamd, the Rapid spam filtering system";
 
       debug = mkOption {
         type = types.bool;
@@ -205,10 +236,38 @@ in
         description = "Whether to run the rspamd daemon in debug mode.";
       };
 
-      socketActivation = mkOption {
-        type = types.bool;
+      locals = mkOption {
+        type = with types; attrsOf (submodule (configFileModule "locals"));
+        default = {};
+        description = ''
+          Local configuration files, written into <filename>/etc/rspamd/local.d/{name}</filename>.
+        '';
+        example = literalExample ''
+          { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
+            "arc.conf".text = "allow_envfrom_empty = true;";
+          }
+        '';
+      };
+
+      overrides = mkOption {
+        type = with types; attrsOf (submodule (configFileModule "overrides"));
+        default = {};
+        description = ''
+          Overridden configuration files, written into <filename>/etc/rspamd/override.d/{name}</filename>.
+        '';
+        example = literalExample ''
+          { "redis.conf".source = "/nix/store/.../etc/dir/redis.conf";
+            "arc.conf".text = "allow_envfrom_empty = true;";
+          }
+        '';
+      };
+
+      localLuaRules = mkOption {
+        default = null;
+        type = types.nullOr types.path;
         description = ''
-          Enable systemd socket activation for rspamd.
+          Path of file to link to <filename>/etc/rspamd/rspamd.local.lua</filename> for local
+          rules written in Lua
         '';
       };
 
@@ -255,7 +314,7 @@ in
         description = ''
           User to use when no root privileges are required.
         '';
-       };
+      };
 
       group = mkOption {
         type = types.string;
@@ -263,7 +322,30 @@ in
         description = ''
           Group to use when no root privileges are required.
         '';
-       };
+      };
+
+      postfix = {
+        enable = mkOption {
+          type = types.bool;
+          default = false;
+          description = "Add rspamd milter to postfix main.conf";
+        };
+
+        config = mkOption {
+          type = with types; attrsOf (either bool (either str (listOf str)));
+          description = ''
+            Addon to postfix configuration
+          '';
+          default = {
+            smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+            non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+          };
+          example = {
+            smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+            non_smtpd_milters = ["unix:/run/rspamd/rspamd-milter.sock"];
+          };
+        };
+      };
     };
   };
 
@@ -271,13 +353,25 @@ in
   ###### implementation
 
   config = mkIf cfg.enable {
-
-    services.rspamd.socketActivation = mkDefault (!opts.bindSocket.isDefined && !opts.bindUISocket.isDefined);
-
-    assertions = [ {
-      assertion = !cfg.socketActivation || !(opts.bindSocket.isDefined || opts.bindUISocket.isDefined);
-      message = "Can't use socketActivation for rspamd when using renamed bind socket options";
-    } ];
+    services.rspamd.overrides = configOverrides;
+    services.rspamd.workers = mkIf cfg.postfix.enable {
+      controller = {};
+      rspamd_proxy = {
+        bindSockets = [ {
+          mode = "0660";
+          socket = "/run/rspamd/rspamd-milter.sock";
+          owner = cfg.user;
+          group = postfixCfg.group;
+        } ];
+        extraConfig = ''
+          upstream "local" {
+            default = yes; # Self-scan upstreams are always default
+            self_scan = yes; # Enable self-scan
+          }
+        '';
+      };
+    };
+    services.postfix.config = mkIf cfg.postfix.enable cfg.postfix.config;
 
     # Allow users to run 'rspamc' and 'rspamadm'.
     environment.systemPackages = [ pkgs.rspamd ];
@@ -294,22 +388,20 @@ in
       gid = config.ids.gids.rspamd;
     };
 
-    environment.etc."rspamd.conf".source = rspamdConfFile;
+    environment.etc."rspamd".source = rspamdDir;
 
     systemd.services.rspamd = {
       description = "Rspamd Service";
 
-      wantedBy = mkIf (!cfg.socketActivation) [ "multi-user.target" ];
-      after = [ "network.target" ] ++
-       (if cfg.socketActivation then allSocketNames else []);
-      requires = mkIf cfg.socketActivation allSocketNames;
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      restartTriggers = [ rspamdDir ];
 
       serviceConfig = {
-        ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} --user=${cfg.user} --group=${cfg.group} --pid=/run/rspamd.pid -c ${rspamdConfFile} -f";
+        ExecStart = "${pkgs.rspamd}/bin/rspamd ${optionalString cfg.debug "-d"} --user=${cfg.user} --group=${cfg.group} --pid=/run/rspamd.pid -c /etc/rspamd/rspamd.conf -f";
         Restart = "always";
         RuntimeDirectory = "rspamd";
         PrivateTmp = true;
-        Sockets = mkIf cfg.socketActivation (concatStringsSep " " allSocketNames);
       };
 
       preStart = ''
@@ -317,24 +409,10 @@ in
         ${pkgs.coreutils}/bin/chown ${cfg.user}:${cfg.group} /var/lib/rspamd
       '';
     };
-    systemd.sockets = mkIf cfg.socketActivation
-      (listToAttrs (map (each: {
-        name = "rspamd-${each.name}-${toString each.index}";
-        value = {
-          description = "Rspamd socket ${toString each.index} for worker ${each.name}";
-          wantedBy = [ "sockets.target" ];
-          listenStreams = (listenStreams each.value.socket);
-          socketConfig = {
-            BindIPv6Only = mkIf (isIPv6Socket each.value.socket) "ipv6-only";
-            Service = "rspamd.service";
-            SocketUser = mkIf (isUnixSocket each.value.socket) each.value.owner;
-            SocketGroup = mkIf (isUnixSocket each.value.socket) each.value.group;
-            SocketMode = mkIf (isUnixSocket each.value.socket) each.value.mode;
-          };
-        };
-      }) allMappedSockets));
   };
   imports = [
+    (mkRemovedOptionModule [ "services" "rspamd" "socketActivation" ]
+	     "Socket activation never worked correctly and could at this time not be fixed and so was removed")
     (mkRenamedOptionModule [ "services" "rspamd" "bindSocket" ] [ "services" "rspamd" "workers" "normal" "bindSockets" ])
     (mkRenamedOptionModule [ "services" "rspamd" "bindUISocket" ] [ "services" "rspamd" "workers" "controller" "bindSockets" ])
   ];
diff --git a/nixos/modules/services/mail/rss2email.nix b/nixos/modules/services/mail/rss2email.nix
new file mode 100644
index 0000000000000..5f3b2877008f8
--- /dev/null
+++ b/nixos/modules/services/mail/rss2email.nix
@@ -0,0 +1,136 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.rss2email;
+in {
+
+  ###### interface
+
+  options = {
+
+    services.rss2email = {
+
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable rss2email.";
+      };
+
+      to = mkOption {
+        type = types.str;
+        description = "Mail address to which to send emails";
+      };
+
+      interval = mkOption {
+        type = types.str;
+        default = "12h";
+        description = "How often to check the feeds, in systemd interval format";
+      };
+
+      config = mkOption {
+        type = with types; attrsOf (either str (either int bool));
+        default = {};
+        description = ''
+          The configuration to give rss2email.
+
+          Default will use system-wide <literal>sendmail</literal> to send the
+          email. This is rss2email's default when running
+          <literal>r2e new</literal>.
+
+          This set contains key-value associations that will be set in the
+          <literal>[DEFAULT]</literal> block along with the
+          <literal>to</literal> parameter.
+
+          See
+          <literal>https://github.com/rss2email/rss2email/blob/master/r2e.1</literal>
+          for more information on which parameters are accepted.
+        '';
+      };
+
+      feeds = mkOption {
+        description = "The feeds to watch.";
+        type = types.attrsOf (types.submodule {
+          options = {
+            url = mkOption {
+              type = types.str;
+              description = "The URL at which to fetch the feed.";
+            };
+
+            to = mkOption {
+              type = with types; nullOr str;
+              default = null;
+              description = ''
+                Email address to which to send feed items.
+
+                If <literal>null</literal>, this will not be set in the
+                configuration file, and rss2email will make it default to
+                <literal>rss2email.to</literal>.
+              '';
+            };
+          };
+        });
+      };
+    };
+
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    users.groups = {
+      rss2email.gid = config.ids.gids.rss2email;
+    };
+
+    users.users = {
+      rss2email = {
+        description = "rss2email user";
+        uid = config.ids.uids.rss2email;
+        group = "rss2email";
+      };
+    };
+
+    services.rss2email.config.to = cfg.to;
+
+    systemd.services.rss2email = let
+      conf = pkgs.writeText "rss2email.cfg" (lib.generators.toINI {} ({
+          DEFAULT = cfg.config;
+        } // lib.mapAttrs' (name: feed: nameValuePair "feed.${name}" (
+          { inherit (feed) url; } //
+          lib.optionalAttrs (feed.to != null) { inherit (feed) to; }
+        )) cfg.feeds
+      ));
+    in
+    {
+      preStart = ''
+        mkdir -p /var/rss2email
+        chmod 700 /var/rss2email
+
+        cp ${conf} /var/rss2email/conf.cfg
+        if [ ! -f /var/rss2email/db.json ]; then
+          echo '{"version":2,"feeds":[]}' > /var/rss2email/db.json
+        fi
+
+        chown -R rss2email:rss2email /var/rss2email
+      '';
+      path = [ pkgs.system-sendmail ];
+      serviceConfig = {
+        ExecStart =
+          "${pkgs.rss2email}/bin/r2e -c /var/rss2email/conf.cfg -d /var/rss2email/db.json run";
+        User = "rss2email";
+        PermissionsStartOnly = "true";
+      };
+    };
+
+    systemd.timers.rss2email = {
+      partOf = [ "rss2email.service" ];
+      wantedBy = [ "timers.target" ];
+      timerConfig.OnBootSec = "0";
+      timerConfig.OnUnitActiveSec = cfg.interval;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
+}
diff --git a/nixos/modules/services/misc/airsonic.nix b/nixos/modules/services/misc/airsonic.nix
index 083587b8ebb1b..8b2ec82c77054 100644
--- a/nixos/modules/services/misc/airsonic.nix
+++ b/nixos/modules/services/misc/airsonic.nix
@@ -25,6 +25,14 @@ in {
         '';
       };
 
+      virtualHost = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Name of the nginx virtualhost to use and setup. If null, do not setup any virtualhost.
+        '';
+      };
+
       listenAddress = mkOption {
         type = types.string;
         default = "127.0.0.1";
@@ -73,6 +81,24 @@ in {
           ${cfg.home}/transcoders.
         '';
       };
+
+      jvmOptions = mkOption {
+        description = ''
+          Extra command line options for the JVM running AirSonic.
+          Useful for sending jukebox output to non-default alsa
+          devices.
+        '';
+        default = [
+        ];
+        type = types.listOf types.str;
+        example = [
+          "-Djavax.sound.sampled.Clip='#CODEC [plughw:1,0]'"
+          "-Djavax.sound.sampled.Port='#Port CODEC [hw:1]'"
+          "-Djavax.sound.sampled.SourceDataLine='#CODEC [plughw:1,0]'"
+          "-Djavax.sound.sampled.TargetDataLine='#CODEC [plughw:1,0]'"
+        ];
+      };
+
     };
   };
 
@@ -98,6 +124,9 @@ in {
           -Dserver.port=${toString cfg.port} \
           -Dairsonic.contextPath=${cfg.contextPath} \
           -Djava.awt.headless=true \
+          ${optionalString (cfg.virtualHost != null)
+            "-Dserver.use-forward-headers=true"} \
+          ${toString cfg.jvmOptions} \
           -verbose:gc \
           -jar ${pkgs.airsonic}/webapps/airsonic.war
         '';
@@ -107,6 +136,13 @@ in {
       };
     };
 
+    services.nginx = mkIf (cfg.virtualHost != null) {
+      enable = true;
+      virtualHosts."${cfg.virtualHost}" = {
+        locations."${cfg.contextPath}".proxyPass = "http://${cfg.listenAddress}:${toString cfg.port}";
+      };
+    };
+
     users.users.airsonic = {
       description = "Airsonic service user";
       name = cfg.user;
diff --git a/nixos/modules/services/misc/bees.nix b/nixos/modules/services/misc/bees.nix
new file mode 100644
index 0000000000000..b0ed2d5c2862d
--- /dev/null
+++ b/nixos/modules/services/misc/bees.nix
@@ -0,0 +1,123 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.beesd;
+
+  logLevels = { emerg = 0; alert = 1; crit = 2; err = 3; warning = 4; notice = 5; info = 6; debug = 7; };
+
+  fsOptions = with types; {
+    options.spec = mkOption {
+      type = str;
+      description = ''
+        Description of how to identify the filesystem to be duplicated by this
+        instance of bees. Note that deduplication crosses subvolumes; one must
+        not configure multiple instances for subvolumes of the same filesystem
+        (or block devices which are part of the same filesystem), but only for
+        completely independent btrfs filesystems.
+        </para>
+        <para>
+        This must be in a format usable by findmnt; that could be a key=value
+        pair, or a bare path to a mount point.
+      '';
+      example = "LABEL=MyBulkDataDrive";
+    };
+    options.hashTableSizeMB = mkOption {
+      type = types.addCheck types.int (n: mod n 16 == 0);
+      default = 1024; # 1GB; default from upstream beesd script
+      description = ''
+        Hash table size in MB; must be a multiple of 16.
+        </para>
+        <para>
+        A larger ratio of index size to storage size means smaller blocks of
+        duplicate content are recognized.
+        </para>
+        <para>
+        If you have 1TB of data, a 4GB hash table (which is to say, a value of
+        4096) will permit 4KB extents (the smallest possible size) to be
+        recognized, whereas a value of 1024 -- creating a 1GB hash table --
+        will recognize only aligned duplicate blocks of 16KB.
+      '';
+    };
+    options.verbosity = mkOption {
+      type = types.enum (attrNames logLevels ++ attrValues logLevels);
+      apply = v: if isString v then logLevels.${v} else v;
+      default = "info";
+      description = "Log verbosity (syslog keyword/level).";
+    };
+    options.workDir = mkOption {
+      type = str;
+      default = ".beeshome";
+      description = ''
+        Name (relative to the root of the filesystem) of the subvolume where
+        the hash table will be stored.
+      '';
+    };
+    options.extraOptions = mkOption {
+      type = listOf str;
+      default = [];
+      description = ''
+        Extra command-line options passed to the daemon. See upstream bees documentation.
+      '';
+      example = literalExample ''
+        [ "--thread-count" "4" ]
+      '';
+    };
+  };
+
+in {
+
+  options.services.beesd = {
+    filesystems = mkOption {
+      type = with types; attrsOf (submodule fsOptions);
+      description = "BTRFS filesystems to run block-level deduplication on.";
+      default = { };
+      example = literalExample ''
+        {
+          root = {
+            spec = "LABEL=root";
+            hashTableSizeMB = 2048;
+            verbosity = "crit";
+            extraOptions = [ "--loadavg-target" "5.0" ];
+          };
+        }
+      '';
+    };
+  };
+  config = {
+    systemd.services = mapAttrs' (name: fs: nameValuePair "beesd@${name}" {
+      description = "Block-level BTRFS deduplication for %i";
+      after = [ "sysinit.target" ];
+
+      serviceConfig = let
+        configOpts = [
+          fs.spec
+          "verbosity=${toString fs.verbosity}"
+          "idxSizeMB=${toString fs.hashTableSizeMB}"
+          "workDir=${fs.workDir}"
+        ];
+        configOptsStr = escapeShellArgs configOpts;
+      in {
+        # Values from https://github.com/Zygo/bees/blob/v0.6.1/scripts/beesd%40.service.in
+        ExecStart = "${pkgs.bees}/bin/bees-service-wrapper run ${configOptsStr} -- --no-timestamps ${escapeShellArgs fs.extraOptions}";
+        ExecStopPost = "${pkgs.bees}/bin/bees-service-wrapper cleanup ${configOptsStr}";
+        CPUAccounting = true;
+        CPUWeight = 12;
+        IOSchedulingClass = "idle";
+        IOSchedulingPriority = 7;
+        IOWeight = 10;
+        KillMode = "control-group";
+        KillSignal = "SIGTERM";
+        MemoryAccounting = true;
+        Nice = 19;
+        Restart = "on-abnormal";
+        StartupCPUWeight = 25;
+        StartupIOWeight = 25;
+        SyslogIdentifier = "bees"; # would otherwise be "bees-service-wrapper"
+      };
+      wantedBy = ["multi-user.target"];
+    }) cfg.filesystems;
+  };
+}
diff --git a/nixos/modules/services/misc/docker-registry.nix b/nixos/modules/services/misc/docker-registry.nix
index 9a3966ab30aa2..f3d90e532c887 100644
--- a/nixos/modules/services/misc/docker-registry.nix
+++ b/nixos/modules/services/misc/docker-registry.nix
@@ -18,7 +18,7 @@ let
       delete.enabled = cfg.enableDelete;
     };
     http = {
-      addr = ":${builtins.toString cfg.port}";
+      addr = "${cfg.listenAddress}:${builtins.toString cfg.port}";
       headers.X-Content-Type-Options = ["nosniff"];
     };
     health.storagedriver = {
diff --git a/nixos/modules/services/misc/emby.nix b/nixos/modules/services/misc/emby.nix
index 64cc9c610ac39..0ad4a3f7376fd 100644
--- a/nixos/modules/services/misc/emby.nix
+++ b/nixos/modules/services/misc/emby.nix
@@ -36,11 +36,18 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       preStart = ''
-        test -d ${cfg.dataDir} || {
-          echo "Creating initial Emby data directory in ${cfg.dataDir}"
-          mkdir -p ${cfg.dataDir}
-          chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
-          }
+        if [ -d ${cfg.dataDir} ]
+        then
+            for plugin in ${cfg.dataDir}/plugins/*
+            do
+                echo "Correcting permissions of plugin: $plugin"
+                chmod u+w $plugin
+            done
+        else
+            echo "Creating initial Emby data directory in ${cfg.dataDir}"
+            mkdir -p ${cfg.dataDir}
+            chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
+        fi
       '';
 
       serviceConfig = {
@@ -48,7 +55,7 @@ in
         User = cfg.user;
         Group = cfg.group;
         PermissionsStartOnly = "true";
-        ExecStart = "${pkgs.emby}/bin/MediaBrowser.Server.Mono";
+        ExecStart = "${pkgs.emby}/bin/emby -programdata ${cfg.dataDir}";
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/misc/exhibitor.nix b/nixos/modules/services/misc/exhibitor.nix
index a90c7f402e7f3..665084a8ae054 100644
--- a/nixos/modules/services/misc/exhibitor.nix
+++ b/nixos/modules/services/misc/exhibitor.nix
@@ -405,6 +405,9 @@ in
         cp -Rf ${pkgs.zookeeper}/* ${cfg.baseDir}/zookeeper
         chown -R zookeeper ${cfg.baseDir}/zookeeper/conf
         chmod -R u+w ${cfg.baseDir}/zookeeper/conf
+        replace_what=$(echo ${pkgs.zookeeper} | sed 's/[\/&]/\\&/g')
+        replace_with=$(echo ${cfg.baseDir}/zookeeper | sed 's/[\/&]/\\&/g')
+        sed -i 's/'"$replace_what"'/'"$replace_with"'/g' ${cfg.baseDir}/zookeeper/bin/zk*.sh
       '';
     };
     users.users = singleton {
diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix
index a222325579fec..be4d38719785a 100644
--- a/nixos/modules/services/misc/gitea.nix
+++ b/nixos/modules/services/misc/gitea.nix
@@ -6,6 +6,7 @@ let
   cfg = config.services.gitea;
   gitea = cfg.package;
   pg = config.services.postgresql;
+  useMysql = cfg.database.type == "mysql";
   usePostgresql = cfg.database.type == "postgres";
   configFile = pkgs.writeText "app.ini" ''
     APP_NAME = ${cfg.appName}
@@ -14,7 +15,7 @@ let
 
     [database]
     DB_TYPE = ${cfg.database.type}
-    HOST = ${cfg.database.host}:${toString cfg.database.port}
+    HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port}
     NAME = ${cfg.database.name}
     USER = ${cfg.database.user}
     PASSWD = #dbpass#
@@ -45,6 +46,9 @@ let
     ROOT_PATH = ${cfg.log.rootPath}
     LEVEL = ${cfg.log.level}
 
+    [service]
+    DISABLE_REGISTRATION = ${boolToString cfg.disableRegistration}
+
     ${cfg.extraConfig}
   '';
 in
@@ -148,6 +152,13 @@ in
           '';
         };
 
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/mysqld/mysqld.sock";
+          description = "Path to the unix socket file to use for authentication.";
+        };
+
         path = mkOption {
           type = types.str;
           default = "${cfg.stateDir}/data/gitea.db";
@@ -240,6 +251,18 @@ in
         description = "Upper level of template and static files path.";
       };
 
+      disableRegistration = mkEnableOption "the registration lock" // {
+        description = ''
+          By default any user can create an account on this <literal>gitea</literal> instance.
+          This can be disabled by using this option.
+
+          <emphasis>Note:</emphasis> please keep in mind that this should be added after the initial
+          deploy unless <link linkend="opt-services.gitea.useWizard">services.gitea.useWizard</link>
+          is <literal>true</literal> as the first registered user will be the administrator if
+          no install wizard is used.
+        '';
+      };
+
       extraConfig = mkOption {
         type = types.str;
         default = "";
@@ -253,9 +276,9 @@ in
 
     systemd.services.gitea = {
       description = "gitea";
-      after = [ "network.target" "postgresql.service" ];
+      after = [ "network.target" ] ++ lib.optional usePostgresql "postgresql.service" ++ lib.optional useMysql "mysql.service";
       wantedBy = [ "multi-user.target" ];
-      path = [ gitea.bin ];
+      path = [ gitea.bin pkgs.gitAndTools.git ];
 
       preStart = let
         runConfig = "${cfg.stateDir}/custom/conf/app.ini";
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 5bf66354f487e..25c258ebe1341 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -14,15 +14,16 @@ let
   pathUrlQuote = url: replaceStrings ["/"] ["%2F"] url;
   pgSuperUser = config.services.postgresql.superUser;
 
-  databaseYml = ''
-    production:
-      adapter: postgresql
-      database: ${cfg.databaseName}
-      host: ${cfg.databaseHost}
-      password: ${cfg.databasePassword}
-      username: ${cfg.databaseUsername}
-      encoding: utf8
-  '';
+  databaseConfig = {
+    production = {
+      adapter = "postgresql";
+      database = cfg.databaseName;
+      host = cfg.databaseHost;
+      password = cfg.databasePassword;
+      username = cfg.databaseUsername;
+      encoding = "utf8";
+    };
+  };
 
   gitalyToml = pkgs.writeText "gitaly.toml" ''
     socket_path = "${lib.escape ["\""] gitalySocket}"
@@ -45,34 +46,31 @@ let
     '') gitlabConfig.production.repositories.storages))}
   '';
 
-  gitlabShellYml = ''
-    user: ${cfg.user}
-    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"
-    log_file: "${cfg.statePath}/log/gitlab-shell.log"
-    redis:
-      bin: ${pkgs.redis}/bin/redis-cli
-      host: 127.0.0.1
-      port: 6379
-      database: 0
-      namespace: resque:gitlab
-  '';
+  gitlabShellConfig = {
+    user = cfg.user;
+    gitlab_url = "http+unix://${pathUrlQuote gitlabSocket}";
+    http_settings.self_signed_cert = false;
+    repos_path = "${cfg.statePath}/repositories";
+    secret_file = "${cfg.statePath}/config/gitlab_shell_secret";
+    log_file = "${cfg.statePath}/log/gitlab-shell.log";
+    custom_hooks_dir = "${cfg.statePath}/custom_hooks";
+    redis = {
+      bin = "${pkgs.redis}/bin/redis-cli";
+      host = "127.0.0.1";
+      port = 6379;
+      database = 0;
+      namespace = "resque:gitlab";
+    };
+  };
 
-  redisYml = ''
-    production:
-      url: redis://localhost:6379/
-  '';
+  redisConfig.production.url = "redis://localhost:6379/";
 
-  secretsYml = ''
-    production:
-      secret_key_base: ${cfg.secrets.secret}
-      otp_key_base: ${cfg.secrets.otp}
-      db_key_base: ${cfg.secrets.db}
-      openid_connect_signing_key: ${builtins.toJSON cfg.secrets.jws}
-  '';
+  secretsConfig.production = {
+    secret_key_base = cfg.secrets.secret;
+    otp_key_base = cfg.secrets.otp;
+    db_key_base = cfg.secrets.db;
+    openid_connect_signing_key = cfg.secrets.jws;
+  };
 
   gitlabConfig = {
     # These are the default settings from config/gitlab.example.yml
@@ -114,12 +112,8 @@ let
         upload_pack = true;
         receive_pack = true;
       };
-      workhorse = {
-        secret_file = "${cfg.statePath}/.gitlab_workhorse_secret";
-      };
-      git = {
-        bin_path = "git";
-      };
+      workhorse.secret_file = "${cfg.statePath}/.gitlab_workhorse_secret";
+      git.bin_path = "git";
       monitoring = {
         ip_whitelist = [ "127.0.0.0/8" "::1/128" ];
         sidekiq_exporter = {
@@ -137,7 +131,7 @@ let
     HOME = "${cfg.statePath}/home";
     UNICORN_PATH = "${cfg.statePath}/";
     GITLAB_PATH = "${cfg.packages.gitlab}/share/gitlab/";
-    GITLAB_STATE_PATH = "${cfg.statePath}";
+    GITLAB_STATE_PATH = cfg.statePath;
     GITLAB_UPLOADS_PATH = "${cfg.statePath}/uploads";
     SCHEMA = "${cfg.statePath}/db/schema.rb";
     GITLAB_LOG_PATH = "${cfg.statePath}/log";
@@ -145,13 +139,11 @@ let
     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 "gitlab-redis.yml" redisYml;
+    GITLAB_REDIS_CONFIG_FILE = pkgs.writeText "redis.yml" (builtins.toJSON redisConfig);
     prometheus_multiproc_dir = "/run/gitlab";
     RAILS_ENV = "production";
   };
 
-  unicornConfig = builtins.readFile ./defaultUnicornConfig.rb;
-
   gitlab-rake = pkgs.stdenv.mkDerivation rec {
     name = "gitlab-rake";
     buildInputs = [ pkgs.makeWrapper ];
@@ -161,8 +153,7 @@ let
       mkdir -p $out/bin
       makeWrapper ${cfg.packages.gitlab.rubyEnv}/bin/rake $out/bin/gitlab-rake \
           ${concatStrings (mapAttrsToList (name: value: "--set ${name} '${value}' ") gitlabEnv)} \
-          --set GITLAB_CONFIG_PATH '${cfg.statePath}/config' \
-          --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar config.services.postgresql.package ]}:$PATH' \
+          --set PATH '${lib.makeBinPath [ pkgs.nodejs pkgs.gzip pkgs.git pkgs.gnutar config.services.postgresql.package pkgs.coreutils pkgs.procps ]}:$PATH' \
           --set RAKEOPT '-f ${cfg.packages.gitlab}/share/gitlab/Rakefile' \
           --run 'cd ${cfg.packages.gitlab}/share/gitlab'
      '';
@@ -203,6 +194,7 @@ in {
         default = pkgs.gitlab;
         defaultText = "pkgs.gitlab";
         description = "Reference to the gitlab package";
+        example = "pkgs.gitlab-ee";
       };
 
       packages.gitlab-shell = mkOption {
@@ -304,7 +296,6 @@ in {
 
       initialRootPassword = mkOption {
         type = types.str;
-        default = "UseNixOS!";
         description = ''
           Initial password of the root account if this is a new install.
         '';
@@ -459,10 +450,30 @@ in {
       }
     ];
 
+    systemd.tmpfiles.rules = [
+      "d /run/gitlab 0755 ${cfg.user} ${cfg.group} -"
+      "d ${gitlabEnv.HOME} 0750 ${cfg.user} ${cfg.group} -"
+      "d ${cfg.backupPath} 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}/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/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/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}/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} -"
+    ];
+
     systemd.services.gitlab-sidekiq = {
-      after = [ "network.target" "redis.service" ];
+      after = [ "network.target" "redis.service" "gitlab.service" ];
       wantedBy = [ "multi-user.target" ];
-      partOf = [ "gitlab.service" ];
       environment = gitlabEnv;
       path = with pkgs; [
         config.services.postgresql.package
@@ -484,11 +495,14 @@ in {
     };
 
     systemd.services.gitaly = {
-      after = [ "network.target" "gitlab.service" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      environment.HOME = gitlabEnv.HOME;
-      environment.GITLAB_SHELL_CONFIG_PATH = gitlabEnv.GITLAB_SHELL_CONFIG_PATH;
-      path = with pkgs; [ gitAndTools.git cfg.packages.gitaly.rubyEnv cfg.packages.gitaly.rubyEnv.wrappedRuby ];
+      path = with pkgs; [
+        openssh
+        gitAndTools.git
+        cfg.packages.gitaly.rubyEnv
+        cfg.packages.gitaly.rubyEnv.wrappedRuby
+      ];
       serviceConfig = {
         Type = "simple";
         User = cfg.user;
@@ -501,10 +515,8 @@ in {
     };
 
     systemd.services.gitlab-workhorse = {
-      after = [ "network.target" "gitlab.service" ];
+      after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      environment.HOME = gitlabEnv.HOME;
-      environment.GITLAB_SHELL_CONFIG_PATH = gitlabEnv.GITLAB_SHELL_CONFIG_PATH;
       path = with pkgs; [
         gitAndTools.git
         gnutar
@@ -512,10 +524,6 @@ in {
         openssh
         gitlab-workhorse
       ];
-      preStart = ''
-        mkdir -p /run/gitlab
-        chown ${cfg.user}:${cfg.group} /run/gitlab
-      '';
       serviceConfig = {
         PermissionsStartOnly = true; # preStart must be run as root
         Type = "simple";
@@ -536,7 +544,7 @@ in {
     };
 
     systemd.services.gitlab = {
-      after = [ "network.target" "postgresql.service" "redis.service" ];
+      after = [ "gitlab-workhorse.service" "gitaly.service" "network.target" "postgresql.service" "redis.service" ];
       requires = [ "gitlab-sidekiq.service" ];
       wantedBy = [ "multi-user.target" ];
       environment = gitlabEnv;
@@ -549,82 +557,59 @@ in {
         gnupg
       ];
       preStart = ''
-        mkdir -p ${cfg.backupPath}
-        mkdir -p ${cfg.statePath}/builds
-        mkdir -p ${cfg.statePath}/repositories
-        mkdir -p ${gitlabConfig.production.shared.path}/artifacts
-        mkdir -p ${gitlabConfig.production.shared.path}/lfs-objects
-        mkdir -p ${gitlabConfig.production.shared.path}/pages
-        mkdir -p ${cfg.statePath}/log
-        mkdir -p ${cfg.statePath}/tmp/pids
-        mkdir -p ${cfg.statePath}/tmp/sockets
-        mkdir -p ${cfg.statePath}/shell
-        mkdir -p ${cfg.statePath}/db
-        mkdir -p ${cfg.statePath}/uploads
-
-        rm -rf ${cfg.statePath}/config ${cfg.statePath}/shell/hooks
-        mkdir -p ${cfg.statePath}/config
-
-        ${pkgs.openssl}/bin/openssl rand -hex 32 > ${cfg.statePath}/config/gitlab_shell_secret
-
-        mkdir -p /run/gitlab
-        mkdir -p ${cfg.statePath}/log
-        ln -sf ${cfg.statePath}/log /run/gitlab/log
-        ln -sf ${cfg.statePath}/tmp /run/gitlab/tmp
-        ln -sf ${cfg.statePath}/uploads /run/gitlab/uploads
-        ln -sf $GITLAB_SHELL_CONFIG_PATH /run/gitlab/shell-config.yml
-        chown -R ${cfg.user}:${cfg.group} /run/gitlab
-
-        # Prepare home directory
-        mkdir -p ${gitlabEnv.HOME}/.ssh
-        touch ${gitlabEnv.HOME}/.ssh/authorized_keys
-        chown -R ${cfg.user}:${cfg.group} ${gitlabEnv.HOME}/
-
         cp -rf ${cfg.packages.gitlab}/share/gitlab/db/* ${cfg.statePath}/db
-        cp -rf ${cfg.packages.gitlab}/share/gitlab/config.dist/* ${cfg.statePath}/config
-        ${optionalString cfg.smtp.enable ''
-          ln -sf ${smtpSettings} ${cfg.statePath}/config/initializers/smtp_settings.rb
-        ''}
-        ln -sf ${cfg.statePath}/config /run/gitlab/config
+        rm -rf ${cfg.statePath}/config
+        mkdir ${cfg.statePath}/config
         if [ -e ${cfg.statePath}/lib ]; then
           rm ${cfg.statePath}/lib
         fi
-        ln -sf ${pkgs.gitlab}/share/gitlab/lib ${cfg.statePath}/lib
+
+        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
+        ${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 -fs ${pkgs.writeText "gitlab.yml" (builtins.toJSON gitlabConfig)} ${cfg.statePath}/config/gitlab.yml
-        ln -fs ${pkgs.writeText "database.yml" databaseYml} ${cfg.statePath}/config/database.yml
-        ln -fs ${pkgs.writeText "secrets.yml" secretsYml} ${cfg.statePath}/config/secrets.yml
-        ln -fs ${pkgs.writeText "unicorn.rb" unicornConfig} ${cfg.statePath}/config/unicorn.rb
-
-        chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/
-        chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/
+        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 -fs ${pkgs.writeText "config.yml" gitlabShellYml} "$GITLAB_SHELL_CONFIG_PATH"
-        ln -fs ${cfg.packages.gitlab-shell}/hooks "$GITLAB_SHELL_HOOKS_PATH"
+        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
 
-        if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then
-          if ! test -e "${cfg.statePath}/db-created"; then
+        chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}/
+        chmod -R ug+rwX,o-rwx+X ${cfg.statePath}/
+        chown -R ${cfg.user}:${cfg.group} /run/gitlab
+
+        if ! test -e "${cfg.statePath}/db-created"; then
+          if [ "${cfg.databaseHost}" = "127.0.0.1" ]; then
             ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql postgres -c "CREATE ROLE ${cfg.databaseUsername} WITH LOGIN NOCREATEDB NOCREATEROLE ENCRYPTED PASSWORD '${cfg.databasePassword}'"
             ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} ${config.services.postgresql.package}/bin/createdb --owner ${cfg.databaseUsername} ${cfg.databaseName}
-            touch "${cfg.statePath}/db-created"
+
+            # enable required pg_trgm extension for gitlab
+            ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql ${cfg.databaseName} -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"
           fi
 
-          # enable required pg_trgm extension for gitlab
-          ${pkgs.sudo}/bin/sudo -u ${pgSuperUser} psql ${cfg.databaseName} -c "CREATE EXTENSION IF NOT EXISTS pg_trgm"
+          ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:schema:load
+
+          touch "${cfg.statePath}/db-created"
         fi
 
         # Always do the db migrations just to be sure the database is up-to-date
-        ${gitlab-rake}/bin/gitlab-rake db:migrate RAILS_ENV=production
+        ${pkgs.sudo}/bin/sudo -u ${cfg.user} -H ${gitlab-rake}/bin/gitlab-rake db:migrate
 
-        # The gitlab:setup task is horribly broken somehow, the db:migrate
-        # task above and the db:seed_fu below will do the same for setting
-        # up the initial database
         if ! test -e "${cfg.statePath}/db-seeded"; then
-          ${gitlab-rake}/bin/gitlab-rake db:seed_fu RAILS_ENV=production \
+          ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${gitlab-rake}/bin/gitlab-rake db:seed_fu \
             GITLAB_ROOT_PASSWORD='${cfg.initialRootPassword}' GITLAB_ROOT_EMAIL='${cfg.initialRootEmail}'
           touch "${cfg.statePath}/db-seeded"
         fi
@@ -632,12 +617,12 @@ in {
         # The gitlab:shell:create_hooks task seems broken for fixing links
         # so we instead delete all the hooks and create them anew
         rm -f ${cfg.statePath}/repositories/**/*.git/hooks
-        ${gitlab-rake}/bin/gitlab-rake gitlab:shell:create_hooks RAILS_ENV=production
+        ${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.
-        chown -R ${cfg.user}:${cfg.group} ${cfg.statePath}
-        chmod -R ug+rwX,o-rwx+X ${cfg.statePath}
         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
diff --git a/nixos/modules/services/misc/gitlab.xml b/nixos/modules/services/misc/gitlab.xml
index 67b9f1d3e474e..ab99d7bd3a601 100644
--- a/nixos/modules/services/misc/gitlab.xml
+++ b/nixos/modules/services/misc/gitlab.xml
@@ -3,20 +3,22 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-gitlab">
-
-<title>Gitlab</title>
-
-<para>Gitlab is a feature-rich git hosting service.</para>
-
-<section xml:id="module-services-gitlab-prerequisites"><title>Prerequisites</title>
-
-<para>The gitlab service exposes only an Unix socket at
-<literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to configure a
-webserver to proxy HTTP requests to the socket.</para>
-
-<para>For instance, the following configuration could be used to use nginx as
-    frontend proxy:
-
+ <title>Gitlab</title>
+ <para>
+  Gitlab is a feature-rich git hosting service.
+ </para>
+ <section xml:id="module-services-gitlab-prerequisites">
+  <title>Prerequisites</title>
+
+  <para>
+   The gitlab service exposes only an Unix socket at
+   <literal>/run/gitlab/gitlab-workhorse.socket</literal>. You need to
+   configure a webserver to proxy HTTP requests to the socket.
+  </para>
+
+  <para>
+   For instance, the following configuration could be used to use nginx as
+   frontend proxy:
 <programlisting>
 <link linkend="opt-services.nginx.enable">services.nginx</link> = {
   <link linkend="opt-services.nginx.enable">enable</link> = true;
@@ -31,21 +33,24 @@ webserver to proxy HTTP requests to the socket.</para>
   };
 };
 </programlisting>
-</para>
-
-</section>
-
-<section xml:id="module-services-gitlab-configuring"><title>Configuring</title>
-
-<para>Gitlab depends on both PostgreSQL and Redis and will automatically enable
-both services. In the case of PostgreSQL, a database and a role will be created.
-</para>
-
-<para>The default state dir is <literal>/var/gitlab/state</literal>. This is where
-all data like the repositories and uploads will be stored.</para>
-
-<para>A basic configuration with some custom settings could look like this:
-
+  </para>
+ </section>
+ <section xml:id="module-services-gitlab-configuring">
+  <title>Configuring</title>
+
+  <para>
+   Gitlab depends on both PostgreSQL and Redis and will automatically enable
+   both services. In the case of PostgreSQL, a database and a role will be
+   created.
+  </para>
+
+  <para>
+   The default state dir is <literal>/var/gitlab/state</literal>. This is where
+   all data like the repositories and uploads will be stored.
+  </para>
+
+  <para>
+   A basic configuration with some custom settings could look like this:
 <programlisting>
 services.gitlab = {
   <link linkend="opt-services.gitlab.enable">enable</link> = true;
@@ -105,40 +110,41 @@ services.gitlab = {
   };
 };
 </programlisting>
-</para>
-
-<para>If you're setting up a new Gitlab instance, generate new secrets. You
-for instance use <literal>tr -dc A-Za-z0-9 &lt; /dev/urandom | head -c 128</literal>
-to generate a new secret. Gitlab encrypts sensitive data stored in the database.
-If you're restoring an existing Gitlab instance, you must specify the secrets
-secret from <literal>config/secrets.yml</literal> located in your Gitlab state
-folder.</para>
-
-<para>Refer to <xref linkend="ch-options" /> for all available configuration
-options for the <link linkend="opt-services.gitlab.enable">services.gitlab</link> module.</para>
-
-</section>
-
-<section xml:id="module-services-gitlab-maintenance"><title>Maintenance</title>
-
-<para>You can run Gitlab's rake tasks with <literal>gitlab-rake</literal>
-which will be available on the system when gitlab is enabled. You will
-have to run the command as the user that you configured to run gitlab
-with.</para>
-
-<para>For example, to backup a Gitlab instance:
-
+  </para>
+
+  <para>
+   If you're setting up a new Gitlab instance, generate new secrets. You for
+   instance use <literal>tr -dc A-Za-z0-9 &lt; /dev/urandom | head -c
+   128</literal> to generate a new secret. Gitlab encrypts sensitive data
+   stored in the database. If you're restoring an existing Gitlab instance, you
+   must specify the secrets secret from <literal>config/secrets.yml</literal>
+   located in your Gitlab state folder.
+  </para>
+
+  <para>
+   Refer to <xref linkend="ch-options" /> for all available configuration
+   options for the
+   <link linkend="opt-services.gitlab.enable">services.gitlab</link> module.
+  </para>
+ </section>
+ <section xml:id="module-services-gitlab-maintenance">
+  <title>Maintenance</title>
+
+  <para>
+   You can run Gitlab's rake tasks with <literal>gitlab-rake</literal> which
+   will be available on the system when gitlab is enabled. You will have to run
+   the command as the user that you configured to run gitlab with.
+  </para>
+
+  <para>
+   For example, to backup a Gitlab instance:
 <programlisting>
 $ sudo -u git -H gitlab-rake gitlab:backup:create
 </programlisting>
-
-A list of all availabe rake tasks can be obtained by running:
-
+   A list of all availabe rake tasks can be obtained by running:
 <programlisting>
 $ sudo -u git -H gitlab-rake -T
 </programlisting>
-</para>
-
-</section>
-
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/misc/home-assistant.nix b/nixos/modules/services/misc/home-assistant.nix
index 0756e81612ac0..4ccfa22c89e70 100644
--- a/nixos/modules/services/misc/home-assistant.nix
+++ b/nixos/modules/services/misc/home-assistant.nix
@@ -6,9 +6,18 @@ let
   cfg = config.services.home-assistant;
 
   # cfg.config != null can be assumed here
-  configFile = pkgs.writeText "configuration.json"
+  configJSON = pkgs.writeText "configuration.json"
     (builtins.toJSON (if cfg.applyDefaultConfig then
-    (lib.recursiveUpdate defaultConfig cfg.config) else cfg.config));
+    (recursiveUpdate defaultConfig cfg.config) else cfg.config));
+  configFile = pkgs.runCommand "configuration.yaml" { } ''
+    ${pkgs.remarshal}/bin/json2yaml -i ${configJSON} -o $out
+  '';
+
+  lovelaceConfigJSON = pkgs.writeText "ui-lovelace.json"
+    (builtins.toJSON cfg.lovelaceConfig);
+  lovelaceConfigFile = pkgs.runCommand "ui-lovelace.yaml" { } ''
+    ${pkgs.remarshal}/bin/json2yaml -i ${lovelaceConfigJSON} -o $out
+  '';
 
   availableComponents = pkgs.home-assistant.availableComponents;
 
@@ -44,7 +53,9 @@ let
   # If you are changing this, please update the description in applyDefaultConfig
   defaultConfig = {
     homeassistant.time_zone = config.time.timeZone;
-    http.server_port = (toString cfg.port);
+    http.server_port = cfg.port;
+  } // optionalAttrs (cfg.lovelaceConfig != null) {
+    lovelace.mode = "yaml";
   };
 
 in {
@@ -99,6 +110,53 @@ in {
       '';
     };
 
+    configWritable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to make <filename>configuration.yaml</filename> writable.
+        This only has an effect if <option>config</option> is set.
+        This will allow you to edit it from Home Assistant's web interface.
+        However, bear in mind that it will be overwritten at every start of the service.
+      '';
+    };
+
+    lovelaceConfig = mkOption {
+      default = null;
+      type = with types; nullOr attrs;
+      # from https://www.home-assistant.io/lovelace/yaml-mode/
+      example = literalExample ''
+        {
+          title = "My Awesome Home";
+          views = [ {
+            title = "Example";
+            cards = [ {
+              type = "markdown";
+              title = "Lovelace";
+              content = "Welcome to your **Lovelace UI**.";
+            } ];
+          } ];
+        }
+      '';
+      description = ''
+        Your <filename>ui-lovelace.yaml</filename> as a Nix attribute set.
+        Setting this option will automatically add
+        <literal>lovelace.mode = "yaml";</literal> to your <option>config</option>.
+        Beware that setting this option will delete your previous <filename>ui-lovelace.yaml</filename>
+      '';
+    };
+
+    lovelaceConfigWritable = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Whether to make <filename>ui-lovelace.yaml</filename> writable.
+        This only has an effect if <option>lovelaceConfig</option> is set.
+        This will allow you to edit it from Home Assistant's web interface.
+        However, bear in mind that it will be overwritten at every start of the service.
+      '';
+    };
+
     package = mkOption {
       default = pkgs.home-assistant;
       defaultText = "pkgs.home-assistant";
@@ -144,12 +202,17 @@ in {
     systemd.services.home-assistant = {
       description = "Home Assistant";
       after = [ "network.target" ];
-      preStart = lib.optionalString (cfg.config != null) ''
-        config=${cfg.configDir}/configuration.yaml
-        rm -f $config
-        ${pkgs.remarshal}/bin/json2yaml -i ${configFile} -o $config
-        chmod 444 $config
-      '';
+      preStart = optionalString (cfg.config != null) (if cfg.configWritable then ''
+        cp --no-preserve=mode ${configFile} "${cfg.configDir}/configuration.yaml"
+      '' else ''
+        rm -f "${cfg.configDir}/configuration.yaml"
+        ln -s ${configFile} "${cfg.configDir}/configuration.yaml"
+      '') + optionalString (cfg.lovelaceConfig != null) (if cfg.lovelaceConfigWritable then ''
+        cp --no-preserve=mode ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+      '' else ''
+        rm -f "${cfg.configDir}/ui-lovelace.yaml"
+        ln -s ${lovelaceConfigFile} "${cfg.configDir}/ui-lovelace.yaml"
+      '');
       serviceConfig = {
         ExecStart = "${package}/bin/hass --config '${cfg.configDir}'";
         User = "hass";
@@ -157,6 +220,7 @@ in {
         Restart = "on-failure";
         ProtectSystem = "strict";
         ReadWritePaths = "${cfg.configDir}";
+        KillSignal = "SIGINT";
         PrivateTmp = true;
         RemoveIPC = true;
       };
diff --git a/nixos/modules/services/misc/lidarr.nix b/nixos/modules/services/misc/lidarr.nix
new file mode 100644
index 0000000000000..627f22334fe85
--- /dev/null
+++ b/nixos/modules/services/misc/lidarr.nix
@@ -0,0 +1,46 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+  cfg = config.services.lidarr;
+in
+{
+  options = {
+    services.lidarr = {
+      enable = mkEnableOption "Lidarr";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.lidarr = {
+      description = "Lidarr";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        [ ! -d /var/lib/lidarr ] && mkdir -p /var/lib/lidarr
+        chown -R lidarr:lidarr /var/lib/lidarr
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = "lidarr";
+        Group = "lidarr";
+        PermissionsStartOnly = "true";
+        ExecStart = "${pkgs.lidarr}/bin/Lidarr";
+        Restart = "on-failure";
+
+        StateDirectory = "/var/lib/lidarr/";
+        StateDirectoryMode = "0770";
+      };
+    };
+
+    users.users.lidarr = {
+      uid = config.ids.uids.lidarr;
+      home = "/var/lib/lidarr";
+      group = "lidarr";
+    };
+
+    users.groups.lidarr.gid = config.ids.gids.lidarr;
+  };
+}
diff --git a/nixos/modules/services/misc/matrix-synapse.nix b/nixos/modules/services/misc/matrix-synapse.nix
index 18e13f6ac0300..a01e34d736297 100644
--- a/nixos/modules/services/misc/matrix-synapse.nix
+++ b/nixos/modules/services/misc/matrix-synapse.nix
@@ -651,12 +651,16 @@ in {
 
     services.postgresql.enable = mkIf usePostgresql (mkDefault true);
 
-    systemd.services.matrix-synapse = {
+    systemd.services.matrix-synapse =
+    let
+      python = (pkgs.python3.withPackages (ps: with ps; [ (ps.toPythonModule cfg.package) ]));
+    in
+    {
       description = "Synapse Matrix homeserver";
       after = [ "network.target" "postgresql.service" ];
       wantedBy = [ "multi-user.target" ];
       preStart = ''
-        ${cfg.package}/bin/homeserver \
+        ${python.interpreter} -m synapse.app.homeserver \
           --config-path ${configFile} \
           --keys-directory ${cfg.dataDir} \
           --generate-keys
@@ -687,10 +691,11 @@ in {
         WorkingDirectory = cfg.dataDir;
         PermissionsStartOnly = true;
         ExecStart = ''
-          ${cfg.package}/bin/homeserver \
+          ${python.interpreter} -m synapse.app.homeserver \
             ${ concatMapStringsSep "\n  " (x: "--config-path ${x} \\") ([ configFile ] ++ cfg.extraConfigFiles) }
             --keys-directory ${cfg.dataDir}
         '';
+        ExecReload = "${pkgs.utillinux}/bin/kill -HUP $MAINPID";
         Restart = "on-failure";
       };
     };
diff --git a/nixos/modules/services/misc/nix-daemon.nix b/nixos/modules/services/misc/nix-daemon.nix
index c0eb882c58f37..9a8116a03e88e 100644
--- a/nixos/modules/services/misc/nix-daemon.nix
+++ b/nixos/modules/services/misc/nix-daemon.nix
@@ -62,11 +62,15 @@ let
         ''}
         $extraOptions
         END
-      '' + optionalString cfg.checkConfig ''
-        echo "Checking that Nix can read nix.conf..."
-        ln -s $out ./nix.conf
-        NIX_CONF_DIR=$PWD ${cfg.package}/bin/nix show-config >/dev/null
-      '');
+      '' + optionalString cfg.checkConfig (
+            if pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform then ''
+              echo "Ignore nix.checkConfig when cross-compiling"
+            '' else ''
+              echo "Checking that Nix can read nix.conf..."
+              ln -s $out ./nix.conf
+              NIX_CONF_DIR=$PWD ${cfg.package}/bin/nix show-config >/dev/null
+            '')
+      );
 
 in
 
@@ -113,11 +117,11 @@ in
 
       buildCores = mkOption {
         type = types.int;
-        default = 1;
+        default = 0;
         example = 64;
         description = ''
           This option defines the maximum number of concurrent tasks during
-          one build. It affects, e.g., -j option for make. The default is 1.
+          one build. It affects, e.g., -j option for make.
           The special value 0 means that the builder should use all
           available CPU cores in the system. Some builds may become
           non-deterministic with this option; use with care! Packages will
@@ -345,7 +349,6 @@ in
         type = types.listOf types.str;
         default =
           [
-            "$HOME/.nix-defexpr/channels"
             "nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos"
             "nixos-config=/etc/nixos/configuration.nix"
             "/nix/var/nix/profiles/per-user/root/channels"
@@ -400,8 +403,8 @@ in
     systemd.sockets.nix-daemon.wantedBy = [ "sockets.target" ];
 
     systemd.services.nix-daemon =
-      { path = [ nix pkgs.utillinux ]
-          ++ optionals cfg.distributedBuilds [ config.programs.ssh.package pkgs.gzip ]
+      { path = [ nix pkgs.utillinux config.programs.ssh.package ]
+          ++ optionals cfg.distributedBuilds [ pkgs.gzip ]
           ++ optionals (!isNix20) [ pkgs.openssl.bin ];
 
         environment = cfg.envVars
@@ -436,7 +439,7 @@ in
 
     # Set up the environment variables for running Nix.
     environment.sessionVariables = cfg.envVars //
-      { NIX_PATH = concatStringsSep ":" cfg.nixPath;
+      { NIX_PATH = cfg.nixPath;
       };
 
     environment.extraInit = optionalString (!isNix20)
@@ -446,6 +449,10 @@ in
         if [ "$USER" != root -o ! -w /nix/var/nix/db ]; then
             export NIX_REMOTE=daemon
         fi
+      '' + ''
+        if [ -e "$HOME/.nix-defexpr/channels" ]; then
+          export NIX_PATH="$HOME/.nix-defexpr/channels''${NIX_PATH:+:$NIX_PATH}"
+        fi
       '';
 
     nix.nrBuildUsers = mkDefault (lib.max 32 cfg.maxJobs);
diff --git a/nixos/modules/services/misc/nix-optimise.nix b/nixos/modules/services/misc/nix-optimise.nix
index 6f75e4dd03ea8..416529f690e05 100644
--- a/nixos/modules/services/misc/nix-optimise.nix
+++ b/nixos/modules/services/misc/nix-optimise.nix
@@ -40,6 +40,8 @@ in
 
     systemd.services.nix-optimise =
       { description = "Nix Store Optimiser";
+        # No point running it inside a nixos-container. It should be on the host instead.
+        unitConfig.ConditionVirtualization = "!container";
         serviceConfig.ExecStart = "${config.nix.package}/bin/nix-store --optimise";
         startAt = optionals cfg.automatic cfg.dates;
       };
diff --git a/nixos/modules/services/misc/nixos-manual.nix b/nixos/modules/services/misc/nixos-manual.nix
index 993b59590bb0c..df3e71c80dea3 100644
--- a/nixos/modules/services/misc/nixos-manual.nix
+++ b/nixos/modules/services/misc/nixos-manual.nix
@@ -1,91 +1,21 @@
-# This module includes the NixOS man-pages in the system environment,
-# and optionally starts a browser that shows the NixOS manual on one
-# of the virtual consoles.  The latter is useful for the installation
+# This module optionally starts a browser that shows the NixOS manual
+# on one of the virtual consoles which is useful for the installation
 # CD.
 
-{ config, lib, pkgs, baseModules, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
 let
-
   cfg = config.services.nixosManual;
-
-  /* For the purpose of generating docs, evaluate options with each derivation
-    in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}".
-    It isn't perfect, but it seems to cover a vast majority of use cases.
-    Caveat: even if the package is reached by a different means,
-    the path above will be shown and not e.g. `${config.services.foo.package}`. */
-  manual = import ../../../doc/manual rec {
-    inherit pkgs config;
-    version = config.system.nixos.release;
-    revision = "release-${version}";
-    options =
-      let
-        scrubbedEval = evalModules {
-          modules = [ { nixpkgs.localSystem = config.nixpkgs.localSystem; } ] ++ baseModules;
-          args = (config._module.args) // { modules = [ ]; };
-          specialArgs = { pkgs = scrubDerivations "pkgs" pkgs; };
-        };
-        scrubDerivations = namePrefix: pkgSet: mapAttrs
-          (name: value:
-            let wholeName = "${namePrefix}.${name}"; in
-            if isAttrs value then
-              scrubDerivations wholeName value
-              // (optionalAttrs (isDerivation value) { outPath = "\${${wholeName}}"; })
-            else value
-          )
-          pkgSet;
-      in scrubbedEval.options;
-  };
-
-  entry = "${manual.manual}/share/doc/nixos/index.html";
-
-  helpScript = pkgs.writeScriptBin "nixos-help"
-    ''
-      #! ${pkgs.runtimeShell} -e
-      # Finds first executable browser in a colon-separated list.
-      # (see how xdg-open defines BROWSER)
-      browser="$(
-        IFS=: ; for b in $BROWSER; do
-          [ -n "$(type -P "$b" || true)" ] && echo "$b" && break
-        done
-      )"
-      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
-        fi
-      fi
-      exec "$browser" ${entry}
-    '';
-
-  desktopItem = pkgs.makeDesktopItem {
-    name = "nixos-manual";
-    desktopName = "NixOS Manual";
-    genericName = "View NixOS documentation in a web browser";
-    icon = "nix-snowflake";
-    exec = "${helpScript}/bin/nixos-help";
-    categories = "System";
-  };
+  cfgd = config.documentation;
 in
 
 {
 
   options = {
 
-    services.nixosManual.enable = mkOption {
-      type = types.bool;
-      default = true;
-      description = ''
-        Whether to build the NixOS manual pages.
-      '';
-    };
-
+    # TODO(@oxij): rename this to `.enable` eventually.
     services.nixosManual.showManual = mkOption {
       type = types.bool;
       default = false;
@@ -114,37 +44,30 @@ in
   };
 
 
-  config = mkIf cfg.enable {
-
-    system.build.manual = manual;
-
-    environment.systemPackages = []
-      ++ optionals config.services.xserver.enable [ desktopItem pkgs.nixos-icons ]
-      ++ optional  config.documentation.man.enable manual.manpages
-      ++ optionals config.documentation.doc.enable [ manual.manual helpScript ];
-
-    boot.extraTTYs = mkIf cfg.showManual ["tty${toString cfg.ttyNumber}"];
-
-    systemd.services = optionalAttrs cfg.showManual
-      { "nixos-manual" =
-        { description = "NixOS Manual";
-          wantedBy = [ "multi-user.target" ];
-          serviceConfig =
-            { ExecStart = "${cfg.browser} ${entry}";
-              StandardInput = "tty";
-              StandardOutput = "tty";
-              TTYPath = "/dev/tty${toString cfg.ttyNumber}";
-              TTYReset = true;
-              TTYVTDisallocate = true;
-              Restart = "always";
-            };
+  config = mkMerge [
+    (mkIf cfg.showManual {
+      assertions = singleton {
+        assertion = cfgd.enable && cfgd.nixos.enable;
+        message   = "Can't enable `services.nixosManual.showManual` without `documentation.nixos.enable`";
+      };
+    })
+    (mkIf (cfg.showManual && cfgd.enable && cfgd.nixos.enable) {
+      boot.extraTTYs = [ "tty${toString cfg.ttyNumber}" ];
+
+      systemd.services."nixos-manual" = {
+        description = "NixOS Manual";
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          ExecStart = "${cfg.browser} ${config.system.build.manual.manualHTMLIndex}";
+          StandardInput = "tty";
+          StandardOutput = "tty";
+          TTYPath = "/dev/tty${toString cfg.ttyNumber}";
+          TTYReset = true;
+          TTYVTDisallocate = true;
+          Restart = "always";
         };
       };
-
-      services.mingetty.helpLine = "\nRun `nixos-help` "
-        + lib.optionalString cfg.showManual "or press <Alt-F${toString cfg.ttyNumber}> "
-        + "for the NixOS manual.";
-
-  };
+    })
+  ];
 
 }
diff --git a/nixos/modules/services/misc/nscd-sssd.conf b/nixos/modules/services/misc/nscd-sssd.conf
deleted file mode 100644
index 92380f3e4ba43..0000000000000
--- a/nixos/modules/services/misc/nscd-sssd.conf
+++ /dev/null
@@ -1,36 +0,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            hosts           yes
-positive-time-to-live   hosts           600
-negative-time-to-live   hosts           5
-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/misc/nzbget.nix b/nixos/modules/services/misc/nzbget.nix
index a472b6c7157c1..6ab98751c57b4 100644
--- a/nixos/modules/services/misc/nzbget.nix
+++ b/nixos/modules/services/misc/nzbget.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.nzbget;
+  dataDir = builtins.dirOf cfg.configFile;
 in {
   options = {
     services.nzbget = {
@@ -16,6 +17,20 @@ in {
         description = "The NZBGet package to use";
       };
 
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/nzbget";
+        description = "The directory where NZBGet stores its configuration files.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the NZBGet web interface
+        '';
+      };
+
       user = mkOption {
         type = types.str;
         default = "nzbget";
@@ -27,6 +42,12 @@ in {
         default = "nzbget";
         description = "Group under which NZBGet runs";
       };
+
+      configFile = mkOption {
+        type = types.str;
+        default = "/var/lib/nzbget/nzbget.conf";
+        description = "Path for NZBGet's config file. (If this doesn't exist, the default config template is copied here.)";
+      };
     };
   };
 
@@ -40,35 +61,25 @@ in {
         p7zip
       ];
       preStart = ''
-        datadir=/var/lib/nzbget
         cfgtemplate=${cfg.package}/share/nzbget/nzbget.conf
-        test -d $datadir || {
-          echo "Creating nzbget data directory in $datadir"
-          mkdir -p $datadir
-        }
-        test -f $configfile || {
-          echo "nzbget.conf not found. Copying default config $cfgtemplate to $configfile"
-          cp $cfgtemplate $configfile
-          echo "Setting $configfile permissions to 0700 (needs to be written and contains plaintext credentials)"
-          chmod 0700 $configfile
+        if [ ! -f ${cfg.configFile} ]; then
+          echo "${cfg.configFile} not found. Copying default config $cfgtemplate to ${cfg.configFile}"
+          install -m 0700 $cfgtemplate ${cfg.configFile}
           echo "Setting temporary \$MAINDIR variable in default config required in order to allow nzbget to complete initial start"
           echo "Remember to change this to a proper value once NZBGet startup has been completed"
-          sed -i -e 's/MainDir=.*/MainDir=\/tmp/g' $configfile
-        }
-        echo "Ensuring proper ownership of $datadir (${cfg.user}:${cfg.group})."
-        chown -R ${cfg.user}:${cfg.group} $datadir
+          sed -i -e 's/MainDir=.*/MainDir=\/tmp/g' ${cfg.configFile}
+        fi
       '';
 
       script = ''
-        configfile=/var/lib/nzbget/nzbget.conf
-        args="--daemon --configfile $configfile"
-        # The script in preStart (above) copies nzbget's config template to datadir on first run, containing paths that point to the nzbget derivation installed at the time. 
-        # These paths break when nzbget is upgraded & the original derivation is garbage collected. If such broken paths are found in the config file, override them to point to 
+        args="--daemon --configfile ${cfg.configFile}"
+        # The script in preStart (above) copies nzbget's config template to datadir on first run, containing paths that point to the nzbget derivation installed at the time.
+        # These paths break when nzbget is upgraded & the original derivation is garbage collected. If such broken paths are found in the config file, override them to point to
         # the currently installed nzbget derivation.
         cfgfallback () {
-          local hit=`grep -Po "(?<=^$1=).*+" "$configfile" | sed 's/[ \t]*$//'` # Strip trailing whitespace
+          local hit=`grep -Po "(?<=^$1=).*+" "${cfg.configFile}" | sed 's/[ \t]*$//'` # Strip trailing whitespace
           ( test $hit && test -e $hit ) || {
-            echo "In $configfile, valid $1 not found; falling back to $1=$2"
+            echo "In ${cfg.configFile}, valid $1 not found; falling back to $1=$2"
             args+=" -o $1=$2"
           }
         }
@@ -78,6 +89,8 @@ in {
       '';
 
       serviceConfig = {
+        StateDirectory = dataDir;
+        StateDirectoryMode = "0700";
         Type = "forking";
         User = cfg.user;
         Group = cfg.group;
@@ -86,6 +99,10 @@ in {
       };
     };
 
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 8989 ];
+    };
+
     users.users = mkIf (cfg.user == "nzbget") {
       nzbget = {
         group = cfg.group;
diff --git a/nixos/modules/services/misc/packagekit.nix b/nixos/modules/services/misc/packagekit.nix
index 2d1ff7bb41170..bce21e8acff31 100644
--- a/nixos/modules/services/misc/packagekit.nix
+++ b/nixos/modules/services/misc/packagekit.nix
@@ -6,11 +6,8 @@ let
 
   cfg = config.services.packagekit;
 
-  backend = "nix";
-
   packagekitConf = ''
 [Daemon]
-DefaultBackend=${backend}
 KeepCache=false
     '';
 
diff --git a/nixos/modules/services/misc/plex.nix b/nixos/modules/services/misc/plex.nix
index 8fe5879c27648..b06c1c4bbc68b 100644
--- a/nixos/modules/services/misc/plex.nix
+++ b/nixos/modules/services/misc/plex.nix
@@ -145,7 +145,8 @@ in
         PLEX_MEDIA_SERVER_HOME="${cfg.package}/usr/lib/plexmediaserver";
         PLEX_MEDIA_SERVER_MAX_PLUGIN_PROCS="6";
         PLEX_MEDIA_SERVER_TMPDIR="/tmp";
-        LD_LIBRARY_PATH="${cfg.package}/usr/lib/plexmediaserver";
+        PLEX_MEDIA_SERVER_USE_SYSLOG="true";
+        LD_LIBRARY_PATH="/run/opengl-driver/lib:${cfg.package}/usr/lib/plexmediaserver";
         LC_ALL="en_US.UTF-8";
         LANG="en_US.UTF-8";
       };
diff --git a/nixos/modules/services/misc/pykms.nix b/nixos/modules/services/misc/pykms.nix
index a11296e1bd02c..ef90d124a284d 100644
--- a/nixos/modules/services/misc/pykms.nix
+++ b/nixos/modules/services/misc/pykms.nix
@@ -5,20 +5,8 @@ with lib;
 let
   cfg = config.services.pykms;
 
-  home = "/var/lib/pykms";
-
-  services = {
-    serviceConfig = {
-      Restart = "on-failure";
-      RestartSec = "10s";
-      StartLimitInterval = "1min";
-      PrivateTmp = true;
-      ProtectSystem = "full";
-      ProtectHome = true;
-    };
-  };
-
 in {
+  meta.maintainers = with lib.maintainers; [ peterhoeg ];
 
   options = {
     services.pykms = rec {
@@ -51,39 +39,38 @@ in {
         default = false;
         description = "Whether the listening port should be opened automatically.";
       };
+
+      memoryLimit = mkOption {
+        type = types.str;
+        default = "64M";
+        description = "How much memory to use at most.";
+      };
     };
   };
 
   config = mkIf cfg.enable {
     networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewallPort [ cfg.port ];
 
-    systemd.services = {
-      pykms = services // {
-        description = "Python KMS";
-        wantedBy = [ "multi-user.target" ];
-        serviceConfig = with pkgs; {
-          User = "pykms";
-          Group = "pykms";
-          ExecStartPre = "${getBin pykms}/bin/create_pykms_db.sh ${home}/clients.db";
-          ExecStart = "${getBin pykms}/bin/server.py ${optionalString cfg.verbose "--verbose"} ${cfg.listenAddress} ${toString cfg.port}";
-          WorkingDirectory = home;
-          MemoryLimit = "64M";
-        };
-      };
-    };
-
-    users = {
-      users.pykms = {
-        name = "pykms";
-        group = "pykms";
-        home  = home;
-        createHome = true;
-        uid = config.ids.uids.pykms;
-        description = "PyKMS daemon user";
-      };
-
-      groups.pykms = {
-        gid = config.ids.gids.pykms;
+    systemd.services.pykms = let
+      home = "/var/lib/pykms";
+    in {
+      description = "Python KMS";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      # python programs with DynamicUser = true require HOME to be set
+      environment.HOME = home;
+      serviceConfig = with pkgs; {
+        DynamicUser = true;
+        StateDirectory = baseNameOf home;
+        ExecStartPre = "${getBin pykms}/bin/create_pykms_db.sh ${home}/clients.db";
+        ExecStart = lib.concatStringsSep " " ([
+          "${getBin pykms}/bin/server.py"
+          cfg.listenAddress
+          (toString cfg.port)
+        ] ++ lib.optional cfg.verbose "--verbose");
+        WorkingDirectory = home;
+        Restart = "on-failure";
+        MemoryLimit = cfg.memoryLimit;
       };
     };
   };
diff --git a/nixos/modules/services/misc/redmine.nix b/nixos/modules/services/misc/redmine.nix
index f763ba21d0b2c..98e9c8953c844 100644
--- a/nixos/modules/services/misc/redmine.nix
+++ b/nixos/modules/services/misc/redmine.nix
@@ -5,7 +5,7 @@ with lib;
 let
   cfg = config.services.redmine;
 
-  bundle = "${pkgs.redmine}/share/redmine/bin/bundle";
+  bundle = "${cfg.package}/share/redmine/bin/bundle";
 
   databaseYml = pkgs.writeText "database.yml" ''
     production:
@@ -15,6 +15,7 @@ let
       port: ${toString cfg.database.port}
       username: ${cfg.database.user}
       password: #dbpass#
+      ${optionalString (cfg.database.socket != null) "socket: ${cfg.database.socket}"}
   '';
 
   configurationYml = pkgs.writeText "configuration.yml" ''
@@ -29,6 +30,26 @@ let
     ${cfg.extraConfig}
   '';
 
+  additionalEnvironment = pkgs.writeText "additional_environment.rb" ''
+    config.logger = Logger.new("${cfg.stateDir}/log/production.log", 14, 1048576)
+    config.logger.level = Logger::INFO
+
+    ${cfg.extraEnv}
+  '';
+
+  unpackTheme = unpack "theme";
+  unpackPlugin = unpack "plugin";
+  unpack = id: (name: source:
+    pkgs.stdenv.mkDerivation {
+      name = "redmine-${id}-${name}";
+      buildInputs = [ pkgs.unzip ];
+      buildCommand = ''
+        mkdir -p $out
+        cd $out
+        unpackFile ${source}
+      '';
+  });
+
 in
 
 {
@@ -40,6 +61,22 @@ in
         description = "Enable the Redmine service.";
       };
 
+      # default to the 4.x series not forcing major version upgrade of those on the 3.x series
+      package = mkOption {
+        type = types.package;
+        default = if versionAtLeast config.system.stateVersion "19.03"
+          then pkgs.redmine_4
+          else pkgs.redmine
+        ;
+        defaultText = "pkgs.redmine";
+        description = ''
+          Which Redmine package to use. This defaults to version 3.x if
+          <literal>system.stateVersion &lt; 19.03</literal> and version 4.x
+          otherwise.
+        '';
+        example = "pkgs.redmine_4.override { ruby = pkgs.ruby_2_4; }";
+      };
+
       user = mkOption {
         type = types.str;
         default = "redmine";
@@ -52,6 +89,12 @@ in
         description = "Group under which Redmine is ran.";
       };
 
+      port = mkOption {
+        type = types.int;
+        default = 3000;
+        description = "Port on which Redmine is ran.";
+      };
+
       stateDir = mkOption {
         type = types.str;
         default = "/var/lib/redmine";
@@ -66,6 +109,54 @@ in
 
           See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration
         '';
+        example = literalExample ''
+          email_delivery:
+            delivery_method: smtp
+            smtp_settings:
+              address: mail.example.com
+              port: 25
+        '';
+      };
+
+      extraEnv = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration in additional_environment.rb.
+
+          See https://svn.redmine.org/redmine/trunk/config/additional_environment.rb.example
+        '';
+        example = literalExample ''
+          config.logger.level = Logger::DEBUG
+        '';
+      };
+
+      themes = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        description = "Set of themes.";
+        example = literalExample ''
+          {
+            dkuk-redmine_alex_skin = builtins.fetchurl {
+              url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip;
+              sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+            };
+          }
+        '';
+      };
+
+      plugins = mkOption {
+        type = types.attrsOf types.path;
+        default = {};
+        description = "Set of plugins.";
+        example = literalExample ''
+          {
+            redmine_env_auth = builtins.fetchurl {
+              url = https://github.com/Intera/redmine_env_auth/archive/0.6.zip;
+              sha256 = "0yyr1yjd8gvvh832wdc8m3xfnhhxzk2pk3gm2psg5w9jdvd6skak";
+            };
+          }
+        '';
       };
 
       database = {
@@ -78,7 +169,7 @@ in
 
         host = mkOption {
           type = types.str;
-          default = "127.0.0.1";
+          default = (if cfg.database.socket != null then "localhost" else "127.0.0.1");
           description = "Database host address.";
         };
 
@@ -119,6 +210,13 @@ in
             <option>database.user</option>.
           '';
         };
+
+        socket = mkOption {
+          type = types.nullOr types.path;
+          default = null;
+          example = "/run/mysqld/mysqld.sock";
+          description = "Path to the unix socket file to use for authentication.";
+        };
       };
     };
   };
@@ -126,17 +224,20 @@ in
   config = mkIf cfg.enable {
 
     assertions = [
-      { assertion = cfg.database.passwordFile != null || cfg.database.password != "";
-        message = "either services.redmine.database.passwordFile or services.redmine.database.password must be set";
+      { 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";
       }
     ];
 
-    environment.systemPackages = [ pkgs.redmine ];
+    environment.systemPackages = [ cfg.package ];
 
     systemd.services.redmine = {
       after = [ "network.target" (if cfg.database.type == "mysql2" then "mysql.service" else "postgresql.service") ];
       wantedBy = [ "multi-user.target" ];
-      environment.HOME = "${pkgs.redmine}/share/redmine";
+      environment.HOME = "${cfg.package}/share/redmine";
       environment.RAILS_ENV = "production";
       environment.RAILS_CACHE = "${cfg.stateDir}/cache";
       environment.REDMINE_LANG = "en";
@@ -151,43 +252,84 @@ in
         subversion
       ];
       preStart = ''
-        # start with a fresh config directory every time
-        rm -rf ${cfg.stateDir}/config
-        cp -r ${pkgs.redmine}/share/redmine/config.dist ${cfg.stateDir}/config
+        # ensure cache directory exists for db:migrate command
+        mkdir -p "${cfg.stateDir}/cache"
 
-        # create the basic state directory layout pkgs.redmine expects
-        mkdir -p /run/redmine
+        # create the basic directory layout the redmine package expects
+        mkdir -p /run/redmine/public
 
         for i in config files log plugins tmp; do
-          mkdir -p ${cfg.stateDir}/$i
-          ln -fs ${cfg.stateDir}/$i /run/redmine/$i
+          mkdir -p "${cfg.stateDir}/$i"
+          ln -fs "${cfg.stateDir}/$i" /run/redmine/
         done
 
-        # ensure cache directory exists for db:migrate command
-        mkdir -p ${cfg.stateDir}/cache
+        for i in plugin_assets themes; do
+          mkdir -p "${cfg.stateDir}/public/$i"
+          ln -fs "${cfg.stateDir}/public/$i" /run/redmine/public/
+        done
+
+
+        # start with a fresh config directory
+        # the config directory is copied instead of linked as some mutable data is stored in there
+        rm -rf "${cfg.stateDir}/config/"*
+        cp -r ${cfg.package}/share/redmine/config.dist/* "${cfg.stateDir}/config/"
 
         # link in the application configuration
-        ln -fs ${configurationYml} ${cfg.stateDir}/config/configuration.yml
+        ln -fs ${configurationYml} "${cfg.stateDir}/config/configuration.yml"
 
-        chmod -R ug+rwX,o-rwx+x ${cfg.stateDir}/
+        # link in the additional environment configuration
+        ln -fs ${additionalEnvironment} "${cfg.stateDir}/config/additional_environment.rb"
+
+
+        # link in all user specified themes
+        rm -rf "${cfg.stateDir}/public/themes/"*
+        for theme in ${concatStringsSep " " (mapAttrsToList unpackTheme cfg.themes)}; do
+          ln -fs $theme/* "${cfg.stateDir}/public/themes"
+        done
 
-        # handle database.passwordFile
+        # link in redmine provided themes
+        ln -sf ${cfg.package}/share/redmine/public/themes.dist/* "${cfg.stateDir}/public/themes/"
+
+
+        # link in all user specified plugins
+        rm -rf "${cfg.stateDir}/plugins/"*
+        for plugin in ${concatStringsSep " " (mapAttrsToList unpackPlugin cfg.plugins)}; do
+          ln -fs $plugin/* "${cfg.stateDir}/plugins/''${plugin##*-redmine-plugin-}"
+        done
+
+
+        # ensure correct permissions for most files
+        chmod -R ug+rwX,o-rwx+x "${cfg.stateDir}/"
+
+
+        # handle database.passwordFile & permissions
         DBPASS=$(head -n1 ${cfg.database.passwordFile})
-        cp -f ${databaseYml} ${cfg.stateDir}/config/database.yml
-        sed -e "s,#dbpass#,$DBPASS,g" -i ${cfg.stateDir}/config/database.yml
-        chmod 440 ${cfg.stateDir}/config/database.yml
+        cp -f ${databaseYml} "${cfg.stateDir}/config/database.yml"
+        sed -e "s,#dbpass#,$DBPASS,g" -i "${cfg.stateDir}/config/database.yml"
+        chmod 440 "${cfg.stateDir}/config/database.yml"
+
 
         # generate a secret token if required
         if ! test -e "${cfg.stateDir}/config/initializers/secret_token.rb"; then
           ${bundle} exec rake generate_secret_token
-          chmod 440 ${cfg.stateDir}/config/initializers/secret_token.rb
+          chmod 440 "${cfg.stateDir}/config/initializers/secret_token.rb"
         fi
 
+
         # ensure everything is owned by ${cfg.user}
-        chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+        chown -R ${cfg.user}:${cfg.group} "${cfg.stateDir}"
+
+
+        # execute redmine required commands prior to starting the application
+        # NOTE: su required in case using mysql socket authentication
+        /run/wrappers/bin/su -s ${pkgs.bash}/bin/bash -m -l redmine -c '${bundle} exec rake db:migrate'
+        /run/wrappers/bin/su -s ${pkgs.bash}/bin/bash -m -l redmine -c '${bundle} exec rake redmine:plugins:migrate'
+        /run/wrappers/bin/su -s ${pkgs.bash}/bin/bash -m -l redmine -c '${bundle} exec rake redmine:load_default_data'
+
 
-        ${bundle} exec rake db:migrate
-        ${bundle} exec rake redmine:load_default_data
+        # log files don't exist until after first command has been executed
+        # correct ownership of files generated by calling exec rake ...
+        chown -R ${cfg.user}:${cfg.group} "${cfg.stateDir}/log"
       '';
 
       serviceConfig = {
@@ -196,13 +338,13 @@ in
         User = cfg.user;
         Group = cfg.group;
         TimeoutSec = "300";
-        WorkingDirectory = "${pkgs.redmine}/share/redmine";
-        ExecStart="${bundle} exec rails server webrick -e production -P ${cfg.stateDir}/redmine.pid";
+        WorkingDirectory = "${cfg.package}/share/redmine";
+        ExecStart="${bundle} exec rails server webrick -e production -p ${toString cfg.port} -P '${cfg.stateDir}/redmine.pid'";
       };
 
     };
 
-    users.extraUsers = optionalAttrs (cfg.user == "redmine") (singleton
+    users.users = optionalAttrs (cfg.user == "redmine") (singleton
       { name = "redmine";
         group = cfg.group;
         home = cfg.stateDir;
@@ -210,7 +352,7 @@ in
         uid = config.ids.uids.redmine;
       });
 
-    users.extraGroups = optionalAttrs (cfg.group == "redmine") (singleton
+    users.groups = optionalAttrs (cfg.group == "redmine") (singleton
       { name = "redmine";
         gid = config.ids.gids.redmine;
       });
diff --git a/nixos/modules/services/misc/rippled.nix b/nixos/modules/services/misc/rippled.nix
index 9d9a0ba44da59..cdf61730de33a 100644
--- a/nixos/modules/services/misc/rippled.nix
+++ b/nixos/modules/services/misc/rippled.nix
@@ -85,70 +85,70 @@ let
   portOptions = { name, ...}: {
     options = {
       name = mkOption {
-	internal = true;
-	default = name;
+        internal = true;
+        default = name;
       };
 
       ip = mkOption {
-	default = "127.0.0.1";
-	description = "Ip where rippled listens.";
-	type = types.str;
+        default = "127.0.0.1";
+        description = "Ip where rippled listens.";
+        type = types.str;
       };
 
       port = mkOption {
-	description = "Port where rippled listens.";
-	type = types.int;
+        description = "Port where rippled listens.";
+        type = types.int;
       };
 
       protocol = mkOption {
-	description = "Protocols expose by rippled.";
-	type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]);
+        description = "Protocols expose by rippled.";
+        type = types.listOf (types.enum ["http" "https" "ws" "wss" "peer"]);
       };
 
       user = mkOption {
-	description = "When set, these credentials will be required on HTTP/S requests.";
-	type = types.str;
-	default = "";
+        description = "When set, these credentials will be required on HTTP/S requests.";
+        type = types.str;
+        default = "";
       };
 
       password = mkOption {
-	description = "When set, these credentials will be required on HTTP/S requests.";
-	type = types.str;
-	default = "";
+        description = "When set, these credentials will be required on HTTP/S requests.";
+        type = types.str;
+        default = "";
       };
 
       admin = mkOption {
-	description = "A comma-separated list of admin IP addresses.";
-	type = types.listOf types.str;
-	default = ["127.0.0.1"];
+        description = "A comma-separated list of admin IP addresses.";
+        type = types.listOf types.str;
+        default = ["127.0.0.1"];
       };
 
       ssl = {
-	key = mkOption {
-	  description = ''
-	    Specifies the filename holding the SSL key in PEM format.
-	  '';
-	  default = null;
-	  type = types.nullOr types.path;
-	};
-
-	cert = mkOption {
-	  description = ''
-	    Specifies the path to the SSL certificate file in PEM format.
-	    This is not needed if the chain includes it.
-	  '';
-	  default = null;
-	  type = types.nullOr types.path;
-	};
-
-	chain = mkOption {
-	  description = ''
-	    If you need a certificate chain, specify the path to the
-	    certificate chain here. The chain may include the end certificate.
-	  '';
-	  default = null;
-	  type = types.nullOr types.path;
-	};
+        key = mkOption {
+          description = ''
+            Specifies the filename holding the SSL key in PEM format.
+          '';
+          default = null;
+          type = types.nullOr types.path;
+        };
+
+        cert = mkOption {
+          description = ''
+            Specifies the path to the SSL certificate file in PEM format.
+            This is not needed if the chain includes it.
+          '';
+          default = null;
+          type = types.nullOr types.path;
+        };
+
+        chain = mkOption {
+          description = ''
+            If you need a certificate chain, specify the path to the
+            certificate chain here. The chain may include the end certificate.
+          '';
+          default = null;
+          type = types.nullOr types.path;
+        };
       };
     };
   };
@@ -175,14 +175,14 @@ let
 
       onlineDelete = mkOption {
         description = "Enable automatic purging of older ledger information.";
-        type = types.addCheck (types.nullOr types.int) (v: v > 256);
+        type = types.nullOr (types.addCheck types.int (v: v > 256));
         default = cfg.ledgerHistory;
       };
 
       advisoryDelete = mkOption {
         description = ''
-	        If set, then require administrative RPC call "can_delete"
-	        to enable online deletion of ledger records.
+          If set, then require administrative RPC call "can_delete"
+          to enable online deletion of ledger records.
         '';
         type = types.nullOr types.bool;
         default = null;
@@ -207,168 +207,168 @@ in
       enable = mkEnableOption "rippled";
 
       package = mkOption {
-	description = "Which rippled package to use.";
-	type = types.package;
-	default = pkgs.rippled;
-	defaultText = "pkgs.rippled";
+        description = "Which rippled package to use.";
+        type = types.package;
+        default = pkgs.rippled;
+        defaultText = "pkgs.rippled";
       };
 
       ports = mkOption {
-	description = "Ports exposed by rippled";
-	type = with types; attrsOf (submodule portOptions);
-	default = {
-	  rpc = {
-	    port = 5005;
-	    admin = ["127.0.0.1"];
-	    protocol = ["http"];
-	  };
-
-	  peer = {
-	    port = 51235;
-	    ip = "0.0.0.0";
-	    protocol = ["peer"];
-	  };
-
-	  ws_public = {
-	    port = 5006;
-	    ip = "0.0.0.0";
-	    protocol = ["ws" "wss"];
-	  };
-	};
+        description = "Ports exposed by rippled";
+        type = with types; attrsOf (submodule portOptions);
+        default = {
+          rpc = {
+            port = 5005;
+            admin = ["127.0.0.1"];
+            protocol = ["http"];
+          };
+
+          peer = {
+            port = 51235;
+            ip = "0.0.0.0";
+            protocol = ["peer"];
+          };
+
+          ws_public = {
+            port = 5006;
+            ip = "0.0.0.0";
+            protocol = ["ws" "wss"];
+          };
+        };
       };
 
       nodeDb = mkOption {
-	description = "Rippled main database options.";
-	type = with types; nullOr (submodule dbOptions);
-	default = {
-	  type = "rocksdb";
-	  extraOpts = ''
-	    open_files=2000
-	    filter_bits=12
-	    cache_mb=256
-	    file_size_pb=8
-	    file_size_mult=2;
-	  '';
-	};
+        description = "Rippled main database options.";
+        type = with types; nullOr (submodule dbOptions);
+        default = {
+          type = "rocksdb";
+          extraOpts = ''
+            open_files=2000
+            filter_bits=12
+            cache_mb=256
+            file_size_pb=8
+            file_size_mult=2;
+          '';
+        };
       };
 
       tempDb = mkOption {
-	description = "Rippled temporary database options.";
-	type = with types; nullOr (submodule dbOptions);
-	default = null;
+        description = "Rippled temporary database options.";
+        type = with types; nullOr (submodule dbOptions);
+        default = null;
       };
 
       importDb = mkOption {
-	description = "Settings for performing a one-time import.";
-	type = with types; nullOr (submodule dbOptions);
-	default = null;
+        description = "Settings for performing a one-time import.";
+        type = with types; nullOr (submodule dbOptions);
+        default = null;
       };
 
       nodeSize = mkOption {
-	description = ''
-	  Rippled size of the node you are running.
-	  "tiny", "small", "medium", "large", and "huge"
-	'';
-	type = types.enum ["tiny" "small" "medium" "large" "huge"];
-	default = "small";
+        description = ''
+          Rippled size of the node you are running.
+          "tiny", "small", "medium", "large", and "huge"
+        '';
+        type = types.enum ["tiny" "small" "medium" "large" "huge"];
+        default = "small";
       };
 
       ips = mkOption {
-	description = ''
-	  List of hostnames or ips where the Ripple protocol is served.
-	  For a starter list, you can either copy entries from:
-	  https://ripple.com/ripple.txt or if you prefer you can let it
-	   default to r.ripple.com 51235
-
-	  A port may optionally be specified after adding a space to the
-	  address. By convention, if known, IPs are listed in from most
-	  to least trusted.
-	'';
-	type = types.listOf types.str;
-	default = ["r.ripple.com 51235"];
+        description = ''
+          List of hostnames or ips where the Ripple protocol is served.
+          For a starter list, you can either copy entries from:
+          https://ripple.com/ripple.txt or if you prefer you can let it
+           default to r.ripple.com 51235
+
+          A port may optionally be specified after adding a space to the
+          address. By convention, if known, IPs are listed in from most
+          to least trusted.
+        '';
+        type = types.listOf types.str;
+        default = ["r.ripple.com 51235"];
       };
 
       ipsFixed = mkOption {
-	description = ''
-	  List of IP addresses or hostnames to which rippled should always
-	  attempt to maintain peer connections with. This is useful for
-	  manually forming private networks, for example to configure a
-	  validation server that connects to the Ripple network through a
-	  public-facing server, or for building a set of cluster peers.
+        description = ''
+          List of IP addresses or hostnames to which rippled should always
+          attempt to maintain peer connections with. This is useful for
+          manually forming private networks, for example to configure a
+          validation server that connects to the Ripple network through a
+          public-facing server, or for building a set of cluster peers.
 
-	  A port may optionally be specified after adding a space to the address
-	'';
-	type = types.listOf types.str;
-	default = [];
+          A port may optionally be specified after adding a space to the address
+        '';
+        type = types.listOf types.str;
+        default = [];
       };
 
       validators = mkOption {
-	description = ''
-	  List of nodes to always accept as validators. Nodes are specified by domain
-	  or public key.
-	'';
-	type = types.listOf types.str;
-	default = [
-	  "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7  RL1"
-	  "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj  RL2"
-	  "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C  RL3"
-	  "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS  RL4"
-	  "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA  RL5"
-	];
+        description = ''
+          List of nodes to always accept as validators. Nodes are specified by domain
+          or public key.
+        '';
+        type = types.listOf types.str;
+        default = [
+          "n949f75evCHwgyP4fPVgaHqNHxUVN15PsJEZ3B3HnXPcPjcZAoy7  RL1"
+          "n9MD5h24qrQqiyBC8aeqqCWvpiBiYQ3jxSr91uiDvmrkyHRdYLUj  RL2"
+          "n9L81uNCaPgtUJfaHh89gmdvXKAmSt5Gdsw2g1iPWaPkAHW5Nm4C  RL3"
+          "n9KiYM9CgngLvtRCQHZwgC2gjpdaZcCcbt3VboxiNFcKuwFVujzS  RL4"
+          "n9LdgEtkmGB9E2h3K4Vp7iGUaKuq23Zr32ehxiU8FWY7xoxbWTSA  RL5"
+        ];
       };
 
       databasePath = mkOption {
-	description = ''
-	  Path to the ripple database.
-	'';
-	type = types.path;
-	default = "/var/lib/rippled";
+        description = ''
+          Path to the ripple database.
+        '';
+        type = types.path;
+        default = "/var/lib/rippled";
       };
 
       validationQuorum = mkOption {
-	description = ''
-	  The minimum number of trusted validations a ledger must have before
-	  the server considers it fully validated.
-	'';
-	type = types.int;
-	default = 3;
+        description = ''
+          The minimum number of trusted validations a ledger must have before
+          the server considers it fully validated.
+        '';
+        type = types.int;
+        default = 3;
       };
 
       ledgerHistory = mkOption {
-	description = ''
-	  The number of past ledgers to acquire on server startup and the minimum
-	  to maintain while running.
-	'';
-	type = types.either types.int (types.enum ["full"]);
-	default = 1296000; # 1 month
+        description = ''
+          The number of past ledgers to acquire on server startup and the minimum
+          to maintain while running.
+        '';
+        type = types.either types.int (types.enum ["full"]);
+        default = 1296000; # 1 month
       };
 
       fetchDepth = mkOption {
-	description = ''
-	  The number of past ledgers to serve to other peers that request historical
-	  ledger data (or "full" for no limit).
-	'';
-	type = types.either types.int (types.enum ["full"]);
-	default = "full";
+        description = ''
+          The number of past ledgers to serve to other peers that request historical
+          ledger data (or "full" for no limit).
+        '';
+        type = types.either types.int (types.enum ["full"]);
+        default = "full";
       };
 
       sntpServers = mkOption {
-	description = ''
-	  IP address or domain of NTP servers to use for time synchronization.;
-	'';
-	type = types.listOf types.str;
-	default = [
-	  "time.windows.com"
-	  "time.apple.com"
-	  "time.nist.gov"
-	  "pool.ntp.org"
-	];
+        description = ''
+          IP address or domain of NTP servers to use for time synchronization.;
+        '';
+        type = types.listOf types.str;
+        default = [
+          "time.windows.com"
+          "time.apple.com"
+          "time.nist.gov"
+          "pool.ntp.org"
+        ];
       };
 
       logLevel = mkOption {
         description = "Logging verbosity.";
-	type = types.enum ["debug" "error" "info"];
-	default = "error";
+        type = types.enum ["debug" "error" "info"];
+        default = "error";
       };
 
       statsd = {
@@ -389,14 +389,14 @@ in
 
       extraConfig = mkOption {
         default = "";
-	description = ''
-	  Extra lines to be added verbatim to the rippled.cfg configuration file.
-	'';
+        description = ''
+          Extra lines to be added verbatim to the rippled.cfg configuration file.
+        '';
       };
 
       config = mkOption {
-	internal = true;
-	default = pkgs.writeText "rippled.conf" rippledCfg;
+        internal = true;
+        default = pkgs.writeText "rippled.conf" rippledCfg;
       };
     };
   };
@@ -410,8 +410,8 @@ in
       { name = "rippled";
         description = "Ripple server user";
         uid = config.ids.uids.rippled;
-	home = cfg.databasePath;
-	createHome = true;
+        home = cfg.databasePath;
+        createHome = true;
       };
 
     systemd.services.rippled = {
@@ -421,8 +421,8 @@ in
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/rippled --fg --conf ${cfg.config}";
         User = "rippled";
-	Restart = "on-failure";
-	LimitNOFILE=10000;
+        Restart = "on-failure";
+        LimitNOFILE=10000;
       };
     };
 
diff --git a/nixos/modules/services/misc/sickbeard.nix b/nixos/modules/services/misc/sickbeard.nix
new file mode 100644
index 0000000000000..5cfbbe516ae1b
--- /dev/null
+++ b/nixos/modules/services/misc/sickbeard.nix
@@ -0,0 +1,92 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  name = "sickbeard";
+
+  cfg = config.services.sickbeard;
+  sickbeard = cfg.package;
+
+in
+{
+
+  ###### interface
+
+  options = {
+    services.sickbeard = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Whether to enable the sickbeard server.";
+      };
+      package = mkOption {
+        type = types.package;
+        default = pkgs.sickbeard;
+        example = literalExample "pkgs.sickrage";
+        description =''
+          Enable <literal>pkgs.sickrage</literal> or <literal>pkgs.sickgear</literal>
+          as an alternative to SickBeard
+        '';
+      };
+      dataDir = mkOption {
+        type = types.path;
+        default = "/var/lib/${name}";
+        description = "Path where to store data files.";
+      };
+      configFile = mkOption {
+        type = types.path;
+        default = "${cfg.dataDir}/config.ini";
+        description = "Path to config file.";
+      };
+      port = mkOption {
+        type = types.ints.u16;
+        default = 8081;
+        description = "Port to bind to.";
+      };
+      user = mkOption {
+        type = types.str;
+        default = name;
+        description = "User to run the service as";
+      };
+      group = mkOption {
+        type = types.str;
+        default = name;
+        description = "Group to run the service as";
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    users.users = optionalAttrs (cfg.user == name) (singleton {
+      name = name;
+      uid = config.ids.uids.sickbeard;
+      group = cfg.group;
+      description = "sickbeard user";
+      home = cfg.dataDir;
+      createHome = true;
+    });
+
+    users.groups = optionalAttrs (cfg.group == name) (singleton {
+      name = name;
+      gid = config.ids.gids.sickbeard;
+    });
+
+    systemd.services.sickbeard = {
+      description = "Sickbeard Server";
+      wantedBy    = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart = "${sickbeard}/SickBeard.py --datadir ${cfg.dataDir} --config ${cfg.configFile} --port ${toString cfg.port}";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/misc/sonarr.nix b/nixos/modules/services/misc/sonarr.nix
index 97b67a0b5033c..a99445a268d7d 100644
--- a/nixos/modules/services/misc/sonarr.nix
+++ b/nixos/modules/services/misc/sonarr.nix
@@ -9,6 +9,32 @@ in
   options = {
     services.sonarr = {
       enable = mkEnableOption "Sonarr";
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/sonarr/.config/NzbDrone";
+        description = "The directory where Sonarr stores its data files.";
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open ports in the firewall for the Sonarr web interface
+        '';
+      };
+
+      user = mkOption {
+        type = types.str;
+        default = "sonarr";
+        description = "User account under which Sonaar runs.";
+      };
+
+      group = mkOption {
+        type = types.str;
+        default = "sonarr";
+        description = "Group under which Sonaar runs.";
+      };
     };
   };
 
@@ -18,30 +44,38 @@ in
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       preStart = ''
-        test -d /var/lib/sonarr/ || {
-          echo "Creating sonarr data directory in /var/lib/sonarr/"
-          mkdir -p /var/lib/sonarr/
+        test -d ${cfg.dataDir} || {
+          echo "Creating sonarr data directory in ${cfg.dataDir}"
+          mkdir -p ${cfg.dataDir}
         }
-        chown -R sonarr:sonarr /var/lib/sonarr/
-        chmod 0700 /var/lib/sonarr/
+        chown -R ${cfg.user}:${cfg.group} ${cfg.dataDir}
+        chmod 0700 ${cfg.dataDir}
       '';
 
       serviceConfig = {
         Type = "simple";
-        User = "sonarr";
-        Group = "sonarr";
+        User = cfg.user;
+        Group = cfg.group;
         PermissionsStartOnly = "true";
-        ExecStart = "${pkgs.sonarr}/bin/NzbDrone --no-browser";
+        ExecStart = "${pkgs.sonarr}/bin/NzbDrone -nobrowser -data='${cfg.dataDir}'";
         Restart = "on-failure";
       };
     };
 
-    users.users.sonarr = {
-      uid = config.ids.uids.sonarr;
-      home = "/var/lib/sonarr";
-      group = "sonarr";
+    networking.firewall = mkIf cfg.openFirewall {
+      allowedTCPPorts = [ 8989 ];
+    };
+
+    users.users = mkIf (cfg.user == "sonarr") {
+      sonarr = {
+        group = cfg.group;
+        home = cfg.dataDir;
+        uid = config.ids.uids.sonarr;
+      };
     };
-    users.groups.sonarr.gid = config.ids.gids.sonarr;
 
+    users.groups = mkIf (cfg.group == "sonarr") {
+      sonarr.gid = config.ids.gids.sonarr;
+    };
   };
 }
diff --git a/nixos/modules/services/misc/sssd.nix b/nixos/modules/services/misc/sssd.nix
index e818f4a4804d7..fe472a6c68e52 100644
--- a/nixos/modules/services/misc/sssd.nix
+++ b/nixos/modules/services/misc/sssd.nix
@@ -75,7 +75,6 @@ in {
       };
 
       system.nssModules = optional cfg.enable pkgs.sssd;
-      services.nscd.config = builtins.readFile ./nscd-sssd.conf;
       services.dbus.packages = [ pkgs.sssd ];
     })
 
diff --git a/nixos/modules/services/misc/taskserver/doc.xml b/nixos/modules/services/misc/taskserver/doc.xml
index 21d25ecf391f1..5eac8d9ef7841 100644
--- a/nixos/modules/services/misc/taskserver/doc.xml
+++ b/nixos/modules/services/misc/taskserver/doc.xml
@@ -2,101 +2,93 @@
     xmlns:xlink="http://www.w3.org/1999/xlink"
     version="5.0"
     xml:id="module-taskserver">
-
-  <title>Taskserver</title>
+ <title>Taskserver</title>
+ <para>
+  Taskserver is the server component of
+  <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a free and
+  open source todo list application.
+ </para>
+ <para>
+  <emphasis>Upstream documentation:</emphasis>
+  <link xlink:href="https://taskwarrior.org/docs/#taskd"/>
+ </para>
+ <section xml:id="module-services-taskserver-configuration">
+  <title>Configuration</title>
 
   <para>
-    Taskserver is the server component of
-    <link xlink:href="https://taskwarrior.org/">Taskwarrior</link>, a free and
-    open source todo list application.
+   Taskserver does all of its authentication via TLS using client certificates,
+   so you either need to roll your own CA or purchase a certificate from a
+   known CA, which allows creation of client certificates. These certificates
+   are usually advertised as <quote>server certificates</quote>.
   </para>
 
   <para>
-    <emphasis>Upstream documentation:</emphasis>
-    <link xlink:href="https://taskwarrior.org/docs/#taskd"/>
+   So in order to make it easier to handle your own CA, there is a helper tool
+   called <command>nixos-taskserver</command> which manages the custom CA along
+   with Taskserver organisations, users and groups.
   </para>
 
-  <section xml:id="module-services-taskserver-configuration">
-    <title>Configuration</title>
-
-    <para>
-      Taskserver does all of its authentication via TLS using client
-      certificates, so you either need to roll your own CA or purchase a
-      certificate from a known CA, which allows creation of client
-      certificates.
-
-      These certificates are usually advertised as
-      <quote>server certificates</quote>.
-    </para>
-
-    <para>
-      So in order to make it easier to handle your own CA, there is a helper
-      tool called <command>nixos-taskserver</command> which manages the custom
-      CA along with Taskserver organisations, users and groups.
-    </para>
-
-    <para>
-      While the client certificates in Taskserver only authenticate whether a
-      user is allowed to connect, every user has its own UUID which identifies
-      it as an entity.
-    </para>
-
-    <para>
-      With <command>nixos-taskserver</command> the client certificate is created
-      along with the UUID of the user, so it handles all of the credentials
-      needed in order to setup the Taskwarrior client to work with a Taskserver.
-    </para>
-  </section>
+  <para>
+   While the client certificates in Taskserver only authenticate whether a user
+   is allowed to connect, every user has its own UUID which identifies it as an
+   entity.
+  </para>
 
-  <section xml:id="module-services-taskserver-nixos-taskserver-tool">
-    <title>The nixos-taskserver tool</title>
+  <para>
+   With <command>nixos-taskserver</command> the client certificate is created
+   along with the UUID of the user, so it handles all of the credentials needed
+   in order to setup the Taskwarrior client to work with a Taskserver.
+  </para>
+ </section>
+ <section xml:id="module-services-taskserver-nixos-taskserver-tool">
+  <title>The nixos-taskserver tool</title>
 
-    <para>
-      Because Taskserver by default only provides scripts to setup users
-      imperatively, the <command>nixos-taskserver</command> tool is used for
-      addition and deletion of organisations along with users and groups defined
-      by <xref linkend="opt-services.taskserver.organisations"/> and as well for
-      imperative set up.
-    </para>
+  <para>
+   Because Taskserver by default only provides scripts to setup users
+   imperatively, the <command>nixos-taskserver</command> tool is used for
+   addition and deletion of organisations along with users and groups defined
+   by <xref linkend="opt-services.taskserver.organisations"/> and as well for
+   imperative set up.
+  </para>
 
-    <para>
-      The tool is designed to not interfere if the command is used to manually
-      set up some organisations, users or groups.
-    </para>
+  <para>
+   The tool is designed to not interfere if the command is used to manually set
+   up some organisations, users or groups.
+  </para>
 
-    <para>
-      For example if you add a new organisation using
-      <command>nixos-taskserver org add foo</command>, the organisation is not
-      modified and deleted no matter what you define in
-      <option>services.taskserver.organisations</option>, even if you're adding
-      the same organisation in that option.
-    </para>
+  <para>
+   For example if you add a new organisation using <command>nixos-taskserver
+   org add foo</command>, the organisation is not modified and deleted no
+   matter what you define in
+   <option>services.taskserver.organisations</option>, even if you're adding
+   the same organisation in that option.
+  </para>
 
-    <para>
-      The tool is modelled to imitate the official <command>taskd</command>
-      command, documentation for each subcommand can be shown by using the
-      <option>--help</option> switch.
-    </para>
-  </section>
-  <section xml:id="module-services-taskserver-declarative-ca-management">
-    <title>Declarative/automatic CA management</title>
+  <para>
+   The tool is modelled to imitate the official <command>taskd</command>
+   command, documentation for each subcommand can be shown by using the
+   <option>--help</option> switch.
+  </para>
+ </section>
+ <section xml:id="module-services-taskserver-declarative-ca-management">
+  <title>Declarative/automatic CA management</title>
 
-    <para>
-      Everything is done according to what you specify in the module options,
-      however in order to set up a Taskwarrior client for synchronisation with a
-      Taskserver instance, you have to transfer the keys and certificates to the
-      client machine.
-    </para>
+  <para>
+   Everything is done according to what you specify in the module options,
+   however in order to set up a Taskwarrior client for synchronisation with a
+   Taskserver instance, you have to transfer the keys and certificates to the
+   client machine.
+  </para>
 
-    <para>
-      This is done using
-      <command>nixos-taskserver user export $orgname $username</command> which
-      is printing a shell script fragment to stdout which can either be used
-      verbatim or adjusted to import the user on the client machine.
-    </para>
+  <para>
+   This is done using <command>nixos-taskserver user export $orgname
+   $username</command> which is printing a shell script fragment to stdout
+   which can either be used verbatim or adjusted to import the user on the
+   client machine.
+  </para>
 
-    <para>
-      For example, let's say you have the following configuration:
+  <para>
+   For example, let's say you have the following configuration:
 <screen>
 {
   <xref linkend="opt-services.taskserver.enable"/> = true;
@@ -105,40 +97,39 @@
   <link linkend="opt-services.taskserver.organisations._name_.users">services.taskserver.organisations.my-company.users</link> = [ "alice" ];
 }
 </screen>
-      This creates an organisation called <literal>my-company</literal> with the
-      user <literal>alice</literal>.
-    </para>
+   This creates an organisation called <literal>my-company</literal> with the
+   user <literal>alice</literal>.
+  </para>
 
-    <para>
-      Now in order to import the <literal>alice</literal> user to another
-      machine <literal>alicebox</literal>, all we need to do is something like
-      this:
+  <para>
+   Now in order to import the <literal>alice</literal> user to another machine
+   <literal>alicebox</literal>, all we need to do is something like this:
 <screen>
 $ ssh server nixos-taskserver user export my-company alice | sh
 </screen>
-      Of course, if no SSH daemon is available on the server you can also copy
-      &amp; paste it directly into a shell.
-    </para>
+   Of course, if no SSH daemon is available on the server you can also copy
+   &amp; paste it directly into a shell.
+  </para>
 
-    <para>
-      After this step the user should be set up and you can start synchronising
-      your tasks for the first time with <command>task sync init</command> on
-      <literal>alicebox</literal>.
-    </para>
+  <para>
+   After this step the user should be set up and you can start synchronising
+   your tasks for the first time with <command>task sync init</command> on
+   <literal>alicebox</literal>.
+  </para>
 
-    <para>
-      Subsequent synchronisation requests merely require the command
-      <command>task sync</command> after that stage.
-    </para>
-  </section>
-  <section xml:id="module-services-taskserver-manual-ca-management">
-    <title>Manual CA management</title>
+  <para>
+   Subsequent synchronisation requests merely require the command <command>task
+   sync</command> after that stage.
+  </para>
+ </section>
+ <section xml:id="module-services-taskserver-manual-ca-management">
+  <title>Manual CA management</title>
 
-    <para>
-      If you set any options within
-      <link linkend="opt-services.taskserver.pki.manual.ca.cert">service.taskserver.pki.manual</link>.*,
-      <command>nixos-taskserver</command> won't issue certificates, but you can
-      still use it for adding or removing user accounts.
-    </para>
-  </section>
+  <para>
+   If you set any options within
+   <link linkend="opt-services.taskserver.pki.manual.ca.cert">service.taskserver.pki.manual</link>.*,
+   <command>nixos-taskserver</command> won't issue certificates, but you can
+   still use it for adding or removing user accounts.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/misc/weechat.nix b/nixos/modules/services/misc/weechat.nix
index 1fcfb440485dc..c6ff540ea12f4 100644
--- a/nixos/modules/services/misc/weechat.nix
+++ b/nixos/modules/services/misc/weechat.nix
@@ -46,10 +46,12 @@ in
         Group = "weechat";
         RemainAfterExit = "yes";
       };
-      script = "exec ${pkgs.screen}/bin/screen -Dm -S ${cfg.sessionName} ${cfg.binary}";
+      script = "exec ${config.security.wrapperDir}/screen -Dm -S ${cfg.sessionName} ${cfg.binary}";
       wantedBy = [ "multi-user.target" ];
       wants = [ "network.target" ];
     };
+
+    security.wrappers.screen.source = "${pkgs.screen}/bin/screen";
   };
 
   meta.doc = ./weechat.xml;
diff --git a/nixos/modules/services/misc/weechat.xml b/nixos/modules/services/misc/weechat.xml
index de86dede2eb55..7255edfb9da36 100644
--- a/nixos/modules/services/misc/weechat.xml
+++ b/nixos/modules/services/misc/weechat.xml
@@ -3,22 +3,24 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-weechat">
+ <title>WeeChat</title>
+ <para>
+  <link xlink:href="https://weechat.org/">WeeChat</link> is a fast and
+  extensible IRC client.
+ </para>
+ <section xml:id="module-services-weechat-basic-usage">
+  <title>Basic Usage</title>
 
-<title>WeeChat</title>
-<para><link xlink:href="https://weechat.org/">WeeChat</link> is a fast and extensible IRC client.</para>
-
-<section><title>Basic Usage</title>
-<para>
-By default, the module creates a
-<literal><link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</link></literal> unit
-which runs the chat client in a detached
-<literal><link xlink:href="https://www.gnu.org/software/screen/">screen</link></literal> session.
-
-</para>
-
-<para>
-This can be done by enabling the <literal>weechat</literal> service:
+  <para>
+   By default, the module creates a
+   <literal><link xlink:href="https://www.freedesktop.org/wiki/Software/systemd/">systemd</link></literal>
+   unit which runs the chat client in a detached
+   <literal><link xlink:href="https://www.gnu.org/software/screen/">screen</link></literal>
+   session.
+  </para>
 
+  <para>
+   This can be done by enabling the <literal>weechat</literal> service:
 <programlisting>
 { ... }:
 
@@ -26,19 +28,22 @@ This can be done by enabling the <literal>weechat</literal> service:
   <link linkend="opt-services.weechat.enable">services.weechat.enable</link> = true;
 }
 </programlisting>
-</para>
-<para>
-The service is managed by a dedicated user
-named <literal>weechat</literal> in the state directory
-<literal>/var/lib/weechat</literal>.
-</para>
-</section>
-<section><title>Re-attaching to WeeChat</title>
-<para>
-WeeChat runs in a screen session owned by a dedicated user. To explicitly
-allow your another user to attach to this session, the <literal>screenrc</literal> needs to be tweaked
-by adding <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link> support:
+  </para>
+
+  <para>
+   The service is managed by a dedicated user named <literal>weechat</literal>
+   in the state directory <literal>/var/lib/weechat</literal>.
+  </para>
+ </section>
+ <section xml:id="module-services-weechat-reattach">
+  <title>Re-attaching to WeeChat</title>
 
+  <para>
+   WeeChat runs in a screen session owned by a dedicated user. To explicitly
+   allow your another user to attach to this session, the
+   <literal>screenrc</literal> needs to be tweaked by adding
+   <link xlink:href="https://www.gnu.org/software/screen/manual/html_node/Multiuser.html#Multiuser">multiuser</link>
+   support:
 <programlisting>
 {
   <link linkend="opt-programs.screen.screenrc">programs.screen.screenrc</link> = ''
@@ -47,15 +52,15 @@ by adding <link xlink:href="https://www.gnu.org/software/screen/manual/html_node
   '';
 }
 </programlisting>
-
-Now, the session can be re-attached like this:
-
+   Now, the session can be re-attached like this:
 <programlisting>
-screen -r weechat-screen
+screen -x weechat/weechat-screen
 </programlisting>
-</para>
-<para>
-<emphasis>The session name can be changed using <link linkend="opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis>
-</para>
-</section>
+  </para>
+
+  <para>
+   <emphasis>The session name can be changed using
+   <link linkend="opt-services.weechat.sessionName">services.weechat.sessionName.</link></emphasis>
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/misc/zoneminder.nix b/nixos/modules/services/misc/zoneminder.nix
new file mode 100644
index 0000000000000..ae7de7850d9fe
--- /dev/null
+++ b/nixos/modules/services/misc/zoneminder.nix
@@ -0,0 +1,360 @@
+{ config, lib, pkgs, ... }:
+
+let
+  cfg = config.services.zoneminder;
+  pkg = pkgs.zoneminder;
+
+  dirName = pkg.dirName;
+
+  user = "zoneminder";
+  group = {
+    nginx = config.services.nginx.group;
+    none  = user;
+  }."${cfg.webserver}";
+
+  useNginx = cfg.webserver == "nginx";
+
+  defaultDir = "/var/lib/${user}";
+  home = if useCustomDir then cfg.storageDir else defaultDir;
+
+  useCustomDir = !(builtins.isNull cfg.storageDir);
+
+  socket = "/run/phpfpm/${dirName}.sock";
+
+  zms = "/cgi-bin/zms";
+
+  dirs = dirList: [ dirName ] ++ map (e: "${dirName}/${e}") dirList;
+
+  cacheDirs = [ "swap" ];
+  libDirs   = [ "events" "exports" "images" "sounds" ];
+
+  dirStanzas = baseDir:
+    lib.concatStringsSep "\n" (map (e:
+      "ZM_DIR_${lib.toUpper e}=${baseDir}/${e}"
+      ) libDirs);
+
+  defaultsFile = pkgs.writeText "60-defaults.conf" ''
+    # 01-system-paths.conf
+    ${dirStanzas home}
+    ZM_PATH_ARP=${lib.getBin pkgs.nettools}/bin/arp
+    ZM_PATH_LOGS=/var/log/${dirName}
+    ZM_PATH_MAP=/dev/shm
+    ZM_PATH_SOCKS=/run/${dirName}
+    ZM_PATH_SWAP=/var/cache/${dirName}/swap
+    ZM_PATH_ZMS=${zms}
+
+    # 02-multiserver.conf
+    ZM_SERVER_HOST=
+
+    # Database
+    ZM_DB_TYPE=mysql
+    ZM_DB_HOST=${cfg.database.host}
+    ZM_DB_NAME=${cfg.database.name}
+    ZM_DB_USER=${cfg.database.username}
+    ZM_DB_PASS=${cfg.database.password}
+
+    # Web
+    ZM_WEB_USER=${user}
+    ZM_WEB_GROUP=${group}
+  '';
+
+  configFile = pkgs.writeText "80-nixos.conf" ''
+    # You can override defaults here
+
+    ${cfg.extraConfig}
+  '';
+
+  phpExtensions = with pkgs.phpPackages; [
+    { pkg = apcu; name = "apcu"; }
+  ];
+
+in {
+  options = {
+    services.zoneminder = with lib; {
+      enable = lib.mkEnableOption ''
+        ZoneMinder
+        </para><para>
+        If you intend to run the database locally, you should set
+        `config.services.zoneminder.database.createLocally` to true. Otherwise,
+        when set to `false` (the default), you will have to create the database
+        and database user as well as populate the database yourself.
+      '';
+
+      webserver = mkOption {
+        type = types.enum [ "nginx" "none" ];
+        default = "nginx";
+        description = ''
+          The webserver to configure for the PHP frontend.
+          </para>
+          <para>
+
+          Set it to `none` if you want to configure it yourself. PRs are welcome
+          for support for other web servers.
+        '';
+      };
+
+      hostname = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          The hostname on which to listen.
+        '';
+      };
+
+      port = mkOption {
+        type = types.int;
+        default = 8095;
+        description = ''
+          The port on which to listen.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Open the firewall port(s).
+        '';
+      };
+
+      database = {
+        createLocally = mkOption {
+          type = types.bool;
+          default = false;
+          description = ''
+            Create the database and database user locally.
+          '';
+        };
+
+        host = mkOption {
+          type = types.str;
+          default = "localhost";
+          description = ''
+            Hostname hosting the database.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "zm";
+          description = ''
+            Name of database.
+          '';
+        };
+
+        username = mkOption {
+          type = types.str;
+          default = "zmuser";
+          description = ''
+            Username for accessing the database.
+          '';
+        };
+
+        password = mkOption {
+          type = types.str;
+          default = "zmpass";
+          description = ''
+            Username for accessing the database.
+          '';
+        };
+      };
+
+      cameras = mkOption {
+        type = types.int;
+        default = 1;
+        description = ''
+          Set this to the number of cameras you expect to support.
+        '';
+      };
+
+      storageDir = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/storage/tank";
+        description = ''
+          ZoneMinder can generate quite a lot of data, so in case you don't want
+          to use the default ${home}, you can override the path here.
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Additional configuration added verbatim to the configuration file.
+        '';
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable {
+
+    environment.etc = {
+      "zoneminder/60-defaults.conf".source = defaultsFile;
+      "zoneminder/80-nixos.conf".source    = configFile;
+    };
+
+    networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [ cfg.port ];
+
+    services = {
+      fcgiwrap = lib.mkIf useNginx {
+        enable = true;
+        preforkProcesses = cfg.cameras;
+        inherit user group;
+      };
+
+      mysql = lib.mkIf cfg.database.createLocally {
+        ensureDatabases = [ cfg.database.name ];
+        ensureUsers = [{
+          name = cfg.database.username;
+          ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
+          initialDatabases = [
+            { inherit (cfg.database) name; schema = "${pkg}/share/zoneminder/db/zm_create.sql"; }
+          ];
+        }];
+      };
+
+      nginx = lib.mkIf useNginx {
+        enable = true;
+        virtualHosts = {
+          "${cfg.hostname}" = {
+            default = true;
+            root = "${pkg}/share/zoneminder/www";
+            listen = [ { addr = "0.0.0.0"; inherit (cfg) port; } ];
+            extraConfig = let
+              fcgi = config.services.fcgiwrap;
+            in ''
+              index index.php;
+
+              location / {
+                try_files $uri $uri/ /index.php?$args =404;
+
+                location ~ /api/(css|img|ico) {
+                  rewrite ^/api(.+)$ /api/app/webroot/$1 break;
+                  try_files $uri $uri/ =404;
+                }
+
+                location ~ \.(gif|ico|jpg|jpeg|png)$ {
+                  access_log off;
+                  expires 30d;
+                }
+
+                location /api {
+                  rewrite ^/api(.+)$ /api/app/webroot/index.php?p=$1 last;
+                }
+
+                location /cgi-bin {
+                  gzip off;
+
+                  include ${pkgs.nginx}/conf/fastcgi_params;
+                  fastcgi_param SCRIPT_FILENAME ${pkg}/libexec/zoneminder/${zms};
+                  fastcgi_param HTTP_PROXY "";
+                  fastcgi_intercept_errors on;
+
+                  fastcgi_pass ${fcgi.socketType}:${fcgi.socketAddress};
+                }
+
+                location /cache {
+                  alias /var/cache/${dirName};
+                }
+
+                location ~ \.php$ {
+                  try_files $uri =404;
+                  fastcgi_index index.php;
+
+                  include ${pkgs.nginx}/conf/fastcgi_params;
+                  fastcgi_param SCRIPT_FILENAME $request_filename;
+                  fastcgi_param HTTP_PROXY "";
+
+                  fastcgi_pass unix:${socket};
+                }
+              }
+            '';
+          };
+        };
+      };
+
+      phpfpm = lib.mkIf useNginx {
+        phpOptions = ''
+          date.timezone = "${config.time.timeZone}"
+
+          ${lib.concatStringsSep "\n" (map (e:
+          "extension=${e.pkg}/lib/php/extensions/${e.name}.so") phpExtensions)}
+        '';
+        pools.zoneminder = {
+          listen = socket;
+          extraConfig = ''
+            user = ${user}
+            group = ${group}
+
+            listen.owner = ${user}
+            listen.group = ${group}
+            listen.mode = 0660
+
+            pm = dynamic
+            pm.start_servers = 1
+            pm.min_spare_servers = 1
+            pm.max_spare_servers = 2
+            pm.max_requests = 500
+            pm.max_children = 5
+            pm.status_path = /$pool-status
+            ping.path = /$pool-ping
+          '';
+        };
+      };
+    };
+
+    systemd.services = {
+      zoneminder = with pkgs; rec {
+        inherit (zoneminder.meta) description;
+        documentation = [ "https://zoneminder.readthedocs.org/en/latest/" ];
+        path = [
+          coreutils
+          procps
+          psmisc
+        ];
+        after = [ "mysql.service" "nginx.service" ];
+        wantedBy = [ "multi-user.target" ];
+        restartTriggers = [ defaultsFile configFile ];
+        preStart = lib.mkIf useCustomDir ''
+          install -dm775 -o ${user} -g ${group} ${cfg.storageDir}/{${lib.concatStringsSep "," libDirs}}
+        '';
+        serviceConfig = {
+          User = user;
+          Group = group;
+          SupplementaryGroups = [ "video" ];
+          ExecStart  = "${zoneminder}/bin/zmpkg.pl start";
+          ExecStop   = "${zoneminder}/bin/zmpkg.pl stop";
+          ExecReload = "${zoneminder}/bin/zmpkg.pl restart";
+          PIDFile = "/run/${dirName}/zm.pid";
+          Type = "forking";
+          Restart = "on-failure";
+          RestartSec = "10s";
+          CacheDirectory = dirs cacheDirs;
+          RuntimeDirectory = dirName;
+          ReadWriteDirectories = lib.mkIf useCustomDir [ cfg.storageDir ];
+          StateDirectory = dirs (if useCustomDir then [] else libDirs);
+          LogsDirectory = dirName;
+          PrivateTmp = true;
+          ProtectSystem = "strict";
+          ProtectKernelTunables = true;
+          SystemCallArchitectures = "native";
+          NoNewPrivileges = true;
+        };
+      };
+    };
+
+    users.groups."${user}" = {
+      gid = config.ids.gids.zoneminder;
+    };
+
+    users.users."${user}" = {
+      uid = config.ids.uids.zoneminder;
+      group = user;
+      inherit home;
+      inherit (pkgs.zoneminder.meta) description;
+    };
+  };
+
+  meta.maintainers = with lib.maintainers; [ peterhoeg ];
+}
diff --git a/nixos/modules/services/monitoring/alerta.nix b/nixos/modules/services/monitoring/alerta.nix
new file mode 100644
index 0000000000000..8f4258e26dedc
--- /dev/null
+++ b/nixos/modules/services/monitoring/alerta.nix
@@ -0,0 +1,116 @@
+{ options, config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.alerta;
+
+  alertaConf = pkgs.writeTextFile {
+    name = "alertad.conf";
+    text = ''
+      DATABASE_URL = '${cfg.databaseUrl}'
+      DATABASE_NAME = '${cfg.databaseName}'
+      LOG_FILE = '${cfg.logDir}/alertad.log'
+      LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
+      CORS_ORIGINS = [ ${concatMapStringsSep ", " (s: "\"" + s + "\"") cfg.corsOrigins} ];
+      AUTH_REQUIRED = ${if cfg.authenticationRequired then "True" else "False"}
+      SIGNUP_ENABLED = ${if cfg.signupEnabled then "True" else "False"}
+      ${cfg.extraConfig}
+    '';
+  };
+in
+{
+  options.services.alerta = {
+    enable = mkEnableOption "alerta";
+
+    port = mkOption {
+      type = types.int;
+      default = 5000;
+      description = "Port of Alerta";
+    };
+
+    bind = mkOption {
+      type = types.str;
+      default = "0.0.0.0";
+      example = literalExample "0.0.0.0";
+      description = "Address to bind to. The default is to bind to all addresses";
+    };
+
+    logDir = mkOption {
+      type = types.path;
+      description = "Location where the logfiles are stored";
+      default = "/var/log/alerta";
+    };
+
+    databaseUrl = mkOption {
+      type = types.str;
+      description = "URL of the MongoDB or PostgreSQL database to connect to";
+      default = "mongodb://localhost";
+      example = "mongodb://localhost";
+    };
+
+    databaseName = mkOption {
+      type = types.str;
+      description = "Name of the database instance to connect to";
+      default = "monitoring";
+      example = "monitoring";
+    };
+
+    corsOrigins = mkOption {
+      type = types.listOf types.str;
+      description = "List of URLs that can access the API for Cross-Origin Resource Sharing (CORS)";
+      example = [ "http://localhost" "http://localhost:5000" ];
+      default = [ "http://localhost" "http://localhost:5000" ];
+    };
+
+    authenticationRequired = mkOption {
+      type = types.bool;
+      description = "Whether users must authenticate when using the web UI or command-line tool";
+      default = false;
+    };
+
+    signupEnabled = mkOption {
+      type = types.bool;
+      description = "Whether to prevent sign-up of new users via the web UI";
+      default = true;
+    };
+
+    extraConfig = mkOption {
+      description = "These lines go into alertad.conf verbatim.";
+      default = "";
+      type = types.lines;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.alerta = {
+      description = "Alerta Monitoring System";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      environment = {
+        ALERTA_SVR_CONF_FILE = alertaConf;
+      };
+      serviceConfig = {
+        ExecStart = "${pkgs.python36Packages.alerta-server}/bin/alertad run --port ${toString cfg.port} --host ${cfg.bind}";
+        User = "alerta";
+        Group = "alerta";
+        PermissionsStartOnly = true;
+      };
+      preStart = ''
+        mkdir -p ${cfg.logDir}
+        chown alerta:alerta ${cfg.logDir}
+      '';
+    };
+
+    environment.systemPackages = [ pkgs.python36Packages.alerta ];
+
+    users.users.alerta = {
+      uid = config.ids.uids.alerta;
+      description = "Alerta user";
+    };
+
+    users.groups.alerta = {
+      gid = config.ids.gids.alerta;
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/apcupsd.nix b/nixos/modules/services/monitoring/apcupsd.nix
index 839116de6265b..7ee870183cac4 100644
--- a/nixos/modules/services/monitoring/apcupsd.nix
+++ b/nixos/modules/services/monitoring/apcupsd.nix
@@ -180,7 +180,7 @@ in
       serviceConfig = {
         Type = "oneshot";
         ExecStart = "${pkgs.apcupsd}/bin/apcupsd --killpower -f ${configFile}";
-        TimeoutSec = 0;
+        TimeoutSec = "infinity";
         StandardOutput = "tty";
         RemainAfterExit = "yes";
       };
diff --git a/nixos/modules/services/monitoring/collectd.nix b/nixos/modules/services/monitoring/collectd.nix
index 6606980cdad88..45e3312c0f447 100644
--- a/nixos/modules/services/monitoring/collectd.nix
+++ b/nixos/modules/services/monitoring/collectd.nix
@@ -88,6 +88,8 @@ in {
         ExecStart = "${cfg.package}/sbin/collectd -C ${conf} -f";
         User = cfg.user;
         PermissionsStartOnly = true;
+        Restart = "on-failure";
+        RestartSec = 3;
       };
 
       preStart = ''
diff --git a/nixos/modules/services/monitoring/datadog-agent.nix b/nixos/modules/services/monitoring/datadog-agent.nix
index 35c08f5e7e303..a4d29d45bacf7 100644
--- a/nixos/modules/services/monitoring/datadog-agent.nix
+++ b/nixos/modules/services/monitoring/datadog-agent.nix
@@ -7,7 +7,7 @@ let
 
   ddConf = {
     dd_url              = "https://app.datadoghq.com";
-    skip_ssl_validation = "no";
+    skip_ssl_validation = false;
     confd_path          = "/etc/datadog-agent/conf.d";
     additional_checksd  = "/etc/datadog-agent/checks.d";
     use_dogstatsd       = true;
@@ -16,6 +16,7 @@ let
   // optionalAttrs (cfg.hostname != null) { inherit (cfg) hostname; }
   // optionalAttrs (cfg.tags != null ) { tags = concatStringsSep ", " cfg.tags; }
   // optionalAttrs (cfg.enableLiveProcessCollection) { process_config = { enabled = "true"; }; }
+  // optionalAttrs (cfg.enableTraceAgent) { apm_config = { enabled = true; }; }
   // cfg.extraConfig;
 
   # Generate Datadog configuration files for each configured checks.
@@ -132,6 +133,15 @@ in {
       default = false;
       type = types.bool;
     };
+
+    enableTraceAgent = mkOption {
+      description = ''
+        Whether to enable the trace agent.
+      '';
+      default = false;
+      type = types.bool;
+    };
+
     checks = mkOption {
       description = ''
         Configuration for all Datadog checks. Keys of this attribute
@@ -176,7 +186,7 @@ in {
       type = types.attrs;
       default = {
         init_config = {};
-        instances = [ { use-mount = "no"; } ];
+        instances = [ { use_mount = "false"; } ];
       };
     };
 
@@ -213,7 +223,6 @@ in {
           Group = "datadog";
           Restart = "always";
           RestartSec = 2;
-          PrivateTmp = true;
         };
         restartTriggers = [ datadogPkg ] ++ map (etc: etc.source) etcfiles;
       } attrs;
@@ -245,6 +254,16 @@ in {
           ${pkgs.datadog-process-agent}/bin/agent --config /etc/datadog-agent/datadog.yaml
         '';
       });
+
+      datadog-trace-agent = lib.mkIf cfg.enableTraceAgent (makeService {
+        description = "Datadog Trace Agent";
+        path = [ ];
+        script = ''
+          export DD_API_KEY=$(head -n 1 ${cfg.apiKeyFile})
+          ${pkgs.datadog-trace-agent}/bin/trace-agent -config /etc/datadog-agent/datadog.yaml
+        '';
+      });
+
     };
 
     environment.etc = etcfiles;
diff --git a/nixos/modules/services/monitoring/grafana-reporter.nix b/nixos/modules/services/monitoring/grafana-reporter.nix
new file mode 100644
index 0000000000000..149026d20188f
--- /dev/null
+++ b/nixos/modules/services/monitoring/grafana-reporter.nix
@@ -0,0 +1,66 @@
+{ options, config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.grafana_reporter;
+
+in {
+  options.services.grafana_reporter = {
+    enable = mkEnableOption "grafana_reporter";
+
+    grafana = {
+      protocol = mkOption {
+        description = "Grafana protocol.";
+        default = "http";
+        type = types.enum ["http" "https"];
+      };
+      addr = mkOption {
+        description = "Grafana address.";
+        default = "127.0.0.1";
+        type = types.str;
+      };
+      port = mkOption {
+        description = "Grafana port.";
+        default = 3000;
+        type = types.int;
+      };
+
+    };
+    addr = mkOption {
+      description = "Listening address.";
+      default = "127.0.0.1";
+      type = types.str;
+    };
+
+    port = mkOption {
+      description = "Listening port.";
+      default = 8686;
+      type = types.int;
+    };
+
+    templateDir = mkOption {
+      description = "Optional template directory to use custom tex templates";
+      default = "${pkgs.grafana_reporter}";
+      type = types.str;
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.grafana_reporter = {
+      description = "Grafana Reporter Service Daemon";
+      wantedBy = ["multi-user.target"];
+      after = ["network.target"];
+      serviceConfig = let
+        args = lib.concatSepString " " [
+          "-proto ${cfg.grafana.protocol}://"
+          "-ip ${cfg.grafana.addr}:${toString cfg.grafana.port}"
+          "-port :${toString cfg.port}"
+          "-templates ${cfg.templateDir}"
+        ];
+      in {
+        ExecStart = "${pkgs.grafana_reporter.bin}/bin/grafana-reporter ${args}";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix
index c30647f5460be..85879cfe0b330 100644
--- a/nixos/modules/services/monitoring/grafana.nix
+++ b/nixos/modules/services/monitoring/grafana.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.grafana;
+  opt = options.services.grafana;
 
   envOptions = {
     PATHS_DATA = cfg.dataDir;
@@ -41,8 +42,166 @@ let
     AUTH_ANONYMOUS_ORG_ROLE = cfg.auth.anonymous.org_role;
 
     ANALYTICS_REPORTING_ENABLED = boolToString cfg.analytics.reporting.enable;
+
+    SMTP_ENABLE = boolToString cfg.smtp.enable;
+    SMTP_HOST = cfg.smtp.host;
+    SMTP_USER = cfg.smtp.user;
+    SMTP_PASSWORD = cfg.smtp.password;
+    SMTP_FROM_ADDRESS = cfg.smtp.fromAddress;
   } // cfg.extraOptions;
 
+  datasourceConfiguration = {
+    apiVersion = 1;
+    datasources = cfg.provision.datasources;
+  };
+
+  datasourceFile = pkgs.writeText "datasource.yaml" (builtins.toJSON datasourceConfiguration);
+
+  dashboardConfiguration = {
+    apiVersion = 1;
+    providers = cfg.provision.dashboards;
+  };
+
+  dashboardFile = pkgs.writeText "dashboard.yaml" (builtins.toJSON dashboardConfiguration);
+
+  provisionConfDir =  pkgs.runCommand "grafana-provisioning" { } ''
+    mkdir -p $out/{datasources,dashboards}
+    ln -sf ${datasourceFile} $out/datasources/datasource.yaml
+    ln -sf ${dashboardFile} $out/dashboards/dashboard.yaml
+  '';
+
+  # Get a submodule without any embedded metadata:
+  _filter = x: filterAttrs (k: v: k != "_module") x;
+
+  # http://docs.grafana.org/administration/provisioning/#datasources
+  grafanaTypes.datasourceConfig = types.submodule {
+    options = {
+      name = mkOption {
+        type = types.str;
+        description = "Name of the datasource. Required";
+      };
+      type = mkOption {
+        type = types.enum ["graphite" "prometheus" "cloudwatch" "elasticsearch" "influxdb" "opentsdb" "mysql" "mssql" "postgres" "loki"];
+        description = "Datasource type. Required";
+      };
+      access = mkOption {
+        type = types.enum ["proxy" "direct"];
+        default = "proxy";
+        description = "Access mode. proxy or direct (Server or Browser in the UI). Required";
+      };
+      orgId = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Org id. will default to orgId 1 if not specified";
+      };
+      url = mkOption {
+        type = types.str;
+        description = "Url of the datasource";
+      };
+      password = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Database password, if used";
+      };
+      user = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Database user, if used";
+      };
+      database = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Database name, if used";
+      };
+      basicAuth = mkOption {
+        type = types.nullOr types.bool;
+        default = null;
+        description = "Enable/disable basic auth";
+      };
+      basicAuthUser = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Basic auth username";
+      };
+      basicAuthPassword = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Basic auth password";
+      };
+      withCredentials = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Enable/disable with credentials headers";
+      };
+      isDefault = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Mark as default datasource. Max one per org";
+      };
+      jsonData = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Datasource specific configuration";
+      };
+      secureJsonData = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        description = "Datasource specific secure configuration";
+      };
+      version = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Version";
+      };
+      editable = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Allow users to edit datasources from the UI.";
+      };
+    };
+  };
+
+  # http://docs.grafana.org/administration/provisioning/#dashboards
+  grafanaTypes.dashboardConfig = types.submodule {
+    options = {
+      name = mkOption {
+        type = types.str;
+        default = "default";
+        description = "Provider name";
+      };
+      orgId = mkOption {
+        type = types.int;
+        default = 1;
+        description = "Organization ID";
+      };
+      folder = mkOption {
+        type = types.str;
+        default = "";
+        description = "Add dashboards to the speciied folder";
+      };
+      type = mkOption {
+        type = types.str;
+        default = "file";
+        description = "Dashboard provider type";
+      };
+      disableDeletion = mkOption {
+        type = types.bool;
+        default = false;
+        description = "Disable deletion when JSON file is removed";
+      };
+      updateIntervalSeconds = mkOption {
+        type = types.int;
+        default = 10;
+        description = "How often Grafana will scan for changed dashboards";
+      };
+      options = {
+        path = mkOption {
+          type = types.path;
+          description = "Path grafana will watch for dashboards";
+        };
+      };
+    };
+  };
 in {
   options.services.grafana = {
     enable = mkEnableOption "grafana";
@@ -134,11 +293,23 @@ in {
       };
 
       password = mkOption {
-        description = "Database password.";
+        description = ''
+          Database password.
+          This option is mutual exclusive with the passwordFile option.
+        '';
         default = "";
         type = types.str;
       };
 
+      passwordFile = mkOption {
+        description = ''
+          File that containts the database password.
+          This option is mutual exclusive with the password option.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+
       path = mkOption {
         description = "Database path.";
         default = "${cfg.dataDir}/data/grafana.db";
@@ -150,8 +321,25 @@ in {
           Sets the maximum amount of time (in seconds) a connection may be reused.
           For MySQL this setting should be shorter than the `wait_timeout' variable.
         '';
-        default = 14400;
-        type = types.int;
+        default = "unlimited";
+        example = 14400;
+        type = types.either types.int (types.enum [ "unlimited" ]);
+      };
+    };
+
+    provision = {
+      enable = mkEnableOption "provision";
+      datasources = mkOption {
+        description = "Grafana datasources configuration";
+        default = [];
+        type = types.listOf grafanaTypes.datasourceConfig;
+        apply = x: map _filter x;
+      };
+      dashboards = mkOption {
+        description = "Grafana dashboard configuration";
+        default = [];
+        type = types.listOf grafanaTypes.dashboardConfig;
+        apply = x: map _filter x;
       };
     };
 
@@ -163,16 +351,69 @@ in {
       };
 
       adminPassword = mkOption {
-        description = "Default admin password.";
+        description = ''
+          Default admin password.
+          This option is mutual exclusive with the adminPasswordFile option.
+        '';
         default = "admin";
         type = types.str;
       };
 
+      adminPasswordFile = mkOption {
+        description = ''
+          Default admin password.
+          This option is mutual exclusive with the <literal>adminPassword</literal> option.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+
       secretKey = mkOption {
         description = "Secret key used for signing.";
         default = "SW2YcwTIb9zpOOhoPsMm";
         type = types.str;
       };
+
+      secretKeyFile = mkOption {
+        description = "Secret key used for signing.";
+        default = null;
+        type = types.nullOr types.path;
+      };
+    };
+
+    smtp = {
+      enable = mkEnableOption "smtp";
+      host = mkOption {
+        description = "Host to connect to";
+        default = "localhost:25";
+        type = types.str;
+      };
+      user = mkOption {
+        description = "User used for authentication";
+        default = "";
+        type = types.str;
+      };
+      password = mkOption {
+        description = ''
+          Password used for authentication.
+          This option is mutual exclusive with the passwordFile option.
+        '';
+        default = "";
+        type = types.str;
+      };
+      passwordFile = mkOption {
+        description = ''
+          Password used for authentication.
+          This option is mutual exclusive with the password option.
+        '';
+        default = null;
+        type = types.nullOr types.path;
+      };
+      fromAddress = mkOption {
+        description = "Email address used for sending";
+        default = "admin@grafana.localhost";
+        type = types.str;
+      };
     };
 
     users = {
@@ -240,13 +481,37 @@ in {
   };
 
   config = mkIf cfg.enable {
-    warnings = optional (
-      cfg.database.password != options.services.grafana.database.password.default ||
-      cfg.security.adminPassword != options.services.grafana.security.adminPassword.default
-    ) "Grafana passwords will be stored as plaintext in the Nix store!";
+    warnings = flatten [
+      (optional (
+        cfg.database.password != opt.database.password.default ||
+        cfg.security.adminPassword != opt.security.adminPassword.default
+      ) "Grafana passwords will be stored as plaintext in the Nix store!")
+      (optional (
+        any (x: x.password != null || x.basicAuthPassword != null || x.secureJsonData != null) cfg.provision.datasources
+      ) "Datasource passwords will be stored as plaintext in the Nix store!")
+    ];
 
     environment.systemPackages = [ cfg.package ];
 
+    assertions = [
+      {
+        assertion = cfg.database.password != opt.database.password.default -> cfg.database.passwordFile == null;
+        message = "Cannot set both password and passwordFile";
+      }
+      {
+        assertion = cfg.security.adminPassword != opt.security.adminPassword.default -> cfg.security.adminPasswordFile == null;
+        message = "Cannot set both adminPassword and adminPasswordFile";
+      }
+      {
+        assertion = cfg.security.secretKeyFile != opt.security.secretKeyFile.default -> cfg.security.secretKeyFile == null;
+        message = "Cannot set both secretKey and secretKeyFile";
+      }
+      {
+        assertion = cfg.smtp.password != opt.smtp.password.default -> cfg.smtp.passwordFile == null;
+        message = "Cannot set both password and secretKeyFile";
+      }
+    ];
+
     systemd.services.grafana = {
       description = "Grafana Service Daemon";
       wantedBy = ["multi-user.target"];
@@ -254,8 +519,25 @@ in {
       environment = {
         QT_QPA_PLATFORM = "offscreen";
       } // mapAttrs' (n: v: nameValuePair "GF_${n}" (toString v)) envOptions;
+      script = ''
+        ${optionalString (cfg.database.passwordFile != null) ''
+          export GF_DATABASE_PASSWORD="$(cat ${escapeShellArg cfg.database.passwordFile})"
+        ''}
+        ${optionalString (cfg.security.adminPasswordFile != null) ''
+          export GF_SECURITY_ADMIN_PASSWORD="$(cat ${escapeShellArg cfg.security.adminPasswordFile})"
+        ''}
+        ${optionalString (cfg.security.secretKeyFile != null) ''
+          export GF_SECURITY_SECRET_KEY="$(cat ${escapeShellArg cfg.security.secretKeyFile})"
+        ''}
+        ${optionalString (cfg.smtp.passwordFile != null) ''
+          export GF_SMTP_PASSWORD="$(cat ${escapeShellArg cfg.smtp.passwordFile})"
+        ''}
+        ${optionalString cfg.provision.enable ''
+          export GF_PATHS_PROVISIONING=${provisionConfDir};
+        ''}
+        exec ${cfg.package.bin}/bin/grafana-server -homepath ${cfg.dataDir}
+      '';
       serviceConfig = {
-        ExecStart = "${cfg.package.bin}/bin/grafana-server -homepath ${cfg.dataDir}";
         WorkingDirectory = cfg.dataDir;
         User = "grafana";
       };
diff --git a/nixos/modules/services/monitoring/kapacitor.nix b/nixos/modules/services/monitoring/kapacitor.nix
new file mode 100644
index 0000000000000..a4bdfa8f80535
--- /dev/null
+++ b/nixos/modules/services/monitoring/kapacitor.nix
@@ -0,0 +1,192 @@
+{ options, config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.kapacitor;
+
+  kapacitorConf = pkgs.writeTextFile {
+    name = "kapacitord.conf";
+    text = ''
+      hostname="${config.networking.hostName}"
+      data_dir="${cfg.dataDir}"
+
+      [http]
+        bind-address = "${cfg.bind}:${toString cfg.port}"
+        log-enabled = false
+        auth-enabled = false
+
+      [task]
+        dir = "${cfg.dataDir}/tasks"
+        snapshot-interval = "${cfg.taskSnapshotInterval}"
+
+      [replay]
+        dir = "${cfg.dataDir}/replay"
+
+      [storage]
+        boltdb = "${cfg.dataDir}/kapacitor.db"
+
+      ${optionalString (cfg.loadDirectory != null) ''
+        [load]
+          enabled = true
+          dir = "${cfg.loadDirectory}"
+      ''}
+
+      ${optionalString (cfg.defaultDatabase.enable) ''
+        [[influxdb]]
+          name = "default"
+          enabled = true
+          default = true
+          urls = [ "${cfg.defaultDatabase.url}" ]
+          username = "${cfg.defaultDatabase.username}"
+          password = "${cfg.defaultDatabase.password}"
+      ''}
+
+      ${optionalString (cfg.alerta.enable) ''
+        [alerta]
+          enabled = true
+          url = "${cfg.alerta.url}"
+          token = "${cfg.alerta.token}"
+          environment = "${cfg.alerta.environment}"
+          origin = "${cfg.alerta.origin}"
+      ''}
+
+      ${cfg.extraConfig}
+    '';
+  };
+in
+{
+  options.services.kapacitor = {
+    enable = mkEnableOption "kapacitor";
+
+    dataDir = mkOption {
+      type = types.path;
+      example = "/var/lib/kapacitor";
+      default = "/var/lib/kapacitor";
+      description = "Location where Kapacitor stores its state";
+    };
+
+    port = mkOption {
+      type = types.int;
+      default = 9092;
+      description = "Port of Kapacitor";
+    };
+
+    bind = mkOption {
+      type = types.str;
+      default = "";
+      example = literalExample "0.0.0.0";
+      description = "Address to bind to. The default is to bind to all addresses";
+    };
+
+    extraConfig = mkOption {
+      description = "These lines go into kapacitord.conf verbatim.";
+      default = "";
+      type = types.lines;
+    };
+
+    user = mkOption {
+      type = types.str;
+      default = "kapacitor";
+      description = "User account under which Kapacitor runs";
+    };
+
+    group = mkOption {
+      type = types.str;
+      default = "kapacitor";
+      description = "Group under which Kapacitor runs";
+    };
+
+    taskSnapshotInterval = mkOption {
+      type = types.str;
+      description = "Specifies how often to snapshot the task state  (in InfluxDB time units)";
+      default = "1m0s";
+      example = "1m0s";
+    };
+
+    loadDirectory = mkOption {
+      type = types.nullOr types.path;
+      description = "Directory where to load services from, such as tasks, templates and handlers (or null to disable service loading on startup)";
+      default = null;
+    };
+
+    defaultDatabase = {
+      enable = mkEnableOption "kapacitor.defaultDatabase";
+
+      url = mkOption {
+        description = "The URL to an InfluxDB server that serves as the default database";
+        example = "http://localhost:8086";
+        type = types.string;
+      };
+
+      username = mkOption {
+        description = "The username to connect to the remote InfluxDB server";
+        type = types.string;
+      };
+
+      password = mkOption {
+        description = "The password to connect to the remote InfluxDB server";
+        type = types.string;
+      };
+    };
+
+    alerta = {
+      enable = mkEnableOption "kapacitor alerta integration";
+
+      url = mkOption {
+        description = "The URL to the Alerta REST API";
+        default = "http://localhost:5000";
+        example = "http://localhost:5000";
+        type = types.string;
+      };
+
+      token = mkOption {
+        description = "Default Alerta authentication token";
+        type = types.str;
+        default = "";
+      };
+
+      environment = mkOption {
+        description = "Default Alerta environment";
+        type = types.str;
+        default = "Production";
+      };
+
+      origin = mkOption {
+        description = "Default origin of alert";
+        type = types.str;
+        default = "kapacitor";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ pkgs.kapacitor ];
+
+    systemd.services.kapacitor = {
+      description = "Kapacitor Real-Time Stream Processing Engine";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      serviceConfig = {
+        ExecStart = "${pkgs.kapacitor}/bin/kapacitord -config ${kapacitorConf}";
+        User = "kapacitor";
+        Group = "kapacitor";
+        PermissionsStartOnly = true;
+      };
+      preStart = ''
+        mkdir -p ${cfg.dataDir}
+        chown ${cfg.user}:${cfg.group} ${cfg.dataDir}
+      '';
+    };
+
+    users.users.kapacitor = {
+      uid = config.ids.uids.kapacitor;
+      description = "Kapacitor user";
+      home = cfg.dataDir;
+    };
+
+    users.groups.kapacitor = {
+      gid = config.ids.gids.kapacitor;
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/monit.nix b/nixos/modules/services/monitoring/monit.nix
index d48e5c550abbf..32e14ab21ffc8 100644
--- a/nixos/modules/services/monitoring/monit.nix
+++ b/nixos/modules/services/monitoring/monit.nix
@@ -1,33 +1,30 @@
-# Monit system watcher
-# http://mmonit.org/monit/
-
 {config, pkgs, lib, ...}:
 
-let inherit (lib) mkOption mkIf;
+with lib;
+
+let
+  cfg = config.services.monit;
 in
 
 {
-  options = {
-    services.monit = {
-      enable = mkOption {
-        default = false;
-        description = ''
-          Whether to run Monit system watcher.
-        '';
-      };
-      config = mkOption {
-        default = "";
-        description = "monitrc content";
-      };
+  options.services.monit = {
+
+    enable = mkEnableOption "Monit";
+
+    config = mkOption {
+      type = types.lines;
+      default = "";
+      description = "monitrc content";
     };
+
   };
 
-  config = mkIf config.services.monit.enable {
+  config = mkIf cfg.enable {
 
     environment.systemPackages = [ pkgs.monit ];
 
     environment.etc."monitrc" = {
-      text = config.services.monit.config;
+      text = cfg.config;
       mode = "0400";
     };
 
diff --git a/nixos/modules/services/monitoring/munin.nix b/nixos/modules/services/monitoring/munin.nix
index ff9604c7dbcd1..f67986327245e 100644
--- a/nixos/modules/services/monitoring/munin.nix
+++ b/nixos/modules/services/monitoring/munin.nix
@@ -4,9 +4,9 @@
 # TODO: LWP/Pg perl libs aren't recognized
 
 # TODO: support fastcgi
-# http://munin-monitoring.org/wiki/CgiHowto2
-# spawn-fcgi -s /var/run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
-# spawn-fcgi -s /var/run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
+# http://guide.munin-monitoring.org/en/latest/example/webserver/apache-cgi.html
+# spawn-fcgi -s /run/munin/fastcgi-graph.sock -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-graph
+# spawn-fcgi -s /run/munin/fastcgi-html.sock  -U www-data   -u munin -g munin /usr/lib/munin/cgi/munin-cgi-html
 # https://paste.sh/vofcctHP#-KbDSXVeWoifYncZmLfZzgum
 # nginx http://munin.readthedocs.org/en/latest/example/webserver/nginx.html
 
@@ -22,7 +22,9 @@ let
       dbdir     /var/lib/munin
       htmldir   /var/www/munin
       logdir    /var/log/munin
-      rundir    /var/run/munin
+      rundir    /run/munin
+
+      ${lib.optionalString (cronCfg.extraCSS != "") "staticdir ${customStaticDir}"}
 
       ${cronCfg.extraGlobalConfig}
 
@@ -63,6 +65,11 @@ let
       [ipmi*]
       user root
       group root
+
+      [munin*]
+      env.UPDATE_STATSFILE /var/lib/munin/munin-update.stats
+
+      ${nodeCfg.extraPluginConfig}
     '';
 
   pluginConfDir = pkgs.stdenv.mkDerivation {
@@ -72,6 +79,54 @@ let
       ln -s ${pluginConf} $out/nixos-config
     '';
   };
+
+  # Copy one Munin plugin into the Nix store with a specific name.
+  # This is suitable for use with plugins going directly into /etc/munin/plugins,
+  # i.e. munin.extraPlugins.
+  internOnePlugin = name: path:
+    "cp -a '${path}' '${name}'";
+
+  # Copy an entire tree of Munin plugins into a single directory in the Nix
+  # store, with no renaming.
+  # This is suitable for use with munin-node-configure --suggest, i.e.
+  # munin.extraAutoPlugins.
+  internManyPlugins = name: path:
+    "find '${path}' -type f -perm /a+x -exec cp -a -t . '{}' '+'";
+
+  # Use the appropriate intern-fn to copy the plugins into the store and patch
+  # them afterwards in an attempt to get them to run on NixOS.
+  internAndFixPlugins = name: intern-fn: paths:
+    pkgs.runCommand name {} ''
+      mkdir -p "$out"
+      cd "$out"
+      ${lib.concatStringsSep "\n"
+          (lib.attrsets.mapAttrsToList intern-fn paths)}
+      chmod -R u+w .
+      find . -type f -exec sed -E -i '
+        s,(/usr)?/s?bin/,/run/current-system/sw/bin/,g
+      ' '{}' '+'
+    '';
+
+  # TODO: write a derivation for munin-contrib, so that for contrib plugins
+  # you can just refer to them by name rather than needing to include a copy
+  # of munin-contrib in your nixos configuration.
+  extraPluginDir = internAndFixPlugins "munin-extra-plugins.d"
+    internOnePlugin nodeCfg.extraPlugins;
+
+  extraAutoPluginDir = internAndFixPlugins "munin-extra-auto-plugins.d"
+    internManyPlugins
+    (builtins.listToAttrs
+      (map
+        (path: { name = baseNameOf path; value = path; })
+        nodeCfg.extraAutoPlugins));
+
+  customStaticDir = pkgs.runCommand "munin-custom-static-data" {} ''
+    cp -a "${pkgs.munin}/etc/opt/munin/static" "$out"
+    cd "$out"
+    chmod -R u+w .
+    echo "${cronCfg.extraCSS}" >> style.css
+    echo "${cronCfg.extraCSS}" >> style-new.css
+  '';
 in
 
 {
@@ -82,11 +137,12 @@ in
 
       enable = mkOption {
         default = false;
+        type = types.bool;
         description = ''
           Enable Munin Node agent. Munin node listens on 0.0.0.0 and
           by default accepts connections only from 127.0.0.1 for security reasons.
 
-          See <link xlink:href='http://munin-monitoring.org/wiki/munin-node.conf' />.
+          See <link xlink:href='http://guide.munin-monitoring.org/en/latest/architecture/index.html' />.
         '';
       };
 
@@ -95,18 +151,108 @@ in
         type = types.lines;
         description = ''
           <filename>munin-node.conf</filename> extra configuration. See
-          <link xlink:href='http://munin-monitoring.org/wiki/munin-node.conf' />
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin-node.conf.html' />
+        '';
+      };
+
+      extraPluginConfig = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          <filename>plugin-conf.d</filename> extra plugin configuration. See
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/plugin/use.html' />
+        '';
+        example = ''
+          [fail2ban_*]
+          user root
         '';
       };
 
-      # TODO: add option to add additional plugins
+      extraPlugins = mkOption {
+        default = {};
+        type = with types; attrsOf path;
+        description = ''
+          Additional Munin plugins to activate. Keys are the name of the plugin
+          symlink, values are the path to the underlying plugin script. You
+          can use the same plugin script multiple times (e.g. for wildcard
+          plugins).
+
+          Note that these plugins do not participate in autoconfiguration. If
+          you want to autoconfigure additional plugins, use
+          <option>services.munin-node.extraAutoPlugins</option>.
+
+          Plugins enabled in this manner take precedence over autoconfigured
+          plugins.
+
+          Plugins will be copied into the Nix store, and it will attempt to
+          modify them to run properly by fixing hardcoded references to
+          <literal>/bin</literal>, <literal>/usr/bin</literal>,
+          <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+        '';
+        example = literalExample ''
+          {
+            zfs_usage_bigpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
+            zfs_usage_smallpool = /src/munin-contrib/plugins/zfs/zfs_usage_;
+            zfs_list = /src/munin-contrib/plugins/zfs/zfs_list;
+          };
+        '';
+      };
 
+      extraAutoPlugins = mkOption {
+        default = [];
+        type = with types; listOf path;
+        description = ''
+          Additional Munin plugins to autoconfigure, using
+          <literal>munin-node-configure --suggest</literal>. These should be
+          the actual paths to the plugin files (or directories containing them),
+          not just their names.
+
+          If you want to manually enable individual plugins instead, use
+          <option>services.munin-node.extraPlugins</option>.
+
+          Note that only plugins that have the 'autoconfig' capability will do
+          anything if listed here, since plugins that cannot autoconfigure
+          won't be automatically enabled by
+          <literal>munin-node-configure</literal>.
+
+          Plugins will be copied into the Nix store, and it will attempt to
+          modify them to run properly by fixing hardcoded references to
+          <literal>/bin</literal>, <literal>/usr/bin</literal>,
+          <literal>/sbin</literal>, and <literal>/usr/sbin</literal>.
+        '';
+        example = literalExample ''
+          [
+            /src/munin-contrib/plugins/zfs
+            /src/munin-contrib/plugins/ssh
+          ];
+        '';
+      };
+
+      disabledPlugins = mkOption {
+        # TODO: figure out why Munin isn't writing the log file and fix it.
+        # In the meantime this at least suppresses a useless graph full of
+        # NaNs in the output.
+        default = [ "munin_stats" ];
+        type = with types; listOf string;
+        description = ''
+          Munin plugins to disable, even if
+          <literal>munin-node-configure --suggest</literal> tries to enable
+          them. To disable a wildcard plugin, use an actual wildcard, as in
+          the example.
+
+          munin_stats is disabled by default as it tries to read
+          <literal>/var/log/munin/munin-update.log</literal> for timing
+          information, and the NixOS build of Munin does not write this file.
+        '';
+        example = [ "diskstats" "zfs_usage_*" ];
+      };
     };
 
     services.munin-cron = {
 
       enable = mkOption {
         default = false;
+        type = types.bool;
         description = ''
           Enable munin-cron. Takes care of all heavy lifting to collect data from
           nodes and draws graphs to html. Runs munin-update, munin-limits,
@@ -119,11 +265,12 @@ in
 
       extraGlobalConfig = mkOption {
         default = "";
+        type = types.lines;
         description = ''
           <filename>munin.conf</filename> extra global configuration.
-          See <link xlink:href='http://munin-monitoring.org/wiki/munin.conf' />.
+          See <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />.
           Useful to setup notifications, see
-          <link xlink:href='http://munin-monitoring.org/wiki/HowToContact' />
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/tutorial/alert.html' />
         '';
         example = ''
           contact.email.command mail -s "Munin notification for ''${var:host}" someone@example.com
@@ -131,14 +278,34 @@ in
       };
 
       hosts = mkOption {
+        default = "";
+        type = types.lines;
+        description = ''
+          Definitions of hosts of nodes to collect data from. Needs at least one
+          host for cron to succeed. See
+          <link xlink:href='http://guide.munin-monitoring.org/en/latest/reference/munin.conf.html' />
+        '';
         example = ''
           [''${config.networking.hostName}]
           address localhost
         '';
+      };
+
+      extraCSS = mkOption {
+        default = "";
+        type = types.lines;
         description = ''
-          Definitions of hosts of nodes to collect data from. Needs at least one
-          hosts for cron to succeed. See
-          <link xlink:href='http://munin-monitoring.org/wiki/munin.conf' />
+          Custom styling for the HTML that munin-cron generates. This will be
+          appended to the CSS files used by munin-cron and will thus take
+          precedence over the builtin styles.
+        '';
+        example = ''
+          /* A simple dark theme. */
+          html, body { background: #222222; }
+          #header, #footer { background: #333333; }
+          img.i, img.iwarn, img.icrit, img.iunkn {
+            filter: invert(100%) hue-rotate(-30deg);
+          }
         '';
       };
 
@@ -155,6 +322,7 @@ in
       description = "Munin monitoring user";
       group = "munin";
       uid = config.ids.uids.munin;
+      home = "/var/lib/munin";
     }];
 
     users.groups = [{
@@ -170,17 +338,30 @@ in
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [ munin smartmontools "/run/current-system/sw" "/run/wrappers" ];
       environment.MUNIN_LIBDIR = "${pkgs.munin}/lib";
-      environment.MUNIN_PLUGSTATE = "/var/run/munin";
+      environment.MUNIN_PLUGSTATE = "/run/munin";
       environment.MUNIN_LOGDIR = "/var/log/munin";
       preStart = ''
-        echo "updating munin plugins..."
+        echo "Updating munin plugins..."
 
         mkdir -p /etc/munin/plugins
         rm -rf /etc/munin/plugins/*
+
+        # Autoconfigure builtin plugins
         ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${pkgs.munin}/lib/plugins --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
 
-        # NOTE: we disable disktstats because plugin seems to fail and it hangs html generation (100% CPU + memory leak)
-        rm /etc/munin/plugins/diskstats || true
+        # Autoconfigure extra plugins
+        ${pkgs.munin}/bin/munin-node-configure --suggest --shell --families contrib,auto,manual --config ${nodeConf} --libdir=${extraAutoPluginDir} --servicedir=/etc/munin/plugins --sconfdir=${pluginConfDir} 2>/dev/null | ${pkgs.bash}/bin/bash
+
+        ${lib.optionalString (nodeCfg.extraPlugins != {}) ''
+            # Link in manually enabled plugins
+            ln -f -s -t /etc/munin/plugins ${extraPluginDir}/*
+          ''}
+
+        ${lib.optionalString (nodeCfg.disabledPlugins != []) ''
+            # Disable plugins
+            cd /etc/munin/plugins
+            rm -f ${toString nodeCfg.disabledPlugins}
+          ''}
       '';
       serviceConfig = {
         ExecStart = "${pkgs.munin}/sbin/munin-node --config ${nodeConf} --servicedir /etc/munin/plugins/ --sconfdir=${pluginConfDir}";
@@ -188,10 +369,14 @@ in
     };
 
     # munin_stats plugin breaks as of 2.0.33 when this doesn't exist
-    systemd.tmpfiles.rules = [ "d /var/run/munin 0755 munin munin -" ];
+    systemd.tmpfiles.rules = [ "d /run/munin 0755 munin munin -" ];
 
   }) (mkIf cronCfg.enable {
 
+    # Munin is hardcoded to use DejaVu Mono and the graphs come out wrong if
+    # it's not available.
+    fonts.fonts = [ pkgs.dejavu_fonts ];
+
     systemd.timers.munin-cron = {
       description = "batch Munin master programs";
       wantedBy = [ "timers.target" ];
@@ -210,7 +395,7 @@ in
     };
 
     systemd.tmpfiles.rules = [
-      "d /var/run/munin 0755 munin munin -"
+      "d /run/munin 0755 munin munin -"
       "d /var/log/munin 0755 munin munin -"
       "d /var/www/munin 0755 munin munin -"
       "d /var/lib/munin 0755 munin munin -"
diff --git a/nixos/modules/services/monitoring/netdata.nix b/nixos/modules/services/monitoring/netdata.nix
index edcaa10d969dd..1d86c5d893de3 100644
--- a/nixos/modules/services/monitoring/netdata.nix
+++ b/nixos/modules/services/monitoring/netdata.nix
@@ -10,9 +10,14 @@ let
     ln -s /run/wrappers/bin/apps.plugin $out/libexec/netdata/plugins.d/apps.plugin
   '';
 
+  plugins = [
+    "${pkgs.netdata}/libexec/netdata/plugins.d"
+    "${wrappedPlugins}/libexec/netdata/plugins.d"
+  ] ++ cfg.extraPluginPaths;
+
   localConfig = {
     global = {
-      "plugins directory" = "${wrappedPlugins}/libexec/netdata/plugins.d ${pkgs.netdata}/libexec/netdata/plugins.d";
+      "plugins directory" = concatStringsSep " " plugins;
     };
     web = {
       "web files owner" = "root";
@@ -53,6 +58,49 @@ in {
         '';
       };
 
+      python = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to enable python-based plugins
+          '';
+        };
+        extraPackages = mkOption {
+          default = ps: [];
+          defaultText = "ps: []";
+          example = literalExample ''
+            ps: [
+              ps.psycopg2
+              ps.docker
+              ps.dnspython
+            ]
+          '';
+          description = ''
+            Extra python packages available at runtime
+            to enable additional python plugins.
+          '';
+        };
+      };
+
+      extraPluginPaths = mkOption {
+        type = types.listOf types.path;
+        default = [ ];
+        example = literalExample ''
+          [ "/path/to/plugins.d" ]
+        '';
+        description = ''
+          Extra paths to add to the netdata global "plugins directory"
+          option.  Useful for when you want to include your own
+          collection scripts.
+          </para><para>
+          Details about writing a custom netdata plugin are available at:
+          <link xlink:href="https://docs.netdata.cloud/collectors/plugins.d/"/>
+          </para><para>
+          Cannot be combined with configText.
+        '';
+      };
+
       config = mkOption {
         type = types.attrsOf types.attrs;
         default = {};
@@ -74,21 +122,27 @@ in {
           message = "Cannot specify both config and configText";
         }
       ];
+
+    systemd.tmpfiles.rules = [
+      "d /var/cache/netdata 0755 ${cfg.user} ${cfg.group} -"
+      "Z /var/cache/netdata - ${cfg.user} ${cfg.group} -"
+      "d /var/log/netdata 0755 ${cfg.user} ${cfg.group} -"
+      "Z /var/log/netdata - ${cfg.user} ${cfg.group} -"
+      "d /var/lib/netdata 0755 ${cfg.user} ${cfg.group} -"
+      "Z /var/lib/netdata - ${cfg.user} ${cfg.group} -"
+      "d /etc/netdata 0755 ${cfg.user} ${cfg.group} -"
+      "Z /etc/netdata - ${cfg.user} ${cfg.group} -"
+    ];
     systemd.services.netdata = {
-      path = with pkgs; [ gawk curl ];
       description = "Real time performance monitoring";
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
-      preStart = concatStringsSep "\n" (map (dir: ''
-        mkdir -vp ${dir}
-        chmod 750 ${dir}
-        chown -R ${cfg.user}:${cfg.group} ${dir}
-        '') [ "/var/cache/netdata"
-              "/var/log/netdata"
-              "/var/lib/netdata" ]);
+      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}";
         TimeoutStopSec = 60;
@@ -96,7 +150,7 @@ in {
     };
 
     security.wrappers."apps.plugin" = {
-      source = "${pkgs.netdata}/libexec/netdata/plugins.d/apps.plugin";
+      source = "${pkgs.netdata}/libexec/netdata/plugins.d/apps.plugin.org";
       capabilities = "cap_dac_read_search,cap_sys_ptrace+ep";
       owner = cfg.user;
       group = cfg.group;
diff --git a/nixos/modules/services/monitoring/osquery.nix b/nixos/modules/services/monitoring/osquery.nix
index ba0dc4c217684..c8c625577d39c 100644
--- a/nixos/modules/services/monitoring/osquery.nix
+++ b/nixos/modules/services/monitoring/osquery.nix
@@ -78,7 +78,7 @@ in
         mkdir -p "$(dirname ${escapeShellArg cfg.databasePath})"
       '';
       serviceConfig = {
-        TimeoutStartSec = 0;
+        TimeoutStartSec = "infinity";
         ExecStart = "${pkgs.osquery}/bin/osqueryd --logger_path ${escapeShellArg cfg.loggerPath} --pidfile ${escapeShellArg cfg.pidfile} --database_path ${escapeShellArg cfg.databasePath}";
         KillMode = "process";
         KillSignal = "SIGTERM";
diff --git a/nixos/modules/services/monitoring/prometheus/alertmanager.nix b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
index 8a47c9f1e7d89..43b4a41eaf33a 100644
--- a/nixos/modules/services/monitoring/prometheus/alertmanager.nix
+++ b/nixos/modules/services/monitoring/prometheus/alertmanager.nix
@@ -5,15 +5,41 @@ with lib;
 let
   cfg = config.services.prometheus.alertmanager;
   mkConfigFile = pkgs.writeText "alertmanager.yml" (builtins.toJSON cfg.configuration);
-  alertmanagerYml =
-    if cfg.configText != null then
-      pkgs.writeText "alertmanager.yml" cfg.configText
-    else mkConfigFile;
+
+  checkedConfig = file: pkgs.runCommand "checked-config" { buildInputs = [ cfg.package ]; } ''
+    ln -s ${file} $out
+    amtool check-config $out
+  '';
+
+  alertmanagerYml = let
+    yml = if cfg.configText != null then
+        pkgs.writeText "alertmanager.yml" cfg.configText
+        else mkConfigFile;
+    in checkedConfig yml;
+
+  cmdlineArgs = cfg.extraFlags ++ [
+    "--config.file ${alertmanagerYml}"
+    "--web.listen-address ${cfg.listenAddress}:${toString cfg.port}"
+    "--log.level ${cfg.logLevel}"
+    ] ++ (optional (cfg.webExternalUrl != null)
+      "--web.external-url ${cfg.webExternalUrl}"
+    ) ++ (optional (cfg.logFormat != null)
+      "--log.format ${cfg.logFormat}"
+  );
 in {
   options = {
     services.prometheus.alertmanager = {
       enable = mkEnableOption "Prometheus Alertmanager";
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.prometheus-alertmanager;
+        defaultText = "pkgs.alertmanager";
+        description = ''
+          Package that should be used for alertmanager.
+        '';
+      };
+
       user = mkOption {
         type = types.str;
         default = "nobody";
@@ -31,8 +57,8 @@ in {
       };
 
       configuration = mkOption {
-        type = types.attrs;
-        default = {};
+        type = types.nullOr types.attrs;
+        default = null;
         description = ''
           Alertmanager configuration as nix attribute set.
         '';
@@ -99,33 +125,45 @@ in {
           Open port in firewall for incoming connections.
         '';
       };
+
+      extraFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Extra commandline options when launching the Alertmanager.
+        '';
+      };
     };
   };
 
+  config = mkMerge [
+    (mkIf cfg.enable {
+      assertions = singleton {
+        assertion = cfg.configuration != null || cfg.configText != null;
+        message = "Can not enable alertmanager without a configuration. "
+         + "Set either the `configuration` or `configText` attribute.";
+      };
+    })
+    (mkIf cfg.enable {
+      networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.port;
+
+      systemd.services.alertmanager = {
+        wantedBy = [ "multi-user.target" ];
+        after    = [ "network.target" ];
+        script = ''
+          ${cfg.package}/bin/alertmanager \
+            ${concatStringsSep " \\\n  " cmdlineArgs}
+        '';
 
-  config = mkIf cfg.enable {
-    networking.firewall.allowedTCPPorts = optional cfg.openFirewall cfg.port;
-
-    systemd.services.alertmanager = {
-      wantedBy = [ "multi-user.target" ];
-      after    = [ "network.target" ];
-      script = ''
-        ${pkgs.prometheus-alertmanager.bin}/bin/alertmanager \
-        --config.file ${alertmanagerYml} \
-        --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
-        --log.level ${cfg.logLevel} \
-        ${optionalString (cfg.webExternalUrl != null) ''--web.external-url ${cfg.webExternalUrl} \''}
-        ${optionalString (cfg.logFormat != null) "--log.format ${cfg.logFormat}"}
-      '';
-
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        Restart  = "always";
-        PrivateTmp = true;
-        WorkingDirectory = "/tmp";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        serviceConfig = {
+          User = cfg.user;
+          Group = cfg.group;
+          Restart  = "always";
+          PrivateTmp = true;
+          WorkingDirectory = "/tmp";
+          ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        };
       };
-    };
-  };
+    })
+  ];
 }
diff --git a/nixos/modules/services/monitoring/prometheus/default.nix b/nixos/modules/services/monitoring/prometheus/default.nix
index 5dda763bd56b7..1b1503ab5fc01 100644
--- a/nixos/modules/services/monitoring/prometheus/default.nix
+++ b/nixos/modules/services/monitoring/prometheus/default.nix
@@ -10,6 +10,13 @@ let
   # Get a submodule without any embedded metadata:
   _filter = x: filterAttrs (k: v: k != "_module") x;
 
+  # a wrapper that verifies that the configuration is valid
+  promtoolCheck = what: name: file: pkgs.runCommand "${name}-${what}-checked"
+    { buildInputs = [ cfg.package ]; } ''
+    ln -s ${file} $out
+    promtool ${what} $out
+  '';
+
   # Pretty-print JSON to a file
   writePrettyJSON = name: x:
     pkgs.runCommand name { } ''
@@ -19,18 +26,19 @@ let
   # This becomes the main config file
   promConfig = {
     global = cfg.globalConfig;
-    rule_files = cfg.ruleFiles ++ [
+    rule_files = map (promtoolCheck "check-rules" "rules") (cfg.ruleFiles ++ [
       (pkgs.writeText "prometheus.rules" (concatStringsSep "\n" cfg.rules))
-    ];
+    ]);
     scrape_configs = cfg.scrapeConfigs;
   };
 
   generatedPrometheusYml = writePrettyJSON "prometheus.yml" promConfig;
 
-  prometheusYml =
-    if cfg.configText != null then
+  prometheusYml = let
+    yml =  if cfg.configText != null then
       pkgs.writeText "prometheus.yml" cfg.configText
-    else generatedPrometheusYml;
+      else generatedPrometheusYml;
+    in promtoolCheck "check-config" "prometheus.yml" yml;
 
   cmdlineArgs = cfg.extraFlags ++ [
     "-storage.local.path=${cfg.dataDir}/metrics"
@@ -39,6 +47,7 @@ let
     "-alertmanager.notification-queue-capacity=${toString cfg.alertmanagerNotificationQueueCapacity}"
     "-alertmanager.timeout=${toString cfg.alertmanagerTimeout}s"
     (optionalString (cfg.alertmanagerURL != []) "-alertmanager.url=${concatStringsSep "," cfg.alertmanagerURL}")
+    (optionalString (cfg.webExternalUrl != null) "-web.external-url=${cfg.webExternalUrl}")
   ];
 
   promTypes.globalConfig = types.submodule {
@@ -316,7 +325,8 @@ let
   promTypes.relabel_config = types.submodule {
     options = {
       source_labels = mkOption {
-        type = types.listOf types.str;
+        type = with types; nullOr (listOf str);
+        default = null;
         description = ''
           The source labels select values from existing labels. Their content
           is concatenated using the configured separator and matched against
@@ -375,6 +385,15 @@ in {
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.prometheus;
+        defaultText = "pkgs.prometheus";
+        description = ''
+          The prometheus package that should be used.
+        '';
+      };
+
       listenAddress = mkOption {
         type = types.str;
         default = "0.0.0.0:9090";
@@ -467,6 +486,16 @@ in {
           Alert manager HTTP API timeout (in seconds).
         '';
       };
+
+      webExternalUrl = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "https://example.com/";
+        description = ''
+          The URL under which Prometheus is externally reachable (for example,
+          if Prometheus is served via a reverse proxy).
+        '';
+      };
     };
   };
 
@@ -484,7 +513,7 @@ in {
       after    = [ "network.target" ];
       script = ''
         #!/bin/sh
-        exec ${pkgs.prometheus}/bin/prometheus \
+        exec ${cfg.package}/bin/prometheus \
           ${concatStringsSep " \\\n  " cmdlineArgs}
       '';
       serviceConfig = {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.nix b/nixos/modules/services/monitoring/prometheus/exporters.nix
index 1d5f400250fd4..0a084561002ff 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters.nix
@@ -30,8 +30,10 @@ let
     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; };
   };
 
   mkExporterOpts = ({ name, port }: {
@@ -123,15 +125,13 @@ let
       systemd.services."prometheus-${name}-exporter" = mkMerge ([{
         wantedBy = [ "multi-user.target" ];
         after = [ "network.target" ];
-        serviceConfig = {
-          Restart = mkDefault "always";
-          PrivateTmp = mkDefault true;
-          WorkingDirectory = mkDefault /tmp;
-        } // mkIf (!(serviceOpts.serviceConfig.DynamicUser or false)) {
-          User = conf.user;
-          Group = conf.group;
-        };
-      } serviceOpts ]);
+        serviceConfig.Restart = mkDefault "always";
+        serviceConfig.PrivateTmp = mkDefault true;
+        serviceConfig.WorkingDirectory = mkDefault /tmp;
+      } serviceOpts ] ++ optional (!(serviceOpts.serviceConfig.DynamicUser or false)) {
+        serviceConfig.User = conf.user;
+        serviceConfig.Group = conf.group;
+      });
   };
 in
 {
@@ -172,5 +172,8 @@ in
     }) exporterOpts)
   );
 
-  meta.doc = ./exporters.xml;
+  meta = {
+    doc = ./exporters.xml;
+    maintainers = [ maintainers.willibutz ];
+  };
 }
diff --git a/nixos/modules/services/monitoring/prometheus/exporters.xml b/nixos/modules/services/monitoring/prometheus/exporters.xml
index be86abb74b44c..7a0a1bdf2c140 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters.xml
+++ b/nixos/modules/services/monitoring/prometheus/exporters.xml
@@ -3,13 +3,19 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-prometheus-exporters">
+ <title>Prometheus exporters</title>
+ <para>
+  Prometheus exporters provide metrics for the
+  <link xlink:href="https://prometheus.io">prometheus monitoring system</link>.
+ </para>
+ <section xml:id="module-services-prometheus-exporters-configuration">
+  <title>Configuration</title>
 
-<title>Prometheus exporters</title>
-
-<para>Prometheus exporters provide metrics for the <link xlink:href="https://prometheus.io">prometheus monitoring system</link>.</para>
-
-<section xml:id="module-services-prometheus-exporters-configuration"><title>Configuration</title>
-  <para>One of the most common exporters is the <link xlink:href="https://github.com/prometheus/node_exporter">node exporter</link>, it provides hardware and OS metrics from the host it's running on. The exporter could be configured as follows:
+  <para>
+   One of the most common exporters is the
+   <link xlink:href="https://github.com/prometheus/node_exporter">node
+   exporter</link>, it provides hardware and OS metrics from the host it's
+   running on. The exporter could be configured as follows:
 <programlisting>
   services.promtheus.exporters.node = {
     enable = true;
@@ -24,43 +30,88 @@
     firewallFilter = "-i br0 -p tcp -m tcp --dport 9100";
   };
 </programlisting>
-It should now serve all metrics from the collectors
-that are explicitly enabled and the ones that are
-<link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled by default</link>, via http under <literal>/metrics</literal>. In this example the firewall should just
-allow incoming connections to the exporter's port on the bridge interface <literal>br0</literal>
-(this would have to be configured seperately of course).
-For more information about configuration see <literal>man configuration.nix</literal> or
-search through the <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available options</link>.
-</para>
-</section>
-<section xml:id="module-services-prometheus-exporters-new-exporter"><title>Adding a new exporter</title>
-  <para>To add a new exporter, it has to be packaged first (see <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for examples), then a module can be added. The postfix exporter is used in this example:</para>
-<itemizedlist>
-  <listitem>
+   It should now serve all metrics from the collectors that are explicitly
+   enabled and the ones that are
+   <link xlink:href="https://github.com/prometheus/node_exporter#enabled-by-default">enabled
+   by default</link>, via http under <literal>/metrics</literal>. In this
+   example the firewall should just allow incoming connections to the
+   exporter's port on the bridge interface <literal>br0</literal> (this would
+   have to be configured seperately of course). For more information about
+   configuration see <literal>man configuration.nix</literal> or search through
+   the
+   <link xlink:href="https://nixos.org/nixos/options.html#prometheus.exporters">available
+   options</link>.
+  </para>
+ </section>
+ <section xml:id="module-services-prometheus-exporters-new-exporter">
+  <title>Adding a new exporter</title>
+
+  <para>
+   To add a new exporter, it has to be packaged first (see
+   <literal>nixpkgs/pkgs/servers/monitoring/prometheus/</literal> for
+   examples), then a module can be added. The postfix exporter is used in this
+   example:
+  </para>
+
+  <itemizedlist>
+   <listitem>
     <para>
-      Some default options for all exporters are provided by
-      <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>:
+     Some default options for all exporters are provided by
+     <literal>nixpkgs/nixos/modules/services/monitoring/prometheus/exporters.nix</literal>:
     </para>
-  </listitem>
-  <listitem override='none'>
+   </listitem>
+   <listitem override='none'>
     <itemizedlist>
-      <listitem><para><literal>enable</literal></para></listitem>
-      <listitem><para><literal>port</literal></para></listitem>
-      <listitem><para><literal>listenAddress</literal></para></listitem>
-      <listitem><para><literal>extraFlags</literal></para></listitem>
-      <listitem><para><literal>openFirewall</literal></para></listitem>
-      <listitem><para><literal>firewallFilter</literal></para></listitem>
-      <listitem><para><literal>user</literal></para></listitem>
-      <listitem><para><literal>group</literal></para></listitem>
+     <listitem>
+      <para>
+       <literal>enable</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>port</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>listenAddress</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>extraFlags</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>openFirewall</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>firewallFilter</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>user</literal>
+      </para>
+     </listitem>
+     <listitem>
+      <para>
+       <literal>group</literal>
+      </para>
+     </listitem>
     </itemizedlist>
-  </listitem>
-  <listitem>
-    <para>As there is already a package available, the module can now be added.
-      This is accomplished by adding a new file to the
-      <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal> directory,
-      which will be called postfix.nix and contains all exporter specific options
-      and configuration:
-      <programlisting>
+   </listitem>
+   <listitem>
+    <para>
+     As there is already a package available, the module can now be added. This
+     is accomplished by adding a new file to the
+     <literal>nixos/modules/services/monitoring/prometheus/exporters/</literal>
+     directory, which will be called postfix.nix and contains all exporter
+     specific options and configuration:
+<programlisting>
         # nixpgs/nixos/modules/services/prometheus/exporters/postfix.nix
         { config, lib, pkgs }:
 
@@ -121,15 +172,16 @@ search through the <link xlink:href="https://nixos.org/nixos/options.html#promet
         }
       </programlisting>
     </para>
-  </listitem>
-  <listitem>
+   </listitem>
+   <listitem>
     <para>
-      This should already be enough for the postfix exporter. Additionally one could
-      now add assertions and conditional default values. This can be done in the
-      'meta-module' that combines all exporter definitions and generates the submodules:
-      <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal>
+     This should already be enough for the postfix exporter. Additionally one
+     could now add assertions and conditional default values. This can be done
+     in the 'meta-module' that combines all exporter definitions and generates
+     the submodules:
+     <literal>nixpkgs/nixos/modules/services/prometheus/exporters.nix</literal>
     </para>
-  </listitem>
-</itemizedlist>
-</section>
+   </listitem>
+  </itemizedlist>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/bind.nix b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
new file mode 100644
index 0000000000000..a9746c4d65d56
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/bind.nix
@@ -0,0 +1,55 @@
+{ config, lib, pkgs }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.bind;
+in
+{
+  port = 9119;
+  extraOpts = {
+    bindURI = mkOption {
+      type = types.str;
+      default = "http://localhost:8053/";
+      description = ''
+        HTTP XML API address of an Bind server.
+      '';
+    };
+    bindTimeout = mkOption {
+      type = types.str;
+      default = "10s";
+      description = ''
+        Timeout for trying to get stats from Bind.
+      '';
+    };
+    bindVersion = mkOption {
+      type = types.enum [ "xml.v2" "xml.v3" "auto" ];
+      default = "auto";
+      description = ''
+        BIND statistics version. Can be detected automatically.
+      '';
+    };
+    bindGroups = mkOption {
+      type = types.listOf (types.enum [ "server" "view" "tasks" ]);
+      default = [ "server" "view" ];
+      description = ''
+        List of statistics to collect. Available: [server, view, tasks]
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = true;
+      ExecStart = ''
+        ${pkgs.prometheus-bind-exporter}/bin/bind_exporter \
+          -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          -bind.pid-file /var/run/named/named.pid \
+          -bind.timeout ${toString cfg.bindTimeout} \
+          -bind.stats-url ${cfg.bindURI} \
+          -bind.stats-version ${cfg.bindVersion} \
+          -bind.stats-groups ${concatStringsSep "," cfg.bindGroups} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
index 4ca6d4e5f8b6d..c47e87a3dc357 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/dovecot.nix
@@ -18,12 +18,34 @@ in
     socketPath = mkOption {
       type = types.path;
       default = "/var/run/dovecot/stats";
-      example = "/var/run/dovecot2/stats";
+      example = "/var/run/dovecot2/old-stats";
       description = ''
         Path under which the stats socket is placed.
         The user/group under which the exporter runs,
         should be able to access the socket in order
         to scrape the metrics successfully.
+
+        Please keep in mind that the stats module has changed in
+        <link xlink:href="https://wiki2.dovecot.org/Upgrading/2.3">Dovecot 2.3+</link> which
+        is not <link xlink:href="https://github.com/kumina/dovecot_exporter/issues/8">compatible with this exporter</link>.
+
+        The following extra config has to be passed to Dovecot to ensure that recent versions
+        work with this exporter:
+        <programlisting>
+        {
+          <xref linkend="opt-services.prometheus.exporters.dovecot.enable" /> = true;
+          <xref linkend="opt-services.prometheus.exporters.dovecot.socketPath" /> = "/var/run/dovecot2/old-stats";
+          <xref linkend="opt-services.dovecot2.extraConfig" /> = '''
+            mail_plugins = $mail_plugins old_stats
+            service old-stats {
+              unix_listener old-stats {
+                user = nobody
+                group = nobody
+              }
+            }
+          ''';
+        }
+        </programlisting>
       '';
     };
     scopes = mkOption {
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
index 404cd0a1896b8..0d91941243254 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/snmp.nix
@@ -60,10 +60,10 @@ in
       DynamicUser = true;
       ExecStart = ''
         ${pkgs.prometheus-snmp-exporter.bin}/bin/snmp_exporter \
-          -config.file ${configFile} \
-          -log.format ${cfg.logFormat} \
-          -log.level ${cfg.logLevel} \
-          -web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
+          --config.file=${configFile} \
+          --log.format=${cfg.logFormat} \
+          --log.level=${cfg.logLevel} \
+          --web.listen-address=${cfg.listenAddress}:${toString cfg.port} \
           ${concatStringsSep " \\\n  " cfg.extraFlags}
       '';
     };
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/tor.nix b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
new file mode 100644
index 0000000000000..e0ae838024258
--- /dev/null
+++ b/nixos/modules/services/monitoring/prometheus/exporters/tor.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs }:
+
+with lib;
+
+let
+  cfg = config.services.prometheus.exporters.tor;
+in
+{
+  port = 9130;
+  extraOpts = {
+    torControlAddress = mkOption {
+      type = types.str;
+      default = "127.0.0.1";
+      description = ''
+        Tor control IP address or hostname.
+      '';
+    };
+
+    torControlPort = mkOption {
+      type = types.int;
+      default = 9051;
+      description = ''
+        Tor control port.
+      '';
+    };
+  };
+  serviceOpts = {
+    serviceConfig = {
+      DynamicUser = true;
+      ExecStart = ''
+        ${pkgs.prometheus-tor-exporter}/bin/prometheus-tor-exporter \
+          -b ${cfg.listenAddress} \
+          -p ${toString cfg.port} \
+          -a ${cfg.torControlAddress} \
+          -c ${toString cfg.torControlPort} \
+          ${concatStringsSep " \\\n  " cfg.extraFlags}
+      '';
+    };
+
+    # CPython requires a process to either have $HOME defined or run as a UID
+    # defined in /etc/passwd. The latter is false with DynamicUser, so define a
+    # dummy $HOME. https://bugs.python.org/issue10496
+    environment = { HOME = "/var/empty"; };
+  };
+}
diff --git a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
index 8dbf2d735ab96..aaed76175b845 100644
--- a/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
+++ b/nixos/modules/services/monitoring/prometheus/exporters/varnish.nix
@@ -69,6 +69,7 @@ in
     path = [ pkgs.varnish ];
     serviceConfig = {
       DynamicUser = true;
+      RestartSec = mkDefault 1;
       ExecStart = ''
         ${pkgs.prometheus-varnish-exporter}/bin/prometheus_varnish_exporter \
           --web.listen-address ${cfg.listenAddress}:${toString cfg.port} \
diff --git a/nixos/modules/services/monitoring/systemhealth.nix b/nixos/modules/services/monitoring/systemhealth.nix
index 20d1dadd3bf2a..32d4314d5f777 100644
--- a/nixos/modules/services/monitoring/systemhealth.nix
+++ b/nixos/modules/services/monitoring/systemhealth.nix
@@ -8,7 +8,7 @@ let
   systemhealth = with pkgs; stdenv.mkDerivation {
     name = "systemhealth-1.0";
     src = fetchurl {
-      url = "http://www.brianlane.com/static/downloads/systemhealth/systemhealth-1.0.tar.bz2";
+      url = "https://www.brianlane.com/downloads/systemhealth/systemhealth-1.0.tar.bz2";
       sha256 = "1q69lz7hmpbdpbz36zb06nzfkj651413n9icx0njmyr3xzq1j9qy";
     };
     buildInputs = [ python ];
diff --git a/nixos/modules/services/network-filesystems/diod.nix b/nixos/modules/services/network-filesystems/diod.nix
index 556fad4d8ab42..063bae6ddb1d6 100644
--- a/nixos/modules/services/network-filesystems/diod.nix
+++ b/nixos/modules/services/network-filesystems/diod.nix
@@ -153,7 +153,6 @@ in
       after = [ "network.target" ];
       serviceConfig = {
         ExecStart = "${pkgs.diod}/sbin/diod -f -c ${diodConfig}";
-        CapabilityBoundingSet = "cap_net_bind_service+=ep";
       };
     };
   };
diff --git a/nixos/modules/services/network-filesystems/glusterfs.nix b/nixos/modules/services/network-filesystems/glusterfs.nix
index 8ac9f801dcb8b..00875c6c4a183 100644
--- a/nixos/modules/services/network-filesystems/glusterfs.nix
+++ b/nixos/modules/services/network-filesystems/glusterfs.nix
@@ -176,10 +176,8 @@ in
       '';
 
       serviceConfig = {
-        Type="forking";
-        PIDFile="/run/glusterd.pid";
         LimitNOFILE=65536;
-        ExecStart="${glusterfs}/sbin/glusterd -p /run/glusterd.pid --log-level=${cfg.logLevel} ${toString cfg.extraFlags}";
+        ExecStart="${glusterfs}/sbin/glusterd --no-daemon --log-level=${cfg.logLevel} ${toString cfg.extraFlags}";
         KillMode=cfg.killMode;
         TimeoutStopSec=cfg.stopKillTimeout;
       };
@@ -198,9 +196,11 @@ in
         install -m 0755 -d /var/log/glusterfs
       '';
 
+      # glustereventsd uses the `gluster` executable
+      path = [ glusterfs ];
+
       serviceConfig = {
         Type="simple";
-        Environment="PYTHONPATH=${glusterfs}/usr/lib/python2.7/site-packages";
         PIDFile="/run/glustereventsd.pid";
         ExecStart="${glusterfs}/sbin/glustereventsd --pid-file /run/glustereventsd.pid";
         ExecReload="/bin/kill -SIGUSR2 $MAINPID";
diff --git a/nixos/modules/services/network-filesystems/ipfs.nix b/nixos/modules/services/network-filesystems/ipfs.nix
index 412d57b27b82c..602cd50d8f553 100644
--- a/nixos/modules/services/network-filesystems/ipfs.nix
+++ b/nixos/modules/services/network-filesystems/ipfs.nix
@@ -74,7 +74,7 @@ in {
 
     services.ipfs = {
 
-      enable = mkEnableOption "Interplanetary File System";
+      enable = mkEnableOption "Interplanetary File System (WARNING: may cause severe network degredation)";
 
       user = mkOption {
         type = types.str;
diff --git a/nixos/modules/services/network-filesystems/openafs/client.nix b/nixos/modules/services/network-filesystems/openafs/client.nix
index 52c0966e05bc5..240c1392088f6 100644
--- a/nixos/modules/services/network-filesystems/openafs/client.nix
+++ b/nixos/modules/services/network-filesystems/openafs/client.nix
@@ -149,11 +149,13 @@ in
       packages = {
         module = mkOption {
           default = config.boot.kernelPackages.openafs;
+          defaultText = "config.boot.kernelPackages.openafs";
           type = types.package;
           description = "OpenAFS kernel module package. MUST match the userland package!";
         };
         programs = mkOption {
           default = getBin pkgs.openafs;
+          defaultText = "config.boot.kernelPackages.openafs";
           type = types.package;
           description = "OpenAFS programs package. MUST match the kernel module package!";
         };
diff --git a/nixos/modules/services/network-filesystems/openafs/server.nix b/nixos/modules/services/network-filesystems/openafs/server.nix
index 4c80ed0839f7c..095024d2c8af0 100644
--- a/nixos/modules/services/network-filesystems/openafs/server.nix
+++ b/nixos/modules/services/network-filesystems/openafs/server.nix
@@ -80,6 +80,7 @@ in {
 
       package = mkOption {
         default = pkgs.openafs.server or pkgs.openafs;
+        defaultText = "pkgs.openafs.server or pkgs.openafs";
         type = types.package;
         description = "OpenAFS package for the server binaries";
       };
diff --git a/nixos/modules/services/networking/bitlbee.nix b/nixos/modules/services/networking/bitlbee.nix
index 392a8d5c2e7ce..274b36171608e 100644
--- a/nixos/modules/services/networking/bitlbee.nix
+++ b/nixos/modules/services/networking/bitlbee.nix
@@ -7,9 +7,10 @@ let
   cfg = config.services.bitlbee;
   bitlbeeUid = config.ids.uids.bitlbee;
 
-  bitlbeePkg = if cfg.libpurple_plugins == []
-  then pkgs.bitlbee
-  else pkgs.bitlbee.override { enableLibPurple = true; };
+  bitlbeePkg = pkgs.bitlbee.override {
+    enableLibPurple = cfg.libpurple_plugins != [];
+    enablePam = cfg.authBackend == "pam";
+  };
 
   bitlbeeConfig = pkgs.writeText "bitlbee.conf"
     ''
@@ -20,6 +21,7 @@ let
     DaemonInterface = ${cfg.interface}
     DaemonPort = ${toString cfg.portNumber}
     AuthMode = ${cfg.authMode}
+    AuthBackend = ${cfg.authBackend}
     Plugindir = ${pkgs.bitlbee-plugins cfg.plugins}/lib/bitlbee
     ${lib.optionalString (cfg.hostName != "") "HostName = ${cfg.hostName}"}
     ${lib.optionalString (cfg.protocols != "") "Protocols = ${cfg.protocols}"}
@@ -31,7 +33,7 @@ let
 
   purple_plugin_path =
     lib.concatMapStringsSep ":"
-      (plugin: "${plugin}/lib/pidgin/")
+      (plugin: "${plugin}/lib/pidgin/:${plugin}/lib/purple-2/")
       cfg.libpurple_plugins
     ;
 
@@ -70,6 +72,16 @@ in
         '';
       };
 
+      authBackend = mkOption {
+        default = "storage";
+        type = types.enum [ "storage" "pam" ];
+        description = ''
+          How users are authenticated
+            storage -- save passwords internally
+            pam -- Linux PAM authentication
+        '';
+      };
+
       authMode = mkOption {
         default = "Open";
         type = types.enum [ "Open" "Closed" "Registered" ];
@@ -147,23 +159,22 @@ in
 
   ###### implementation
 
-  config = mkIf config.services.bitlbee.enable {
-
-    users.users = singleton
-      { name = "bitlbee";
+  config =  mkMerge [
+    (mkIf config.services.bitlbee.enable {
+      users.users = singleton {
+        name = "bitlbee";
         uid = bitlbeeUid;
         description = "BitlBee user";
         home = "/var/lib/bitlbee";
         createHome = true;
       };
 
-    users.groups = singleton
-      { name = "bitlbee";
+      users.groups = singleton {
+        name = "bitlbee";
         gid = config.ids.gids.bitlbee;
       };
 
-    systemd.services.bitlbee =
-      {
+      systemd.services.bitlbee = {
         environment.PURPLE_PLUGIN_PATH = purple_plugin_path;
         description = "BitlBee IRC to other chat networks gateway";
         after = [ "network.target" ];
@@ -172,8 +183,12 @@ in
         serviceConfig.ExecStart = "${bitlbeePkg}/sbin/bitlbee -F -n -c ${bitlbeeConfig}";
       };
 
-    environment.systemPackages = [ bitlbeePkg ];
+      environment.systemPackages = [ bitlbeePkg ];
 
-  };
+    })
+    (mkIf (config.services.bitlbee.authBackend == "pam") {
+      security.pam.services.bitlbee = {};
+    })
+  ];
 
 }
diff --git a/nixos/modules/services/networking/charybdis.nix b/nixos/modules/services/networking/charybdis.nix
index 6d57faa9ac2b7..3d02dc8d13753 100644
--- a/nixos/modules/services/networking/charybdis.nix
+++ b/nixos/modules/services/networking/charybdis.nix
@@ -90,7 +90,7 @@ in
           BANDB_DBPATH = "${cfg.statedir}/ban.db";
         };
         serviceConfig = {
-          ExecStart   = "${charybdis}/bin/charybdis-ircd -foreground -logfile /dev/stdout -configfile ${configFile}";
+          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
diff --git a/nixos/modules/services/networking/chrony.nix b/nixos/modules/services/networking/chrony.nix
index cef30661cc337..77f7025770005 100644
--- a/nixos/modules/services/networking/chrony.nix
+++ b/nixos/modules/services/networking/chrony.nix
@@ -3,23 +3,20 @@
 with lib;
 
 let
+  cfg = config.services.chrony;
 
   stateDir = "/var/lib/chrony";
-
-  keyFile = "/etc/chrony.keys";
-
-  cfg = config.services.chrony;
+  keyFile = "${stateDir}/chrony.keys";
 
   configFile = pkgs.writeText "chrony.conf" ''
     ${concatMapStringsSep "\n" (server: "server " + server) cfg.servers}
 
     ${optionalString
-      cfg.initstepslew.enabled
+      (cfg.initstepslew.enabled && (cfg.servers != []))
       "initstepslew ${toString cfg.initstepslew.threshold} ${concatStringsSep " " cfg.initstepslew.servers}"
     }
 
     driftfile ${stateDir}/chrony.drift
-
     keyfile ${keyFile}
 
     ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
@@ -27,18 +24,11 @@ let
     ${cfg.extraConfig}
   '';
 
-  chronyFlags = "-n -m -u chrony -f ${configFile} ${toString cfg.extraFlags}";
-
+  chronyFlags = "-m -u chrony -f ${configFile} ${toString cfg.extraFlags}";
 in
-
 {
-
-  ###### interface
-
   options = {
-
     services.chrony = {
-
       enable = mkOption {
         default = false;
         description = ''
@@ -83,15 +73,9 @@ in
         description = "Extra flags passed to the chronyd command.";
       };
     };
-
   };
 
-
-  ###### implementation
-
   config = mkIf cfg.enable {
-
-    # Make chronyc available in the system path
     environment.systemPackages = [ pkgs.chrony ];
 
     users.groups = singleton
@@ -109,30 +93,37 @@ in
 
     services.timesyncd.enable = mkForce false;
 
+    systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "chronyd.service"; };
+
     systemd.services.chronyd =
       { description = "chrony NTP daemon";
 
         wantedBy = [ "multi-user.target" ];
-        wants = [ "time-sync.target" ];
-        before = [ "time-sync.target" ];
-        after = [ "network.target" ];
+        wants    = [ "time-sync.target" ];
+        before   = [ "time-sync.target" ];
+        after    = [ "network.target" ];
         conflicts = [ "ntpd.service" "systemd-timesyncd.service" ];
 
         path = [ pkgs.chrony ];
 
-        preStart =
-          ''
-            mkdir -m 0755 -p ${stateDir}
-            touch ${keyFile}
-            chmod 0640 ${keyFile}
-            chown chrony:chrony ${stateDir} ${keyFile}
-          '';
+        preStart = ''
+          mkdir -m 0755 -p ${stateDir}
+          touch ${keyFile}
+          chmod 0640 ${keyFile}
+          chown chrony:chrony ${stateDir} ${keyFile}
+        '';
 
+        unitConfig.ConditionCapability = "CAP_SYS_TIME";
         serviceConfig =
-          { ExecStart = "${pkgs.chrony}/bin/chronyd ${chronyFlags}";
+          { Type = "forking";
+            ExecStart = "${pkgs.chrony}/bin/chronyd ${chronyFlags}";
+
+            ProtectHome = "yes";
+            ProtectSystem = "full";
+            PrivateTmp = "yes";
+
           };
-      };
 
+      };
   };
-
 }
diff --git a/nixos/modules/services/networking/consul.nix b/nixos/modules/services/networking/consul.nix
index ab3f810376811..3a92a883fbf89 100644
--- a/nixos/modules/services/networking/consul.nix
+++ b/nixos/modules/services/networking/consul.nix
@@ -6,9 +6,10 @@ let
   dataDir = "/var/lib/consul";
   cfg = config.services.consul;
 
-  configOptions = { data_dir = dataDir; } //
-    (if cfg.webUi then { ui_dir = "${cfg.package.ui}"; } else { }) //
-    cfg.extraConfig;
+  configOptions = {
+    data_dir = dataDir;
+    ui = cfg.webUi;
+  } // cfg.extraConfig;
 
   configFiles = [ "/etc/consul.json" "/etc/consul-addrs.json" ]
     ++ cfg.extraConfigFiles;
@@ -184,7 +185,7 @@ in
           PermissionsStartOnly = true;
           User = if cfg.dropPrivileges then "consul" else null;
           Restart = "on-failure";
-          TimeoutStartSec = "0";
+          TimeoutStartSec = "infinity";
         } // (optionalAttrs (cfg.leaveOnStop) {
           ExecStop = "${cfg.package.bin}/bin/consul leave";
         });
diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix
index efdbca5d52e84..c217ccaa405aa 100644
--- a/nixos/modules/services/networking/dhcpcd.nix
+++ b/nixos/modules/services/networking/dhcpcd.nix
@@ -71,7 +71,7 @@ let
           # anything ever again ("couldn't resolve ..., giving up on
           # it"), so we silently lose time synchronisation. This also
           # applies to openntpd.
-          ${config.systemd.package}/bin/systemctl try-reload-or-restart ntpd.service openntpd.service || true
+          ${config.systemd.package}/bin/systemctl try-reload-or-restart ntpd.service openntpd.service chronyd.service || true
       fi
 
       ${cfg.runHook}
diff --git a/nixos/modules/services/networking/dnscrypt-proxy.xml b/nixos/modules/services/networking/dnscrypt-proxy.xml
index a97579202523e..f90eef69848c7 100644
--- a/nixos/modules/services/networking/dnscrypt-proxy.xml
+++ b/nixos/modules/services/networking/dnscrypt-proxy.xml
@@ -3,67 +3,64 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="sec-dnscrypt-proxy">
-
-  <title>DNSCrypt client proxy</title>
+ <title>DNSCrypt client proxy</title>
+ <para>
+  The DNSCrypt client proxy relays DNS queries to a DNSCrypt enabled upstream
+  resolver. The traffic between the client and the upstream resolver is
+  encrypted and authenticated, mitigating the risk of MITM attacks, DNS
+  poisoning attacks, and third-party snooping (assuming the upstream is
+  trustworthy).
+ </para>
+ <sect1 xml:id="sec-dnscrypt-proxy-configuration">
+  <title>Basic configuration</title>
 
   <para>
-    The DNSCrypt client proxy relays DNS queries to a DNSCrypt enabled
-    upstream resolver. The traffic between the client and the upstream
-    resolver is encrypted and authenticated, mitigating the risk of MITM
-    attacks, DNS poisoning attacks, and third-party snooping (assuming the
-    upstream is trustworthy).
-  </para>
-
-  <sect1 xml:id="sec-dnscrypt-proxy-configuration"><title>Basic configuration</title>
-
-  <para>
-    To enable the client proxy, set
-    <programlisting>
+   To enable the client proxy, set
+<programlisting>
 <xref linkend="opt-services.dnscrypt-proxy.enable"/> = true;
     </programlisting>
   </para>
 
   <para>
-    Enabling the client proxy does not alter the system nameserver; to
-    relay local queries, prepend <literal>127.0.0.1</literal> to
-    <option>networking.nameservers</option>.
+   Enabling the client proxy does not alter the system nameserver; to relay
+   local queries, prepend <literal>127.0.0.1</literal> to
+   <option>networking.nameservers</option>.
   </para>
-
-  </sect1>
-
-  <sect1 xml:id="sec-dnscrypt-proxy-forwarder"><title>As a forwarder for another DNS client</title>
+ </sect1>
+ <sect1 xml:id="sec-dnscrypt-proxy-forwarder">
+  <title>As a forwarder for another DNS client</title>
 
   <para>
-    To run the DNSCrypt proxy client as a forwarder for another
-    DNS client, change the default proxy listening port to a
-    non-standard value and point the other client to it:
-    <programlisting>
+   To run the DNSCrypt proxy client as a forwarder for another DNS client,
+   change the default proxy listening port to a non-standard value and point
+   the other client to it:
+<programlisting>
 <xref linkend="opt-services.dnscrypt-proxy.localPort"/> = 43;
     </programlisting>
   </para>
 
-  <sect2 xml:id="sec-dnscrypt-proxy-forwarder-dsnmasq"><title>dnsmasq</title>
-  <para>
-    <programlisting>
+  <sect2 xml:id="sec-dnscrypt-proxy-forwarder-dsnmasq">
+   <title>dnsmasq</title>
+   <para>
+<programlisting>
 {
   <xref linkend="opt-services.dnsmasq.enable"/> = true;
   <xref linkend="opt-services.dnsmasq.servers"/> = [ "127.0.0.1#43" ];
 }
     </programlisting>
-  </para>
+   </para>
   </sect2>
 
-  <sect2 xml:id="sec-dnscrypt-proxy-forwarder-unbound"><title>unbound</title>
-  <para>
-    <programlisting>
+  <sect2 xml:id="sec-dnscrypt-proxy-forwarder-unbound">
+   <title>unbound</title>
+   <para>
+<programlisting>
 {
   <xref linkend="opt-services.unbound.enable"/> = true;
   <xref linkend="opt-services.unbound.forwardAddresses"/> = [ "127.0.0.1@43" ];
 }
     </programlisting>
-  </para>
+   </para>
   </sect2>
-
-  </sect1>
-
+ </sect1>
 </chapter>
diff --git a/nixos/modules/services/networking/epmd.nix b/nixos/modules/services/networking/epmd.nix
new file mode 100644
index 0000000000000..692b75e4f0865
--- /dev/null
+++ b/nixos/modules/services/networking/epmd.nix
@@ -0,0 +1,56 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.epmd;
+
+in
+
+{
+  ###### interface
+  options.services.epmd = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable socket activation for Erlang Port Mapper Daemon (epmd),
+        which acts as a name server on all hosts involved in distributed
+        Erlang computations.
+      '';
+    };
+    package = mkOption {
+      type = types.package;
+      default = pkgs.erlang;
+      description = ''
+        The Erlang package to use to get epmd binary. That way you can re-use
+        an Erlang runtime that is already installed for other purposes.
+      '';
+    };
+  };
+
+  ###### implementation
+  config = mkIf cfg.enable {
+    systemd.sockets.epmd = rec {
+      description = "Erlang Port Mapper Daemon Activation Socket";
+      wantedBy = [ "sockets.target" ];
+      before = wantedBy;
+      socketConfig = {
+        ListenStream = "4369";
+        Accept = "false";
+      };
+    };
+
+    systemd.services.epmd = {
+      description = "Erlang Port Mapper Daemon";
+      after = [ "network.target" ];
+      requires = [ "epmd.socket" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${cfg.package}/bin/epmd -systemd";
+        Type = "notify";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/eternal-terminal.nix b/nixos/modules/services/networking/eternal-terminal.nix
new file mode 100644
index 0000000000000..be7337ece7e4f
--- /dev/null
+++ b/nixos/modules/services/networking/eternal-terminal.nix
@@ -0,0 +1,89 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.eternal-terminal;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+
+    services.eternal-terminal = {
+
+      enable = mkEnableOption "Eternal Terminal server";
+
+      port = mkOption {
+        default = 2022;
+        type = types.int;
+        description = ''
+          The port the server should listen on. Will use the server's default (2022) if not specified.
+        '';
+      };
+
+      verbosity = mkOption {
+        default = 0;
+        type = types.enum (lib.range 0 9);
+        description = ''
+          The verbosity level (0-9).
+        '';
+      };
+
+      silent = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If enabled, disables all logging.
+        '';
+      };
+
+      logSize = mkOption {
+        default = 20971520;
+        type = types.int;
+        description = ''
+          The maximum log size.
+        '';
+      };
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+
+    # We need to ensure the et package is fully installed because
+    # the (remote) et client runs the `etterminal` binary when it
+    # connects.
+    environment.systemPackages = [ pkgs.eternal-terminal ];
+
+    systemd.services = {
+      eternal-terminal = {
+        description = "Eternal Terminal server.";
+        wantedBy = [ "multi-user.target" ];
+        after = [ "syslog.target" "network.target" ];
+        serviceConfig = {
+          Type = "forking";
+          ExecStart = "${pkgs.eternal-terminal}/bin/etserver --daemon --cfgfile=${pkgs.writeText "et.cfg" ''
+            ; et.cfg : Config file for Eternal Terminal
+            ;
+
+            [Networking]
+            port = ${toString cfg.port}
+
+            [Debug]
+            verbose = ${toString cfg.verbosity}
+            silent = ${if cfg.silent then "1" else "0"}
+            logsize = ${toString cfg.logSize}
+          ''}";
+          Restart = "on-failure";
+          KillMode = "process";
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/firefox/sync-server.nix b/nixos/modules/services/networking/firefox/sync-server.nix
index 97d223a56cab9..6842aa7356171 100644
--- a/nixos/modules/services/networking/firefox/sync-server.nix
+++ b/nixos/modules/services/networking/firefox/sync-server.nix
@@ -13,7 +13,7 @@ let
     overrides = ${cfg.privateConfig}
 
     [server:main]
-    use = egg:Paste#http
+    use = egg:gunicorn
     host = ${cfg.listen.address}
     port = ${toString cfg.listen.port}
 
@@ -30,6 +30,8 @@ let
     audiences = ${removeSuffix "/" cfg.publicUrl}
   '';
 
+  user = "syncserver";
+  group = "syncserver";
 in
 
 {
@@ -126,15 +128,14 @@ in
 
   config = mkIf cfg.enable {
 
-    systemd.services.syncserver = let
-      syncServerEnv = pkgs.python.withPackages(ps: with ps; [ syncserver pasteScript requests ]);
-      user = "syncserver";
-      group = "syncserver";
-    in {
+    systemd.services.syncserver = {
       after = [ "network.target" ];
       description = "Firefox Sync Server";
       wantedBy = [ "multi-user.target" ];
-      path = [ pkgs.coreutils syncServerEnv ];
+      path = [
+        pkgs.coreutils
+        (pkgs.python.withPackages (ps: [ pkgs.syncserver ps.gunicorn ]))
+      ];
 
       serviceConfig = {
         User = user;
@@ -166,14 +167,17 @@ in
           chown ${user}:${group} ${defaultDbLocation}
         fi
       '';
-      serviceConfig.ExecStart = "${syncServerEnv}/bin/paster serve ${syncServerIni}";
+
+      script = ''
+        gunicorn --paste ${syncServerIni}
+      '';
     };
 
-    users.users.syncserver = {
-      group = "syncserver";
+    users.users.${user} = {
+      inherit group;
       isSystemUser = true;
     };
 
-    users.groups.syncserver = {};
+    users.groups.${group} = {};
   };
 }
diff --git a/nixos/modules/services/networking/firewall.nix b/nixos/modules/services/networking/firewall.nix
index 86463f276c65d..aba64e4f60ff0 100644
--- a/nixos/modules/services/networking/firewall.nix
+++ b/nixos/modules/services/networking/firewall.nix
@@ -58,6 +58,9 @@ let
     ${text}
   ''; in "${dir}/bin/${name}";
 
+  defaultInterface = { default = mapAttrs (name: value: cfg."${name}") commonOptions; };
+  allInterfaces = defaultInterface // cfg.interfaces;
+
   startScript = writeShScript "firewall-start" ''
     ${helpers}
 
@@ -154,7 +157,7 @@ let
           ip46tables -A nixos-fw -p tcp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
         ''
       ) cfg.allowedTCPPorts
-    ) cfg.interfaces)}
+    ) allInterfaces)}
 
     # Accept connections to the allowed TCP port ranges.
     ${concatStrings (mapAttrsToList (iface: cfg:
@@ -164,7 +167,7 @@ let
           ip46tables -A nixos-fw -p tcp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
         ''
       ) cfg.allowedTCPPortRanges
-    ) cfg.interfaces)}
+    ) allInterfaces)}
 
     # Accept packets on the allowed UDP ports.
     ${concatStrings (mapAttrsToList (iface: cfg:
@@ -173,7 +176,7 @@ let
           ip46tables -A nixos-fw -p udp --dport ${toString port} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
         ''
       ) cfg.allowedUDPPorts
-    ) cfg.interfaces)}
+    ) allInterfaces)}
 
     # Accept packets on the allowed UDP port ranges.
     ${concatStrings (mapAttrsToList (iface: cfg:
@@ -183,7 +186,7 @@ let
           ip46tables -A nixos-fw -p udp --dport ${range} -j nixos-fw-accept ${optionalString (iface != "default") "-i ${iface}"}
         ''
       ) cfg.allowedUDPPortRanges
-    ) cfg.interfaces)}
+    ) allInterfaces)}
 
     # Accept IPv4 multicast.  Not a big security risk since
     # probably nobody is listening anyway.
@@ -508,15 +511,11 @@ in
       };
 
       interfaces = mkOption {
-        default = {
-          default = mapAttrs (name: value: cfg."${name}") commonOptions;
-        };
+        default = { };
         type = with types; attrsOf (submodule [ { options = commonOptions; } ]);
         description =
           ''
-            Interface-specific open ports. Setting this value will override
-            all values of the <literal>networking.firewall.allowed*</literal>
-            options.
+            Interface-specific open ports.
           '';
       };
     } // commonOptions;
diff --git a/nixos/modules/services/networking/flannel.nix b/nixos/modules/services/networking/flannel.nix
index b93e28e34efd8..6c43573851b25 100644
--- a/nixos/modules/services/networking/flannel.nix
+++ b/nixos/modules/services/networking/flannel.nix
@@ -73,11 +73,35 @@ in {
       };
     };
 
+    kubeconfig = mkOption {
+      description = ''
+        Path to kubeconfig to use for storing flannel config using the
+        Kubernetes API
+      '';
+      type = types.nullOr types.path;
+      default = null;
+    };
+
     network = mkOption {
       description = " IPv4 network in CIDR format to use for the entire flannel network.";
       type = types.str;
     };
 
+    nodeName = mkOption {
+      description = ''
+        Needed when running with Kubernetes as backend as this cannot be auto-detected";
+      '';
+      type = types.nullOr types.str;
+      default = with config.networking; (hostName + optionalString (!isNull domain) ".${domain}");
+      example = "node1.example.com";
+    };
+
+    storageBackend = mkOption {
+      description = "Determines where flannel stores its configuration at runtime";
+      type = types.enum ["etcd" "kubernetes"];
+      default = "etcd";
+    };
+
     subnetLen = mkOption {
       description = ''
         The size of the subnet allocated to each host. Defaults to 24 (i.e. /24)
@@ -122,17 +146,22 @@ in {
       after = [ "network.target" ];
       environment = {
         FLANNELD_PUBLIC_IP = cfg.publicIp;
+        FLANNELD_IFACE = cfg.iface;
+      } // optionalAttrs (cfg.storageBackend == "etcd") {
         FLANNELD_ETCD_ENDPOINTS = concatStringsSep "," cfg.etcd.endpoints;
         FLANNELD_ETCD_KEYFILE = cfg.etcd.keyFile;
         FLANNELD_ETCD_CERTFILE = cfg.etcd.certFile;
         FLANNELD_ETCD_CAFILE = cfg.etcd.caFile;
-        FLANNELD_IFACE = cfg.iface;
         ETCDCTL_CERT_FILE = cfg.etcd.certFile;
         ETCDCTL_KEY_FILE = cfg.etcd.keyFile;
         ETCDCTL_CA_FILE = cfg.etcd.caFile;
         ETCDCTL_PEERS = concatStringsSep "," cfg.etcd.endpoints;
+      } // optionalAttrs (cfg.storageBackend == "kubernetes") {
+        FLANNELD_KUBE_SUBNET_MGR = "true";
+        FLANNELD_KUBECONFIG_FILE = cfg.kubeconfig;
+        NODE_NAME = cfg.nodeName;
       };
-      preStart = ''
+      preStart = mkIf (cfg.storageBackend == "etcd") ''
         echo "setting network configuration"
         until ${pkgs.etcdctl.bin}/bin/etcdctl set /coreos.com/network/config '${builtins.toJSON networkConfig}'
         do
@@ -149,6 +178,12 @@ in {
       serviceConfig.ExecStart = "${cfg.package}/bin/flannel";
     };
 
-    services.etcd.enable = mkDefault (cfg.etcd.endpoints == ["http://127.0.0.1:2379"]);
+    services.etcd.enable = mkDefault (cfg.storageBackend == "etcd" && cfg.etcd.endpoints == ["http://127.0.0.1:2379"]);
+
+    # for some reason, flannel doesn't let you configure this path
+    # see: https://github.com/coreos/flannel/blob/master/Documentation/configuration.md#configuration
+    environment.etc."kube-flannel/net-conf.json" = mkIf (cfg.storageBackend == "kubernetes") {
+      source = pkgs.writeText "net-conf.json" (builtins.toJSON networkConfig);
+    };
   };
 }
diff --git a/nixos/modules/services/networking/flashpolicyd.nix b/nixos/modules/services/networking/flashpolicyd.nix
index 5b83ce131389b..9c51b88ef6776 100644
--- a/nixos/modules/services/networking/flashpolicyd.nix
+++ b/nixos/modules/services/networking/flashpolicyd.nix
@@ -11,7 +11,7 @@ let
 
     src = pkgs.fetchurl {
       name = "flashpolicyd_v0.6.zip";
-      url = "http://www.adobe.com/content/dotcom/en/devnet/flashplayer/articles/socket_policy_files/_jcr_content/articlePrerequistes/multiplefiles/node_1277808777771/file.res/flashpolicyd_v0.6%5B1%5D.zip";
+      url = "https://download.adobe.com/pub/adobe/devnet/flashplayer/articles/socket_policy_files/flashpolicyd_v0.6.zip";
       sha256 = "16zk237233npwfq1m4ksy4g5lzy1z9fp95w7pz0cdlpmv0fv9sm3";
     };
 
@@ -35,9 +35,9 @@ in
   ###### interface
 
   options = {
-  
+
     services.flashpolicyd = {
-    
+
       enable = mkOption {
         default = false;
         description =
@@ -47,13 +47,13 @@ in
             connections to your server.
           '';
       };
-      
+
       policy = mkOption {
         default =
           ''
             <?xml version="1.0"?>
             <!DOCTYPE cross-domain-policy SYSTEM "/xml/dtds/cross-domain-policy.dtd">
-            <cross-domain-policy> 
+            <cross-domain-policy>
               <site-control permitted-cross-domain-policies="master-only"/>
               <allow-access-from domain="*" to-ports="*" />
             </cross-domain-policy>
diff --git a/nixos/modules/services/networking/hostapd.nix b/nixos/modules/services/networking/hostapd.nix
index 3af0441a89d8e..9f74e4963296e 100644
--- a/nixos/modules/services/networking/hostapd.nix
+++ b/nixos/modules/services/networking/hostapd.nix
@@ -157,9 +157,9 @@ in
       { description = "hostapd wireless AP";
 
         path = [ pkgs.hostapd ];
-        wantedBy = [ "network.target" ];
-
-        after = [ "${cfg.interface}-cfg.service" "nat.service" "bind.service" "dhcpd.service" "sys-subsystem-net-devices-${cfg.interface}.device" ];
+        after = [ "sys-subsystem-net-devices-${cfg.interface}.device" ];
+        bindsTo = [ "sys-subsystem-net-devices-${cfg.interface}.device" ];
+        requiredBy = [ "network-link-${cfg.interface}.service" ];
 
         serviceConfig =
           { ExecStart = "${pkgs.hostapd}/bin/hostapd ${configFile}";
diff --git a/nixos/modules/services/networking/iwd.nix b/nixos/modules/services/networking/iwd.nix
index eb03d2e1d6321..18ed20e288863 100644
--- a/nixos/modules/services/networking/iwd.nix
+++ b/nixos/modules/services/networking/iwd.nix
@@ -22,6 +22,8 @@ in {
 
     systemd.packages = [ pkgs.iwd ];
 
+    systemd.services.iwd.wantedBy = [ "multi-user.target" ];
+
     systemd.tmpfiles.rules = [
       "d /var/lib/iwd 0700 root root -"
     ];
diff --git a/nixos/modules/services/networking/miniupnpd.nix b/nixos/modules/services/networking/miniupnpd.nix
index 19400edb68f96..ab714a6ac75e2 100644
--- a/nixos/modules/services/networking/miniupnpd.nix
+++ b/nixos/modules/services/networking/miniupnpd.nix
@@ -57,32 +57,12 @@ in
   };
 
   config = mkIf cfg.enable {
-    # from miniupnpd/netfilter/iptables_init.sh
     networking.firewall.extraCommands = ''
-      iptables -t nat -N MINIUPNPD
-      iptables -t nat -A PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t mangle -N MINIUPNPD
-      iptables -t mangle -A PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t filter -N MINIUPNPD
-      iptables -t filter -A FORWARD -i ${cfg.externalInterface} ! -o ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t nat -N MINIUPNPD-PCP-PEER
-      iptables -t nat -A POSTROUTING -o ${cfg.externalInterface} -j MINIUPNPD-PCP-PEER
+      ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_init.sh -i ${cfg.externalInterface}
     '';
 
-    # from miniupnpd/netfilter/iptables_removeall.sh
     networking.firewall.extraStopCommands = ''
-      iptables -t nat -F MINIUPNPD
-      iptables -t nat -D PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t nat -X MINIUPNPD
-      iptables -t mangle -F MINIUPNPD
-      iptables -t mangle -D PREROUTING -i ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t mangle -X MINIUPNPD
-      iptables -t filter -F MINIUPNPD
-      iptables -t filter -D FORWARD -i ${cfg.externalInterface} ! -o ${cfg.externalInterface} -j MINIUPNPD
-      iptables -t filter -X MINIUPNPD
-      iptables -t nat -F MINIUPNPD-PCP-PEER
-      iptables -t nat -D POSTROUTING -o ${cfg.externalInterface} -j MINIUPNPD-PCP-PEER
-      iptables -t nat -X MINIUPNPD-PCP-PEER
+      ${pkgs.bash}/bin/bash -x ${pkgs.miniupnpd}/etc/miniupnpd/iptables_removeall.sh -i ${cfg.externalInterface}
     '';
 
     systemd.services.miniupnpd = {
diff --git a/nixos/modules/services/networking/murmur.nix b/nixos/modules/services/networking/murmur.nix
index fcc813e6898f9..a6e90feff7ead 100644
--- a/nixos/modules/services/networking/murmur.nix
+++ b/nixos/modules/services/networking/murmur.nix
@@ -50,7 +50,7 @@ in
       enable = mkOption {
         type = types.bool;
         default = false;
-        description = "If enabled, start the Murmur Service.";
+        description = "If enabled, start the Murmur Mumble server.";
       };
 
       autobanAttempts = mkOption {
diff --git a/nixos/modules/services/networking/mxisd.nix b/nixos/modules/services/networking/mxisd.nix
new file mode 100644
index 0000000000000..0aa6d0d9ecd35
--- /dev/null
+++ b/nixos/modules/services/networking/mxisd.nix
@@ -0,0 +1,125 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.mxisd;
+
+  server = optionalAttrs (cfg.server.name != null) { inherit (cfg.server) name; }
+        // optionalAttrs (cfg.server.port != null) { inherit (cfg.server) port; };
+
+  baseConfig = {
+    matrix.domain = cfg.matrix.domain;
+    key.path = "${cfg.dataDir}/signing.key";
+    storage = {
+      provider.sqlite.database = "${cfg.dataDir}/mxisd.db";
+    };
+  } // optionalAttrs (server != {}) { inherit server; };
+
+  # merges baseConfig and extraConfig into a single file
+  fullConfig = recursiveUpdate baseConfig cfg.extraConfig;
+
+  configFile = pkgs.writeText "mxisd-config.yaml" (builtins.toJSON fullConfig);
+
+in {
+  options = {
+    services.mxisd = {
+      enable = mkEnableOption "mxisd matrix federated identity server";
+
+      package = mkOption {
+        type = types.package;
+        default = pkgs.mxisd;
+        defaultText = "pkgs.mxisd";
+        description = "The mxisd package to use";
+      };
+
+      dataDir = mkOption {
+        type = types.str;
+        default = "/var/lib/mxisd";
+        description = "Where data mxisd uses resides";
+      };
+
+      extraConfig = mkOption {
+        type = types.attrs;
+        default = {};
+        description = "Extra options merged into the mxisd configuration";
+      };
+
+      matrix = {
+
+        domain = mkOption {
+          type = types.str;
+          description = ''
+            the domain of the matrix homeserver
+          '';
+        };
+
+      };
+
+      server = {
+
+        name = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            Public hostname of mxisd, if different from the Matrix domain.
+          '';
+        };
+
+        port = mkOption {
+          type = types.nullOr types.int;
+          default = null;
+          description = ''
+            HTTP port to listen on (unencrypted)
+          '';
+        };
+
+      };
+
+    };
+  };
+
+  config = mkIf cfg.enable {
+    users.users = [
+      {
+        name = "mxisd";
+        group = "mxisd";
+        home = cfg.dataDir;
+        createHome = true;
+        shell = "${pkgs.bash}/bin/bash";
+        uid = config.ids.uids.mxisd;
+      }
+    ];
+
+    users.groups = [
+      {
+        name = "mxisd";
+        gid = config.ids.gids.mxisd;
+      }
+    ];
+
+    systemd.services.mxisd = {
+      description = "a federated identity server for the matrix ecosystem";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+
+      # mxisd / spring.boot needs the configuration to be named "application.yaml"
+      preStart = ''
+        config=${cfg.dataDir}/application.yaml
+        cp ${configFile} $config
+        chmod 444 $config
+      '';
+
+      serviceConfig = {
+        Type = "simple";
+        User = "mxisd";
+        Group = "mxisd";
+        ExecStart = "${cfg.package}/bin/mxisd --spring.config.location=${cfg.dataDir}/ --spring.profiles.active=systemd --java.security.egd=file:/dev/./urandom";
+        WorkingDirectory = cfg.dataDir;
+        PermissionsStartOnly = true;
+        SuccessExitStatus = 143;
+        Restart = "on-failure";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/ndppd.nix b/nixos/modules/services/networking/ndppd.nix
index 1d6c48dd8d378..ba17f1ba825a5 100644
--- a/nixos/modules/services/networking/ndppd.nix
+++ b/nixos/modules/services/networking/ndppd.nix
@@ -5,43 +5,163 @@ with lib;
 let
   cfg = config.services.ndppd;
 
-  configFile = pkgs.runCommand "ndppd.conf" {} ''
-    substitute ${pkgs.ndppd}/etc/ndppd.conf $out \
-      --replace eth0 ${cfg.interface} \
-      --replace 1111:: ${cfg.network}
-  '';
-in {
-  options = {
-    services.ndppd = {
-      enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
+  render = s: f: concatStringsSep "\n" (mapAttrsToList f s);
+  prefer = a: b: if a != null then a else b;
+
+  ndppdConf = prefer cfg.configFile (pkgs.writeText "ndppd.conf" ''
+    route-ttl ${toString cfg.routeTTL}
+    ${render cfg.proxies (proxyInterfaceName: proxy: ''
+    proxy ${prefer proxy.interface proxyInterfaceName} {
+      router ${boolToString proxy.router}
+      timeout ${toString proxy.timeout}
+      ttl ${toString proxy.ttl}
+      ${render proxy.rules (ruleNetworkName: rule: ''
+      rule ${prefer rule.network ruleNetworkName} {
+        ${rule.method}${if rule.method == "iface" then " ${rule.interface}" else ""}
+      }'')}
+    }'')}
+  '');
+
+  proxy = types.submodule {
+    options = {
       interface = mkOption {
-        type = types.string;
-        default = "eth0";
-        example = "ens3";
-        description = "Interface which is on link-level with router.";
+        type = types.nullOr types.str;
+        description = ''
+          Listen for any Neighbor Solicitation messages on this interface,
+          and respond to them according to a set of rules.
+          Defaults to the name of the attrset.
+        '';
+        default = null;
+      };
+      router = mkOption {
+        type = types.bool;
+        description = ''
+          Turns on or off the router flag for Neighbor Advertisement Messages.
+        '';
+        default = true;
+      };
+      timeout = mkOption {
+        type = types.int;
+        description = ''
+          Controls how long to wait for a Neighbor Advertisment Message before 
+          invalidating the entry, in milliseconds.
+        '';
+        default = 500;
+      };
+      ttl = mkOption {
+        type = types.int;
+        description = ''
+          Controls how long a valid or invalid entry remains in the cache, in 
+          milliseconds.
+        '';
+        default = 30000;
       };
+      rules = mkOption {
+        type = types.attrsOf rule;
+        description = ''
+          This is a rule that the target address is to match against. If no netmask
+          is provided, /128 is assumed. You may have several rule sections, and the
+          addresses may or may not overlap.
+        '';
+        default = {};
+      };
+    };
+  };
+
+  rule = types.submodule {
+    options = {
       network = mkOption {
-        type = types.string;
-        default = "1111::";
-        example = "2001:DB8::/32";
-        description = "Network that we proxy.";
+        type = types.nullOr types.str;
+        description = ''
+          This is the target address is to match against. If no netmask
+          is provided, /128 is assumed. The addresses of serveral rules
+          may or may not overlap.
+          Defaults to the name of the attrset.
+        '';
+        default = null;
+      };
+      method = mkOption {
+        type = types.enum [ "static" "iface" "auto" ];
+        description = ''
+          static: Immediately answer any Neighbor Solicitation Messages
+            (if they match the IP rule).
+          iface: Forward the Neighbor Solicitation Message through the specified
+            interface and only respond if a matching Neighbor Advertisement
+            Message is received.
+          auto: Same as iface, but instead of manually specifying the outgoing
+            interface, check for a matching route in /proc/net/ipv6_route.
+        '';
+        default = "auto";
       };
-      configFile = mkOption {
-        type = types.nullOr types.path;
+      interface = mkOption {
+        type = types.nullOr types.str;
+        description = "Interface to use when method is iface.";
         default = null;
-        description = "Path to configuration file.";
       };
     };
   };
 
+in {
+  options.services.ndppd = {
+    enable = mkEnableOption "daemon that proxies NDP (Neighbor Discovery Protocol) messages between interfaces";
+    interface = mkOption {
+      type = types.nullOr types.str;
+      description = ''
+        Interface which is on link-level with router.
+        (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+      '';
+      default = null;
+      example = "eth0";
+    };
+    network = mkOption {
+      type = types.nullOr types.str;
+      description = ''
+        Network that we proxy.
+        (Legacy option, use services.ndppd.proxies.&lt;interface&gt;.rules.&lt;network&gt; instead)
+      '';
+      default = null;
+      example = "1111::/64";
+    };
+    configFile = mkOption {
+      type = types.nullOr types.path;
+      description = "Path to configuration file.";
+      default = null;
+    };
+    routeTTL = mkOption {
+      type = types.int;
+      description = ''
+        This tells 'ndppd' how often to reload the route file /proc/net/ipv6_route,
+        in milliseconds.
+      '';
+      default = 30000;
+    };
+    proxies = mkOption {
+      type = types.attrsOf proxy;
+      description = ''
+        This sets up a listener, that will listen for any Neighbor Solicitation
+        messages, and respond to them according to a set of rules.
+      '';
+      default = {};
+      example = { "eth0".rules."1111::/64" = {}; };
+    };
+  };
+
   config = mkIf cfg.enable {
-    systemd.packages = [ pkgs.ndppd ];
-    environment.etc."ndppd.conf".source = if (cfg.configFile != null) then cfg.configFile else configFile;
+    warnings = mkIf (cfg.interface != null && cfg.network != null) [ ''
+      The options services.ndppd.interface and services.ndppd.network will probably be removed soon,
+      please use services.ndppd.proxies.<interface>.rules.<network> instead.
+    '' ];
+
+    services.ndppd.proxies = mkIf (cfg.interface != null && cfg.network != null) {
+      "${cfg.interface}".rules."${cfg.network}" = {};
+    };
+
     systemd.services.ndppd = {
-      serviceConfig.RuntimeDirectory = [ "ndppd" ];
+      description = "NDP Proxy Daemon";
+      documentation = [ "man:ndppd(1)" "man:ndppd.conf(5)" ];
+      after = [ "network-pre.target" ];
       wantedBy = [ "multi-user.target" ];
+      serviceConfig.ExecStart = "${pkgs.ndppd}/bin/ndppd -c ${ndppdConf}";
     };
   };
-
-  meta.maintainers = with maintainers; [ gnidorah ];
 }
diff --git a/nixos/modules/services/networking/networkmanager.nix b/nixos/modules/services/networking/networkmanager.nix
index 2d76e0676b243..d372dfd8f4129 100644
--- a/nixos/modules/services/networking/networkmanager.nix
+++ b/nixos/modules/services/networking/networkmanager.nix
@@ -488,7 +488,7 @@ in {
       '') cfg.dynamicHosts.hostsDirs);
       serviceConfig = {
         Type = "oneshot";
-        RemainAfterExist = true;
+        RemainAfterExit = true;
       };
     };
 
diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix
index cde47bf23eaef..8b918dab86ddd 100644
--- a/nixos/modules/services/networking/nsd.nix
+++ b/nixos/modules/services/networking/nsd.nix
@@ -435,7 +435,9 @@ let
 
   dnssecZones = (filterAttrs (n: v: if v ? dnssec then v.dnssec else false) zoneConfigs);
 
-  dnssec = length (attrNames dnssecZones) != 0; 
+  dnssec = dnssecZones != {};
+
+  dnssecTools = pkgs.bind.override { enablePython = true; };
 
   signZones = optionalString dnssec ''
     mkdir -p ${stateDir}/dnssec
@@ -445,8 +447,8 @@ let
     ${concatStrings (mapAttrsToList signZone dnssecZones)}
   '';
   signZone = name: zone: ''
-    ${pkgs.bind}/bin/dnssec-keymgr -g ${pkgs.bind}/bin/dnssec-keygen -s ${pkgs.bind}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${policyFile name zone.dnssecPolicy} ${name}
-    ${pkgs.bind}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name}
+    ${dnssecTools}/bin/dnssec-keymgr -g ${dnssecTools}/bin/dnssec-keygen -s ${dnssecTools}/bin/dnssec-settime -K ${stateDir}/dnssec -c ${policyFile name zone.dnssecPolicy} ${name}
+    ${dnssecTools}/bin/dnssec-signzone -S -K ${stateDir}/dnssec -o ${name} -O full -N date ${stateDir}/zones/${name}
     ${nsdPkg}/sbin/nsd-checkzone ${name} ${stateDir}/zones/${name}.signed && mv -v ${stateDir}/zones/${name}.signed ${stateDir}/zones/${name}
   '';
   policyFile = name: policy: pkgs.writeText "${name}.policy" ''
@@ -953,10 +955,6 @@ in
       '';
     };
 
-    nixpkgs.config = mkIf dnssec {
-      bind.enablePython = true;
-    };
-
     systemd.timers."nsd-dnssec" = mkIf dnssec {
       description = "Automatic DNSSEC key rollover";
 
diff --git a/nixos/modules/services/networking/ntpd.nix b/nixos/modules/services/networking/ntpd.nix
index 342350d49ab32..588d1c6edb073 100644
--- a/nixos/modules/services/networking/ntpd.nix
+++ b/nixos/modules/services/networking/ntpd.nix
@@ -15,6 +15,10 @@ let
   configFile = pkgs.writeText "ntp.conf" ''
     driftfile ${stateDir}/ntp.drift
 
+    restrict default ${toString cfg.restrictDefault}
+    restrict -6 default ${toString cfg.restrictDefault}
+    restrict source ${toString cfg.restrictSource}
+
     restrict 127.0.0.1
     restrict -6 ::1
 
@@ -36,9 +40,38 @@ in
       enable = mkOption {
         default = false;
         description = ''
-          Whether to synchronise your machine's time using the NTP
-          protocol.
+          Whether to synchronise your machine's time using ntpd, as a peer in
+          the NTP network.
+          </para>
+          <para>
+          Disables <literal>systemd.timesyncd</literal> if enabled.
+        '';
+      };
+
+      restrictDefault = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          The restriction flags to be set by default.
+          </para>
+          <para>
+          The default flags prevent external hosts from using ntpd as a DDoS
+          reflector, setting system time, and querying OS/ntpd version. As
+          recommended in section 6.5.1.1.3, answer "No" of
+          http://support.ntp.org/bin/view/Support/AccessRestrictions
         '';
+        default = [ "limited" "kod" "nomodify" "notrap" "noquery" "nopeer" ];
+      };
+
+      restrictSource = mkOption {
+        type = types.listOf types.str;
+        description = ''
+          The restriction flags to be set on source.
+          </para>
+          <para>
+          The default flags allow peers to be added by ntpd from configured
+          pool(s), but not by other means.
+        '';
+        default = [ "limited" "kod" "nomodify" "notrap" "noquery" ];
       };
 
       servers = mkOption {
@@ -51,6 +84,7 @@ in
       extraFlags = mkOption {
         type = types.listOf types.str;
         description = "Extra flags passed to the ntpd command.";
+        example = literalExample ''[ "--interface=eth0" ]'';
         default = [];
       };
 
@@ -67,6 +101,8 @@ in
     environment.systemPackages = [ pkgs.ntp ];
     services.timesyncd.enable = mkForce false;
 
+    systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "ntpd.service"; };
+
     users.users = singleton
       { name = ntpUser;
         uid = config.ids.uids.ntp;
diff --git a/nixos/modules/services/networking/nullidentdmod.nix b/nixos/modules/services/networking/nullidentdmod.nix
new file mode 100644
index 0000000000000..786b5227dbadc
--- /dev/null
+++ b/nixos/modules/services/networking/nullidentdmod.nix
@@ -0,0 +1,34 @@
+{ config, lib, pkgs, ... }: with lib; let
+  cfg = config.services.nullidentdmod;
+
+in {
+  options.services.nullidentdmod = with types; {
+    enable = mkEnableOption "Enable the nullidentdmod identd daemon";
+
+    userid = mkOption {
+      type = nullOr str;
+      description = "User ID to return. Set to null to return a random string each time.";
+      default = null;
+      example = "alice";
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.sockets.nullidentdmod = {
+      description = "Socket for identd (NullidentdMod)";
+      listenStreams = [ "113" ];
+      socketConfig.Accept = true;
+      wantedBy = [ "sockets.target" ];
+    };
+
+    systemd.services."nullidentdmod@" = {
+      description = "NullidentdMod service";
+      serviceConfig = {
+        DynamicUser = true;
+        ExecStart = "${pkgs.nullidentdmod}/bin/nullidentdmod${optionalString (cfg.userid != null) " ${cfg.userid}"}";
+        StandardInput = "socket";
+        StandardOutput = "socket";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/nylon.nix b/nixos/modules/services/networking/nylon.nix
index 613b0e0fb51a5..b061ce34ed2cf 100644
--- a/nixos/modules/services/networking/nylon.nix
+++ b/nixos/modules/services/networking/nylon.nix
@@ -142,7 +142,6 @@ in
       description = "Collection of named nylon instances";
       type = with types; loaOf (submodule nylonOpts);
       internal = true;
-      options = [ nylonOpts ];
     };
 
   };
diff --git a/nixos/modules/services/networking/oidentd.nix b/nixos/modules/services/networking/oidentd.nix
index 8cf34623ab5ed..feb84806ba99c 100644
--- a/nixos/modules/services/networking/oidentd.nix
+++ b/nixos/modules/services/networking/oidentd.nix
@@ -28,8 +28,7 @@ with lib;
       after = [ "network.target" ];
       wantedBy = [ "multi-user.target" ];
       serviceConfig.Type = "forking";
-      script = "${pkgs.oidentd}/sbin/oidentd -u oidentd -g nogroup" +
-          optionalString config.networking.enableIPv6 " -a ::";
+      script = "${pkgs.oidentd}/sbin/oidentd -u oidentd -g nogroup";
     };
 
     users.users.oidentd = {
diff --git a/nixos/modules/services/networking/pptpd.nix b/nixos/modules/services/networking/pptpd.nix
index 56a612b910526..d8b9e8f8341a5 100644
--- a/nixos/modules/services/networking/pptpd.nix
+++ b/nixos/modules/services/networking/pptpd.nix
@@ -5,7 +5,7 @@ with lib;
 {
   options = {
     services.pptpd = {
-      enable = mkEnableOption "Whether pptpd should be run on startup.";
+      enable = mkEnableOption "pptpd, the Point-to-Point Tunneling Protocol daemon";
 
       serverIp = mkOption {
         type        = types.string;
diff --git a/nixos/modules/services/networking/prosody.nix b/nixos/modules/services/networking/prosody.nix
index a37ef98caec61..de316e5f46698 100644
--- a/nixos/modules/services/networking/prosody.nix
+++ b/nixos/modules/services/networking/prosody.nix
@@ -228,6 +228,7 @@ let
 
   createSSLOptsStr = o: ''
     ssl = {
+      cafile = "/etc/ssl/certs/ca-bundle.crt";
       key = "${o.key}";
       certificate = "${o.cert}";
       ${concatStringsSep "\n" (mapAttrsToList (name: value: "${name} = ${toLua value};") o.extraOptions)}
@@ -512,6 +513,7 @@ in
         RuntimeDirectory = [ "prosody" ];
         PIDFile = "/run/prosody/prosody.pid";
         ExecStart = "${cfg.package}/bin/prosodyctl start";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
       };
     };
 
diff --git a/nixos/modules/services/networking/redsocks.nix b/nixos/modules/services/networking/redsocks.nix
index a47a78f1005e3..8481f9debf39c 100644
--- a/nixos/modules/services/networking/redsocks.nix
+++ b/nixos/modules/services/networking/redsocks.nix
@@ -267,4 +267,6 @@ in
             "ip46tables -t nat -D OUTPUT -p tcp ${redCond block} -j ${chain} 2>/dev/null || true"
         ) cfg.redsocks;
     };
+
+  meta.maintainers = with lib.maintainers; [ ekleog ];
 }
diff --git a/nixos/modules/services/networking/shairport-sync.nix b/nixos/modules/services/networking/shairport-sync.nix
index 0b87140b0d8db..68e005ab81dad 100644
--- a/nixos/modules/services/networking/shairport-sync.nix
+++ b/nixos/modules/services/networking/shairport-sync.nix
@@ -27,7 +27,7 @@ in
       };
 
       arguments = mkOption {
-        default = "-v -o pulse";
+        default = "-v -o pa";
         description = ''
           Arguments to pass to the daemon. Defaults to a local pulseaudio
           server.
@@ -72,6 +72,7 @@ in
         serviceConfig = {
           User = cfg.user;
           ExecStart = "${pkgs.shairport-sync}/bin/shairport-sync ${cfg.arguments}";
+          RuntimeDirectory = "shairport-sync";
         };
       };
 
diff --git a/nixos/modules/services/networking/ssh/sshd.nix b/nixos/modules/services/networking/ssh/sshd.nix
index c16fbe8a52fa2..95dc8a62a454e 100644
--- a/nixos/modules/services/networking/ssh/sshd.nix
+++ b/nixos/modules/services/networking/ssh/sshd.nix
@@ -11,7 +11,7 @@ let
 
   userOptions = {
 
-    openssh.authorizedKeys = {
+    options.openssh.authorizedKeys = {
       keys = mkOption {
         type = types.listOf types.str;
         default = [];
@@ -130,7 +130,7 @@ in
       };
 
       ports = mkOption {
-        type = types.listOf types.int;
+        type = types.listOf types.port;
         default = [22];
         description = ''
           Specifies on which ports the SSH daemon listens.
@@ -320,7 +320,7 @@ in
     };
 
     users.users = mkOption {
-      options = [ userOptions ];
+      type = with types; loaOf (submodule userOptions);
     };
 
   };
@@ -352,6 +352,10 @@ in
             path = [ cfgc.package pkgs.gawk ];
             environment.LD_LIBRARY_PATH = nssModulesPath;
 
+            restartTriggers = optionals (!cfg.startWhenNeeded) [
+              config.environment.etc."ssh/sshd_config".source
+            ];
+
             preStart =
               ''
                 # Make sure we don't write to stdout, since in case of
@@ -387,6 +391,7 @@ in
                 Restart = "always";
                 Type = "simple";
               });
+
           };
       in
 
diff --git a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
index b16d299917fee..d4f7e95f859fb 100644
--- a/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
+++ b/nixos/modules/services/networking/strongswan-swanctl/swanctl-params.nix
@@ -248,6 +248,14 @@ in {
       </itemizedlist>
     '';
 
+    ppk_id = mkOptionalStrParam ''
+       String identifying the Postquantum Preshared Key (PPK) to be used.
+    '';
+
+    ppk_required = mkYesNoParam no ''
+       Whether a Postquantum Preshared Key (PPK) is required for this connection.
+    '';
+
     keyingtries = mkIntParam 1 ''
       Number of retransmission sequences to perform during initial
       connect. Instead of giving up initiation after the first retransmission
@@ -922,6 +930,36 @@ in {
         <literal>0xffffffff</literal>.
       '';
 
+      set_mark_in = mkStrParam "0/0x00000000" ''
+        Netfilter mark applied to packets after the inbound IPsec SA processed
+        them. This way it's not necessary to mark packets via Netfilter before
+        decryption or right afterwards to match policies or process them
+        differently (e.g. via policy routing).
+
+        An additional mask may be appended to the mark, separated by
+        <literal>/</literal>. The default mask if omitted is 0xffffffff. The
+        special value <literal>%same</literal> uses the value (but not the mask)
+        from <option>mark_in</option> as mark value, which can be fixed,
+        <literal>%unique</literal> or <literal>%unique-dir</literal>.
+
+        Setting marks in XFRM input requires Linux 4.19 or higher.
+      '';
+
+      set_mark_out = mkStrParam "0/0x00000000" ''
+        Netfilter mark applied to packets after the outbound IPsec SA processed
+        them. This allows processing ESP packets differently than the original
+        traffic (e.g. via policy routing).
+
+        An additional mask may be appended to the mark, separated by
+        <literal>/</literal>. The default mask if omitted is 0xffffffff. The
+        special value <literal>%same</literal> uses the value (but not the mask)
+        from <option>mark_out</option> as mark value, which can be fixed,
+        <literal>%unique_</literal> or <literal>%unique-dir</literal>.
+
+        Setting marks in XFRM output is supported since Linux 4.14. Setting a
+        mask requires at least Linux 4.19.
+      '';
+
       tfc_padding = mkParamOfType (with lib.types; either int (enum ["mtu"])) 0 ''
         Pads ESP packets with additional data to have a consistent ESP packet
         size for improved Traffic Flow Confidentiality. The padding defines the
@@ -946,6 +984,33 @@ in {
         supported, but the installation does not fail otherwise.
       '';
 
+      copy_df = mkYesNoParam yes ''
+        Whether to copy the DF bit to the outer IPv4 header in tunnel mode. This
+        effectively disables Path MTU discovery (PMTUD). Controlling this
+        behavior is not supported by all kernel interfaces.
+      '';
+
+      copy_ecn = mkYesNoParam yes ''
+        Whether to copy the ECN (Explicit Congestion Notification) header field
+        to/from the outer IP header in tunnel mode. Controlling this behavior is
+        not supported by all kernel interfaces.
+      '';
+
+      copy_dscp = mkEnumParam [ "out" "in" "yes" "no" ] "out" ''
+        Whether to copy the DSCP (Differentiated Services Field Codepoint)
+        header field to/from the outer IP header in tunnel mode. The value
+        <literal>out</literal> only copies the field from the inner to the outer
+        header, the value <literal>in</literal> does the opposite and only
+        copies the field from the outer to the inner header when decapsulating,
+        the value <literal>yes</literal> copies the field in both directions,
+        and the value <literal>no</literal> disables copying the field
+        altogether. Setting this to <literal>yes</literal> or
+        <literal>in</literal> could allow an attacker to adversely affect other
+        traffic at the receiver, which is why the default is
+        <literal>out</literal>. Controlling this behavior is not supported by
+        all kernel interfaces.
+      '';
+
       start_action = mkEnumParam ["none" "trap" "start"] "none" ''
         Action to perform after loading the configuration.
         <itemizedlist>
@@ -1060,6 +1125,24 @@ in {
       defined in a unique section having the <literal>ike</literal> prefix.
     '';
 
+    ppk = mkPrefixedAttrsOfParams {
+      secret = mkOptionalStrParam ''
+	      Value of the PPK. It may either be an ASCII string, a hex encoded string
+	      if it has a <literal>0x</literal> prefix or a Base64 encoded string if
+	      it has a <literal>0s</literal> prefix in its value. Should have at least
+	      256 bits of entropy for 128-bit security.
+      '';
+
+      id = mkPrefixedAttrsOfParam (mkOptionalStrParam "") ''
+	      PPK identity the PPK belongs to. Multiple unique identities may be
+	      specified, each having an <literal>id</literal> prefix, if a secret is
+	      shared between multiple peers.
+      '';
+    } ''
+	    Postquantum Preshared Key (PPK) section for a specific secret. Each PPK is
+	    defined in a unique section having the <literal>ppk</literal> prefix.
+    '';
+
     private = mkPrefixedAttrsOfParams {
       file = mkOptionalStrParam ''
         File name in the private folder for which this passphrase should be used.
diff --git a/nixos/modules/services/networking/syncthing-relay.nix b/nixos/modules/services/networking/syncthing-relay.nix
new file mode 100644
index 0000000000000..f5ca63e789304
--- /dev/null
+++ b/nixos/modules/services/networking/syncthing-relay.nix
@@ -0,0 +1,121 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.syncthing.relay;
+
+  dataDirectory = "/var/lib/syncthing-relay";
+
+  relayOptions =
+    [
+      "--keys=${dataDirectory}"
+      "--listen=${cfg.listenAddress}:${toString cfg.port}"
+      "--status-srv=${cfg.statusListenAddress}:${toString cfg.statusPort}"
+      "--provided-by=${escapeShellArg cfg.providedBy}"
+    ]
+    ++ optional (cfg.pools != null) "--pools=${escapeShellArg (concatStringsSep "," cfg.pools)}"
+    ++ optional (cfg.globalRateBps != null) "--global-rate=${toString cfg.globalRateBps}"
+    ++ optional (cfg.perSessionRateBps != null) "--per-session-rate=${toString cfg.perSessionRateBps}"
+    ++ cfg.extraOptions;
+in {
+  ###### interface
+
+  options.services.syncthing.relay = {
+    enable = mkEnableOption "Syncthing relay service";
+
+    listenAddress = mkOption {
+      type = types.str;
+      default = "";
+      example = "1.2.3.4";
+      description = ''
+        Address to listen on for relay traffic.
+      '';
+    };
+
+    port = mkOption {
+      type = types.port;
+      default = 22067;
+      description = ''
+        Port to listen on for relay traffic. This port should be added to
+        <literal>networking.firewall.allowedTCPPorts</literal>.
+      '';
+    };
+
+    statusListenAddress = mkOption {
+      type = types.str;
+      default = "";
+      example = "1.2.3.4";
+      description = ''
+        Address to listen on for serving the relay status API.
+      '';
+    };
+
+    statusPort = mkOption {
+      type = types.port;
+      default = 22070;
+      description = ''
+        Port to listen on for serving the relay status API. This port should be
+        added to <literal>networking.firewall.allowedTCPPorts</literal>.
+      '';
+    };
+
+    pools = mkOption {
+      type = types.nullOr (types.listOf types.str);
+      default = null;
+      description = ''
+        Relay pools to join. If null, uses the default global pool.
+      '';
+    };
+
+    providedBy = mkOption {
+      type = types.str;
+      default = "";
+      description = ''
+        Human-readable description of the provider of the relay (you).
+      '';
+    };
+
+    globalRateBps = mkOption {
+      type = types.nullOr types.ints.positive;
+      default = null;
+      description = ''
+        Global bandwidth rate limit in bytes per second.
+      '';
+    };
+
+    perSessionRateBps = mkOption {
+      type = types.nullOr types.ints.positive;
+      default = null;
+      description = ''
+        Per session bandwidth rate limit in bytes per second.
+      '';
+    };
+
+    extraOptions = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Extra command line arguments to pass to strelaysrv.
+      '';
+    };
+  };
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    systemd.services.syncthing-relay = {
+      description = "Syncthing relay service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+
+      serviceConfig = {
+        DynamicUser = true;
+        StateDirectory = baseNameOf dataDirectory;
+
+        Restart = "on-failure";
+        ExecStart = "${pkgs.syncthing-relay}/bin/strelaysrv ${concatStringsSep " " relayOptions}";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/networking/syncthing.nix b/nixos/modules/services/networking/syncthing.nix
index c610b3b666062..702481ec5177b 100644
--- a/nixos/modules/services/networking/syncthing.nix
+++ b/nixos/modules/services/networking/syncthing.nix
@@ -16,6 +16,14 @@ in {
         available on http://127.0.0.1:8384/.
       '';
 
+      guiAddress = mkOption {
+        type = types.str;
+        default = "127.0.0.1:8384";
+        description = ''
+          Address to serve the GUI.
+        '';
+      };
+
       systemService = mkOption {
         type = types.bool;
         default = true;
@@ -23,7 +31,7 @@ in {
       };
 
       user = mkOption {
-        type = types.string;
+        type = types.str;
         default = defaultUser;
         description = ''
           Syncthing will be run under this user (user will be created if it doesn't exist.
@@ -32,7 +40,7 @@ in {
       };
 
       group = mkOption {
-        type = types.string;
+        type = types.str;
         default = "nogroup";
         description = ''
           Syncthing will be run under this group (group will not be created if it doesn't exist.
@@ -41,7 +49,7 @@ in {
       };
 
       all_proxy = mkOption {
-        type = types.nullOr types.string;
+        type = with types; nullOr str;
         default = null;
         example = "socks5://address.com:1234";
         description = ''
@@ -55,8 +63,20 @@ in {
         type = types.path;
         default = "/var/lib/syncthing";
         description = ''
+          Path where synced directories will exist.
+        '';
+      };
+
+      configDir = mkOption {
+        type = types.path;
+        description = ''
           Path where the settings and keys will exist.
         '';
+        default =
+          let
+            nixos = config.system.stateVersion;
+            cond  = versionAtLeast nixos "19.03";
+          in cfg.dataDir + (optionalString cond "/.config/syncthing");
       };
 
       openDefaultPorts = mkOption {
@@ -102,7 +122,7 @@ in {
 
     systemd.packages = [ pkgs.syncthing ];
 
-    users = mkIf (cfg.user == defaultUser) {
+    users = mkIf (cfg.systemService && cfg.user == defaultUser) {
       users."${defaultUser}" =
         { group = cfg.group;
           home  = cfg.dataDir;
@@ -132,7 +152,12 @@ in {
           User = cfg.user;
           Group = cfg.group;
           PermissionsStartOnly = true;
-          ExecStart = "${cfg.package}/bin/syncthing -no-browser -home=${cfg.dataDir}";
+          ExecStart = ''
+            ${cfg.package}/bin/syncthing \
+              -no-browser \
+              -gui-address=${cfg.guiAddress} \
+              -home=${cfg.configDir}
+          '';
         };
       };
 
diff --git a/nixos/modules/services/networking/teamspeak3.nix b/nixos/modules/services/networking/teamspeak3.nix
index 3703921ff7034..9ea9c83e37cd6 100644
--- a/nixos/modules/services/networking/teamspeak3.nix
+++ b/nixos/modules/services/networking/teamspeak3.nix
@@ -41,8 +41,9 @@ in
       };
 
       voiceIP = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
+        type = types.nullOr types.str;
+        default = null;
+        example = "0.0.0.0";
         description = ''
           IP on which the server instance will listen for incoming voice connections. Defaults to any IP.
         '';
@@ -57,8 +58,9 @@ in
       };
 
       fileTransferIP = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
+        type = types.nullOr types.str;
+        default = null;
+        example = "0.0.0.0";
         description = ''
           IP on which the server instance will listen for incoming file transfer connections. Defaults to any IP.
         '';
@@ -73,8 +75,9 @@ in
       };
 
       queryIP = mkOption {
-        type = types.str;
-        default = "0.0.0.0";
+        type = types.nullOr types.str;
+        default = null;
+        example = "0.0.0.0";
         description = ''
           IP on which the server instance will listen for incoming ServerQuery connections. Defaults to any IP.
         '';
@@ -122,9 +125,12 @@ in
         ExecStart = ''
           ${ts3}/bin/ts3server \
             dbsqlpath=${ts3}/lib/teamspeak/sql/ logpath=${cfg.logPath} \
-            voice_ip=${cfg.voiceIP} default_voice_port=${toString cfg.defaultVoicePort} \
-            filetransfer_ip=${cfg.fileTransferIP} filetransfer_port=${toString cfg.fileTransferPort} \
-            query_ip=${cfg.queryIP} query_port=${toString cfg.queryPort}
+            ${optionalString (cfg.voiceIP != null) "voice_ip=${cfg.voiceIP}"} \
+            default_voice_port=${toString cfg.defaultVoicePort} \
+            ${optionalString (cfg.fileTransferIP != null) "filetransfer_ip=${cfg.fileTransferIP}"} \
+            filetransfer_port=${toString cfg.fileTransferPort} \
+            ${optionalString (cfg.queryIP != null) "query_ip=${cfg.queryIP}"} \
+            query_port=${toString cfg.queryPort} license_accepted=1
         '';
         WorkingDirectory = cfg.dataDir;
         User = user;
diff --git a/nixos/modules/services/networking/tinc.nix b/nixos/modules/services/networking/tinc.nix
index 35cdddc590b80..3379efd1afce4 100644
--- a/nixos/modules/services/networking/tinc.nix
+++ b/nixos/modules/services/networking/tinc.nix
@@ -148,14 +148,6 @@ in
         }
       ));
 
-    networking.interfaces = flip mapAttrs' cfg.networks (network: data: nameValuePair
-      ("tinc.${network}")
-      ({
-        virtual = true;
-        virtualType = "${data.interfaceType}";
-      })
-    );
-
     systemd.services = flip mapAttrs' cfg.networks (network: data: nameValuePair
       ("tinc.${network}")
       ({
diff --git a/nixos/modules/services/networking/unifi.nix b/nixos/modules/services/networking/unifi.nix
index ac10e77ba3063..c82e0af2803dc 100644
--- a/nixos/modules/services/networking/unifi.nix
+++ b/nixos/modules/services/networking/unifi.nix
@@ -121,11 +121,12 @@ in
     };
 
     networking.firewall = mkIf cfg.openPorts {
-      # https://help.ubnt.com/hc/en-us/articles/204910084-UniFi-Change-Default-Ports-for-Controller-and-UAPs
+      # https://help.ubnt.com/hc/en-us/articles/218506997
       allowedTCPPorts = [
         8080  # Port for UAP to inform controller.
         8880  # Port for HTTP portal redirect, if guest portal is enabled.
         8843  # Port for HTTPS portal redirect, ditto.
+        6789  # Port for UniFi mobile speed test.
       ];
       allowedUDPPorts = [
         3478  # UDP port used for STUN.
@@ -184,4 +185,5 @@ in
 
   };
 
+  meta.maintainers = with lib.maintainers; [ erictapen ];
 }
diff --git a/nixos/modules/services/networking/wireguard.nix b/nixos/modules/services/networking/wireguard.nix
index 564632a85ae5c..41aff1480a05a 100644
--- a/nixos/modules/services/networking/wireguard.nix
+++ b/nixos/modules/services/networking/wireguard.nix
@@ -202,7 +202,7 @@ let
         };
 
         script = ''
-          modprobe wireguard
+          ${optionalString (!config.boot.isContainer) "modprobe wireguard"}
 
           ${values.preSetup}
 
diff --git a/nixos/modules/services/networking/wpa_supplicant.nix b/nixos/modules/services/networking/wpa_supplicant.nix
index c788528fa47be..cdfe98aa03414 100644
--- a/nixos/modules/services/networking/wpa_supplicant.nix
+++ b/nixos/modules/services/networking/wpa_supplicant.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
@@ -86,7 +86,12 @@ in {
               '';
               description = ''
                 Use this option to configure advanced authentication methods like EAP.
-                See wpa_supplicant.conf(5) for example configurations.
+                See
+                <citerefentry>
+                  <refentrytitle>wpa_supplicant.conf</refentrytitle>
+                  <manvolnum>5</manvolnum>
+                </citerefentry>
+                for example configurations.
 
                 Mutually exclusive with <varname>psk</varname> and <varname>pskRaw</varname>.
               '';
@@ -122,7 +127,12 @@ in {
               '';
               description = ''
                 Extra configuration lines appended to the network block.
-                See wpa_supplicant.conf(5) for available options.
+                See
+                <citerefentry>
+                  <refentrytitle>wpa_supplicant.conf</refentrytitle>
+                  <manvolnum>5</manvolnum>
+                </citerefentry>
+                for available options.
               '';
             };
 
@@ -174,7 +184,12 @@ in {
         '';
         description = ''
           Extra lines appended to the configuration file.
-          See wpa_supplicant.conf(5) for available options.
+          See
+          <citerefentry>
+            <refentrytitle>wpa_supplicant.conf</refentrytitle>
+            <manvolnum>5</manvolnum>
+          </citerefentry>
+          for available options.
         '';
       };
     };
@@ -193,7 +208,7 @@ in {
     # FIXME: start a separate wpa_supplicant instance per interface.
     systemd.services.wpa_supplicant = let
       ifaces = cfg.interfaces;
-      deviceUnit = interface: [ "sys-subsystem-net-devices-${interface}.device" ];
+      deviceUnit = interface: [ "sys-subsystem-net-devices-${utils.escapeSystemdPath interface}.device" ];
     in {
       description = "WPA Supplicant";
 
diff --git a/nixos/modules/services/networking/xl2tpd.nix b/nixos/modules/services/networking/xl2tpd.nix
index 46111a76af808..d0a3ed7bb5e0a 100644
--- a/nixos/modules/services/networking/xl2tpd.nix
+++ b/nixos/modules/services/networking/xl2tpd.nix
@@ -5,7 +5,7 @@ with lib;
 {
   options = {
     services.xl2tpd = {
-      enable = mkEnableOption "Whether xl2tpd should be run on startup.";
+      enable = mkEnableOption "xl2tpd, the Layer 2 Tunnelling Protocol Daemon";
 
       serverIp = mkOption {
         type        = types.string;
diff --git a/nixos/modules/services/networking/xrdp.nix b/nixos/modules/services/networking/xrdp.nix
index 61f22a366a023..a1c5d879f3cb2 100644
--- a/nixos/modules/services/networking/xrdp.nix
+++ b/nixos/modules/services/networking/xrdp.nix
@@ -26,6 +26,12 @@ let
     substituteInPlace $out/sesman.ini \
       --replace LogFile=xrdp-sesman.log LogFile=/dev/null \
       --replace EnableSyslog=1 EnableSyslog=0
+
+    # Ensure that clipboard works for non-ASCII characters
+    sed -i -e '/.*SessionVariables.*/ a\
+    LANG=${config.i18n.defaultLocale}\
+    LOCALE_ARCHIVE=${config.i18n.glibcLocales}/lib/locale/locale-archive
+    ' $out/sesman.ini
   '';
 in
 {
@@ -36,7 +42,7 @@ in
 
     services.xrdp = {
 
-      enable = mkEnableOption "Whether xrdp should be run on startup.";
+      enable = mkEnableOption "xrdp, the Remote Desktop Protocol server";
 
       package = mkOption {
         type = types.package;
diff --git a/nixos/modules/services/networking/zerotierone.nix b/nixos/modules/services/networking/zerotierone.nix
index a4cd368397e71..764af3846fe5d 100644
--- a/nixos/modules/services/networking/zerotierone.nix
+++ b/nixos/modules/services/networking/zerotierone.nix
@@ -39,7 +39,8 @@ in
     systemd.services.zerotierone = {
       description = "ZeroTierOne";
       path = [ cfg.package ];
-      after = [ "network.target" ];
+      bindsTo = [ "network-online.target" ];
+      after = [ "network-online.target" ];
       wantedBy = [ "multi-user.target" ];
       preStart = ''
         mkdir -p /var/lib/zerotier-one/networks.d
diff --git a/nixos/modules/services/networking/znc.nix b/nixos/modules/services/networking/znc.nix
deleted file mode 100644
index f817db2ad000b..0000000000000
--- a/nixos/modules/services/networking/znc.nix
+++ /dev/null
@@ -1,431 +0,0 @@
-{ config, lib, pkgs, ...}:
-
-with lib;
-
-let
-  cfg = config.services.znc;
-
-  defaultUser = "znc"; # Default user to own process.
-
-  # Default user and pass:
-  # un=znc
-  # pw=nixospass
-
-  defaultUserName = "znc";
-  defaultPassBlock = "
-        <Pass password>
-                Method = sha256
-                Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93
-                Salt = l5Xryew4g*!oa(ECfX2o
-        </Pass>
-  ";
-
-  modules = pkgs.buildEnv {
-    name = "znc-modules";
-    paths = cfg.modulePackages;
-  };
-
-  # Keep znc.conf in nix store, then symlink or copy into `dataDir`, depending on `mutable`.
-  mkZncConf = confOpts: ''
-    Version = 1.6.3
-    ${concatMapStrings (n: "LoadModule = ${n}\n") confOpts.modules}
-
-    <Listener l>
-            Port = ${toString confOpts.port}
-            IPv4 = true
-            IPv6 = true
-            SSL = ${boolToString confOpts.useSSL}
-            ${lib.optionalString (confOpts.uriPrefix != null) "URIPrefix = ${confOpts.uriPrefix}"}
-    </Listener>
-
-    <User ${confOpts.userName}>
-            ${confOpts.passBlock}
-            Admin = true
-            Nick = ${confOpts.nick}
-            AltNick = ${confOpts.nick}_
-            Ident = ${confOpts.nick}
-            RealName = ${confOpts.nick}
-            ${concatMapStrings (n: "LoadModule = ${n}\n") confOpts.userModules}
-
-            ${ lib.concatStringsSep "\n" (lib.mapAttrsToList (name: net: ''
-              <Network ${name}>
-                  ${concatMapStrings (m: "LoadModule = ${m}\n") net.modules}
-                  Server = ${net.server} ${lib.optionalString net.useSSL "+"}${toString net.port} ${net.password}
-                  ${concatMapStrings (c: "<Chan #${c}>\n</Chan>\n") net.channels}
-                  ${lib.optionalString net.hasBitlbeeControlChannel ''
-                    <Chan &bitlbee>
-                    </Chan>
-                  ''}
-                  ${net.extraConf}
-              </Network>
-              '') confOpts.networks) }
-    </User>
-    ${confOpts.extraZncConf}
-  '';
-
-  zncConfFile = pkgs.writeTextFile {
-    name = "znc.conf";
-    text = if cfg.zncConf != ""
-      then cfg.zncConf
-      else mkZncConf cfg.confOptions;
-  };
-
-  networkOpts = { ... }: {
-    options = {
-      server = mkOption {
-        type = types.str;
-        example = "chat.freenode.net";
-        description = ''
-          IRC server address.
-        '';
-      };
-
-      port = mkOption {
-        type = types.int;
-        default = 6697;
-        example = 6697;
-        description = ''
-          IRC server port.
-        '';
-      };
-
-      userName = mkOption {
-        default = "";
-        example = "johntron";
-        type = types.string;
-        description = ''
-          A nick identity specific to the IRC server.
-        '';
-      };
-
-      password = mkOption {
-        type = types.str;
-        default = "";
-        description = ''
-          IRC server password, such as for a Slack gateway.
-        '';
-      };
-
-      useSSL = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Whether to use SSL to connect to the IRC server.
-        '';
-      };
-
-      modulePackages = mkOption {
-        type = types.listOf types.package;
-        default = [];
-        example = [ "pkgs.zncModules.push" "pkgs.zncModules.fish" ];
-        description = ''
-          External ZNC modules to build.
-        '';
-      };
-
-      modules = mkOption {
-        type = types.listOf types.str;
-        default = [ "simple_away" ];
-        example = literalExample "[ simple_away sasl ]";
-        description = ''
-          ZNC modules to load.
-        '';
-      };
-
-      channels = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        example = [ "nixos" ];
-        description = ''
-          IRC channels to join.
-        '';
-      };
-
-      hasBitlbeeControlChannel = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to add the special Bitlbee operations channel.
-        '';
-      };
-
-      extraConf = mkOption {
-        default = "";
-        type = types.lines;
-        example = ''
-          Encoding = ^UTF-8
-          FloodBurst = 4
-          FloodRate = 1.00
-          IRCConnectEnabled = true
-          Ident = johntron
-          JoinDelay = 0
-          Nick = johntron
-        '';
-        description = ''
-          Extra config for the network.
-        '';
-      };
-    };
-  };
-
-in
-
-{
-
-  ###### Interface
-
-  options = {
-    services.znc = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          Enable a ZNC service for a user.
-        '';
-      };
-
-      user = mkOption {
-        default = "znc";
-        example = "john";
-        type = types.string;
-        description = ''
-          The name of an existing user account to use to own the ZNC server process.
-          If not specified, a default user will be created to own the process.
-        '';
-      };
-
-      group = mkOption {
-        default = "";
-        example = "users";
-        type = types.string;
-        description = ''
-          Group to own the ZNCserver process.
-        '';
-      };
-
-      dataDir = mkOption {
-        default = "/var/lib/znc/";
-        example = "/home/john/.znc/";
-        type = types.path;
-        description = ''
-          The data directory. Used for configuration files and modules.
-        '';
-      };
-
-      openFirewall = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Whether to open ports in the firewall for ZNC.
-        '';
-      };
-
-      zncConf = mkOption {
-        default = "";
-        example = "See: http://wiki.znc.in/Configuration";
-        type = types.lines;
-        description = ''
-          Config file as generated with `znc --makeconf` to use for the whole ZNC configuration.
-          If specified, `confOptions` will be ignored, and this value, as-is, will be used.
-          If left empty, a conf file with default values will be used.
-        '';
-      };
-
-      confOptions = {
-        modules = mkOption {
-          type = types.listOf types.str;
-          default = [ "webadmin" "adminlog" ];
-          example = [ "partyline" "webadmin" "adminlog" "log" ];
-          description = ''
-            A list of modules to include in the `znc.conf` file.
-          '';
-        };
-
-        userModules = mkOption {
-          type = types.listOf types.str;
-          default = [ "chansaver" "controlpanel" ];
-          example = [ "chansaver" "controlpanel" "fish" "push" ];
-          description = ''
-            A list of user modules to include in the `znc.conf` file.
-          '';
-        };
-
-        userName = mkOption {
-          default = defaultUserName;
-          example = "johntron";
-          type = types.string;
-          description = ''
-            The user name used to log in to the ZNC web admin interface.
-          '';
-        };
-
-        networks = mkOption {
-          default = { };
-          type = with types; attrsOf (submodule networkOpts);
-          description = ''
-            IRC networks to connect the user to.
-          '';
-          example = {
-            "freenode" = {
-              server = "chat.freenode.net";
-              port = 6697;
-              useSSL = true;
-              modules = [ "simple_away" ];
-            };
-          };
-        };
-
-        nick = mkOption {
-          default = "znc-user";
-          example = "john";
-          type = types.string;
-          description = ''
-            The IRC nick.
-          '';
-        };
-
-        passBlock = mkOption {
-          example = defaultPassBlock;
-          type = types.string;
-          description = ''
-            Generate with `nix-shell -p znc --command "znc --makepass"`.
-            This is the password used to log in to the ZNC web admin interface.
-          '';
-        };
-
-        port = mkOption {
-          default = 5000;
-          example = 5000;
-          type = types.int;
-          description = ''
-            Specifies the port on which to listen.
-          '';
-        };
-
-        useSSL = mkOption {
-          default = true;
-          type = types.bool;
-          description = ''
-            Indicates whether the ZNC server should use SSL when listening on the specified port. A self-signed certificate will be generated.
-          '';
-        };
-
-        uriPrefix = mkOption {
-          type = types.nullOr types.str;
-          default = null;
-          example = "/znc/";
-          description = ''
-            An optional URI prefix for the ZNC web interface. Can be
-            used to make ZNC available behind a reverse proxy.
-          '';
-        };
-
-        extraZncConf = mkOption {
-          default = "";
-          type = types.lines;
-          description = ''
-            Extra config to `znc.conf` file.
-          '';
-        };
-      };
-
-      modulePackages = mkOption {
-        type = types.listOf types.package;
-        default = [ ];
-        example = literalExample "[ pkgs.zncModules.fish pkgs.zncModules.push ]";
-        description = ''
-          A list of global znc module packages to add to znc.
-        '';
-      };
-
-      mutable = mkOption {
-        default = true;
-        type = types.bool;
-        description = ''
-          Indicates whether to allow the contents of the `dataDir` directory to be changed
-          by the user at run-time.
-          If true, modifications to the ZNC configuration after its initial creation are not
-            overwritten by a NixOS system rebuild.
-          If false, the ZNC configuration is rebuilt by every system rebuild.
-          If the user wants to manage the ZNC service using the web admin interface, this value
-            should be set to true.
-        '';
-      };
-
-      extraFlags = mkOption {
-        default = [ ];
-        example = [ "--debug" ];
-        type = types.listOf types.str;
-        description = ''
-          Extra flags to use when executing znc command.
-        '';
-      };
-    };
-  };
-
-
-  ###### Implementation
-
-  config = mkIf cfg.enable {
-
-    networking.firewall = mkIf cfg.openFirewall {
-      allowedTCPPorts = [ cfg.confOptions.port ];
-    };
-
-    systemd.services.znc = {
-      description = "ZNC Server";
-      wantedBy = [ "multi-user.target" ];
-      after = [ "network.service" ];
-      serviceConfig = {
-        User = cfg.user;
-        Group = cfg.group;
-        Restart = "always";
-        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
-        ExecStop   = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
-      };
-      preStart = ''
-        ${pkgs.coreutils}/bin/mkdir -p ${cfg.dataDir}/configs
-
-        # If mutable, regenerate conf file every time.
-        ${optionalString (!cfg.mutable) ''
-          ${pkgs.coreutils}/bin/echo "znc is set to be system-managed. Now deleting old znc.conf file to be regenerated."
-          ${pkgs.coreutils}/bin/rm -f ${cfg.dataDir}/configs/znc.conf
-        ''}
-
-        # Ensure essential files exist.
-        if [[ ! -f ${cfg.dataDir}/configs/znc.conf ]]; then
-            ${pkgs.coreutils}/bin/echo "No znc.conf file found in ${cfg.dataDir}. Creating one now."
-            ${pkgs.coreutils}/bin/cp --no-clobber ${zncConfFile} ${cfg.dataDir}/configs/znc.conf
-            ${pkgs.coreutils}/bin/chmod u+rw ${cfg.dataDir}/configs/znc.conf
-            ${pkgs.coreutils}/bin/chown ${cfg.user} ${cfg.dataDir}/configs/znc.conf
-        fi
-
-        if [[ ! -f ${cfg.dataDir}/znc.pem ]]; then
-          ${pkgs.coreutils}/bin/echo "No znc.pem file found in ${cfg.dataDir}. Creating one now."
-          ${pkgs.znc}/bin/znc --makepem --datadir ${cfg.dataDir}
-        fi
-
-        # Symlink modules
-        rm ${cfg.dataDir}/modules || true
-        ln -fs ${modules}/lib/znc ${cfg.dataDir}/modules
-      '';
-      script = "${pkgs.znc}/bin/znc --foreground --datadir ${cfg.dataDir} ${toString cfg.extraFlags}";
-    };
-
-    users.users = optional (cfg.user == defaultUser)
-      { name = defaultUser;
-        description = "ZNC server daemon owner";
-        group = defaultUser;
-        uid = config.ids.uids.znc;
-        home = cfg.dataDir;
-        createHome = true;
-      };
-
-    users.groups = optional (cfg.user == defaultUser)
-      { name = defaultUser;
-        gid = config.ids.gids.znc;
-        members = [ defaultUser ];
-      };
-
-  };
-}
diff --git a/nixos/modules/services/networking/znc/default.nix b/nixos/modules/services/networking/znc/default.nix
new file mode 100644
index 0000000000000..bce5b15a19ec7
--- /dev/null
+++ b/nixos/modules/services/networking/znc/default.nix
@@ -0,0 +1,306 @@
+{ config, lib, pkgs, ...}:
+
+with lib;
+
+let
+
+  cfg = config.services.znc;
+
+  defaultUser = "znc";
+
+  modules = pkgs.buildEnv {
+    name = "znc-modules";
+    paths = cfg.modulePackages;
+  };
+
+  listenerPorts = concatMap (l: optional (l ? Port) l.Port)
+    (attrValues (cfg.config.Listener or {}));
+
+  # Converts the config option to a string
+  semanticString = let
+
+      sortedAttrs = set: sort (l: r:
+        if l == "extraConfig" then false # Always put extraConfig last
+        else if isAttrs set.${l} == isAttrs set.${r} then l < r
+        else isAttrs set.${r} # Attrsets should be last, makes for a nice config
+        # This last case occurs when any side (but not both) is an attrset
+        # The order of these is correct when the attrset is on the right
+        # which we're just returning
+      ) (attrNames set);
+
+      # Specifies an attrset that encodes the value according to its type
+      encode = name: value: {
+          null = [];
+          bool = [ "${name} = ${boolToString value}" ];
+          int = [ "${name} = ${toString value}" ];
+
+          # extraConfig should be inserted verbatim
+          string = [ (if name == "extraConfig" then value else "${name} = ${value}") ];
+
+          # Values like `Foo = [ "bar" "baz" ];` should be transformed into
+          #   Foo=bar
+          #   Foo=baz
+          list = concatMap (encode name) value;
+
+          # Values like `Foo = { bar = { Baz = "baz"; Qux = "qux"; Florps = null; }; };` should be transmed into
+          #   <Foo bar>
+          #     Baz=baz
+          #     Qux=qux
+          #   </Foo>
+          set = concatMap (subname: [
+              "<${name} ${subname}>"
+            ] ++ map (line: "\t${line}") (toLines value.${subname}) ++ [
+              "</${name}>"
+            ]) (filter (v: v != null) (attrNames value));
+
+        }.${builtins.typeOf value};
+
+      # One level "above" encode, acts upon a set and uses encode on each name,value pair
+      toLines = set: concatMap (name: encode name set.${name}) (sortedAttrs set);
+
+    in
+      concatStringsSep "\n" (toLines cfg.config);
+
+  semanticTypes = with types; rec {
+    zncAtom = nullOr (either (either int bool) str);
+    zncAttr = attrsOf (nullOr zncConf);
+    zncAll = either (either zncAtom (listOf zncAtom)) zncAttr;
+    zncConf = attrsOf (zncAll // {
+      # Since this is a recursive type and the description by default contains
+      # the description of its subtypes, infinite recursion would occur without
+      # explicitly breaking this cycle
+      description = "znc values (null, atoms (str, int, bool), list of atoms, or attrsets of znc values)";
+    });
+  };
+
+in
+
+{
+
+  imports = [ ./options.nix ];
+
+  options = {
+    services.znc = {
+      enable = mkEnableOption "ZNC";
+
+      user = mkOption {
+        default = "znc";
+        example = "john";
+        type = types.str;
+        description = ''
+          The name of an existing user account to use to own the ZNC server
+          process. If not specified, a default user will be created.
+        '';
+      };
+
+      group = mkOption {
+        default = defaultUser;
+        example = "users";
+        type = types.str;
+        description = ''
+          Group to own the ZNC process.
+        '';
+      };
+
+      dataDir = mkOption {
+        default = "/var/lib/znc/";
+        example = "/home/john/.znc/";
+        type = types.path;
+        description = ''
+          The state directory for ZNC. The config and the modules will be linked
+          to from this directory as well.
+        '';
+      };
+
+      openFirewall = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to open ports in the firewall for ZNC. Does work with
+          ports for listeners specified in
+          <option>services.znc.config.Listener</option>.
+        '';
+      };
+
+      config = mkOption {
+        type = semanticTypes.zncConf;
+        default = {};
+        example = literalExample ''
+          {
+            LoadModule = [ "webadmin" "adminlog" ];
+            User.paul = {
+              Admin = true;
+              Nick = "paul";
+              AltNick = "paul1";
+              LoadModule = [ "chansaver" "controlpanel" ];
+              Network.freenode = {
+                Server = "chat.freenode.net +6697";
+                LoadModule = [ "simple_away" ];
+                Chan = {
+                  "#nixos" = { Detached = false; };
+                  "##linux" = { Disabled = true; };
+                };
+              };
+              Pass.password = {
+                Method = "sha256";
+                Hash = "e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93";
+                Salt = "l5Xryew4g*!oa(ECfX2o";
+              };
+            };
+          }
+        '';
+        description = ''
+          Configuration for ZNC, see
+          <literal>https://wiki.znc.in/Configuration</literal> for details. The
+          Nix value declared here will be translated directly to the xml-like
+          format ZNC expects. This is much more flexible than the legacy options
+          under <option>services.znc.confOptions.*</option>, but also can't do
+          any type checking.
+          </para>
+          <para>
+          You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+          to view the current value. By default it contains a listener for port
+          5000 with SSL enabled.
+          </para>
+          <para>
+          Nix attributes called <literal>extraConfig</literal> will be inserted
+          verbatim into the resulting config file.
+          </para>
+          <para>
+          If <option>services.znc.useLegacyConfig</option> is turned on, the
+          option values in <option>services.znc.confOptions.*</option> will be
+          gracefully be applied to this option.
+          </para>
+          <para>
+          If you intend to update the configuration through this option, be sure
+          to enable <option>services.znc.mutable</option>, otherwise none of the
+          changes here will be applied after the initial deploy.
+        '';
+      };
+
+      configFile = mkOption {
+        type = types.path;
+        example = "~/.znc/configs/znc.conf";
+        description = ''
+          Configuration file for ZNC. It is recommended to use the
+          <option>config</option> option instead.
+          </para>
+          <para>
+          Setting this option will override any auto-generated config file
+          through the <option>confOptions</option> or <option>config</option>
+          options.
+        '';
+      };
+
+      modulePackages = mkOption {
+        type = types.listOf types.package;
+        default = [ ];
+        example = literalExample "[ pkgs.zncModules.fish pkgs.zncModules.push ]";
+        description = ''
+          A list of global znc module packages to add to znc.
+        '';
+      };
+
+      mutable = mkOption {
+        default = true; # TODO: Default to true when config is set, make sure to not delete the old config if present
+        type = types.bool;
+        description = ''
+          Indicates whether to allow the contents of the
+          <literal>dataDir</literal> directory to be changed by the user at
+          run-time.
+          </para>
+          <para>
+          If enabled, modifications to the ZNC configuration after its initial
+          creation are not overwritten by a NixOS rebuild. If disabled, the
+          ZNC configuration is rebuilt on every NixOS rebuild.
+          </para>
+          <para>
+          If the user wants to manage the ZNC service using the web admin
+          interface, this option should be enabled.
+        '';
+      };
+
+      extraFlags = mkOption {
+        default = [ ];
+        example = [ "--debug" ];
+        type = types.listOf types.str;
+        description = ''
+          Extra arguments to use for executing znc.
+        '';
+      };
+    };
+  };
+
+
+  ###### Implementation
+
+  config = mkIf cfg.enable {
+
+    services.znc = {
+      configFile = mkDefault (pkgs.writeText "znc-generated.conf" semanticString);
+      config = {
+        Version = (builtins.parseDrvName pkgs.znc.name).version;
+        Listener.l.Port = mkDefault 5000;
+        Listener.l.SSL = mkDefault true;
+      };
+    };
+
+    networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall listenerPorts;
+
+    systemd.services.znc = {
+      description = "ZNC Server";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network-online.target" ];
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        Restart = "always";
+        ExecStart = "${pkgs.znc}/bin/znc --foreground --datadir ${cfg.dataDir} ${escapeShellArgs cfg.extraFlags}";
+        ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+        ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
+      };
+      preStart = ''
+        mkdir -p ${cfg.dataDir}/configs
+
+        # If mutable, regenerate conf file every time.
+        ${optionalString (!cfg.mutable) ''
+          echo "znc is set to be system-managed. Now deleting old znc.conf file to be regenerated."
+          rm -f ${cfg.dataDir}/configs/znc.conf
+        ''}
+
+        # Ensure essential files exist.
+        if [[ ! -f ${cfg.dataDir}/configs/znc.conf ]]; then
+            echo "No znc.conf file found in ${cfg.dataDir}. Creating one now."
+            cp --no-clobber ${cfg.configFile} ${cfg.dataDir}/configs/znc.conf
+            chmod u+rw ${cfg.dataDir}/configs/znc.conf
+            chown ${cfg.user} ${cfg.dataDir}/configs/znc.conf
+        fi
+
+        if [[ ! -f ${cfg.dataDir}/znc.pem ]]; then
+          echo "No znc.pem file found in ${cfg.dataDir}. Creating one now."
+          ${pkgs.znc}/bin/znc --makepem --datadir ${cfg.dataDir}
+        fi
+
+        # Symlink modules
+        rm ${cfg.dataDir}/modules || true
+        ln -fs ${modules}/lib/znc ${cfg.dataDir}/modules
+      '';
+    };
+
+    users.users = optional (cfg.user == defaultUser)
+      { name = defaultUser;
+        description = "ZNC server daemon owner";
+        group = defaultUser;
+        uid = config.ids.uids.znc;
+        home = cfg.dataDir;
+        createHome = true;
+      };
+
+    users.groups = optional (cfg.user == defaultUser)
+      { name = defaultUser;
+        gid = config.ids.gids.znc;
+        members = [ defaultUser ];
+      };
+
+  };
+}
diff --git a/nixos/modules/services/networking/znc/options.nix b/nixos/modules/services/networking/znc/options.nix
new file mode 100644
index 0000000000000..048dbd7386300
--- /dev/null
+++ b/nixos/modules/services/networking/znc/options.nix
@@ -0,0 +1,270 @@
+{ lib, config, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.znc;
+
+  networkOpts = {
+    options = {
+
+      server = mkOption {
+        type = types.str;
+        example = "chat.freenode.net";
+        description = ''
+          IRC server address.
+        '';
+      };
+
+      port = mkOption {
+        type = types.ints.u16;
+        default = 6697;
+        description = ''
+          IRC server port.
+        '';
+      };
+
+      password = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          IRC server password, such as for a Slack gateway.
+        '';
+      };
+
+      useSSL = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to use SSL to connect to the IRC server.
+        '';
+      };
+
+      modules = mkOption {
+        type = types.listOf types.str;
+        default = [ "simple_away" ];
+        example = literalExample "[ simple_away sasl ]";
+        description = ''
+          ZNC network modules to load.
+        '';
+      };
+
+      channels = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "nixos" ];
+        description = ''
+          IRC channels to join.
+        '';
+      };
+
+      hasBitlbeeControlChannel = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to add the special Bitlbee operations channel.
+        '';
+      };
+
+      extraConf = mkOption {
+        default = "";
+        type = types.lines;
+        example = ''
+          Encoding = ^UTF-8
+          FloodBurst = 4
+          FloodRate = 1.00
+          IRCConnectEnabled = true
+          Ident = johntron
+          JoinDelay = 0
+          Nick = johntron
+        '';
+        description = ''
+          Extra config for the network. Consider using
+          <option>services.znc.config</option> instead.
+        '';
+      };
+    };
+  };
+
+in
+
+{
+
+  options = {
+    services.znc = {
+
+      useLegacyConfig = mkOption {
+        default = true;
+        type = types.bool;
+        description = ''
+          Whether to propagate the legacy options under
+          <option>services.znc.confOptions.*</option> to the znc config. If this
+          is turned on, the znc config will contain a user with the default name
+          "znc", global modules "webadmin" and "adminlog" will be enabled by
+          default, and more, all controlled through the
+          <option>services.znc.confOptions.*</option> options.
+          You can use <command>nix-instantiate --eval --strict '&lt;nixpkgs/nixos&gt;' -A config.services.znc.config</command>
+          to view the current value of the config.
+          </para>
+          <para>
+          In any case, if you need more flexibility,
+          <option>services.znc.config</option> can be used to override/add to
+          all of the legacy options.
+        '';
+      };
+
+      confOptions = {
+        modules = mkOption {
+          type = types.listOf types.str;
+          default = [ "webadmin" "adminlog" ];
+          example = [ "partyline" "webadmin" "adminlog" "log" ];
+          description = ''
+            A list of modules to include in the `znc.conf` file.
+          '';
+        };
+
+        userModules = mkOption {
+          type = types.listOf types.str;
+          default = [ "chansaver" "controlpanel" ];
+          example = [ "chansaver" "controlpanel" "fish" "push" ];
+          description = ''
+            A list of user modules to include in the `znc.conf` file.
+          '';
+        };
+
+        userName = mkOption {
+          default = "znc";
+          example = "johntron";
+          type = types.str;
+          description = ''
+            The user name used to log in to the ZNC web admin interface.
+          '';
+        };
+
+        networks = mkOption {
+          default = { };
+          type = with types; attrsOf (submodule networkOpts);
+          description = ''
+            IRC networks to connect the user to.
+          '';
+          example = literalExample ''
+            {
+              "freenode" = {
+                server = "chat.freenode.net";
+                port = 6697;
+                useSSL = true;
+                modules = [ "simple_away" ];
+              };
+            };
+          '';
+        };
+
+        nick = mkOption {
+          default = "znc-user";
+          example = "john";
+          type = types.str;
+          description = ''
+            The IRC nick.
+          '';
+        };
+
+        passBlock = mkOption {
+          example = literalExample ''
+            &lt;Pass password&gt;
+               Method = sha256
+               Hash = e2ce303c7ea75c571d80d8540a8699b46535be6a085be3414947d638e48d9e93
+               Salt = l5Xryew4g*!oa(ECfX2o
+            &lt;/Pass&gt;
+          '';
+          type = types.str;
+          description = ''
+            Generate with `nix-shell -p znc --command "znc --makepass"`.
+            This is the password used to log in to the ZNC web admin interface.
+            You can also set this through
+            <option>services.znc.config.User.&lt;username&gt;.Pass.Method</option>
+            and co.
+          '';
+        };
+
+        port = mkOption {
+          default = 5000;
+          type = types.int;
+          description = ''
+            Specifies the port on which to listen.
+          '';
+        };
+
+        useSSL = mkOption {
+          default = true;
+          type = types.bool;
+          description = ''
+            Indicates whether the ZNC server should use SSL when listening on
+            the specified port. A self-signed certificate will be generated.
+          '';
+        };
+
+        uriPrefix = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          example = "/znc/";
+          description = ''
+            An optional URI prefix for the ZNC web interface. Can be
+            used to make ZNC available behind a reverse proxy.
+          '';
+        };
+
+        extraZncConf = mkOption {
+          default = "";
+          type = types.lines;
+          description = ''
+            Extra config to `znc.conf` file.
+          '';
+        };
+      };
+
+    };
+  };
+
+  config = mkIf cfg.useLegacyConfig {
+
+    services.znc.config = let
+      c = cfg.confOptions;
+      # defaults here should override defaults set in the non-legacy part
+      mkDefault = mkOverride 900;
+    in {
+      LoadModule = mkDefault c.modules;
+      Listener.l = {
+        Port = mkDefault c.port;
+        IPv4 = mkDefault true;
+        IPv6 = mkDefault true;
+        SSL = mkDefault c.useSSL;
+        URIPrefix = c.uriPrefix;
+      };
+      User.${c.userName} = {
+        Admin = mkDefault true;
+        Nick = mkDefault c.nick;
+        AltNick = mkDefault "${c.nick}_";
+        Ident = mkDefault c.nick;
+        RealName = mkDefault c.nick;
+        LoadModule = mkDefault c.userModules;
+        Network = mapAttrs (name: net: {
+          LoadModule = mkDefault net.modules;
+          Server = mkDefault "${net.server} ${optionalString net.useSSL "+"}${toString net.port} ${net.password}";
+          Chan = optionalAttrs net.hasBitlbeeControlChannel { "&bitlbee" = mkDefault {}; } //
+            listToAttrs (map (n: nameValuePair "#${n}" (mkDefault {})) net.channels);
+          extraConfig = if net.extraConf == "" then mkDefault null else net.extraConf;
+        }) c.networks;
+        extraConfig = [ c.passBlock ];
+      };
+      extraConfig = optional (c.extraZncConf != "") c.extraZncConf;
+    };
+  };
+
+  imports = [
+    (mkRemovedOptionModule ["services" "znc" "zncConf"] ''
+      Instead of `services.znc.zncConf = "... foo ...";`, use
+      `services.znc.configFile = pkgs.writeText "znc.conf" "... foo ...";`.
+    '')
+  ];
+}
diff --git a/nixos/modules/services/printing/cupsd.nix b/nixos/modules/services/printing/cupsd.nix
index dbf18ec1d1149..3a43ebbb889cb 100644
--- a/nixos/modules/services/printing/cupsd.nix
+++ b/nixos/modules/services/printing/cupsd.nix
@@ -250,7 +250,7 @@ in
       drivers = mkOption {
         type = types.listOf types.path;
         default = [];
-        example = literalExample "[ pkgs.gutenprint pkgs.hplip pkgs.splix ]";
+        example = literalExample "with pkgs; [ gutenprint hplip splix cups-googlecloudprint ]";
         description = ''
           CUPS drivers to use. Drivers provided by CUPS, cups-filters,
           Ghostscript and Samba are added unconditionally. If this list contains
@@ -316,6 +316,10 @@ in
             mkdir -m 0755 -p ${cfg.tempDir}
 
             mkdir -m 0755 -p /var/lib/cups
+            # While cups will automatically create self-signed certificates if accessed via TLS,
+            # this directory to store the certificates needs to be created manually.
+            mkdir -m 0700 -p /var/lib/cups/ssl
+
             # Backwards compatibility
             if [ ! -L /etc/cups ]; then
               mv /etc/cups/* /var/lib/cups
diff --git a/nixos/modules/services/search/elasticsearch-curator.nix b/nixos/modules/services/search/elasticsearch-curator.nix
index 43785c392feed..8cb1275284a3d 100644
--- a/nixos/modules/services/search/elasticsearch-curator.nix
+++ b/nixos/modules/services/search/elasticsearch-curator.nix
@@ -82,11 +82,12 @@ in {
   };
 
   config = mkIf cfg.enable {
-
     systemd.services.elasticsearch-curator = {
       startAt = cfg.interval;
       serviceConfig = {
-        ExecStart = ''${pkgs.python36Packages.elasticsearch-curator}/bin/curator --config ${curatorConfig} ${curatorAction}'';
+        ExecStart =
+          "${pkgs.python3Packages.elasticsearch-curator}/bin/curator" +
+          " --config ${curatorConfig} ${curatorAction}";
       };
     };
   };
diff --git a/nixos/modules/services/search/kibana.nix b/nixos/modules/services/search/kibana.nix
index ca36bba58c026..3539b3ddb4f1c 100644
--- a/nixos/modules/services/search/kibana.nix
+++ b/nixos/modules/services/search/kibana.nix
@@ -149,7 +149,10 @@ in {
       after = [ "network.target" "elasticsearch.service" ];
       environment = { BABEL_CACHE_PATH = "${cfg.dataDir}/.babelcache.json"; };
       serviceConfig = {
-        ExecStart = "${cfg.package}/bin/kibana --config ${cfgFile}";
+        ExecStart =
+          "${cfg.package}/bin/kibana" +
+          " --config ${cfgFile}" +
+          " --path.data ${cfg.dataDir}";
         User = "kibana";
         WorkingDirectory = cfg.dataDir;
       };
diff --git a/nixos/modules/services/search/solr.nix b/nixos/modules/services/search/solr.nix
index 90140a337ed8c..7200c40e89f7f 100644
--- a/nixos/modules/services/search/solr.nix
+++ b/nixos/modules/services/search/solr.nix
@@ -6,142 +6,105 @@ let
 
   cfg = config.services.solr;
 
-  # Assemble all jars needed for solr
-  solrJars = pkgs.stdenv.mkDerivation {
-    name = "solr-jars";
-
-    src = pkgs.fetchurl {
-      url = http://archive.apache.org/dist/tomcat/tomcat-5/v5.5.36/bin/apache-tomcat-5.5.36.tar.gz;
-      sha256 = "01mzvh53wrs1p2ym765jwd00gl6kn8f9k3nhdrnhdqr8dhimfb2p";
-    };
-
-    installPhase = ''
-      mkdir -p $out/lib
-      cp common/lib/*.jar $out/lib/
-      ln -s ${pkgs.ant}/lib/ant/lib/ant.jar $out/lib/
-      ln -s ${cfg.solrPackage}/lib/ext/* $out/lib/
-      ln -s ${pkgs.jdk.home}/lib/tools.jar $out/lib/
-    '' + optionalString (cfg.extraJars != []) ''
-      for f in ${concatStringsSep " " cfg.extraJars}; do
-         cp $f $out/lib
-      done
-    '';
-  };
-
-in {
+in
 
+{
   options = {
     services.solr = {
-      enable = mkOption {
-        type = types.bool;
-        default = false;
-        description = ''
-          Enables the solr service.
-        '';
-      };
-
-      javaPackage = mkOption {
-        type = types.package;
-        default = pkgs.jre;
-        defaultText = "pkgs.jre";
-        description = ''
-          Which Java derivation to use for running solr.
-        '';
-      };
+      enable = mkEnableOption "Enables the solr service.";
 
-      solrPackage = mkOption {
+      package = mkOption {
         type = types.package;
         default = pkgs.solr;
         defaultText = "pkgs.solr";
-        description = ''
-          Which solr derivation to use for running solr.
-        '';
+        description = "Which Solr package to use.";
       };
 
-      extraJars = mkOption {
-        type = types.listOf types.path;
-        default = [];
-        description = ''
-          List of paths pointing to jars. Jars are copied to commonLibFolder to be available to java/solr.
-        '';
+      port = mkOption {
+        type = types.int;
+        default = 8983;
+        description = "Port on which Solr is ran.";
       };
 
-      log4jConfiguration = mkOption {
-        type = types.lines;
-        default = ''
-          log4j.rootLogger=INFO, stdout
-          log4j.appender.stdout=org.apache.log4j.ConsoleAppender
-          log4j.appender.stdout.Target=System.out
-          log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
-          log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
-        '';
-        description = ''
-          Contents of the <literal>log4j.properties</literal> used. By default,
-          everything is logged to stdout (picked up by systemd) with level INFO.
-        '';
+      stateDir = mkOption {
+        type = types.path;
+        default = "/var/lib/solr";
+        description = "The solr home directory containing config, data, and logging files.";
       };
 
-      user = mkOption {
-        type = types.str;
-        description = ''
-          The user that should run the solr process and.
-          the working directories.
-        '';
+      extraJavaOptions = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = "Extra command line options given to the java process running Solr.";
       };
 
-      group = mkOption {
+      user = mkOption {
         type = types.str;
-        description = ''
-          The group that will own the working directory.
-        '';
+        default = "solr";
+        description = "User under which Solr is ran.";
       };
 
-      solrHome = mkOption {
+      group = mkOption {
         type = types.str;
-        description = ''
-          The solr home directory. It is your own responsibility to
-          make sure this directory contains a working solr configuration,
-          and is writeable by the the user running the solr service.
-          Failing to do so, the solr will not start properly.
-        '';
-      };
-
-      extraJavaOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          Extra command line options given to the java process running
-          solr.
-        '';
-      };
-
-      extraWinstoneOptions = mkOption {
-        type = types.listOf types.str;
-        default = [];
-        description = ''
-          Extra command line options given to the Winstone, which is
-          the servlet container hosting solr.
-        '';
+        default = "solr";
+        description = "Group under which Solr is ran.";
       };
     };
   };
 
   config = mkIf cfg.enable {
 
-    services.winstone.solr = {
-      serviceName = "solr";
-      inherit (cfg) user group javaPackage;
-      warFile = "${cfg.solrPackage}/lib/solr.war";
-      extraOptions = [
-        "--commonLibFolder=${solrJars}/lib"
-        "--useJasper"
-      ] ++ cfg.extraWinstoneOptions;
-      extraJavaOptions = [
-        "-Dsolr.solr.home=${cfg.solrHome}"
-        "-Dlog4j.configuration=file://${pkgs.writeText "log4j.properties" cfg.log4jConfiguration}"
-      ] ++ cfg.extraJavaOptions;
+    environment.systemPackages = [ cfg.package ];
+
+    systemd.services.solr = {
+      after = [ "network.target" "remote-fs.target" "nss-lookup.target" "systemd-journald-dev-log.socket" ];
+      wantedBy = [ "multi-user.target" ];
+
+      environment = {
+        SOLR_HOME = "${cfg.stateDir}/data";
+        LOG4J_PROPS = "${cfg.stateDir}/log4j2.xml";
+        SOLR_LOGS_DIR = "${cfg.stateDir}/logs";
+        SOLR_PORT = "${toString cfg.port}";
+      };
+      path = with pkgs; [
+        gawk
+        procps
+      ];
+      preStart = ''
+        mkdir -p "${cfg.stateDir}/data";
+        mkdir -p "${cfg.stateDir}/logs";
+
+        if ! test -e "${cfg.stateDir}/data/solr.xml"; then
+          install -D -m0640 ${cfg.package}/server/solr/solr.xml "${cfg.stateDir}/data/solr.xml"
+          install -D -m0640 ${cfg.package}/server/solr/zoo.cfg "${cfg.stateDir}/data/zoo.cfg"
+        fi
+
+        if ! test -e "${cfg.stateDir}/log4j2.xml"; then
+          install -D -m0640 ${cfg.package}/server/resources/log4j2.xml "${cfg.stateDir}/log4j2.xml"
+        fi
+      '';
+
+      serviceConfig = {
+        User = cfg.user;
+        Group = cfg.group;
+        ExecStart="${cfg.package}/bin/solr start -f -a \"${concatStringsSep " " cfg.extraJavaOptions}\"";
+        ExecStop="${cfg.package}/bin/solr stop";
+      };
     };
 
+    users.users = optionalAttrs (cfg.user == "solr") (singleton
+      { name = "solr";
+        group = cfg.group;
+        home = cfg.stateDir;
+        createHome = true;
+        uid = config.ids.uids.solr;
+      });
+
+    users.groups = optionalAttrs (cfg.group == "solr") (singleton
+      { name = "solr";
+        gid = config.ids.gids.solr;
+      });
+
   };
 
 }
diff --git a/nixos/modules/services/security/certmgr.nix b/nixos/modules/services/security/certmgr.nix
index 22d5817ec4f0d..e89078883ebed 100644
--- a/nixos/modules/services/security/certmgr.nix
+++ b/nixos/modules/services/security/certmgr.nix
@@ -30,13 +30,20 @@ let
 
   preStart = ''
     ${concatStringsSep " \\\n" (["mkdir -p"] ++ map escapeShellArg specPaths)}
-    ${pkgs.certmgr}/bin/certmgr -f ${certmgrYaml} check
+    ${cfg.package}/bin/certmgr -f ${certmgrYaml} check
   '';
 in
 {
   options.services.certmgr = {
     enable = mkEnableOption "certmgr";
 
+    package = mkOption {
+      type = types.package;
+      default = pkgs.certmgr;
+      defaultText = "pkgs.certmgr";
+      description = "Which certmgr package to use in the service.";
+    };
+
     defaultRemote = mkOption {
       type = types.str;
       default = "127.0.0.1:8888";
@@ -187,7 +194,7 @@ in
       serviceConfig = {
         Restart = "always";
         RestartSec = "10s";
-        ExecStart = "${pkgs.certmgr}/bin/certmgr -f ${certmgrYaml}";
+        ExecStart = "${cfg.package}/bin/certmgr -f ${certmgrYaml}";
       };
     };
   };
diff --git a/nixos/modules/services/security/clamav.nix b/nixos/modules/services/security/clamav.nix
index 9ad0095252dea..04b433f8f2bf6 100644
--- a/nixos/modules/services/security/clamav.nix
+++ b/nixos/modules/services/security/clamav.nix
@@ -95,7 +95,7 @@ in
     environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
     environment.etc."clamav/clamd.conf".source = clamdConfigFile;
 
-    systemd.services.clamav-daemon = optionalAttrs cfg.daemon.enable {
+    systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
       description = "ClamAV daemon (clamd)";
       after = optional cfg.updater.enable "clamav-freshclam.service";
       requires = optional cfg.updater.enable "clamav-freshclam.service";
@@ -116,7 +116,7 @@ in
       };
     };
 
-    systemd.timers.clamav-freshclam = optionalAttrs cfg.updater.enable {
+    systemd.timers.clamav-freshclam = mkIf cfg.updater.enable {
       description = "Timer for ClamAV virus database updater (freshclam)";
       wantedBy = [ "timers.target" ];
       timerConfig = {
@@ -125,7 +125,7 @@ in
       };
     };
 
-    systemd.services.clamav-freshclam = optionalAttrs cfg.updater.enable {
+    systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
       description = "ClamAV virus database updater (freshclam)";
       restartTriggers = [ freshclamConfigFile ];
 
@@ -137,6 +137,7 @@ in
       serviceConfig = {
         Type = "oneshot";
         ExecStart = "${pkg}/bin/freshclam";
+        SuccessExitStatus = "1"; # if databases are up to date
         PrivateTmp = "yes";
         PrivateDevices = "yes";
       };
diff --git a/nixos/modules/services/security/munge.nix b/nixos/modules/services/security/munge.nix
index 5bca15833544b..504bc66c6d10f 100644
--- a/nixos/modules/services/security/munge.nix
+++ b/nixos/modules/services/security/munge.nix
@@ -50,11 +50,9 @@ in
       path = [ pkgs.munge pkgs.coreutils ];
 
       preStart = ''
-        chmod 0700 ${cfg.password}
+        chmod 0400 ${cfg.password}
         mkdir -p /var/lib/munge -m 0711
         chown -R munge:munge /var/lib/munge
-        mkdir -p /var/log/munge -m 0700
-        chown -R munge:munge /var/log/munge
         mkdir -p /run/munge -m 0755
         chown -R munge:munge /run/munge
       '';
diff --git a/nixos/modules/services/security/nginx-sso.nix b/nixos/modules/services/security/nginx-sso.nix
new file mode 100644
index 0000000000000..d792f90abe64b
--- /dev/null
+++ b/nixos/modules/services/security/nginx-sso.nix
@@ -0,0 +1,58 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.nginx.sso;
+  pkg = getBin pkgs.nginx-sso;
+  configYml = pkgs.writeText "nginx-sso.yml" (builtins.toJSON cfg.configuration);
+in {
+  options.services.nginx.sso = {
+    enable = mkEnableOption "nginx-sso service";
+
+    configuration = mkOption {
+      type = types.attrsOf types.unspecified;
+      default = {};
+      example = literalExample ''
+        {
+          listen = { addr = "127.0.0.1"; port = 8080; };
+
+          providers.token.tokens = {
+            myuser = "MyToken";
+          };
+
+          acl = {
+            rule_sets = [
+              {
+                rules = [ { field = "x-application"; equals = "MyApp"; } ];
+                allow = [ "myuser" ];
+              }
+            ];
+          };
+        }
+      '';
+      description = ''
+        nginx-sso configuration
+        (<link xlink:href="https://github.com/Luzifer/nginx-sso/wiki/Main-Configuration">documentation</link>)
+        as a Nix attribute set.
+      '';
+    };
+  };
+
+  config = mkIf cfg.enable {
+    systemd.services.nginx-sso = {
+      description = "Nginx SSO Backend";
+      after = [ "network.target" ];
+      wantedBy = [ "multi-user.target" ];
+      serviceConfig = {
+        ExecStart = ''
+          ${pkg}/bin/nginx-sso \
+            --config ${configYml} \
+            --frontend-dir ${pkg}/share/frontend
+        '';
+        Restart = "always";
+        DynamicUser = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix
index 9f0261038d5be..8136a5c763a9d 100644
--- a/nixos/modules/services/security/sks.nix
+++ b/nixos/modules/services/security/sks.nix
@@ -5,6 +5,9 @@ with lib;
 let
   cfg = config.services.sks;
   sksPkg = cfg.package;
+  dbConfig = pkgs.writeText "DB_CONFIG" ''
+    ${cfg.extraDbConfig}
+  '';
 
 in {
   meta.maintainers = with maintainers; [ primeos calbrecht jcumming ];
@@ -39,6 +42,20 @@ in {
         '';
       };
 
+      extraDbConfig = mkOption {
+        type = types.str;
+        default = "";
+        description = ''
+          Set contents of the files "KDB/DB_CONFIG" and "PTree/DB_CONFIG" within
+          the ''${dataDir} directory. This is used to configure options for the
+          database for the sks key server.
+
+          Documentation of available options are available in the file named
+          "sampleConfig/DB_CONFIG" in the following repository:
+          https://bitbucket.org/skskeyserver/sks-keyserver/src
+        '';
+      };
+
       hkpAddress = mkOption {
         default = [ "127.0.0.1" "::1" ];
         type = types.listOf types.str;
@@ -99,6 +116,17 @@ in {
           ${lib.optionalString (cfg.webroot != null)
             "ln -sfT \"${cfg.webroot}\" web"}
           mkdir -p dump
+          # Check that both database configs are symlinks before overwriting them
+          if [ -e KDB/DB_CONFIG ] && [ ! -L KBD/DB_CONFIG ]; then
+            echo "KDB/DB_CONFIG exists but is not a symlink." >&2
+            exit 1
+          fi
+          if [ -e PTree/DB_CONFIG ] && [ ! -L PTree/DB_CONFIG ]; then
+            echo "PTree/DB_CONFIG exists but is not a symlink." >&2
+            exit 1
+          fi
+          ln -sf ${dbConfig} KDB/DB_CONFIG
+          ln -sf ${dbConfig} PTree/DB_CONFIG
           ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/
           ${sksPkg}/bin/sks cleandb || true
           ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
diff --git a/nixos/modules/services/security/sshguard.nix b/nixos/modules/services/security/sshguard.nix
index 137c3d610186c..3892cd5c72b86 100644
--- a/nixos/modules/services/security/sshguard.nix
+++ b/nixos/modules/services/security/sshguard.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   cfg = config.services.sshguard;
+
 in {
 
   ###### interface
@@ -77,65 +78,65 @@ in {
             Systemd services sshguard should receive logs of.
           '';
       };
-
     };
-
   };
 
-
   ###### implementation
 
   config = mkIf cfg.enable {
 
-    environment.systemPackages = [ pkgs.sshguard pkgs.iptables pkgs.ipset ];
-
     environment.etc."sshguard.conf".text = let
-        list_services = ( name:  "-t ${name} ");
-      in ''
-        BACKEND="${pkgs.sshguard}/libexec/sshg-fw-ipset"
-        LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl -afb -p info -n1 ${toString (map list_services cfg.services)} -o cat"
+      args = lib.concatStringsSep " " ([
+        "-afb"
+        "-p info"
+        "-o cat"
+        "-n1"
+      ] ++ (map (name: "-t ${escapeShellArg name}") cfg.services));
+    in ''
+      BACKEND="${pkgs.sshguard}/libexec/sshg-fw-ipset"
+      LOGREADER="LANG=C ${pkgs.systemd}/bin/journalctl ${args}"
+    '';
+
+    systemd.services.sshguard = {
+      description = "SSHGuard brute-force attacks protection system";
+
+      wantedBy = [ "multi-user.target" ];
+      after = [ "network.target" ];
+      partOf = optional config.networking.firewall.enable "firewall.service";
+
+      path = with pkgs; [ 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
+      '';
+
+      preStop = ''
+        ${pkgs.iptables}/bin/iptables  -D INPUT -m set --match-set sshguard4 src -j DROP
+        ${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP
       '';
 
-    systemd.services.sshguard =
-      { description = "SSHGuard brute-force attacks protection system";
-
-        wantedBy = [ "multi-user.target" ];
-        after = [ "network.target" ];
-        partOf = optional config.networking.firewall.enable "firewall.service";
-
-        path = [ pkgs.iptables pkgs.ipset pkgs.iproute pkgs.systemd ];
-
-        postStart = ''
-          mkdir -p /var/lib/sshguard
-          ${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
-        '';
-
-        preStop = ''
-          ${pkgs.iptables}/bin/iptables -D INPUT -m set --match-set sshguard4 src -j DROP
-          ${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP
-        '';
-
-        unitConfig.Documentation = "man:sshguard(8)";
-
-        serviceConfig = {
-            Type = "simple";
-            ExecStart = let
-                list_whitelist = ( name:  "-w ${name} ");
-              in ''
-                 ${pkgs.sshguard}/bin/sshguard -a ${toString cfg.attack_threshold} ${optionalString (cfg.blacklist_threshold != null) "-b ${toString cfg.blacklist_threshold}:${cfg.blacklist_file} "}-i /run/sshguard/sshguard.pid -p ${toString cfg.blocktime} -s ${toString cfg.detection_time} ${toString (map list_whitelist cfg.whitelist)}
-              '';
-            PIDFile = "/run/sshguard/sshguard.pid";
-            Restart = "always";
-
-            ReadOnlyDirectories = "/";
-            ReadWriteDirectories = "/run/sshguard /var/lib/sshguard";
-            RuntimeDirectory = "sshguard";
-            StateDirectory = "sshguard";
-            CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
-         };
+      unitConfig.Documentation = "man:sshguard(8)";
+
+      serviceConfig = {
+        Type = "simple";
+        ExecStart = let
+          args = lib.concatStringsSep " " ([
+            "-a ${toString cfg.attack_threshold}"
+            "-p ${toString cfg.blocktime}"
+            "-s ${toString cfg.detection_time}"
+            (optionalString (cfg.blacklist_threshold != null) "-b ${toString cfg.blacklist_threshold}:${cfg.blacklist_file}")
+          ] ++ (map (name: "-w ${escapeShellArg name}") cfg.whitelist));
+        in "${pkgs.sshguard}/bin/sshguard ${args}";
+        Restart = "always";
+        ProtectSystem = "strict";
+        ProtectHome = "tmpfs";
+        RuntimeDirectory = "sshguard";
+        StateDirectory = "sshguard";
+        CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
       };
+    };
   };
 }
diff --git a/nixos/modules/services/security/tor.nix b/nixos/modules/services/security/tor.nix
index def77ba69e58b..61b751bb518b8 100644
--- a/nixos/modules/services/security/tor.nix
+++ b/nixos/modules/services/security/tor.nix
@@ -57,6 +57,11 @@ let
     AutomapHostsSuffixes ${concatStringsSep "," cfg.client.dns.automapHostsSuffixes}
     ''}
   ''
+  # Explicitly disable the SOCKS server if the client is disabled.  In
+  # particular, this makes non-anonymous hidden services possible.
+  + optionalString (! cfg.client.enable) ''
+  SOCKSPort 0
+  ''
   # Relay config
   + optionalString cfg.relay.enable ''
     ORPort ${toString cfg.relay.port}
@@ -87,6 +92,7 @@ let
   # Hidden services
   + concatStrings (flip mapAttrsToList cfg.hiddenServices (n: v: ''
     HiddenServiceDir ${torDirectory}/onion/${v.name}
+    ${optionalString (v.version != null) "HiddenServiceVersion ${toString v.version}"}
     ${flip concatMapStrings v.map (p: ''
       HiddenServicePort ${toString p.port} ${p.destination}
     '')}
@@ -208,7 +214,7 @@ in
           enable = mkOption {
             type = types.bool;
             default = false;
-            description = "Whether to enable tor transaprent proxy";
+            description = "Whether to enable tor transparent proxy";
           };
 
           listenAddress = mkOption {
@@ -662,6 +668,12 @@ in
                  };
                }));
              };
+
+             version = mkOption {
+               default = null;
+               description = "Rendezvous service descriptor version to publish for the hidden service. Currently, versions 2 and 3 are supported. (Default: 2)";
+               type = types.nullOr (types.enum [ 2 3 ]);
+             };
           };
 
           config = {
diff --git a/nixos/modules/services/system/cloud-init.nix b/nixos/modules/services/system/cloud-init.nix
index 1a700828ce77c..3ad555f78ef82 100644
--- a/nixos/modules/services/system/cloud-init.nix
+++ b/nixos/modules/services/system/cloud-init.nix
@@ -3,13 +3,20 @@
 with lib;
 
 let cfg = config.services.cloud-init;
-    path = with pkgs; [ cloud-init nettools utillinux e2fsprogs shadow openssh iproute ];
+    path = with pkgs; [
+      cloud-init
+      iproute
+      nettools
+      openssh
+      shadow
+      utillinux
+    ] ++ optional cfg.btrfs.enable btrfs-progs
+      ++ optional cfg.ext4.enable e2fsprogs
+    ;
 in
 {
   options = {
-
     services.cloud-init = {
-
       enable = mkOption {
         type = types.bool;
         default = false;
@@ -29,6 +36,22 @@ in
         '';
       };
 
+      btrfs.enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Allow the cloud-init service to operate `btrfs` filesystem.
+        '';
+      };
+
+      ext4.enable = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Allow the cloud-init service to operate `ext4` filesystem.
+        '';
+      };
+
       config = mkOption {
         type = types.str;
         default = ''
@@ -96,7 +119,7 @@ in
           { Type = "oneshot";
             ExecStart = "${pkgs.cloud-init}/bin/cloud-init init --local";
             RemainAfterExit = "yes";
-            TimeoutSec = "0";
+            TimeoutSec = "infinity";
             StandardOutput = "journal+console";
           };
       };
@@ -114,7 +137,7 @@ in
           { Type = "oneshot";
             ExecStart = "${pkgs.cloud-init}/bin/cloud-init init";
             RemainAfterExit = "yes";
-            TimeoutSec = "0";
+            TimeoutSec = "infinity";
             StandardOutput = "journal+console";
           };
       };
@@ -130,7 +153,7 @@ in
           { Type = "oneshot";
             ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=config";
             RemainAfterExit = "yes";
-            TimeoutSec = "0";
+            TimeoutSec = "infinity";
             StandardOutput = "journal+console";
           };
       };
@@ -146,7 +169,7 @@ in
           { Type = "oneshot";
             ExecStart = "${pkgs.cloud-init}/bin/cloud-init modules --mode=final";
             RemainAfterExit = "yes";
-            TimeoutSec = "0";
+            TimeoutSec = "infinity";
             StandardOutput = "journal+console";
           };
       };
diff --git a/nixos/modules/services/system/kerberos.nix b/nixos/modules/services/system/kerberos.nix
deleted file mode 100644
index e2c45ed64ac0f..0000000000000
--- a/nixos/modules/services/system/kerberos.nix
+++ /dev/null
@@ -1,64 +0,0 @@
-{pkgs, config, lib, ...}:
-
-let
-
-  inherit (lib) mkOption mkIf;
-
-  inherit (pkgs) heimdalFull;
-
-  stateDir = "/var/heimdal";
-in
-
-{
-
-  ###### interface
-
-  options = {
-
-    services.kerberos_server = {
-
-      enable = mkOption {
-        default = false;
-        description = ''
-          Enable the kerberos authentification server.
-        '';
-      };
-
-    };
-
-  };
-
-
-  ###### implementation
-
-  config = mkIf config.services.kerberos_server.enable {
-
-    environment.systemPackages = [ heimdalFull ];
-
-    services.xinetd.enable = true;
-    services.xinetd.services = lib.singleton
-      { name = "kerberos-adm";
-        flags = "REUSE NAMEINARGS";
-        protocol = "tcp";
-        user = "root";
-        server = "${pkgs.tcp_wrappers}/bin/tcpd";
-        serverArgs = "${pkgs.heimdalFull}/libexec/heimdal/kadmind";
-      };
-
-    systemd.services.kdc = {
-      description = "Key Distribution Center daemon";
-      wantedBy = [ "multi-user.target" ];
-      preStart = ''
-        mkdir -m 0755 -p ${stateDir}
-      '';
-      script = "${heimdalFull}/libexec/heimdal/kdc";
-    };
-
-    systemd.services.kpasswdd = {
-      description = "Kerberos Password Changing daemon";
-      wantedBy = [ "multi-user.target" ];
-      script = "${heimdalFull}/libexec/heimdal/kpasswdd";
-    };
-  };
-
-}
diff --git a/nixos/modules/services/system/kerberos/default.nix b/nixos/modules/services/system/kerberos/default.nix
new file mode 100644
index 0000000000000..26ac85de402fb
--- /dev/null
+++ b/nixos/modules/services/system/kerberos/default.nix
@@ -0,0 +1,80 @@
+{pkgs, config, lib, ...}:
+
+let
+  inherit (lib) mkOption mkIf types length attrNames;
+  cfg = config.services.kerberos_server;
+  kerberos = config.krb5.kerberos;
+
+  aclEntry = {
+    options = {
+      principal = mkOption {
+        type = types.str;
+        description = "Which principal the rule applies to";
+      };
+      access = mkOption {
+        type = types.either
+          (types.listOf (types.enum ["add" "cpw" "delete" "get" "list" "modify"]))
+          (types.enum ["all"]);
+        default = "all";
+        description = "The changes the principal is allowed to make.";
+      };
+      target = mkOption {
+        type = types.str;
+        default = "*";
+        description = "The principals that 'access' applies to.";
+      };
+    };
+  };
+
+  realm = {
+    options = {
+      acl = mkOption {
+        type = types.listOf (types.submodule aclEntry);
+        default = [
+          { principal = "*/admin"; access = "all"; }
+          { principal = "admin"; access = "all"; }
+        ];
+        description = ''
+          The privileges granted to a user.
+        '';
+      };
+    };
+  };
+in
+
+{
+  imports = [
+    ./mit.nix
+    ./heimdal.nix
+  ];
+
+  ###### interface
+  options = {
+    services.kerberos_server = {
+      enable = mkOption {
+        default = false;
+        description = ''
+          Enable the kerberos authentification server.
+        '';
+      };
+
+      realms = mkOption {
+        type = types.attrsOf (types.submodule realm);
+        description = ''
+          The realm(s) to serve keys for.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    environment.systemPackages = [ kerberos ];
+    assertions = [{
+      assertion = length (attrNames cfg.realms) <= 1;
+      message = "Only one realm per server is currently supported.";
+    }];
+  };
+}
diff --git a/nixos/modules/services/system/kerberos/heimdal.nix b/nixos/modules/services/system/kerberos/heimdal.nix
new file mode 100644
index 0000000000000..d0f470f836edd
--- /dev/null
+++ b/nixos/modules/services/system/kerberos/heimdal.nix
@@ -0,0 +1,68 @@
+{ pkgs, config, lib, ... } :
+
+let
+  inherit (lib) mkIf concatStringsSep concatMapStrings toList mapAttrs
+    mapAttrsToList attrValues;
+  cfg = config.services.kerberos_server;
+  kerberos = config.krb5.kerberos;
+  stateDir = "/var/heimdal";
+  aclFiles = mapAttrs
+    (name: {acl, ...}: pkgs.writeText "${name}.acl" (concatMapStrings ((
+      {principal, access, target, ...} :
+      "${principal}\t${concatStringsSep "," (toList access)}\t${target}\n"
+    )) acl)) cfg.realms;
+
+  kdcConfigs = mapAttrsToList (name: value: ''
+    database = {
+      dbname = ${stateDir}/heimdal
+      acl_file = ${value}
+    }
+  '') aclFiles;
+  kdcConfFile = pkgs.writeText "kdc.conf" ''
+    [kdc]
+    ${concatStringsSep "\n" kdcConfigs}
+  '';
+in
+
+{
+  # No documentation about correct triggers, so guessing at them.
+
+  config = mkIf (cfg.enable && kerberos == pkgs.heimdalFull) {
+    systemd.services.kadmind = {
+      description = "Kerberos Administration Daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig.ExecStart =
+        "${kerberos}/libexec/heimdal/kadmind --config-file=/etc/heimdal-kdc/kdc.conf";
+      restartTriggers = [ kdcConfFile ];
+    };
+
+    systemd.services.kdc = {
+      description = "Key Distribution Center daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig.ExecStart =
+        "${kerberos}/libexec/heimdal/kdc --config-file=/etc/heimdal-kdc/kdc.conf";
+      restartTriggers = [ kdcConfFile ];
+    };
+
+    systemd.services.kpasswdd = {
+      description = "Kerberos Password Changing daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig.ExecStart = "${kerberos}/libexec/heimdal/kpasswdd";
+      restartTriggers = [ kdcConfFile ];
+    };
+
+    environment.etc = {
+      # Can be set via the --config-file option to KDC
+      "heimdal-kdc/kdc.conf".source = kdcConfFile;
+    };
+  };
+}
diff --git a/nixos/modules/services/system/kerberos/mit.nix b/nixos/modules/services/system/kerberos/mit.nix
new file mode 100644
index 0000000000000..a53d9dd0c6b5c
--- /dev/null
+++ b/nixos/modules/services/system/kerberos/mit.nix
@@ -0,0 +1,68 @@
+{ pkgs, config, lib, ... } :
+
+let
+  inherit (lib) mkIf concatStrings concatStringsSep concatMapStrings toList
+    mapAttrs mapAttrsToList attrValues;
+  cfg = config.services.kerberos_server;
+  kerberos = config.krb5.kerberos;
+  stateDir = "/var/lib/krb5kdc";
+  PIDFile = "/run/kdc.pid";
+  aclMap = {
+    add = "a"; cpw = "c"; delete = "d"; get = "i"; list = "l"; modify = "m";
+    all = "*";
+  };
+  aclFiles = mapAttrs
+    (name: {acl, ...}: (pkgs.writeText "${name}.acl" (concatMapStrings (
+      {principal, access, target, ...} :
+      let access_code = map (a: aclMap.${a}) (toList access); in
+      "${principal} ${concatStrings access_code} ${target}\n"
+    ) acl))) cfg.realms;
+  kdcConfigs = mapAttrsToList (name: value: ''
+    ${name} = {
+      acl_file = ${value}
+    }
+  '') aclFiles;
+  kdcConfFile = pkgs.writeText "kdc.conf" ''
+    [realms]
+    ${concatStringsSep "\n" kdcConfigs}
+  '';
+  env = {
+    # What Debian uses, could possibly link directly to Nix store?
+    KRB5_KDC_PROFILE = "/etc/krb5kdc/kdc.conf";
+  };
+in
+
+{
+  config = mkIf (cfg.enable && kerberos == pkgs.krb5Full) {
+    systemd.services.kadmind = {
+      description = "Kerberos Administration Daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig.ExecStart = "${kerberos}/bin/kadmind -nofork";
+      restartTriggers = [ kdcConfFile ];
+      environment = env;
+    };
+
+    systemd.services.kdc = {
+      description = "Key Distribution Center daemon";
+      wantedBy = [ "multi-user.target" ];
+      preStart = ''
+        mkdir -m 0755 -p ${stateDir}
+      '';
+      serviceConfig = {
+        Type = "forking";
+        PIDFile = PIDFile;
+        ExecStart = "${kerberos}/bin/krb5kdc -P ${PIDFile}";
+      };
+      restartTriggers = [ kdcConfFile ];
+      environment = env;
+    };
+
+    environment.etc = {
+      "krb5kdc/kdc.conf".source = kdcConfFile;
+    };
+    environment.variables = env;
+  };
+}
diff --git a/nixos/modules/services/system/nscd.conf b/nixos/modules/services/system/nscd.conf
index 6d0dcacf97787..603a5d01accee 100644
--- a/nixos/modules/services/system/nscd.conf
+++ b/nixos/modules/services/system/nscd.conf
@@ -1,28 +1,52 @@
+# We basically use nscd as a proxy for forwarding nss requests to appropriate
+# nss modules, as we run nscd with LD_LIBRARY_PATH set to the directory
+# containing all such modules
+# Note that we can not use `enable-cache no` As this will actually cause nscd
+# to just reject the nss requests it receives, which then causes glibc to
+# fallback to trying to handle the request by itself. Which won't work as glibc
+# is not aware of the path in which the nss modules live.  As a workaround, we
+# have `enable-cache yes` with an explicit ttl of 0
 server-user             nscd
 threads                 1
 paranoia                no
 debug-level             0
 
 enable-cache            passwd          yes
-positive-time-to-live   passwd          600
-negative-time-to-live   passwd          20
+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           3600
-negative-time-to-live   group           60
+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           5
+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/saslauthd.nix b/nixos/modules/services/system/saslauthd.nix
index c8ddca9a0db63..8fcf4fb91fc46 100644
--- a/nixos/modules/services/system/saslauthd.nix
+++ b/nixos/modules/services/system/saslauthd.nix
@@ -16,7 +16,7 @@ in
 
     services.saslauthd = {
 
-      enable = mkEnableOption "Whether to enable the Cyrus SASL authentication daemon.";
+      enable = mkEnableOption "saslauthd, the Cyrus SASL authentication daemon";
 
       package = mkOption {
         default = pkgs.cyrus_sasl.bin;
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index 719eb76f42c28..f544928fb6b1d 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -143,6 +143,9 @@ in
           ${getLib pkgs.lz4}/lib/liblz4*.so*               mr,
           ${getLib pkgs.libkrb5}/lib/lib*.so*              mr,
           ${getLib pkgs.keyutils}/lib/libkeyutils*.so*     mr,
+          ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so.* mr,
+          ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so.* mr,
+          ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so.* mr,
 
           @{PROC}/sys/kernel/random/uuid   r,
           @{PROC}/sys/vm/overcommit_memory r,
diff --git a/nixos/modules/services/ttys/kmscon.nix b/nixos/modules/services/ttys/kmscon.nix
index 59c45fcb44ee1..82b6a51028e34 100644
--- a/nixos/modules/services/ttys/kmscon.nix
+++ b/nixos/modules/services/ttys/kmscon.nix
@@ -4,6 +4,8 @@ let
 
   cfg = config.services.kmscon;
 
+  autologinArg = lib.optionalString (cfg.autologinUser != null) "-f ${cfg.autologinUser}";
+
   configDir = pkgs.writeTextFile { name = "kmscon-config"; destination = "/kmscon.conf"; text = cfg.extraConfig; };
 in {
   options = {
@@ -39,6 +41,15 @@ in {
         default = "";
         example = "--term xterm-256color";
       };
+
+      autologinUser = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Username of the account that will be automatically logged in at the console.
+          If unspecified, a login prompt is shown as usual.
+        '';
+      };
     };
   };
 
@@ -61,7 +72,7 @@ in {
 
       [Service]
       ExecStart=
-      ExecStart=${pkgs.kmscon}/bin/kmscon "--vt=%I" ${cfg.extraOptions} --seats=seat0 --no-switchvt --configdir ${configDir} --login -- ${pkgs.shadow}/bin/login -p
+      ExecStart=${pkgs.kmscon}/bin/kmscon "--vt=%I" ${cfg.extraOptions} --seats=seat0 --no-switchvt --configdir ${configDir} --login -- ${pkgs.shadow}/bin/login -p ${autologinArg}
       UtmpIdentifier=%I
       TTYPath=/dev/%I
       TTYReset=yes
diff --git a/nixos/modules/services/web-apps/atlassian/confluence.nix b/nixos/modules/services/web-apps/atlassian/confluence.nix
index f896d92fd6fc3..15744d90cc79f 100644
--- a/nixos/modules/services/web-apps/atlassian/confluence.nix
+++ b/nixos/modules/services/web-apps/atlassian/confluence.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.services.confluence;
 
-  pkg = pkgs.atlassian-confluence.override (optionalAttrs cfg.sso.enable {
+  pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
     enableSSO = cfg.sso.enable;
     crowdProperties = ''
       application.name                        ${cfg.sso.applicationName}
@@ -125,7 +125,12 @@ in
         };
       };
 
-
+      package = mkOption {
+        type = types.package;
+        default = pkgs.atlassian-confluence;
+        defaultText = "pkgs.atlassian-confluence";
+        description = "Atlassian Confluence package to use.";
+      };
 
       jrePackage = mkOption {
         type = types.package;
@@ -166,7 +171,7 @@ in
         ln -sf ${cfg.home}/{logs,work,temp,server.xml} /run/confluence
         ln -sf ${cfg.home} /run/confluence/home
 
-        chown -R ${cfg.user} ${cfg.home}
+        chown ${cfg.user} ${cfg.home}
 
         sed -e 's,port="8090",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
         '' + (lib.optionalString cfg.proxy.enable ''
diff --git a/nixos/modules/services/web-apps/atlassian/crowd.nix b/nixos/modules/services/web-apps/atlassian/crowd.nix
index b6cb9f3b7c418..c144b21bdaf21 100644
--- a/nixos/modules/services/web-apps/atlassian/crowd.nix
+++ b/nixos/modules/services/web-apps/atlassian/crowd.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.services.crowd;
 
-  pkg = pkgs.atlassian-crowd.override {
+  pkg = cfg.package.override {
     home = cfg.home;
     port = cfg.listenPort;
     openidPassword = cfg.openidPassword;
@@ -93,6 +93,13 @@ in
         };
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.atlassian-crowd;
+        defaultText = "pkgs.atlassian-crowd";
+        description = "Atlassian Crowd package to use.";
+      };
+
       jrePackage = mkOption {
         type = types.package;
         default = pkgs.oraclejre8;
@@ -130,9 +137,10 @@ in
         mkdir -p ${cfg.home}/{logs,database,work}
 
         mkdir -p /run/atlassian-crowd
-        ln -sf ${cfg.home}/{database,work,server.xml} /run/atlassian-crowd
+        ln -sf ${cfg.home}/{database,logs,work,server.xml} /run/atlassian-crowd
 
-        chown -R ${cfg.user}:${cfg.group} ${cfg.home}
+        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 ''
diff --git a/nixos/modules/services/web-apps/atlassian/jira.nix b/nixos/modules/services/web-apps/atlassian/jira.nix
index f5ec0a5f31b8b..0b3a5722d6c86 100644
--- a/nixos/modules/services/web-apps/atlassian/jira.nix
+++ b/nixos/modules/services/web-apps/atlassian/jira.nix
@@ -6,7 +6,7 @@ let
 
   cfg = config.services.jira;
 
-  pkg = pkgs.atlassian-jira.override (optionalAttrs cfg.sso.enable {
+  pkg = cfg.package.override (optionalAttrs cfg.sso.enable {
     enableSSO = cfg.sso.enable;
     crowdProperties = ''
       application.name                        ${cfg.sso.applicationName}
@@ -131,6 +131,13 @@ in
         };
       };
 
+      package = mkOption {
+        type = types.package;
+        default = pkgs.atlassian-jira;
+        defaultText = "pkgs.atlassian-jira";
+        description = "Atlassian JIRA package to use.";
+      };
+
       jrePackage = mkOption {
         type = types.package;
         default = pkgs.oraclejre8;
@@ -171,7 +178,7 @@ in
         ln -sf ${cfg.home}/{logs,work,temp,server.xml} /run/atlassian-jira
         ln -sf ${cfg.home} /run/atlassian-jira/home
 
-        chown -R ${cfg.user} ${cfg.home}
+        chown ${cfg.user} ${cfg.home}
 
         sed -e 's,port="8080",port="${toString cfg.listenPort}" address="${cfg.listenAddress}",' \
         '' + (lib.optionalString cfg.proxy.enable ''
diff --git a/nixos/modules/services/web-apps/codimd.nix b/nixos/modules/services/web-apps/codimd.nix
new file mode 100644
index 0000000000000..a0af28eac7cc9
--- /dev/null
+++ b/nixos/modules/services/web-apps/codimd.nix
@@ -0,0 +1,920 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.codimd;
+
+  prettyJSON = conf:
+    pkgs.runCommand "codimd-config.json" { } ''
+      echo '${builtins.toJSON conf}' | ${pkgs.jq}/bin/jq \
+        '{production:del(.[]|nulls)|del(.[][]?|nulls)}' > $out
+    '';
+in
+{
+  options.services.codimd = {
+    enable = mkEnableOption "the CodiMD Markdown Editor";
+
+    groups = mkOption {
+      type = types.listOf types.str;
+      default = [];
+      description = ''
+        Groups to which the codimd user should be added.
+      '';
+    };
+
+    workDir = mkOption {
+      type = types.path;
+      default = "/var/lib/codimd";
+      description = ''
+        Working directory for the CodiMD service.
+      '';
+    };
+
+    configuration = {
+      debug = mkEnableOption "debug mode";
+      domain = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "codimd.org";
+        description = ''
+          Domain name for the CodiMD instance.
+        '';
+      };
+      urlPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/url/path/to/codimd";
+        description = ''
+          Path under which CodiMD is accessible.
+        '';
+      };
+      host = mkOption {
+        type = types.str;
+        default = "localhost";
+        description = ''
+          Address to listen on.
+        '';
+      };
+      port = mkOption {
+        type = types.int;
+        default = 3000;
+        example = "80";
+        description = ''
+          Port to listen on.
+        '';
+      };
+      path = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/run/codimd.sock";
+        description = ''
+          Specify where a UNIX domain socket should be placed.
+        '';
+      };
+      allowOrigin = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "localhost" "codimd.org" ];
+        description = ''
+          List of domains to whitelist.
+        '';
+      };
+      useSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable to use SSL server. This will also enable
+          <option>protocolUseSSL</option>.
+        '';
+      };
+      hsts = {
+        enable = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Wheter to enable HSTS if HTTPS is also enabled.
+          '';
+        };
+        maxAgeSeconds = mkOption {
+          type = types.int;
+          default = 31536000;
+          description = ''
+            Max duration for clients to keep the HSTS status.
+          '';
+        };
+        includeSubdomains = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to include subdomains in HSTS.
+          '';
+        };
+        preload = mkOption {
+          type = types.bool;
+          default = true;
+          description = ''
+            Whether to allow preloading of the site's HSTS status.
+          '';
+        };
+      };
+      csp = mkOption {
+        type = types.nullOr types.attrs;
+        default = null;
+        example = literalExample ''
+          {
+            enable = true;
+            directives = {
+              scriptSrc = "trustworthy.scripts.example.com";
+            };
+            upgradeInsecureRequest = "auto";
+            addDefaults = true;
+          }
+        '';
+        description = ''
+          Specify the Content Security Policy which is passed to Helmet.
+          For configuration details see <link xlink:href="https://helmetjs.github.io/docs/csp/"
+          >https://helmetjs.github.io/docs/csp/</link>.
+        '';
+      };
+      protocolUseSSL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable to use TLS for resource paths.
+          This only applies when <option>domain</option> is set.
+        '';
+      };
+      urlAddPort = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Enable to add the port to callback URLs.
+          This only applies when <option>domain</option> is set
+          and only for ports other than 80 and 443.
+        '';
+      };
+      useCDN = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to use CDN resources or not.
+        '';
+      };
+      allowAnonymous = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to allow anonymous usage.
+        '';
+      };
+      allowAnonymousEdits = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow guests to edit existing notes with the `freely' permission,
+          when <option>allowAnonymous</option> is enabled.
+        '';
+      };
+      allowFreeURL = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to allow note creation by accessing a nonexistent note URL.
+        '';
+      };
+      defaultPermission = mkOption {
+        type = types.enum [ "freely" "editable" "limited" "locked" "private" ];
+        default = "editable";
+        description = ''
+          Default permissions for notes.
+          This only applies for signed-in users.
+        '';
+      };
+      dbURL = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = ''
+          postgres://user:pass@host:5432/dbname
+        '';
+        description = ''
+          Specify which database to use.
+          CodiMD supports mysql, postgres, sqlite and mssql.
+          See <link xlink:href="https://sequelize.readthedocs.io/en/v3/">
+          https://sequelize.readthedocs.io/en/v3/</link> for more information.
+          Note: This option overrides <option>db</option>.
+        '';
+      };
+      db = mkOption {
+        type = types.attrs;
+        default = {};
+        example = literalExample ''
+          {
+            dialect = "sqlite";
+            storage = "/var/lib/codimd/db.codimd.sqlite";
+          }
+        '';
+        description = ''
+          Specify the configuration for sequelize.
+          CodiMD supports mysql, postgres, sqlite and mssql.
+          See <link xlink:href="https://sequelize.readthedocs.io/en/v3/">
+          https://sequelize.readthedocs.io/en/v3/</link> for more information.
+          Note: This option overrides <option>db</option>.
+        '';
+      };
+      sslKeyPath= mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/codimd/codimd.key";
+        description = ''
+          Path to the SSL key. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      sslCertPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/codimd/codimd.crt";
+        description = ''
+          Path to the SSL cert. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      sslCAPath = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        example = [ "/var/lib/codimd/ca.crt" ];
+        description = ''
+          SSL ca chain. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      dhParamPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "/var/lib/codimd/dhparam.pem";
+        description = ''
+          Path to the SSL dh params. Needed when <option>useSSL</option> is enabled.
+        '';
+      };
+      tmpPath = mkOption {
+        type = types.str;
+        default = "/tmp";
+        description = ''
+          Path to the temp directory CodiMD should use.
+          Note that <option>serviceConfig.PrivateTmp</option> is enabled for
+          the CodiMD systemd service by default.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      defaultNotePath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/default.md";
+        description = ''
+          Path to the default Note file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      docsPath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/docs";
+        description = ''
+          Path to the docs directory.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      indexPath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/views/index.ejs";
+        description = ''
+          Path to the index template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      hackmdPath = mkOption {
+        type = types.nullOr types.str;
+        default = "./public/views/hackmd.ejs";
+        description = ''
+          Path to the hackmd template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      errorPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        defaultText = "./public/views/error.ejs";
+        description = ''
+          Path to the error template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      prettyPath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        defaultText = "./public/views/pretty.ejs";
+        description = ''
+          Path to the pretty template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      slidePath = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        defaultText = "./public/views/slide.hbs";
+        description = ''
+          Path to the slide template file.
+          (Non-canonical paths are relative to CodiMD's base directory)
+        '';
+      };
+      uploadsPath = mkOption {
+        type = types.str;
+        default = "${cfg.workDir}/uploads";
+        defaultText = "/var/lib/codimd/uploads";
+        description = ''
+          Path under which uploaded files are saved.
+        '';
+      };
+      sessionName = mkOption {
+        type = types.str;
+        default = "connect.sid";
+        description = ''
+          Specify the name of the session cookie.
+        '';
+      };
+      sessionSecret = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Specify the secret used to sign the session cookie.
+          If unset, one will be generated on startup.
+        '';
+      };
+      sessionLife = mkOption {
+        type = types.int;
+        default = 1209600000;
+        description = ''
+          Session life time in milliseconds.
+        '';
+      };
+      heartbeatInterval = mkOption {
+        type = types.int;
+        default = 5000;
+        description = ''
+          Specify the socket.io heartbeat interval.
+        '';
+      };
+      heartbeatTimeout = mkOption {
+        type = types.int;
+        default = 10000;
+        description = ''
+          Specify the socket.io heartbeat timeout.
+        '';
+      };
+      documentMaxLength = mkOption {
+        type = types.int;
+        default = 100000;
+        description = ''
+          Specify the maximum document length.
+        '';
+      };
+      email = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable email sign-in.
+        '';
+      };
+      allowEmailRegister = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Wether to enable email registration.
+        '';
+      };
+      allowGravatar = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to use gravatar as profile picture source.
+        '';
+      };
+      imageUploadType = mkOption {
+        type = types.enum [ "imgur" "s3" "minio" "filesystem" ];
+        default = "filesystem";
+        description = ''
+          Specify where to upload images.
+        '';
+      };
+      minio = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            accessKey = mkOption {
+              type = types.str;
+              description = ''
+                Minio access key.
+              '';
+            };
+            secretKey = mkOption {
+              type = types.str;
+              description = ''
+                Minio secret key.
+              '';
+            };
+            endpoint = mkOption {
+              type = types.str;
+              description = ''
+                Minio endpoint.
+              '';
+            };
+            port = mkOption {
+              type = types.int;
+              default = 9000;
+              description = ''
+                Minio listen port.
+              '';
+            };
+            secure = mkOption {
+              type = types.bool;
+              default = true;
+              description = ''
+                Whether to use HTTPS for Minio.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the minio third-party integration.";
+      };
+      s3 = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            accessKeyId = mkOption {
+              type = types.str;
+              description = ''
+                AWS access key id.
+              '';
+            };
+            secretAccessKey = mkOption {
+              type = types.str;
+              description = ''
+                AWS access key.
+              '';
+            };
+            region = mkOption {
+              type = types.str;
+              description = ''
+                AWS S3 region.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the s3 third-party integration.";
+      };
+      s3bucket = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Specify the bucket name for upload types <literal>s3</literal> and <literal>minio</literal>.
+        '';
+      };
+      allowPDFExport = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to enable PDF exports.
+        '';
+      };
+      imgur.clientId = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Imgur API client ID.
+        '';
+      };
+      azure = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            connectionString = mkOption {
+              type = types.str;
+              description = ''
+                Azure Blob Storage connection string.
+              '';
+            };
+            container = mkOption {
+              type = types.str;
+              description = ''
+                Azure Blob Storage container name.
+                It will be created if non-existent.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the azure third-party integration.";
+      };
+      oauth2 = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            authorizationURL = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth authorization URL.
+              '';
+            };
+            tokenURL = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth token URL.
+              '';
+            };
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Specify the OAuth client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the OAuth integration.";
+      };
+      facebook = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Facebook API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Facebook API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the facebook third-party integration";
+      };
+      twitter = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            consumerKey = mkOption {
+              type = types.str;
+              description = ''
+                Twitter API consumer key.
+              '';
+            };
+            consumerSecret = mkOption {
+              type = types.str;
+              description = ''
+                Twitter API consumer secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Twitter third-party integration.";
+      };
+      github = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                GitHub API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Github API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the GitHub third-party integration.";
+      };
+      gitlab = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            baseURL = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                GitLab API authentication endpoint.
+                Only needed for other endpoints than gitlab.com.
+              '';
+            };
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                GitLab API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                GitLab API client secret.
+              '';
+            };
+            scope = mkOption {
+              type = types.enum [ "api" "read_user" ];
+              default = "api";
+              description = ''
+                GitLab API requested scope.
+                GitLab snippet import/export requires api scope.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the GitLab third-party integration.";
+      };
+      mattermost = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            baseURL = mkOption {
+              type = types.str;
+              description = ''
+                Mattermost authentication endpoint.
+              '';
+            };
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Mattermost API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Mattermost API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Mattermost third-party integration.";
+      };
+      dropbox = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Dropbox API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Dropbox API client secret.
+              '';
+            };
+            appKey = mkOption {
+              type = types.str;
+              description = ''
+                Dropbox app key.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Dropbox third-party integration.";
+      };
+      google = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            clientID = mkOption {
+              type = types.str;
+              description = ''
+                Google API client ID.
+              '';
+            };
+            clientSecret = mkOption {
+              type = types.str;
+              description = ''
+                Google API client secret.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the Google third-party integration.";
+      };
+      ldap = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            providerName = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                Optional name to be displayed at login form, indicating the LDAP provider.
+              '';
+            };
+            url = mkOption {
+              type = types.str;
+              example = "ldap://localhost";
+              description = ''
+                URL of LDAP server.
+              '';
+            };
+            bindDn = mkOption {
+              type = types.str;
+              description = ''
+                Bind DN for LDAP access.
+              '';
+            };
+            bindCredentials = mkOption {
+              type = types.str;
+              description = ''
+                Bind credentials for LDAP access.
+              '';
+            };
+            searchBase = mkOption {
+              type = types.str;
+              example = "o=users,dc=example,dc=com";
+              description = ''
+                LDAP directory to begin search from.
+              '';
+            };
+            searchFilter = mkOption {
+              type = types.str;
+              example = "(uid={{username}})";
+              description = ''
+                LDAP filter to search with.
+              '';
+            };
+            searchAttributes = mkOption {
+              type = types.listOf types.str;
+              example = [ "displayName" "mail" ];
+              description = ''
+                LDAP attributes to search with.
+              '';
+            };
+            userNameField = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                LDAP field which is used as the username on CodiMD.
+                By default <option>useridField</option> is used.
+              '';
+            };
+            useridField = mkOption {
+              type = types.str;
+              example = "uid";
+              description = ''
+                LDAP field which is a unique identifier for users on CodiMD.
+              '';
+            };
+            tlsca = mkOption {
+              type = types.str;
+              example = "server-cert.pem,root.pem";
+              description = ''
+                Root CA for LDAP TLS in PEM format.
+              '';
+            };
+          };
+        });
+        default = null;
+        description = "Configure the LDAP integration.";
+      };
+      saml = mkOption {
+        type = types.nullOr (types.submodule {
+          options = {
+            idpSsoUrl = mkOption {
+              type = types.str;
+              example = "https://idp.example.com/sso";
+              description = ''
+                IdP authentication endpoint.
+              '';
+            };
+            idpCert = mkOption {
+              type = types.path;
+              example = "/path/to/cert.pem";
+              description = ''
+                Path to IdP certificate file in PEM format.
+              '';
+            };
+            issuer = mkOption {
+              type = types.str;
+              default = "";
+              description = ''
+                Optional identity of the service provider.
+                This defaults to the server URL.
+              '';
+            };
+            identifierFormat = mkOption {
+              type = types.str;
+              default = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress";
+              description = ''
+                Optional name identifier format.
+              '';
+            };
+            groupAttribute = mkOption {
+              type = types.str;
+              default = "";
+              example = "memberOf";
+              description = ''
+                Optional attribute name for group list.
+              '';
+            };
+            externalGroups = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "Temporary-staff" "External-users" ];
+              description = ''
+                Excluded group names.
+              '';
+            };
+            requiredGroups = mkOption {
+              type = types.listOf types.str;
+              default = [];
+              example = [ "Hackmd-users" "Codimd-users" ];
+              description = ''
+                Required group names.
+              '';
+            };
+            attribute = {
+              id = mkOption {
+                type = types.str;
+                default = "";
+                description = ''
+                  Attribute map for `id'.
+                  Defaults to `NameID' of SAML response.
+                '';
+              };
+              username = mkOption {
+                type = types.str;
+                default = "";
+                description = ''
+                  Attribute map for `username'.
+                  Defaults to `NameID' of SAML response.
+                '';
+              };
+              email = mkOption {
+                type = types.str;
+                default = "";
+                description = ''
+                  Attribute map for `email'.
+                  Defaults to `NameID' of SAML response if
+                  <option>identifierFormat</option> has
+                  the default value.
+                '';
+              };
+            };
+          };
+        });
+        default = null;
+        description = "Configure the SAML integration.";
+      };
+    };
+  };
+
+  config = mkIf cfg.enable {
+    assertions = [
+      { assertion = cfg.configuration.db == {} -> (
+          cfg.configuration.dbURL != "" && cfg.configuration.dbURL != null
+        );
+        message = "Database configuration for CodiMD missing."; }
+    ];
+    users.groups.codimd = {};
+    users.users.codimd = {
+      description = "CodiMD service user";
+      group = "codimd";
+      extraGroups = cfg.groups;
+      home = cfg.workDir;
+      createHome = true;
+    };
+
+    systemd.services.codimd = {
+      description = "CodiMD Service";
+      wantedBy = [ "multi-user.target" ];
+      after = [ "networking.target" ];
+      preStart = ''
+        mkdir -p ${cfg.workDir}
+        chown -R codimd: ${cfg.workDir}
+      '';
+      serviceConfig = {
+        WorkingDirectory = cfg.workDir;
+        ExecStart = "${pkgs.codimd}/bin/codimd";
+        Environment = [
+          "CMD_CONFIG_FILE=${prettyJSON cfg.configuration}"
+          "NODE_ENV=production"
+        ];
+        Restart = "always";
+        User = "codimd";
+        PermissionsStartOnly = true;
+        PrivateTmp = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
new file mode 100644
index 0000000000000..ccaa2cff1c230
--- /dev/null
+++ b/nixos/modules/services/web-apps/icingaweb2/icingaweb2.nix
@@ -0,0 +1,626 @@
+{ config, lib, pkgs, ... }: with lib; let
+  cfg = config.services.icingaweb2;
+  poolName = "icingaweb2";
+  phpfpmSocketName = "/var/run/phpfpm/${poolName}.sock";
+
+  formatBool = b: if b then "1" else "0";
+
+  configIni = let
+    config = cfg.generalConfig;
+  in ''
+    [global]
+    show_stacktraces = "${formatBool config.showStacktraces}"
+    show_application_state_messages = "${formatBool config.showApplicationStateMessages}"
+    module_path = "${pkgs.icingaweb2}/modules${optionalString (builtins.length config.modulePath > 0) ":${concatStringsSep ":" config.modulePath}"}"
+    config_backend = "${config.configBackend}"
+    ${optionalString (config.configBackend == "db") ''config_resource = "${config.configResource}"''}
+
+    [logging]
+    log = "${config.log}"
+    ${optionalString (config.log != "none") ''level = "${config.logLevel}"''}
+    ${optionalString (config.log == "php" || config.log == "syslog") ''application = "${config.logApplication}"''}
+    ${optionalString (config.log == "syslog") ''facility = "${config.logFacility}"''}
+    ${optionalString (config.log == "file") ''file = "${config.logFile}"''}
+
+    [themes]
+    default = "${config.themeDefault}"
+    disabled = "${formatBool config.themeDisabled}"
+
+    [authentication]
+    ${optionalString (config.authDefaultDomain != null) ''default_domain = "${config.authDefaultDomain}"''}
+  '';
+
+  resourcesIni = concatStringsSep "\n" (mapAttrsToList (name: config: ''
+    [${name}]
+    type = "${config.type}"
+    ${optionalString (config.type == "db") ''
+      db = "${config.db}"
+      host = "${config.host}"
+      ${optionalString (config.port != null) ''port = "${toString config.port}"''}
+      username = "${config.username}"
+      password = "${config.password}"
+      dbname = "${config.dbname}"
+      ${optionalString (config.charset != null) ''charset = "${config.charset}"''}
+      use_ssl = "${formatBool config.useSSL}"
+      ${optionalString (config.sslCert != null) ''ssl_cert = "${config.sslCert}"''}
+      ${optionalString (config.sslKey != null) ''ssl_cert = "${config.sslKey}"''}
+      ${optionalString (config.sslCA != null) ''ssl_cert = "${config.sslCA}"''}
+      ${optionalString (config.sslCApath != null) ''ssl_cert = "${config.sslCApath}"''}
+      ${optionalString (config.sslCipher != null) ''ssl_cert = "${config.sslCipher}"''}
+    ''}
+    ${optionalString (config.type == "ldap") ''
+      hostname = "${config.host}"
+      ${optionalString (config.port != null) ''port = "${toString config.port}"''}
+      root_dn = "${config.rootDN}"
+      bind_dn = "${config.username}"
+      bind_pw = "${config.password}"
+      encryption = "${config.ldapEncryption}"
+      timeout = "${toString config.ldapTimeout}"
+    ''}
+    ${optionalString (config.type == "ssh") ''
+      user = "${config.username}"
+      private_key = "${config.sshPrivateKey}"
+    ''}
+
+  '') cfg.resources);
+
+  authenticationIni = concatStringsSep "\n" (mapAttrsToList (name: config: ''
+    [${name}]
+    backend = "${config.backend}"
+    ${optionalString (config.domain != null) ''domain = "${config.domain}"''}
+    ${optionalString (config.backend == "external" && config.externalStripRegex != null) ''strip_username_regexp = "${config.externalStripRegex}"''}
+    ${optionalString (config.backend != "external") ''resource = "${config.resource}"''}
+    ${optionalString (config.backend == "ldap" || config.backend == "msldap") ''
+      ${optionalString (config.ldapUserClass != null) ''user_class = "${config.ldapUserClass}"''}
+      ${optionalString (config.ldapUserNameAttr != null) ''user_name_attribute = "${config.ldapUserNameAttr}"''}
+      ${optionalString (config.ldapFilter != null) ''filter = "${config.ldapFilter}"''}
+    ''}
+  '') cfg.authentications);
+
+  groupsIni = concatStringsSep "\n" (mapAttrsToList (name: config: ''
+    [${name}]
+    backend = "${config.backend}"
+    resource = "${config.resource}"
+    ${optionalString (config.backend != "db") ''
+      ${optionalString (config.ldapUserClass != null) ''user_class = "${config.ldapUserClass}"''}
+      ${optionalString (config.ldapUserNameAttr != null) ''user_name_attribute = "${config.ldapUserNameAttr}"''}
+      ${optionalString (config.ldapGroupClass != null) ''group_class = "${config.ldapGroupClass}"''}
+      ${optionalString (config.ldapGroupNameAttr != null) ''group_name_attribute = "${config.ldapGroupNameAttr}"''}
+      ${optionalString (config.ldapGroupFilter != null) ''group_filter = "${config.ldapGroupFilter}"''}
+    ''}
+    ${optionalString (config.backend == "msldap" && config.ldapNestedSearch) ''nested_group_search = "1"''}
+  '') cfg.groupBackends);
+
+  rolesIni = let
+    optionalList = var: attribute: optionalString (builtins.length var > 0) ''${attribute} = "${concatStringsSep "," var}"'';
+  in concatStringsSep "\n" (mapAttrsToList (name: config: ''
+    [${name}]
+    ${optionalList config.users "users"}
+    ${optionalList config.groups "groups"}
+    ${optionalList config.permissions "permissions"}
+    ${optionalList config.permissions "permissions"}
+    ${concatStringsSep "\n" (mapAttrsToList (key: value: optionalList value key) config.extraAssignments)}
+  '') cfg.roles);
+
+in {
+  options.services.icingaweb2 = with types; {
+    enable = mkEnableOption "the icingaweb2 web interface";
+
+    pool = mkOption {
+      type = str;
+      default = "${poolName}";
+      description = ''
+         Name of existing PHP-FPM pool that is used to run Icingaweb2.
+         If not specified, a pool will automatically created with default values.
+      '';
+    };
+
+    virtualHost = mkOption {
+      type = nullOr str;
+      default = "icingaweb2";
+      description = ''
+        Name of the nginx virtualhost to use and setup. If null, no virtualhost is set up.
+      '';
+    };
+
+    timezone = mkOption {
+      type = str;
+      default = "UTC";
+      example = "Europe/Berlin";
+      description = "PHP-compliant timezone specification";
+    };
+
+    modules = {
+      doc.enable = mkEnableOption "the icingaweb2 doc module";
+      migrate.enable = mkEnableOption "the icingaweb2 migrate module";
+      setup.enable = mkEnableOption "the icingaweb2 setup module";
+      test.enable = mkEnableOption "the icingaweb2 test module";
+      translation.enable = mkEnableOption "the icingaweb2 translation module";
+    };
+
+    modulePackages = mkOption {
+      type = attrsOf package;
+      default = {};
+      example = literalExample ''
+        {
+          "snow" = pkgs.icingaweb2Modules.theme-snow;
+        }
+      '';
+      description = ''
+        Name-package attrset of Icingaweb 2 modules packages to enable.
+
+        If you enable modules manually (e.g. via the web ui), they will not be touched.
+      '';
+    };
+
+    generalConfig = {
+      mutable = mkOption {
+        type = bool;
+        default = false;
+        description = ''
+          Make config.ini mutable (e.g. via the web interface).
+          Not that you need to update module_path manually.
+        '';
+      };
+
+      showStacktraces = mkOption {
+        type = bool;
+        default = true;
+        description = "Enable stack traces in the Web UI";
+      };
+
+      showApplicationStateMessages = mkOption {
+        type = bool;
+        default = true;
+        description = "Enable application state messages in the Web UI";
+      };
+
+      modulePath = mkOption {
+        type = listOf str;
+        default = [];
+        description = "List of additional module search paths";
+      };
+
+      configBackend = mkOption {
+        type = enum [ "ini" "db" "none" ];
+        default = "db";
+        description = "Where to store user preferences";
+      };
+
+      configResource = mkOption {
+        type = nullOr str;
+        default = null;
+        description = "Database resource where user preferences are stored (if they are stored in a database)";
+      };
+
+      log = mkOption {
+        type = enum [ "syslog" "php" "file" "none" ];
+        default = "syslog";
+        description = "Logging target";
+      };
+
+      logLevel = mkOption {
+        type = enum [ "ERROR" "WARNING" "INFO" "DEBUG" ];
+        default = "ERROR";
+        description = "Maximum logging level to emit";
+      };
+
+      logApplication = mkOption {
+        type = str;
+        default = "icingaweb2";
+        description = "Application name to log under (syslog and php log)";
+      };
+
+      logFacility = mkOption {
+        type = enum [ "user" "local0" "local1" "local2" "local3" "local4" "local5" "local6" "local7" ];
+        default = "user";
+        description = "Syslog facility to log to";
+      };
+
+      logFile = mkOption {
+        type = str;
+        default = "/var/log/icingaweb2/icingaweb2.log";
+        description = "File to log to";
+      };
+
+      themeDefault = mkOption {
+        type = str;
+        default = "Icinga";
+        description = "Name of the default theme";
+      };
+
+      themeDisabled = mkOption {
+        type = bool;
+        default = false;
+        description = "Disallow users to change the theme";
+      };
+
+      authDefaultDomain = mkOption {
+        type = nullOr str;
+        default = null;
+        description = "Domain for users logging in without a qualified domain";
+      };
+    };
+
+    mutableResources = mkOption {
+      type = bool;
+      default = false;
+      description = "Make resources.ini mutable (e.g. via the web interface)";
+    };
+
+    resources = mkOption {
+      default = {};
+      description = "Icingaweb 2 resources to define";
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          name = mkOption {
+            visible = false;
+            default = name;
+            type = str;
+            description = "Name of this resource";
+          };
+
+          type = mkOption {
+            type = enum [ "db" "ldap" "ssh" ];
+            default = "db";
+            description = "Type of this resouce";
+          };
+
+          db = mkOption {
+            type = enum [ "mysql" "pgsql" ];
+            default = "mysql";
+            description = "Type of this database resource";
+          };
+
+          host = mkOption {
+            type = str;
+            description = "Host to connect to";
+          };
+
+          port = mkOption {
+            type = nullOr port;
+            default = null;
+            description = "Port to connect on";
+          };
+
+          username = mkOption {
+            type = str;
+            description = "Database or SSH user or LDAP bind DN to connect with";
+          };
+
+          password = mkOption {
+            type = str;
+            description = "Password for the database user or LDAP bind DN";
+          };
+
+          dbname = mkOption {
+            type = str;
+            description = "Name of the database to connect to";
+          };
+
+          charset = mkOption {
+            type = nullOr str;
+            default = null;
+            example = "utf8";
+            description = "Database character set to connect with";
+          };
+
+          useSSL = mkOption {
+            type = nullOr bool;
+            default = false;
+            description = "Whether to connect to the database using SSL";
+          };
+
+          sslCert = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "The file path to the SSL certificate. Only available for the mysql database.";
+          };
+
+          sslKey = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "The file path to the SSL key. Only available for the mysql database.";
+          };
+
+          sslCA = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "The file path to the SSL certificate authority. Only available for the mysql database.";
+          };
+
+          sslCApath = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "The file path to the directory that contains the trusted SSL CA certificates in PEM format. Only available for the mysql database.";
+          };
+
+          sslCipher = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "A list of one or more permissible ciphers to use for SSL encryption, in a format understood by OpenSSL. Only available for the mysql database.";
+          };
+
+          rootDN = mkOption {
+            type = str;
+            description = "Root object of the LDAP tree";
+          };
+
+          ldapEncryption = mkOption {
+            type = enum [ "none" "starttls" "ldaps" ];
+            default = "none";
+            description = "LDAP encryption to use";
+          };
+
+          ldapTimeout = mkOption {
+            type = ints.positive;
+            default = 5;
+            description = "Connection timeout for every LDAP connection";
+          };
+
+          sshPrivateKey = mkOption {
+            type = str;
+            description = "The path to the private key of the user";
+          };
+        };
+      }));
+    };
+
+    mutableAuthConfig = mkOption {
+      type = bool;
+      default = true;
+      description = "Make authentication.ini mutable (e.g. via the web interface)";
+    };
+
+    authentications = mkOption {
+      default = {};
+      description = "Icingaweb 2 authentications to define";
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          name = mkOption {
+            visible = false;
+            default = name;
+            type = str;
+            description = "Name of this authentication";
+          };
+
+          backend = mkOption {
+            type = enum [ "external" "ldap" "msldap" "db" ];
+            default = "db";
+            description = "The type of this authentication backend";
+          };
+
+          domain = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Domain for domain-aware authentication";
+          };
+
+          externalStripRegex = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Regular expression to strip off specific user name parts";
+          };
+
+          resource = mkOption {
+            type = str;
+            description = "Name of the database/LDAP resource";
+          };
+
+          ldapUserClass = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "LDAP user class";
+          };
+
+          ldapUserNameAttr = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "LDAP attribute which contains the username";
+          };
+
+          ldapFilter = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "LDAP search filter";
+          };
+        };
+      }));
+    };
+
+    mutableGroupsConfig = mkOption {
+      type = bool;
+      default = true;
+      description = "Make groups.ini mutable (e.g. via the web interface)";
+    };
+
+    groupBackends = mkOption {
+      default = {};
+      description = "Icingaweb 2 group backends to define";
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          name = mkOption {
+            visible = false;
+            default = name;
+            type = str;
+            description = "Name of this group backend";
+          };
+
+          backend = mkOption {
+            type = enum [ "ldap" "msldap" "db" ];
+            default = "db";
+            description = "The type of this group backend";
+          };
+
+          resource = mkOption {
+            type = str;
+            description = "Name of the database/LDAP resource";
+          };
+
+          ldapUserClass = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "LDAP user class";
+          };
+
+          ldapUserNameAttr = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "LDAP attribute which contains the username";
+          };
+
+          ldapGroupClass = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "LDAP group class";
+          };
+
+          ldapGroupNameAttr = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "LDAP attribute which contains the groupname";
+          };
+
+          ldapGroupFilter = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "LDAP group search filter";
+          };
+
+          ldapNestedSearch = mkOption {
+            type = bool;
+            default = false;
+            description = "Enable nested group search in Active Directory based on the user";
+          };
+        };
+      }));
+    };
+
+    mutableRolesConfig = mkOption {
+      type = bool;
+      default = true;
+      description = "Make roles.ini mutable (e.g. via the web interface)";
+    };
+
+    roles = mkOption {
+      default = {};
+      description = "Icingaweb 2 roles to define";
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          name = mkOption {
+            visible = false;
+            default = name;
+            type = str;
+            description = "Name of this role";
+          };
+
+          users = mkOption {
+            type = listOf str;
+            default = [];
+            description = "List of users that are assigned to the role";
+          };
+
+          groups = mkOption {
+            type = listOf str;
+            default = [];
+            description = "List of groups that are assigned to the role";
+          };
+
+          permissions = mkOption {
+            type = listOf str;
+            default = [];
+            example = [ "application/share/navigation" "config/*" ];
+            description = "The permissions to grant";
+          };
+
+          extraAssignments = mkOption {
+            type = attrsOf (listOf str);
+            default = {};
+            example = { "monitoring/blacklist/properties" = [ "sla" "customer"]; };
+            description = "Additional assignments of this role";
+          };
+        };
+      }));
+    };
+  };
+
+  config = mkIf cfg.enable {
+    services.phpfpm.poolConfigs = mkIf (cfg.pool == "${poolName}") {
+      "${poolName}" = ''
+        listen = "${phpfpmSocketName}"
+        listen.owner = nginx
+        listen.group = nginx
+        listen.mode = 0600
+        user = icingaweb2
+        pm = dynamic
+        pm.max_children = 75
+        pm.start_servers = 2
+        pm.min_spare_servers = 2
+        pm.max_spare_servers = 10
+      '';
+    };
+
+    services.phpfpm.phpOptions = mkIf (cfg.pool == "${poolName}")
+      ''
+        extension = ${pkgs.phpPackages.imagick}/lib/php/extensions/imagick.so
+        date.timezone = "${cfg.timezone}"
+      '';
+
+    systemd.services."phpfpm-${poolName}".serviceConfig.ReadWritePaths = [ "/etc/icingaweb2" ];
+
+    services.nginx = {
+      enable = true;
+      virtualHosts = mkIf (cfg.virtualHost != null) {
+        "${cfg.virtualHost}" = {
+          root = "${pkgs.icingaweb2}/public";
+
+          extraConfig = ''
+            index index.php;
+            try_files $1 $uri $uri/ /index.php$is_args$args;
+          '';
+
+          locations."~ ..*/.*.php$".extraConfig = ''
+            return 403;
+          '';
+
+          locations."~ ^/index.php(.*)$".extraConfig = ''
+            fastcgi_intercept_errors on;
+            fastcgi_index index.php;
+            include ${config.services.nginx.package}/conf/fastcgi.conf;
+            try_files $uri =404;
+            fastcgi_split_path_info ^(.+\.php)(/.+)$;
+            fastcgi_pass unix:${phpfpmSocketName};
+            fastcgi_param SCRIPT_FILENAME ${pkgs.icingaweb2}/public/index.php;
+          '';
+        };
+      };
+    };
+
+    # /etc/icingaweb2
+    environment.etc = let
+      doModule = name: optionalAttrs (cfg.modules."${name}".enable) (nameValuePair "icingaweb2/enabledModules/${name}" { source = "${pkgs.icingaweb2}/modules/${name}"; });
+    in {}
+      # Module packages
+      // (mapAttrs' (k: v: nameValuePair "icingaweb2/enabledModules/${k}" { source = v; }) cfg.modulePackages)
+      # Built-in modules
+      // doModule "doc"
+      // doModule "migrate"
+      // doModule "setup"
+      // doModule "test"
+      // doModule "translation"
+      # Configs
+      // optionalAttrs (!cfg.generalConfig.mutable) { "icingaweb2/config.ini".text = configIni; }
+      // optionalAttrs (!cfg.mutableResources) { "icingaweb2/resources.ini".text = resourcesIni; }
+      // optionalAttrs (!cfg.mutableAuthConfig) { "icingaweb2/authentication.ini".text = authenticationIni; }
+      // optionalAttrs (!cfg.mutableGroupsConfig) { "icingaweb2/groups.ini".text = groupsIni; }
+      // optionalAttrs (!cfg.mutableRolesConfig) { "icingaweb2/roles.ini".text = rolesIni; };
+
+    # User and group
+    users.groups.icingaweb2 = {};
+    users.users.icingaweb2 = {
+      description = "Icingaweb2 service user";
+      group = "icingaweb2";
+      isSystemUser = true;
+    };
+  };
+}
diff --git a/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix b/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
new file mode 100644
index 0000000000000..167e5e3895682
--- /dev/null
+++ b/nixos/modules/services/web-apps/icingaweb2/module-monitoring.nix
@@ -0,0 +1,157 @@
+{ config, lib, pkgs, ... }: with lib; let
+  cfg = config.services.icingaweb2.modules.monitoring;
+
+  configIni = ''
+    [security]
+    protected_customvars = "${concatStringsSep "," cfg.generalConfig.protectedVars}"
+  '';
+
+  backendsIni = let
+    formatBool = b: if b then "1" else "0";
+  in concatStringsSep "\n" (mapAttrsToList (name: config: ''
+    [${name}]
+    type = "ido"
+    resource = "${config.resource}"
+    disabled = "${formatBool config.disabled}"
+  '') cfg.backends);
+
+  transportsIni = concatStringsSep "\n" (mapAttrsToList (name: config: ''
+    [${name}]
+    type = "${config.type}"
+    ${optionalString (config.instance != null) ''instance = "${config.instance}"''}
+    ${optionalString (config.type == "local" || config.type == "remote") ''path = "${config.path}"''}
+    ${optionalString (config.type != "local") ''
+      host = "${config.host}"
+      ${optionalString (config.port != null) ''port = "${toString config.port}"''}
+      user${optionalString (config.type == "api") "name"} = "${config.username}"
+    ''}
+    ${optionalString (config.type == "api") ''password = "${config.password}"''}
+    ${optionalString (config.type == "remote") ''resource = "${config.resource}"''}
+  '') cfg.transports);
+
+in {
+  options.services.icingaweb2.modules.monitoring = with types; {
+    enable = mkOption {
+      type = bool;
+      default = true;
+      description = "Whether to enable the icingaweb2 monitoring module.";
+    };
+
+    generalConfig = {
+      mutable = mkOption {
+        type = bool;
+        default = false;
+        description = "Make config.ini of the monitoring module mutable (e.g. via the web interface).";
+      };
+
+      protectedVars = mkOption {
+        type = listOf str;
+        default = [ "*pw*" "*pass*" "community" ];
+        description = "List of string patterns for custom variables which should be excluded from user’s view.";
+      };
+    };
+
+    mutableBackends = mkOption {
+      type = bool;
+      default = false;
+      description = "Make backends.ini of the monitoring module mutable (e.g. via the web interface).";
+    };
+
+    backends = mkOption {
+      default = { "icinga" = { resource = "icinga_ido"; }; };
+      description = "Monitoring backends to define";
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          name = mkOption {
+            visible = false;
+            default = name;
+            type = str;
+            description = "Name of this backend";
+          };
+
+          resource = mkOption {
+            type = str;
+            description = "Name of the IDO resource";
+          };
+
+          disabled = mkOption {
+            type = bool;
+            default = false;
+            description = "Disable this backend";
+          };
+        };
+      }));
+    };
+
+    mutableTransports = mkOption {
+      type = bool;
+      default = true;
+      description = "Make commandtransports.ini of the monitoring module mutable (e.g. via the web interface).";
+    };
+
+    transports = mkOption {
+      default = {};
+      description = "Command transports to define";
+      type = attrsOf (submodule ({ name, ... }: {
+        options = {
+          name = mkOption {
+            visible = false;
+            default = name;
+            type = str;
+            description = "Name of this transport";
+          };
+
+          type = mkOption {
+            type = enum [ "api" "local" "remote" ];
+            default = "api";
+            description = "Type of  this transport";
+          };
+
+          instance = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Assign a icinga instance to this transport";
+          };
+
+          path = mkOption {
+            type = str;
+            description = "Path to the socket for local or remote transports";
+          };
+
+          host = mkOption {
+            type = str;
+            description = "Host for the api or remote transport";
+          };
+
+          port = mkOption {
+            type = nullOr str;
+            default = null;
+            description = "Port to connect to for the api or remote transport";
+          };
+
+          username = mkOption {
+            type = str;
+            description = "Username for the api or remote transport";
+          };
+
+          password = mkOption {
+            type = str;
+            description = "Password for the api transport";
+          };
+
+          resource = mkOption {
+            type = str;
+            description = "SSH identity resource for the remote transport";
+          };
+        };
+      }));
+    };
+  };
+
+  config = mkIf (config.services.icingaweb2.enable && cfg.enable) {
+    environment.etc = { "icingaweb2/enabledModules/monitoring" = { source = "${pkgs.icingaweb2}/modules/monitoring"; }; }
+      // optionalAttrs (!cfg.generalConfig.mutable) { "icingaweb2/modules/monitoring/config.ini".text = configIni; }
+      // optionalAttrs (!cfg.mutableBackends) { "icingaweb2/modules/monitoring/backends.ini".text = backendsIni; }
+      // optionalAttrs (!cfg.mutableTransports) { "icingaweb2/modules/monitoring/commandtransports.ini".text = transportsIni; };
+  };
+}
diff --git a/nixos/modules/services/web-apps/matomo-doc.xml b/nixos/modules/services/web-apps/matomo-doc.xml
index 6f878015c5148..20d2de9f41898 100644
--- a/nixos/modules/services/web-apps/matomo-doc.xml
+++ b/nixos/modules/services/web-apps/matomo-doc.xml
@@ -3,28 +3,24 @@
          xmlns:xi="http://www.w3.org/2001/XInclude"
          version="5.0"
          xml:id="module-services-matomo">
-
-  <title>Matomo</title>
-  <para>
-    Matomo is a real-time web analytics application.
-    This module configures php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
-  </para>
-
+ <title>Matomo</title>
+ <para>
+  Matomo is a real-time web analytics application. This module configures
+  php-fpm as backend for Matomo, optionally configuring an nginx vhost as well.
+ </para>
+ <para>
+  An automatic setup is not suported by Matomo, so you need to configure Matomo
+  itself in the browser-based Matomo setup.
+ </para>
+
+ <section xml:id="module-services-matomo-database-setup">
+  <title>Database Setup</title>
   <para>
-    An automatic setup is not suported by Matomo, so you need to configure Matomo itself in the browser-based Matomo setup.
-  </para>
-
-
-  <section xml:id="module-services-matomo-database-setup">
-    <title>Database Setup</title>
-
-    <para>
-      You also need to configure a MariaDB or MySQL database and -user for Matomo yourself,
-      and enter those credentials in your browser.
-      You can use passwordless database authentication via the UNIX_SOCKET authentication plugin
-      with the following SQL commands:
-
-      <programlisting>
+   You also need to configure a MariaDB or MySQL database and -user for Matomo
+   yourself, and enter those credentials in your browser. You can use
+   passwordless database authentication via the UNIX_SOCKET authentication
+   plugin with the following SQL commands:
+   <programlisting>
         # For MariaDB
         INSTALL PLUGIN unix_socket SONAME 'auth_socket';
         CREATE DATABASE matomo;
@@ -36,60 +32,81 @@
         CREATE DATABASE matomo;
         CREATE USER 'matomo'@'localhost' IDENTIFIED WITH auth_socket;
         GRANT ALL PRIVILEGES ON matomo.* TO 'matomo'@'localhost';
-      </programlisting>
+   </programlisting>
+   Then fill in <literal>matomo</literal> as database user and database name,
+   and leave the password field blank. This authentication works by allowing
+   only the <literal>matomo</literal> unix user to authenticate as the
+   <literal>matomo</literal> database user (without needing a password), but no
+   other users. For more information on passwordless login, see
+   <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />.
+  </para>
 
-      Then fill in <literal>matomo</literal> as database user and database name, and leave the password field blank.
-      This authentication works by allowing only the <literal>matomo</literal> unix user to authenticate as the
-      <literal>matomo</literal> database user (without needing a password), but no other users.
-      For more information on passwordless login, see
-      <link xlink:href="https://mariadb.com/kb/en/mariadb/unix_socket-authentication-plugin/" />.
-    </para>
+  <para>
+   Of course, you can use password based authentication as well, e.g. when the
+   database is not on the same host.
+  </para>
+ </section>
 
-    <para>
-      Of course, you can use password based authentication as well, e.g. when the database is not on the same host.
-    </para>
-  </section>
+ <section xml:id="module-services-matomo-archive-processing">
+  <title>Archive Processing</title>
+  <para>
+   This module comes with the systemd service <literal>matomo-archive-processing.service</literal>
+   and a timer that automatically triggers archive processing every hour.
+   This means that you can safely
+   <link xlink:href="https://matomo.org/docs/setup-auto-archiving/#disable-browser-triggers-for-matomo-archiving-and-limit-matomo-reports-to-updating-every-hour">
+    disable browser triggers for Matomo archiving
+   </link> at <literal>Administration > System > General Settings</literal>.
+  </para>
+  <para>
+   With automatic archive processing, you can now also enable to
+   <link xlink:href="https://matomo.org/docs/privacy/#step-2-delete-old-visitors-logs">
+    delete old visitor logs
+   </link> at <literal>Administration > System > Privacy</literal>,
+   but make sure that you run <literal>systemctl start matomo-archive-processing.service</literal>
+   at least once without errors if you have already collected data before,
+   so that the reports get archived before the source data gets deleted.
+  </para>
+ </section>
 
+ <section xml:id="module-services-matomo-backups">
+  <title>Backup</title>
+  <para>
+   You only need to take backups of your MySQL database and the
+   <filename>/var/lib/matomo/config/config.ini.php</filename> file. Use a user
+   in the <literal>matomo</literal> group or root to access the file. For more
+   information, see
+   <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/" />.
+  </para>
+ </section>
 
-  <section xml:id="module-services-matomo-backups">
-    <title>Backup</title>
+ <section xml:id="module-services-matomo-issues">
+  <title>Issues</title>
+  <itemizedlist>
+   <listitem>
     <para>
-      You only need to take backups of your MySQL database and the
-      <filename>/var/lib/matomo/config/config.ini.php</filename> file.
-      Use a user in the <literal>matomo</literal> group or root to access the file.
-      For more information, see <link xlink:href="https://matomo.org/faq/how-to-install/faq_138/" />.
+     Matomo's file integrity check will warn you. This is due to the patches
+     necessary for NixOS, you can safely ignore this.
     </para>
-  </section>
-
-
-  <section xml:id="module-services-matomo-issues">
-    <title>Issues</title>
-    <itemizedlist>
-      <listitem>
-        <para>
-          Matomo's file integrity check will warn you.
-          This is due to the patches necessary for NixOS, you can safely ignore this.
-        </para>
-      </listitem>
-
-      <listitem>
-        <para>
-          Matomo will warn you that the JavaScript tracker is not writable.
-          This is because it's located in the read-only nix store.
-          You can safely ignore this, unless you need a plugin that needs JavaScript tracker access.
-        </para>
-      </listitem>
-    </itemizedlist>
-  </section>
-
-
-  <section xml:id="module-services-matomo-other-web-servers">
-    <title>Using other Web Servers than nginx</title>
-
+   </listitem>
+   <listitem>
     <para>
-      You can use other web servers by forwarding calls for <filename>index.php</filename> and
-      <filename>piwik.php</filename> to the <literal>/run/phpfpm-matomo.sock</literal> fastcgi unix socket.
-      You can use the nginx configuration in the module code as a reference to what else should be configured.
+     Matomo will warn you that the JavaScript tracker is not writable. This is
+     because it's located in the read-only nix store. You can safely ignore
+     this, unless you need a plugin that needs JavaScript tracker access.
     </para>
-  </section>
+   </listitem>
+  </itemizedlist>
+ </section>
+
+ <section xml:id="module-services-matomo-other-web-servers">
+  <title>Using other Web Servers than nginx</title>
+
+  <para>
+   You can use other web servers by forwarding calls for
+   <filename>index.php</filename> and <filename>piwik.php</filename> to the
+   <literal>/run/phpfpm-matomo.sock</literal> fastcgi unix socket. You can use
+   the nginx configuration in the module code as a reference to what else
+   should be configured.
+  </para>
+ </section>
 </chapter>
diff --git a/nixos/modules/services/web-apps/matomo.nix b/nixos/modules/services/web-apps/matomo.nix
index fbbd7715c6b3a..14aca45a342ba 100644
--- a/nixos/modules/services/web-apps/matomo.nix
+++ b/nixos/modules/services/web-apps/matomo.nix
@@ -23,27 +23,51 @@ in {
   options = {
     services.matomo = {
       # NixOS PR for database setup: https://github.com/NixOS/nixpkgs/pull/6963
-      # matomo issue for automatic matomo setup: https://github.com/matomo-org/matomo/issues/10257
-      # TODO: find a nice way to do this when more NixOS MySQL and / or matomo automatic setup stuff is implemented.
+      # Matomo issue for automatic Matomo setup: https://github.com/matomo-org/matomo/issues/10257
+      # TODO: find a nice way to do this when more NixOS MySQL and / or Matomo automatic setup stuff is implemented.
       enable = mkOption {
         type = types.bool;
         default = false;
         description = ''
-          Enable matomo web analytics with php-fpm backend.
+          Enable Matomo web analytics with php-fpm backend.
           Either the nginx option or the webServerUser option is mandatory.
         '';
       };
 
+      package = mkOption {
+        type = types.package;
+        description = ''
+          Matomo package for the service to use.
+          This can be used to point to newer releases from nixos-unstable,
+          as they don't get backported if they are not security-relevant.
+        '';
+        default = pkgs.matomo;
+        defaultText = "pkgs.matomo";
+      };
+
       webServerUser = mkOption {
         type = types.nullOr types.str;
         default = null;
         example = "lighttpd";
-        # TODO: piwik.php might get renamed to matomo.php in future releases
         description = ''
-          Name of the web server user that forwards requests to the ${phpSocket} fastcgi socket for matomo if the nginx
+          Name of the web server user that forwards requests to the ${phpSocket} fastcgi socket for Matomo if the nginx
           option is not used. Either this option or the nginx option is mandatory.
           If you want to use another webserver than nginx, you need to set this to that server's user
-          and pass fastcgi requests to `index.php` and `piwik.php` to this socket.
+          and pass fastcgi requests to `index.php`, `matomo.php` and `piwik.php` (legacy name) to this socket.
+        '';
+      };
+
+      periodicArchiveProcessing = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Enable periodic archive processing, which generates aggregated reports from the visits.
+
+          This means that you can safely disable browser triggers for Matomo archiving,
+          and safely enable to delete old visitor logs.
+          Before deleting visitor logs,
+          make sure though that you run <literal>systemctl start matomo-archive-processing.service</literal>
+          at least once without errors if you have already collected data before.
         '';
       };
 
@@ -62,7 +86,7 @@ in {
           catch_workers_output = yes
         '';
         description = ''
-          Settings for phpfpm's process manager. You might need to change this depending on the load for matomo.
+          Settings for phpfpm's process manager. You might need to change this depending on the load for Matomo.
         '';
       };
 
@@ -72,7 +96,7 @@ in {
             (import ../web-servers/nginx/vhost-options.nix { inherit config lib; })
             {
               # enable encryption by default,
-              # as sensitive login and matomo data should not be transmitted in clear text.
+              # as sensitive login and Matomo data should not be transmitted in clear text.
               options.forceSSL.default = true;
               options.enableACME.default = true;
             }
@@ -87,7 +111,7 @@ in {
           enableACME = false;
         };
         description = ''
-            With this option, you can customize an nginx virtualHost which already has sensible defaults for matomo.
+            With this option, you can customize an nginx virtualHost which already has sensible defaults for Matomo.
             Either this option or the webServerUser option is mandatory.
             Set this to {} to just enable the virtualHost if you don't need any customization.
             If enabled, then by default, the <option>serverName</option> is
@@ -117,29 +141,30 @@ in {
     };
     users.groups.${user} = {};
 
-    systemd.services.matomo_setup_update = {
-      # everything needs to set up and up to date before matomo php files are executed
+    systemd.services.matomo-setup-update = {
+      # everything needs to set up and up to date before Matomo php files are executed
       requiredBy = [ "${phpExecutionUnit}.service" ];
       before = [ "${phpExecutionUnit}.service" ];
       # the update part of the script can only work if the database is already up and running
       requires = [ databaseService ];
       after = [ databaseService ];
-      path = [ pkgs.matomo ];
+      path = [ cfg.package ];
+      environment.PIWIK_USER_PATH = dataDir;
       serviceConfig = {
         Type = "oneshot";
         User = user;
         # hide especially config.ini.php from other
         UMask = "0007";
         # TODO: might get renamed to MATOMO_USER_PATH in future versions
-        Environment = "PIWIK_USER_PATH=${dataDir}";
         # chown + chmod in preStart needs root
         PermissionsStartOnly = true;
       };
+
       # correct ownership and permissions in case they're not correct anymore,
       # e.g. after restoring from backup or moving from another system.
       # Note that ${dataDir}/config/config.ini.php might contain the MySQL password.
       preStart = ''
-        # migrate data from piwik to matomo folder
+        # migrate data from piwik to Matomo folder
         if [ -d ${deprecatedDataDir} ]; then
           echo "Migrating from ${deprecatedDataDir} to ${dataDir}"
           mv -T ${deprecatedDataDir} ${dataDir}
@@ -148,10 +173,10 @@ in {
         chmod -R ug+rwX,o-rwx ${dataDir}
         '';
       script = ''
-            # Use User-Private Group scheme to protect matomo data, but allow administration / backup via matomo group
+            # Use User-Private Group scheme to protect Matomo data, but allow administration / backup via 'matomo' group
             # Copy config folder
             chmod g+s "${dataDir}"
-            cp -r "${pkgs.matomo}/config" "${dataDir}/"
+            cp -r "${cfg.package}/config" "${dataDir}/"
             chmod -R u+rwX,g+rwX,o-rwx "${dataDir}"
 
             # check whether user setup has already been done
@@ -162,9 +187,40 @@ in {
       '';
     };
 
+    # If this is run regularly via the timer,
+    # 'Browser trigger archiving' can be disabled in Matomo UI > Settings > General Settings.
+    systemd.services.matomo-archive-processing = {
+      description = "Archive Matomo reports";
+      # the archiving can only work if the database is already up and running
+      requires = [ databaseService ];
+      after = [ databaseService ];
+
+      # TODO: might get renamed to MATOMO_USER_PATH in future versions
+      environment.PIWIK_USER_PATH = dataDir;
+      serviceConfig = {
+        Type = "oneshot";
+        User = user;
+        UMask = "0007";
+        CPUSchedulingPolicy = "idle";
+        IOSchedulingClass = "idle";
+        ExecStart = "${cfg.package}/bin/matomo-console core:archive --url=https://${user}.${fqdn}";
+      };
+    };
+
+    systemd.timers.matomo-archive-processing = mkIf cfg.periodicArchiveProcessing {
+      description = "Automatically archive Matomo reports every hour";
+
+      wantedBy = [ "timers.target" ];
+      timerConfig = {
+        OnCalendar = "hourly";
+        Persistent = "yes";
+        AccuracySec = "10m";
+      };
+    };
+
     systemd.services.${phpExecutionUnit} = {
-      # stop phpfpm on package upgrade, do database upgrade via matomo_setup_update, and then restart
-      restartTriggers = [ pkgs.matomo ];
+      # stop phpfpm on package upgrade, do database upgrade via matomo-setup-update, and then restart
+      restartTriggers = [ cfg.package ];
       # stop config.ini.php from getting written with read permission for others
       serviceConfig.UMask = "0007";
     };
@@ -193,13 +249,13 @@ in {
       # https://fralef.me/piwik-hardening-with-nginx-and-php-fpm.html
       # https://github.com/perusio/piwik-nginx
       "${user}.${fqdn}" = mkMerge [ cfg.nginx {
-        # don't allow to override the root easily, as it will almost certainly break matomo.
+        # don't allow to override the root easily, as it will almost certainly break Matomo.
         # disadvantage: not shown as default in docs.
-        root = mkForce "${pkgs.matomo}/share";
+        root = mkForce "${cfg.package}/share";
 
         # define locations here instead of as the submodule option's default
         # so that they can easily be extended with additional locations if required
-        # without needing to redefine the matomo ones.
+        # without needing to redefine the Matomo ones.
         # disadvantage: not shown as default in docs.
         locations."/" = {
           index = "index.php";
@@ -208,8 +264,11 @@ in {
         locations."= /index.php".extraConfig = ''
           fastcgi_pass unix:${phpSocket};
         '';
-        # TODO: might get renamed to matomo.php in future versions
-        # allow piwik.php for tracking
+        # allow matomo.php for tracking
+        locations."= /matomo.php".extraConfig = ''
+          fastcgi_pass unix:${phpSocket};
+        '';
+        # allow piwik.php for tracking (deprecated name)
         locations."= /piwik.php".extraConfig = ''
           fastcgi_pass unix:${phpSocket};
         '';
@@ -230,8 +289,11 @@ in {
         locations."= /robots.txt".extraConfig = ''
           return 200 "User-agent: *\nDisallow: /\n";
         '';
-        # TODO: might get renamed to matomo.js in future versions
-        # let browsers cache piwik.js
+        # let browsers cache matomo.js
+        locations."= /matomo.js".extraConfig = ''
+          expires 1M;
+        '';
+        # let browsers cache piwik.js (deprecated name)
         locations."= /piwik.js".extraConfig = ''
           expires 1M;
         '';
diff --git a/nixos/modules/services/web-apps/nextcloud.nix b/nixos/modules/services/web-apps/nextcloud.nix
new file mode 100644
index 0000000000000..ee1354d6a997c
--- /dev/null
+++ b/nixos/modules/services/web-apps/nextcloud.nix
@@ -0,0 +1,489 @@
+{ config, lib, pkgs, ... }@args:
+
+with lib;
+
+let
+  cfg = config.services.nextcloud;
+
+  toKeyValue = generators.toKeyValue {
+    mkKeyValue = generators.mkKeyValueDefault {} " = ";
+  };
+
+  phpOptionsExtensions = ''
+    ${optionalString cfg.caching.apcu "extension=${cfg.phpPackages.apcu}/lib/php/extensions/apcu.so"}
+    ${optionalString cfg.caching.redis "extension=${cfg.phpPackages.redis}/lib/php/extensions/redis.so"}
+    ${optionalString cfg.caching.memcached "extension=${cfg.phpPackages.memcached}/lib/php/extensions/memcached.so"}
+    zend_extension = opcache.so
+    opcache.enable = 1
+  '';
+  phpOptions = {
+    upload_max_filesize = cfg.maxUploadSize;
+    post_max_size = cfg.maxUploadSize;
+    memory_limit = cfg.maxUploadSize;
+  } // cfg.phpOptions;
+  phpOptionsStr = phpOptionsExtensions + (toKeyValue phpOptions);
+
+  occ = pkgs.writeScriptBin "nextcloud-occ" ''
+    #! ${pkgs.stdenv.shell}
+    cd ${pkgs.nextcloud}
+    exec /run/wrappers/bin/sudo -u nextcloud \
+      NEXTCLOUD_CONFIG_DIR="${cfg.home}/config" \
+      ${config.services.phpfpm.phpPackage}/bin/php \
+      -c ${pkgs.writeText "php.ini" phpOptionsStr}\
+      occ $*
+  '';
+
+in {
+  options.services.nextcloud = {
+    enable = mkEnableOption "nextcloud";
+    hostName = mkOption {
+      type = types.str;
+      description = "FQDN for the nextcloud instance.";
+    };
+    home = mkOption {
+      type = types.str;
+      default = "/var/lib/nextcloud";
+      description = "Storage path of nextcloud.";
+    };
+    https = mkOption {
+      type = types.bool;
+      default = false;
+      description = "Enable if there is a TLS terminating proxy in front of nextcloud.";
+    };
+
+    maxUploadSize = mkOption {
+      default = "512M";
+      type = types.str;
+      description = ''
+        Defines the upload limit for files. This changes the relevant options
+        in php.ini and nginx if enabled.
+      '';
+    };
+
+    skeletonDirectory = mkOption {
+      default = "";
+      type = types.str;
+      description = ''
+        The directory where the skeleton files are located. These files will be
+        copied to the data directory of new users. Leave empty to not copy any
+        skeleton files.
+      '';
+    };
+
+    nginx.enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Whether to enable nginx virtual host management.
+        Further nginx configuration can be done by adapting <literal>services.nginx.virtualHosts.&lt;name&gt;</literal>.
+        See <xref linkend="opt-services.nginx.virtualHosts"/> for further information.
+      '';
+    };
+
+    webfinger = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable this option if you plan on using the webfinger plugin.
+        The appropriate nginx rewrite rules will be added to your configuration.
+      '';
+    };
+
+    phpPackages = mkOption {
+      type = types.attrs;
+      default = pkgs.php71Packages;
+      defaultText = "pkgs.php71Packages";
+      description = ''
+        Overridable attribute of the PHP packages set to use.  If any caching
+        module is enabled, it will be taken from here.  Therefore it should
+        match the version of PHP given to
+        <literal>services.phpfpm.phpPackage</literal>.
+      '';
+    };
+
+    phpOptions = mkOption {
+      type = types.attrsOf types.str;
+      default = {
+        "short_open_tag" = "Off";
+        "expose_php" = "Off";
+        "error_reporting" = "E_ALL & ~E_DEPRECATED & ~E_STRICT";
+        "display_errors" = "stderr";
+        "opcache.enable_cli" = "1";
+        "opcache.interned_strings_buffer" = "8";
+        "opcache.max_accelerated_files" = "10000";
+        "opcache.memory_consumption" = "128";
+        "opcache.revalidate_freq" = "1";
+        "opcache.fast_shutdown" = "1";
+        "openssl.cafile" = "/etc/ssl/certs/ca-certificates.crt";
+        "catch_workers_output" = "yes";
+      };
+      description = ''
+        Options for PHP's php.ini file for nextcloud.
+      '';
+    };
+
+    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 nextcloud's PHP pool. See the documentation on <literal>php-fpm.conf</literal> for details on configuration directives.
+      '';
+    };
+
+    config = {
+      dbtype = mkOption {
+        type = types.enum [ "sqlite" "pgsql" "mysql" ];
+        default = "sqlite";
+        description = "Database type.";
+      };
+      dbname = mkOption {
+        type = types.nullOr types.str;
+        default = "nextcloud";
+        description = "Database name.";
+      };
+      dbuser = mkOption {
+        type = types.nullOr types.str;
+        default = "nextcloud";
+        description = "Database user.";
+      };
+      dbpass = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Database password.  Use <literal>dbpassFile</literal> to avoid this
+          being world-readable in the <literal>/nix/store</literal>.
+        '';
+      };
+      dbpassFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The full path to a file that contains the database password.
+        '';
+      };
+      dbhost = mkOption {
+        type = types.nullOr types.str;
+        default = "localhost";
+        description = ''
+          Database host.
+
+          Note: for using Unix authentication with PostgreSQL, this should be
+          set to <literal>/tmp</literal>.
+        '';
+      };
+      dbport = mkOption {
+        type = with types; nullOr (either int str);
+        default = null;
+        description = "Database port.";
+      };
+      dbtableprefix = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Table prefix in Nextcloud database.";
+      };
+      adminuser = mkOption {
+        type = types.str;
+        default = "root";
+        description = "Admin username.";
+      };
+      adminpass = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          Admin password.  Use <literal>adminpassFile</literal> to avoid this
+          being world-readable in the <literal>/nix/store</literal>.
+        '';
+      };
+      adminpassFile = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = ''
+          The full path to a file that contains the admin's password.
+        '';
+      };
+
+      extraTrustedDomains = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Trusted domains, from which the nextcloud installation will be
+          acessible.  You don't need to add
+          <literal>services.nextcloud.hostname</literal> here.
+        '';
+      };
+    };
+
+    caching = {
+      apcu = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Whether to load the APCu module into PHP.
+        '';
+      };
+      redis = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to load the Redis module into PHP.
+          You still need to enable Redis in your config.php.
+          See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+        '';
+      };
+      memcached = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to load the Memcached module into PHP.
+          You still need to enable Memcached in your config.php.
+          See https://docs.nextcloud.com/server/14/admin_manual/configuration_server/caching_configuration.html
+        '';
+      };
+    };
+  };
+
+  config = mkIf cfg.enable (mkMerge [
+    { assertions = let acfg = cfg.config; in [
+        { assertion = !(acfg.dbpass != null && acfg.dbpassFile != null);
+          message = "Please specify no more than one of dbpass or dbpassFile";
+        }
+        { assertion = ((acfg.adminpass != null || acfg.adminpassFile != null)
+            && !(acfg.adminpass != null && acfg.adminpassFile != null));
+          message = "Please specify exactly one of adminpass or adminpassFile";
+        }
+      ];
+    }
+
+    { systemd.timers."nextcloud-cron" = {
+        wantedBy = [ "timers.target" ];
+        timerConfig.OnBootSec = "5m";
+        timerConfig.OnUnitActiveSec = "15m";
+        timerConfig.Unit = "nextcloud-cron.service";
+      };
+
+      systemd.services = {
+        "nextcloud-setup" = let
+          overrideConfig = pkgs.writeText "nextcloud-config.php" ''
+            <?php
+            $CONFIG = [
+              'apps_paths' => [
+                [ 'path' => '${cfg.home}/apps', 'url' => '/apps', 'writable' => false ],
+                [ 'path' => '${cfg.home}/store-apps', 'url' => '/store-apps', 'writable' => true ],
+              ],
+              'datadirectory' => '${cfg.home}/data',
+              'skeletondirectory' => '${cfg.skeletonDirectory}',
+              ${optionalString cfg.caching.apcu "'memcache.local' => '\\OC\\Memcache\\APCu',"}
+              'log_type' => 'syslog',
+            ];
+          '';
+          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;
+            installFlags = concatStringsSep " \\\n    "
+              (mapAttrsToList (k: v: "${k} ${toString v}") {
+              "--database" = ''"${c.dbtype}"'';
+              # The following attributes are optional depending on the type of
+              # database.  Those that evaluate to null on the left hand side
+              # will be omitted.
+              ${if c.dbname != null then "--database-name" else null} = ''"${c.dbname}"'';
+              ${if c.dbhost != null then "--database-host" else null} = ''"${c.dbhost}"'';
+              ${if c.dbport != null then "--database-port" else null} = ''"${toString c.dbport}"'';
+              ${if c.dbuser != null then "--database-user" else null} = ''"${c.dbuser}"'';
+              ${if (any (x: x != null) [c.dbpass c.dbpassFile])
+                 then "--database-pass" else null} = dbpass;
+              ${if c.dbtableprefix != null
+                then "--database-table-prefix" else null} = ''"${toString c.dbtableprefix}"'';
+              "--admin-user" = ''"${c.adminuser}"'';
+              "--admin-pass" = adminpass;
+              "--data-dir" = ''"${cfg.home}/data"'';
+            });
+          in ''
+            ${occ}/bin/nextcloud-occ maintenance:install \
+                ${installFlags}
+          '';
+          occSetTrustedDomainsCmd = concatStringsSep "\n" (imap0
+            (i: v: ''
+              ${occ}/bin/nextcloud-occ config:system:set trusted_domains \
+                ${toString i} --value="${toString v}"
+            '') ([ cfg.hostName ] ++ cfg.config.extraTrustedDomains));
+
+        in {
+          wantedBy = [ "multi-user.target" ];
+          before = [ "phpfpm-nextcloud.service" ];
+          script = ''
+            chmod og+x ${cfg.home}
+            ln -sf ${pkgs.nextcloud}/apps ${cfg.home}/
+            mkdir -p ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps
+            ln -sf ${overrideConfig} ${cfg.home}/config/override.config.php
+
+            chown -R nextcloud:nginx ${cfg.home}/config ${cfg.home}/data ${cfg.home}/store-apps
+
+            # Do not install if already installed
+            if [[ ! -e ${cfg.home}/config/config.php ]]; then
+              ${occInstallCmd}
+            fi
+
+            ${occ}/bin/nextcloud-occ upgrade
+
+            ${occ}/bin/nextcloud-occ config:system:delete trusted_domains
+            ${occSetTrustedDomainsCmd}
+          '';
+          serviceConfig.Type = "oneshot";
+        };
+        "nextcloud-cron" = {
+          environment.NEXTCLOUD_CONFIG_DIR = "${cfg.home}/config";
+          serviceConfig.Type = "oneshot";
+          serviceConfig.User = "nextcloud";
+          serviceConfig.ExecStart = "${pkgs.php}/bin/php -f ${pkgs.nextcloud}/cron.php";
+        };
+      };
+
+      services.phpfpm = {
+        phpOptions = phpOptionsExtensions;
+        phpPackage = pkgs.php71;
+        pools.nextcloud = let
+          phpAdminValues = (toKeyValue
+            (foldr (a: b: a // b) {}
+              (mapAttrsToList (k: v: { "php_admin_value[${k}]" = v; })
+                phpOptions)));
+        in {
+          listen = "/run/phpfpm/nextcloud";
+          extraConfig = ''
+            listen.owner = nginx
+            listen.group = nginx
+            user = nextcloud
+            group = nginx
+            ${cfg.poolConfig}
+            env[NEXTCLOUD_CONFIG_DIR] = ${cfg.home}/config
+            env[PATH] = /run/wrappers/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin:/usr/bin:/bin
+            ${phpAdminValues}
+          '';
+        };
+      };
+
+      users.extraUsers.nextcloud = {
+        home = "${cfg.home}";
+        group = "nginx";
+        createHome = true;
+      };
+
+      environment.systemPackages = [ occ ];
+    }
+
+    (mkIf cfg.nginx.enable {
+      services.nginx = {
+        enable = true;
+        virtualHosts = {
+          "${cfg.hostName}" = {
+            root = pkgs.nextcloud;
+            locations = {
+              "= /robots.txt" = {
+                priority = 100;
+                extraConfig = ''
+                  allow all;
+                  log_not_found off;
+                  access_log off;
+                '';
+              };
+              "/" = {
+                priority = 200;
+                extraConfig = "rewrite ^ /index.php$uri;";
+              };
+              "~ ^/store-apps" = {
+                priority = 201;
+                extraConfig = "root ${cfg.home};";
+              };
+              "= /.well-known/carddav" = {
+                priority = 210;
+                extraConfig = "return 301 $scheme://$host/remote.php/dav;";
+              };
+              "= /.well-known/caldav" = {
+                priority = 210;
+                extraConfig = "return 301 $scheme://$host/remote.php/dav;";
+              };
+              "~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/" = {
+                priority = 300;
+                extraConfig = "deny all;";
+              };
+              "~ ^/(?:\\.|autotest|occ|issue|indie|db_|console)" = {
+                priority = 300;
+                extraConfig = "deny all;";
+              };
+              "~ ^/(?:index|remote|public|cron|core/ajax/update|status|ocs/v[12]|updater/.+|ocs-provider/.+)\\.php(?:$|/)" = {
+                priority = 500;
+                extraConfig = ''
+                  include ${config.services.nginx.package}/conf/fastcgi.conf;
+                  fastcgi_split_path_info ^(.+\.php)(/.*)$;
+                  fastcgi_param PATH_INFO $fastcgi_path_info;
+                  fastcgi_param HTTPS ${if cfg.https then "on" else "off"};
+                  fastcgi_param modHeadersAvailable true;
+                  fastcgi_param front_controller_active true;
+                  fastcgi_pass unix:/run/phpfpm/nextcloud;
+                  fastcgi_intercept_errors on;
+                  fastcgi_request_buffering off;
+                  fastcgi_read_timeout 120s;
+                '';
+              };
+              "~ ^/(?:updater|ocs-provider)(?:$|/)".extraConfig = ''
+                try_files $uri/ =404;
+                index index.php;
+              '';
+              "~ \\.(?:css|js|woff|svg|gif)$".extraConfig = ''
+                try_files $uri /index.php$uri$is_args$args;
+                add_header Cache-Control "public, max-age=15778463";
+                add_header X-Content-Type-Options nosniff;
+                add_header X-XSS-Protection "1; mode=block";
+                add_header X-Robots-Tag none;
+                add_header X-Download-Options noopen;
+                add_header X-Permitted-Cross-Domain-Policies none;
+                access_log off;
+              '';
+              "~ \\.(?:png|html|ttf|ico|jpg|jpeg)$".extraConfig = ''
+                try_files $uri /index.php$uri$is_args$args;
+                access_log off;
+              '';
+            };
+            extraConfig = ''
+              add_header X-Content-Type-Options nosniff;
+              add_header X-XSS-Protection "1; mode=block";
+              add_header X-Robots-Tag none;
+              add_header X-Download-Options noopen;
+              add_header X-Permitted-Cross-Domain-Policies none;
+              error_page 403 /core/templates/403.php;
+              error_page 404 /core/templates/404.php;
+              client_max_body_size ${cfg.maxUploadSize};
+              fastcgi_buffers 64 4K;
+              gzip on;
+              gzip_vary on;
+              gzip_comp_level 4;
+              gzip_min_length 256;
+              gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
+              gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
+
+              ${optionalString cfg.webfinger ''
+                rewrite ^/.well-known/host-meta /public.php?service=host-meta last;
+                rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last;
+              ''}
+            '';
+          };
+        };
+      };
+    })
+  ]);
+
+  meta.doc = ./nextcloud.xml;
+}
diff --git a/nixos/modules/services/web-apps/nextcloud.xml b/nixos/modules/services/web-apps/nextcloud.xml
new file mode 100644
index 0000000000000..9600d1be7c887
--- /dev/null
+++ b/nixos/modules/services/web-apps/nextcloud.xml
@@ -0,0 +1,99 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+         xmlns:xlink="http://www.w3.org/1999/xlink"
+         xmlns:xi="http://www.w3.org/2001/XInclude"
+         version="5.0"
+         xml:id="module-services-nextcloud">
+ <title>Nextcloud</title>
+
+ <para>
+  <link xlink:href="https://nextcloud.com/">Nextcloud</link> is an open-source, self-hostable cloud
+  platform. The server setup can be automated using
+  <link linkend="opt-services.nextcloud.enable">services.nextcloud</link>. A desktop client is packaged
+  at <literal>pkgs.nextcloud-client</literal>.
+ </para>
+
+ <section xml:id="module-services-nextcloud-basic-usage">
+  <title>Basic usage</title>
+  <para>
+   Nextcloud is a PHP-based application which requires an HTTP server
+   (<literal><link linkend="opt-services.nextcloud.enable">services.nextcloud</link></literal> optionally supports
+   <literal><link linkend="opt-services.nginx.enable">services.nginx</link></literal>) and a database
+   (it's recommended to use <literal><link linkend="opt-services.postgresql.enable">services.postgresql</link></literal>).
+  </para>
+  <para>
+    A very basic configuration may look like this:
+<programlisting>{ pkgs, ... }:
+{
+  services.nextcloud = {
+    <link linkend="opt-services.nextcloud.enable">enable</link> = true;
+    <link linkend="opt-services.nextcloud.hostName">hostName</link> = "nextcloud.tld";
+    <link linkend="opt-services.nextcloud.nginx.enable">nginx.enable</link> = true;
+    config = {
+      <link linkend="opt-services.nextcloud.config.dbtype">dbtype</link> = "pgsql";
+      <link linkend="opt-services.nextcloud.config.dbuser">dbuser</link> = "nextcloud";
+      <link linkend="opt-services.nextcloud.config.dbhost">dbhost</link> = "/tmp"; # nextcloud will add /.s.PGSQL.5432 by itself
+      <link linkend="opt-services.nextcloud.config.dbname">dbname</link> = "nextcloud";
+      <link linkend="opt-services.nextcloud.config.adminpassFile">adminpassFile</link> = "/path/to/admin-pass-file";
+      <link linkend="opt-services.nextcloud.config.adminuser">adminuser</link> = "root";
+    };
+  };
+
+  services.postgresql = {
+    <link linkend="opt-services.postgresql.enable">enable</link> = true;
+    <link linkend="opt-services.postgresql.initialScript">initialScript</link> = pkgs.writeText "psql-init" ''
+      CREATE ROLE nextcloud WITH LOGIN;
+      CREATE DATABASE nextcloud WITH OWNER nextcloud;
+    '';
+  };
+
+  # ensure that postgres is running *before* running the setup
+  systemd.services."nextcloud-setup" = {
+    requires = ["postgresql.service"];
+    after = ["postgresql.service"];
+  };
+
+  <link linkend="opt-networking.firewall.allowedTCPPorts">networking.firewall.allowedTCPPorts</link> = [ 80 443 ];
+}</programlisting>
+  </para>
+  <para>
+   The options <literal>hostName</literal> and <literal>nginx.enable</literal> are used internally to configure an
+   HTTP server using <literal><link xlink:href="https://php-fpm.org/">PHP-FPM</link></literal> and <literal>nginx</literal>.
+   The <literal>config</literal> attribute set is used 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>
+  </para>
+  <para>
+    In case the application serves multiple hosts (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
+    <literal><link linkend="opt-services.nextcloud.config.extraTrustedDomains">services.nextcloud.config.extraTrustedDomains</link></literal>.
+  </para>
+ </section>
+
+ <section xml:id="module-services-nextcloud-pitfalls-during-upgrade">
+  <title>Pitfalls</title>
+  <para>
+   Unfortunately Nextcloud appears to be very stateful when it comes to managing its own configuration. The
+   config file lives in the home directory of the <literal>nextcloud</literal> user (by default
+   <literal>/var/lib/nextcloud/config/config.php</literal>) and is also used to track several
+   states of the application (e.g. whether installed or not).
+  </para>
+  <para>
+   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.
+  </para>
+  <para>
+   <emphasis>Warning: don't delete <literal>config.php</literal>! This file tracks the application's state and a deletion can cause unwanted side-effects!</emphasis>
+  </para>
+  <para>
+   <emphasis>Warning: 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>
+ </section>
+
+</chapter>
diff --git a/nixos/modules/services/web-apps/quassel-webserver.nix b/nixos/modules/services/web-apps/quassel-webserver.nix
deleted file mode 100644
index 2ba5698d6cb1b..0000000000000
--- a/nixos/modules/services/web-apps/quassel-webserver.nix
+++ /dev/null
@@ -1,101 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
-  cfg = config.services.quassel-webserver;
-  quassel-webserver = cfg.pkg;
-  settings = ''
-    module.exports = {
-      default: {
-        host: '${cfg.quasselCoreHost}',  // quasselcore host
-        port: ${toString cfg.quasselCorePort},  // quasselcore port
-        initialBacklogLimit: ${toString cfg.initialBacklogLimit},  // Amount of backlogs to fetch per buffer on connection
-        backlogLimit: ${toString cfg.backlogLimit},  // Amount of backlogs to fetch per buffer after first retrieval
-        securecore: ${boolToString cfg.secureCore},  // Connect to the core using SSL
-        theme: '${cfg.theme}'  // Default UI theme
-      },
-      themes: ['default', 'darksolarized'],  //  Available themes
-      forcedefault: ${boolToString cfg.forceHostAndPort},  // Will force default host and port to be used, and will hide the corresponding fields in the UI
-      prefixpath: '${cfg.prefixPath}'  // Configure this if you use a reverse proxy
-    };
-  '';
-  settingsFile = pkgs.writeText "settings-user.js" settings;
-in {
-  options = {
-    services.quassel-webserver = {
-      enable = mkOption {
-        default = false;
-        type = types.bool;
-        description = "Whether to enable the quassel webclient service";
-      };
-      pkg = mkOption {
-        default = pkgs.quassel-webserver;
-        defaultText = "pkgs.quassel-webserver";
-        type = types.package;
-        description = "The quassel-webserver package";
-      };
-      quasselCoreHost = mkOption {
-        default = "";
-        type = types.str;
-        description = "The default host of the quassel core";
-      };
-      quasselCorePort = mkOption {
-        default = 4242;
-        type = types.int;
-        description = "The default quassel core port";
-      };
-      initialBacklogLimit = mkOption {
-        default = 20;
-        type = types.int;
-        description = "Amount of backlogs to fetch per buffer on connection";
-      };
-      backlogLimit = mkOption {
-        default = 100;
-        type = types.int;
-        description = "Amount of backlogs to fetch per buffer after first retrieval";
-      };
-      secureCore = mkOption {
-        default = true;
-        type = types.bool;
-        description = "Connect to the core using SSL";
-      };
-      theme = mkOption {
-        default = "default";
-        type = types.str;
-        description = "default or darksolarized";
-      };
-      prefixPath = mkOption {
-        default = "";
-        type = types.str;
-        description = "Configure this if you use a reverse proxy. Must start with a '/'";
-        example = "/quassel";
-      };
-      port = mkOption {
-        default = 60443;
-        type = types.int;
-        description = "The port the quassel webserver should listen on";
-      };
-      useHttps = mkOption {
-        default = true;
-        type = types.bool;
-        description = "Whether the quassel webserver connection should be a https connection";
-      };
-      forceHostAndPort = mkOption {
-        default = false;
-        type = types.bool;
-        description = "Force the users to use the quasselCoreHost and quasselCorePort defaults";
-      };
-    };
-  };
-
-  config = mkIf cfg.enable {
-    systemd.services.quassel-webserver = {
-      description = "A web server/client for Quassel";
-      wantedBy = [ "multi-user.target" ];
-      serviceConfig = {
-        ExecStart = "${quassel-webserver}/lib/node_modules/quassel-webserver/bin/www -p ${toString cfg.port} -m ${if cfg.useHttps == true then "https" else "http"} -c ${settingsFile}";
-      };
-    };
-  };
-}
diff --git a/nixos/modules/services/web-apps/selfoss.nix b/nixos/modules/services/web-apps/selfoss.nix
index 5571f77334cc7..7b0ce8a8d03f2 100644
--- a/nixos/modules/services/web-apps/selfoss.nix
+++ b/nixos/modules/services/web-apps/selfoss.nix
@@ -21,8 +21,8 @@ let
       db_database=${cfg.database.name}
       db_username=${cfg.database.user}
       db_password=${cfg.database.password}
-      db_port=${if (cfg.database.port != null) then cfg.database.port
-                    else default_port}
+      db_port=${toString (if (cfg.database.port != null) then cfg.database.port
+                    else default_port)}
     ''
     }
     ${cfg.extraConfig}
diff --git a/nixos/modules/services/web-apps/tt-rss.nix b/nixos/modules/services/web-apps/tt-rss.nix
index 2b171aa1b2b29..fa42ce8123427 100644
--- a/nixos/modules/services/web-apps/tt-rss.nix
+++ b/nixos/modules/services/web-apps/tt-rss.nix
@@ -34,7 +34,14 @@ let
       define('DB_HOST', '${optionalString (cfg.database.host != null) cfg.database.host}');
       define('DB_USER', '${cfg.database.user}');
       define('DB_NAME', '${cfg.database.name}');
-      define('DB_PASS', '${optionalString (cfg.database.password != null) (escape ["'" "\\"] cfg.database.password)}');
+      define('DB_PASS', ${
+        if (cfg.database.password != null) then
+          "'${(escape ["'" "\\"] cfg.database.password)}'"
+        else if (cfg.database.passwordFile != null) then
+          "file_get_contents('${cfg.database.passwordFile}')"
+        else
+          ""
+      });
       define('DB_PORT', '${toString dbPort}');
 
       define('AUTH_AUTO_CREATE', ${boolToString cfg.auth.autoCreate});
@@ -46,7 +53,17 @@ let
       define('SINGLE_USER_MODE', ${boolToString cfg.singleUserMode});
 
       define('SIMPLE_UPDATE_MODE', ${boolToString cfg.simpleUpdateMode});
-      define('CHECK_FOR_UPDATES', ${boolToString cfg.checkForUpdates});
+
+      // Never check for updates - the running version of the code should be
+      // controlled entirely by the version of TT-RSS active in the current Nix
+      // profile. If TT-RSS updates itself to a version requiring a database
+      // schema upgrade, and then the SystemD tt-rss.service is restarted, the
+      // old code copied from the Nix store will overwrite the updated version,
+      // causing the code to detect the need for a schema "upgrade" (since the
+      // schema version in the database is different than in the code), but the
+      // update schema operation in TT-RSS will do nothing because the schema
+      // version in the database is newer than that in the code.
+      define('CHECK_FOR_UPDATES', false);
 
       define('FORCE_ARTICLE_PURGE', ${toString cfg.forceArticlePurge});
       define('SESSION_COOKIE_LIFETIME', ${toString cfg.sessionCookieLifetime});
@@ -168,6 +185,14 @@ let
           '';
         };
 
+        passwordFile = mkOption {
+          type = types.nullOr types.str;
+          default = null;
+          description = ''
+            The database user's password.
+          '';
+        };
+
         port = mkOption {
           type = types.nullOr types.int;
           default = null;
@@ -399,14 +424,6 @@ let
         '';
       };
 
-      checkForUpdates = mkOption {
-        type = types.bool;
-        default = true;
-        description = ''
-          Check for updates automatically if running Git version
-        '';
-      };
-
       enableGZipOutput = mkOption {
         type = types.bool;
         default = true;
@@ -474,26 +491,43 @@ let
     };
   };
 
+  imports = [
+    (mkRemovedOptionModule ["services" "tt-rss" "checkForUpdates"] ''
+      This option was removed because setting this to true will cause TT-RSS
+      to be unable to start if an automatic update of the code in
+      services.tt-rss.root leads to a database schema upgrade that is not
+      supported by the code active in the Nix store.
+    '')
+  ];
 
   ###### implementation
 
   config = mkIf cfg.enable {
 
-    services.phpfpm.poolConfigs = mkIf (cfg.pool == "${poolName}") {
-      "${poolName}" = ''
-        listen = "${phpfpmSocketName}";
-        listen.owner = nginx
-        listen.group = nginx
-        listen.mode = 0600
-        user = ${cfg.user}
-        pm = dynamic
-        pm.max_children = 75
-        pm.start_servers = 10
-        pm.min_spare_servers = 5
-        pm.max_spare_servers = 20
-        pm.max_requests = 500
-        catch_workers_output = 1
-      '';
+    assertions = [
+      {
+        assertion = cfg.database.password != null -> cfg.database.passwordFile == null;
+        message = "Cannot set both password and passwordFile";
+      }
+    ];
+
+    services.phpfpm.pools = mkIf (cfg.pool == "${poolName}") {
+      "${poolName}" = {
+        listen = "/var/run/phpfpm/${poolName}.sock";
+        extraConfig = ''
+          listen.owner = nginx
+          listen.group = nginx
+          listen.mode = 0600
+          user = ${cfg.user}
+          pm = dynamic
+          pm.max_children = 75
+          pm.start_servers = 10
+          pm.min_spare_servers = 5
+          pm.max_spare_servers = 20
+          pm.max_requests = 500
+          catch_workers_output = 1
+        '';
+      };
     };
 
     # NOTE: No configuration is done if not using virtual host
@@ -510,7 +544,7 @@ let
           locations."~ \.php$" = {
             extraConfig = ''
               fastcgi_split_path_info ^(.+\.php)(/.+)$;
-              fastcgi_pass unix:${phpfpmSocketName};
+              fastcgi_pass unix:${config.services.phpfpm.pools.${cfg.pool}.listen};
               fastcgi_index index.php;
             '';
           };
@@ -528,6 +562,7 @@ let
           callSql = e:
               if cfg.database.type == "pgsql" then ''
                   ${optionalString (cfg.database.password != null) "PGPASSWORD=${cfg.database.password}"} \
+                  ${optionalString (cfg.database.passwordFile != null) "PGPASSWORD=$(cat ${cfg.database.passwordFile})"} \
                   ${pkgs.sudo}/bin/sudo -u ${cfg.user} ${config.services.postgresql.package}/bin/psql \
                     -U ${cfg.database.user} \
                     ${optionalString (cfg.database.host != null) "-h ${cfg.database.host} --port ${toString dbPort}"} \
@@ -624,7 +659,11 @@ let
     };
 
     users = optionalAttrs (cfg.user == "tt_rss") {
-      users.tt_rss.group = "tt_rss";
+      users.tt_rss = {
+        description = "tt-rss service user";
+        isSystemUser = true;
+        group = "tt_rss";
+      };
       groups.tt_rss = {};
     };
   };
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix
index 73607c6f9a3bb..bb96233478691 100644
--- a/nixos/modules/services/web-servers/apache-httpd/default.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/default.nix
@@ -151,7 +151,7 @@ let
 
 
   loggingConf = (if mainCfg.logFormat != "none" then ''
-    ErrorLog ${mainCfg.logDir}/error_log
+    ErrorLog ${mainCfg.logDir}/error.log
 
     LogLevel notice
 
@@ -160,7 +160,7 @@ let
     LogFormat "%{Referer}i -> %U" referer
     LogFormat "%{User-agent}i" agent
 
-    CustomLog ${mainCfg.logDir}/access_log ${mainCfg.logFormat}
+    CustomLog ${mainCfg.logDir}/access.log ${mainCfg.logFormat}
   '' else ''
     ErrorLog /dev/null
   '');
@@ -187,8 +187,8 @@ let
     SSLRandomSeed startup builtin
     SSLRandomSeed connect builtin
 
-    SSLProtocol All -SSLv2 -SSLv3
-    SSLCipherSuite HIGH:!aNULL:!MD5:!EXP
+    SSLProtocol ${mainCfg.sslProtocols}
+    SSLCipherSuite ${mainCfg.sslCiphers}
     SSLHonorCipherOrder on
   '';
 
@@ -261,8 +261,8 @@ let
     '' else ""}
 
     ${if !isMainServer && mainCfg.logPerVirtualHost then ''
-      ErrorLog ${mainCfg.logDir}/error_log-${cfg.hostName}
-      CustomLog ${mainCfg.logDir}/access_log-${cfg.hostName} ${cfg.logFormat}
+      ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log
+      CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat}
     '' else ""}
 
     ${optionalString (robotsTxt != "") ''
@@ -630,6 +630,19 @@ in
         description =
           "Maximum number of httpd requests answered per httpd child (prefork), 0 means unlimited";
       };
+
+      sslCiphers = mkOption {
+        type = types.str;
+        default = "HIGH:!aNULL:!MD5:!EXP";
+        description = "Cipher Suite available for negotiation in SSL proxy handshake.";
+      };
+
+      sslProtocols = mkOption {
+        type = types.str;
+        default = "All -SSLv2 -SSLv3";
+        example = "All -SSLv2 -SSLv3 -TLSv1";
+        description = "Allowed SSL/TLS protocol versions.";
+      };
     }
 
     # Include the options shared between the main server and virtual hosts.
diff --git a/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix b/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix
index 77194f3474928..5c387700a5d59 100644
--- a/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/limesurvey.nix
@@ -85,7 +85,7 @@ in rec {
     id = mkOption {
       default = "main";
       description = ''
-        A unique identifier necessary to keep multiple owncloud server
+        A unique identifier necessary to keep multiple Limesurvey server
         instances on the same machine apart.  This is used to
         disambiguate the administrative scripts, which get names like
         mediawiki-$id-change-password.
diff --git a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix b/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
index 02695c1c43a1e..e871ae6ff15ad 100644
--- a/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/mediawiki.nix
@@ -83,11 +83,11 @@ let
 
   # Unpack Mediawiki and put the config file in its root directory.
   mediawikiRoot = pkgs.stdenv.mkDerivation rec {
-    name= "mediawiki-1.29.1";
+    name= "mediawiki-1.31.1";
 
     src = pkgs.fetchurl {
-      url = "http://download.wikimedia.org/mediawiki/1.29/${name}.tar.gz";
-      sha256 = "03mpazbxvb011s2nmlw5p6dc43yjgl5yrsilmj1imyykm57bwb3m";
+      url = "https://releases.wikimedia.org/mediawiki/1.31/${name}.tar.gz";
+      sha256 = "13x48clij21cmysjkpnx68vggchrdasqp7b290j87xlfgjhdhnnf";
     };
 
     skins = config.skins;
@@ -111,7 +111,7 @@ let
         sed -i \
         -e 's|/bin/bash|${pkgs.bash}/bin/bash|g' \
         -e 's|/usr/bin/timeout|${pkgs.coreutils}/bin/timeout|g' \
-          $out/includes/limit.sh \
+          $out/includes/shell/limit.sh \
           $out/includes/GlobalFunctions.php
       '';
   };
@@ -311,7 +311,7 @@ in
       description = ''
         Any additional text to be appended to MediaWiki's
         configuration file.  This is a PHP script.  For configuration
-        settings, see <link xlink:href='http://www.mediawiki.org/wiki/Manual:Configuration_settings'/>.
+        settings, see <link xlink:href='https://www.mediawiki.org/wiki/Manual:Configuration_settings'/>.
       '';
     };
 
diff --git a/nixos/modules/services/web-servers/apache-httpd/owncloud.nix b/nixos/modules/services/web-servers/apache-httpd/owncloud.nix
deleted file mode 100644
index 6345a9a569355..0000000000000
--- a/nixos/modules/services/web-servers/apache-httpd/owncloud.nix
+++ /dev/null
@@ -1,608 +0,0 @@
-{ config, lib, pkgs, serverInfo, php, ... }:
-
-with lib;
-
-let
-
-  owncloudConfig = pkgs.writeText "config.php"
-    ''
-      <?php
-
-      /* Only enable this for local development and not in productive environments */
-      /* This will disable the minifier and outputs some additional debug informations */
-      define("DEBUG", false);
-
-      $CONFIG = array(
-      /* Flag to indicate ownCloud is successfully installed (true = installed) */
-      "installed" => true,
-
-      /* Type of database, can be sqlite, mysql or pgsql */
-      "dbtype" => "${config.dbType}",
-
-      /* Name of the ownCloud database */
-      "dbname" => "${config.dbName}",
-
-      /* User to access the ownCloud database */
-      "dbuser" => "${config.dbUser}",
-
-      /* Password to access the ownCloud database */
-      "dbpassword" => "${config.dbPassword}",
-
-      /* Host running the ownCloud database. To specify a port use "HOSTNAME:####"; to specify a unix sockets use "localhost:/path/to/socket". */
-      "dbhost" => "${config.dbServer}",
-
-      /* Prefix for the ownCloud tables in the database */
-      "dbtableprefix" => "",
-
-      /* Force use of HTTPS connection (true = use HTTPS) */
-      "forcessl" => ${config.forceSSL},
-
-      /* Blacklist a specific file and disallow the upload of files with this name - WARNING: USE THIS ONLY IF YOU KNOW WHAT YOU ARE DOING. */
-      "blacklisted_files" => array('.htaccess'),
-
-      /* The automatic hostname detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to manually override the automatic detection. You can also add a port. For example "www.example.com:88" */
-      "overwritehost" => "${config.overwriteHost}",
-
-      /* The automatic protocol detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to manually override the protocol detection. For example "https" */
-      "overwriteprotocol" => "${config.overwriteProtocol}",
-
-      /* The automatic webroot detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to manually override the automatic detection. For example "/domain.tld/ownCloud". The value "/" can be used to remove the root. */
-      "overwritewebroot" => "${config.overwriteWebRoot}",
-
-      /* The automatic detection of ownCloud can fail in certain reverse proxy and CLI/cron situations. This option allows to define a manually override condition as regular expression for the remote ip address. For example "^10\.0\.0\.[1-3]$" */
-      "overwritecondaddr" => "",
-
-      /* A proxy to use to connect to the internet. For example "myproxy.org:88" */
-      "proxy" => "",
-
-      /* The optional authentication for the proxy to use to connect to the internet. The format is: [username]:[password] */
-      "proxyuserpwd" => "",
-
-      /* List of trusted domains, to prevent host header poisoning ownCloud is only using these Host headers */
-      ${if config.trustedDomain != "" then "'trusted_domains' => array('${config.trustedDomain}')," else ""}
-
-      /* Theme to use for ownCloud */
-      "theme" => "",
-
-      /* Optional ownCloud default language - overrides automatic language detection on public pages like login or shared items. This has no effect on the user's language preference configured under "personal -> language" once they have logged in */
-      "default_language" => "${config.defaultLang}",
-
-      /* Path to the parent directory of the 3rdparty directory */
-      "3rdpartyroot" => "",
-
-      /* URL to the parent directory of the 3rdparty directory, as seen by the browser */
-      "3rdpartyurl" => "",
-
-      /* Default app to open on login.
-       * This can be a comma-separated list of app ids.
-       * If the first app is not enabled for the current user,
-       * it will try with the second one and so on. If no enabled app could be found,
-       * the "files" app will be displayed instead. */
-      "defaultapp" => "${config.defaultApp}",
-
-      /* Enable the help menu item in the settings */
-      "knowledgebaseenabled" => true,
-
-      /* Enable installing apps from the appstore */
-      "appstoreenabled" => ${config.appStoreEnable},
-
-      /* URL of the appstore to use, server should understand OCS */
-      "appstoreurl" => "https://api.owncloud.com/v1",
-
-      /* Domain name used by ownCloud for the sender mail address, e.g. no-reply@example.com */
-      "mail_domain" => "${config.mailFromDomain}",
-
-      /* FROM address used by ownCloud for the sender mail address, e.g. owncloud@example.com
-         This setting overwrites the built in 'sharing-noreply' and 'lostpassword-noreply'
-         FROM addresses, that ownCloud uses
-      */
-      "mail_from_address" => "${config.mailFrom}",
-
-      /* Enable SMTP class debugging */
-      "mail_smtpdebug" => false,
-
-      /* Mode to use for sending mail, can be sendmail, smtp, qmail or php, see PHPMailer docs */
-      "mail_smtpmode" => "${config.SMTPMode}",
-
-      /* Host to use for sending mail, depends on mail_smtpmode if this is used */
-      "mail_smtphost" => "${config.SMTPHost}",
-
-      /* Port to use for sending mail, depends on mail_smtpmode if this is used */
-      "mail_smtpport" => ${config.SMTPPort},
-
-      /* SMTP server timeout in seconds for sending mail, depends on mail_smtpmode if this is used */
-      "mail_smtptimeout" => ${config.SMTPTimeout},
-
-      /* SMTP connection prefix or sending mail, depends on mail_smtpmode if this is used.
-         Can be "", ssl or tls */
-      "mail_smtpsecure" => "${config.SMTPSecure}",
-
-      /* authentication needed to send mail, depends on mail_smtpmode if this is used
-       * (false = disable authentication)
-       */
-      "mail_smtpauth" => ${config.SMTPAuth},
-
-      /* authentication type needed to send mail, depends on mail_smtpmode if this is used
-       * Can be LOGIN (default), PLAIN or NTLM */
-      "mail_smtpauthtype" => "${config.SMTPAuthType}",
-
-      /* Username to use for sendmail mail, depends on mail_smtpauth if this is used */
-      "mail_smtpname" => "${config.SMTPUser}",
-
-      /* Password to use for sendmail mail, depends on mail_smtpauth if this is used */
-      "mail_smtppassword" => "${config.SMTPPass}",
-
-      /* memcached servers (Only used when xCache, APC and APCu are absent.) */
-      "memcached_servers" => array(
-          // hostname, port and optional weight. Also see:
-          // http://www.php.net/manual/en/memcached.addservers.php
-          // http://www.php.net/manual/en/memcached.addserver.php
-          //array('localhost', 11211),
-          //array('other.host.local', 11211),
-      ),
-
-      /* How long should ownCloud keep deleted files in the trash bin, default value:  30 days */
-      'trashbin_retention_obligation' => 30,
-
-      /* Disable/Enable auto expire for the trash bin, by default auto expire is enabled */
-      'trashbin_auto_expire' => true,
-
-      /* allow user to change his display name, if it is supported by the back-end */
-      'allow_user_to_change_display_name' => true,
-
-      /* Check 3rdparty apps for malicious code fragments */
-      "appcodechecker" => true,
-
-      /* Check if ownCloud is up to date */
-      "updatechecker" => true,
-
-      /* Are we connected to the internet or are we running in a closed network? */
-      "has_internet_connection" => true,
-
-      /* Check if the ownCloud WebDAV server is working correctly. Can be disabled if not needed in special situations*/
-      "check_for_working_webdav" => true,
-
-      /* Check if .htaccess protection of data is working correctly. Can be disabled if not needed in special situations*/
-      "check_for_working_htaccess" => true,
-
-      /* Place to log to, can be owncloud and syslog (owncloud is log menu item in admin menu) */
-      "log_type" => "owncloud",
-
-      /* File for the owncloud logger to log to, (default is ownloud.log in the data dir) */
-      "logfile" => "${config.dataDir}/owncloud.log",
-
-      /* Loglevel to start logging at. 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR (default is WARN) */
-      "loglevel" => "2",
-
-      /* date format to be used while writing to the owncloud logfile */
-      'logdateformat' => 'F d, Y H:i:s',
-
-      ${tzSetting}
-
-      /* Append all database queries and parameters to the log file.
-       (watch out, this option can increase the size of your log file)*/
-      "log_query" => false,
-
-      /* Whether ownCloud should log the last successfull cron exec */
-      "cron_log" => true,
-
-      /*
-       * Configure the size in bytes log rotation should happen, 0 or false disables the rotation.
-       * This rotates the current owncloud logfile to a new name, this way the total log usage
-       * will stay limited and older entries are available for a while longer. The
-       * total disk usage is twice the configured size.
-       * WARNING: When you use this, the log entries will eventually be lost.
-       */
-      'log_rotate_size' => "104857600", // 104857600, // 100 MiB
-
-      /* Lifetime of the remember login cookie, default is 15 days */
-      "remember_login_cookie_lifetime" => 1296000,
-
-      /* Life time of a session after inactivity */
-      "session_lifetime" => 86400,
-
-      /*
-       * Enable/disable session keep alive when a user is logged in in the Web UI.
-       * This is achieved by sending a "heartbeat" to the server to prevent
-       * the session timing out.
-       */
-      "session_keepalive" => true,
-
-      /* Custom CSP policy, changing this will overwrite the standard policy */
-      "custom_csp_policy" => "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; frame-src *; img-src *; font-src 'self' data:; media-src *",
-
-      /* Enable/disable X-Frame-Restriction */
-      /* HIGH SECURITY RISK IF DISABLED*/
-      "xframe_restriction" => true,
-
-      /* The directory where the user data is stored, default to data in the owncloud
-       * directory. The sqlite database is also stored here, when sqlite is used.
-       */
-      "datadirectory" => "${config.dataDir}/storage",
-
-      /* The directory where the skeleton files are located. These files will be copied to the data
-       * directory of new users. Leave empty to not copy any skeleton files.
-       */
-      // "skeletondirectory" => "",
-
-      /* Enable maintenance mode to disable ownCloud
-         If you want to prevent users to login to ownCloud before you start doing some maintenance work,
-         you need to set the value of the maintenance parameter to true.
-         Please keep in mind that users who are already logged-in are kicked out of ownCloud instantly.
-      */
-      "maintenance" => false,
-
-      "apps_paths" => array(
-
-      /* Set an array of path for your apps directories
-       key 'path' is for the fs path and the key 'url' is for the http path to your
-       applications paths. 'writable' indicates whether the user can install apps in this folder.
-       You must have at least 1 app folder writable or you must set the parameter 'appstoreenabled' to false
-      */
-          array(
-              'path'=> '${config.dataDir}/apps',
-              'url' => '/apps',
-              'writable' => true,
-          ),
-      ),
-      'user_backends'=>array(
-          /*
-          array(
-              'class'=>'OC_User_IMAP',
-              'arguments'=>array('{imap.gmail.com:993/imap/ssl}INBOX')
-          )
-          */
-      ),
-      //links to custom clients
-      'customclient_desktop' => ''', //http://owncloud.org/sync-clients/
-      'customclient_android' => ''', //https://play.google.com/store/apps/details?id=com.owncloud.android
-      'customclient_ios' => ''', //https://itunes.apple.com/us/app/owncloud/id543672169?mt=8
-
-      // PREVIEW
-      'enable_previews' => true,
-      /* the max width of a generated preview, if value is null, there is no limit */
-      'preview_max_x' => null,
-      /* the max height of a generated preview, if value is null, there is no limit */
-      'preview_max_y' => null,
-      /* the max factor to scale a preview, default is set to 10 */
-      'preview_max_scale_factor' => 10,
-      /* custom path for libreoffice / openoffice binary */
-      'preview_libreoffice_path' => '${config.libreofficePath}',
-      /* cl parameters for libreoffice / openoffice */
-      'preview_office_cl_parameters' => ''',
-
-      /* whether avatars should be enabled */
-      'enable_avatars' => true,
-
-      // Extra SSL options to be used for configuration
-      'openssl' => array(
-          'config' => '/etc/ssl/openssl.cnf',
-      ),
-
-      // default cipher used for file encryption, currently we support AES-128-CFB and AES-256-CFB
-      'cipher' => 'AES-256-CFB',
-
-      /* whether usage of the instance should be restricted to admin users only */
-      'singleuser' => false,
-
-      /* all css and js files will be served by the web server statically in one js file and ons css file*/
-      'asset-pipeline.enabled' => false,
-
-      /* where mount.json file should be stored, defaults to data/mount.json */
-      'mount_file' => ''',
-
-      /*
-       * Location of the cache folder, defaults to "data/$user/cache" where "$user" is the current user.
-       *
-       * When specified, the format will change to "$cache_path/$user" where "$cache_path" is the configured
-       * cache directory and "$user" is the user.
-       *
-       */
-      'cache_path' => ''',
-
-      /* EXPERIMENTAL: option whether to include external storage in quota calculation, defaults to false */
-      'quota_include_external_storage' => false,
-
-      /*
-       * specifies how often the filesystem is checked for changes made outside owncloud
-       * 0 -> never check the filesystem for outside changes, provides a performance increase when it's certain that no changes are made directly to the filesystem
-       * 1 -> check each file or folder at most once per request, recomended for general use if outside changes might happen
-       * 2 -> check every time the filesystem is used, causes a performance hit when using external storages, not recomended for regular use
-       */
-      'filesystem_check_changes' => 1,
-
-      /* If true, prevent owncloud from changing the cache due to changes in the filesystem for all storage */
-      'filesystem_cache_readonly' => false,
-
-      /**
-       * define default folder for shared files and folders
-       */
-      'share_folder' => '/',
-
-      'version' => '${config.package.version}',
-
-      'openssl' => '${pkgs.openssl.bin}/bin/openssl'
-
-      );
-
-    '';
-
-  tzSetting = let tz = serverInfo.fullConfig.time.timeZone; in optionalString (!isNull tz) ''
-    /* timezone used while writing to the owncloud logfile (default: UTC) */
-    'logtimezone' => '${tz}',
-  '';
-
-  postgresql = serverInfo.fullConfig.services.postgresql.package;
-
-  setupDb = pkgs.writeScript "setup-owncloud-db" ''
-    #!${pkgs.runtimeShell}
-    PATH="${postgresql}/bin"
-    createuser --no-superuser --no-createdb --no-createrole "${config.dbUser}" || true
-    createdb "${config.dbName}" -O "${config.dbUser}" || true
-    psql -U postgres -d postgres -c "alter user ${config.dbUser} with password '${config.dbPassword}';" || true
-
-    QUERY="CREATE TABLE appconfig
-             ( appid       VARCHAR( 255 ) NOT NULL
-             , configkey   VARCHAR( 255 ) NOT NULL
-             , configvalue VARCHAR( 255 ) NOT NULL
-             );
-           GRANT ALL ON appconfig TO ${config.dbUser};
-           ALTER TABLE appconfig OWNER TO ${config.dbUser};"
-
-    psql -h "/tmp" -U postgres -d ${config.dbName} -Atw -c "$QUERY" || true
-  '';
-
-in
-
-rec {
-
-  extraConfig =
-    ''
-      ${if config.urlPrefix != "" then "Alias ${config.urlPrefix} ${config.package}" else ''
-
-        RewriteEngine On
-        RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-f
-        RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} !-d
-      ''}
-
-      <Directory ${config.package}>
-        Include ${config.package}/.htaccess
-      </Directory>
-    '';
-
-  globalEnvVars = [
-    { name = "OC_CONFIG_PATH"; value = "${config.dataDir}/config/"; }
-  ];
-
-  documentRoot = if config.urlPrefix == "" then config.package else null;
-
-  enablePHP = true;
-
-  options = {
-
-    package = mkOption {
-      type = types.package;
-      default = pkgs.owncloud70;
-      defaultText = "pkgs.owncloud70";
-      example = literalExample "pkgs.owncloud70";
-      description = ''
-          ownCloud package to use.
-      '';
-    };
-
-    urlPrefix = mkOption {
-      default = "";
-      example = "/owncloud";
-      description = ''
-        The URL prefix under which the owncloud service appears.
-      '';
-    };
-
-    id = mkOption {
-      default = "main";
-      description = ''
-        A unique identifier necessary to keep multiple owncloud server
-        instances on the same machine apart.  This is used to
-        disambiguate the administrative scripts, which get names like
-        mediawiki-$id-change-password.
-      '';
-    };
-
-    adminUser = mkOption {
-      default = "owncloud";
-      description = "The admin user name for accessing owncloud.";
-    };
-
-    adminPassword = mkOption {
-      description = "The admin password for accessing owncloud.";
-    };
-
-    dbType = mkOption {
-      default = "pgsql";
-      description = "Type of database, in NixOS, for now, only pgsql.";
-    };
-
-    dbName = mkOption {
-      default = "owncloud";
-      description = "Name of the database that holds the owncloud data.";
-    };
-
-    dbServer = mkOption {
-      default = "localhost:5432";
-      description = ''
-        The location of the database server.
-      '';
-    };
-
-    dbUser = mkOption {
-      default = "owncloud";
-      description = "The user name for accessing the database.";
-    };
-
-    dbPassword = mkOption {
-      example = "foobar";
-      description = ''
-        The password of the database user.  Warning: this is stored in
-        cleartext in the Nix store!
-      '';
-    };
-
-    forceSSL = mkOption {
-      default = "false";
-      description = "Force use of HTTPS connection.";
-    };
-
-    adminAddr = mkOption {
-      default = serverInfo.serverConfig.adminAddr;
-      example = "admin@example.com";
-      description = ''
-        Emergency contact e-mail address.  Defaults to the Apache
-        admin address.
-      '';
-    };
-
-    siteName = mkOption {
-      default = "owncloud";
-      example = "Foobar owncloud";
-      description = "Name of the owncloud";
-    };
-
-    trustedDomain = mkOption {
-      default = "";
-      description = "Trusted domain";
-    };
-
-    defaultLang = mkOption {
-      default = "";
-      description = "Default language";
-    };
-
-    defaultApp = mkOption {
-      default = "";
-      description = "Default application";
-    };
-
-    appStoreEnable = mkOption {
-      default = "true";
-      description = "Enable app store";
-    };
-
-    mailFrom = mkOption {
-      default = "no-reply";
-      description = "Mail from";
-    };
-
-    mailFromDomain = mkOption {
-      default = "example.xyz";
-      description = "Mail from domain";
-    };
-
-    SMTPMode = mkOption {
-      default = "smtp";
-      description = "Which mode to use for sending mail: sendmail, smtp, qmail or php.";
-    };
-
-    SMTPHost = mkOption {
-      default = "";
-      description = "SMTP host";
-    };
-
-    SMTPPort = mkOption {
-      default = "25";
-      description = "SMTP port";
-    };
-
-    SMTPTimeout = mkOption {
-      default = "10";
-      description = "SMTP mode";
-    };
-
-    SMTPSecure = mkOption {
-      default = "ssl";
-      description = "SMTP secure";
-    };
-
-    SMTPAuth = mkOption {
-      default = "true";
-      description = "SMTP auth";
-    };
-
-    SMTPAuthType = mkOption {
-      default = "LOGIN";
-      description = "SMTP auth type";
-    };
-
-    SMTPUser = mkOption {
-      default = "";
-      description = "SMTP user";
-    };
-
-    SMTPPass = mkOption {
-      default = "";
-      description = "SMTP pass";
-    };
-
-    dataDir = mkOption {
-      default = "/var/lib/owncloud";
-      description = "Data dir";
-    };
-
-    libreofficePath = mkOption {
-      default = "/usr/bin/libreoffice";
-      description = "Path for LibreOffice/OpenOffice binary.";
-    };
-
-    overwriteHost = mkOption {
-      default = "";
-      description = "The automatic hostname detection of ownCloud can fail in
-        certain reverse proxy and CLI/cron situations. This option allows to
-        manually override the automatic detection. You can also add a port.";
-    };
-
-    overwriteProtocol = mkOption {
-      default = "";
-      description = "The automatic protocol detection of ownCloud can fail in
-        certain reverse proxy and CLI/cron situations. This option allows to
-        manually override the protocol detection.";
-    };
-
-    overwriteWebRoot = mkOption {
-      default = "";
-      description = "The automatic webroot detection of ownCloud can fail in
-        certain reverse proxy and CLI/cron situations. This option allows to
-        manually override the automatic detection.";
-    };
-
-  };
-
-  startupScript = pkgs.writeScript "owncloud_startup.sh" ''
-
-    if [ ! -d ${config.dataDir}/config ]; then
-      mkdir -p ${config.dataDir}/config
-      cp ${owncloudConfig} ${config.dataDir}/config/config.php
-      mkdir -p ${config.dataDir}/storage
-      mkdir -p ${config.dataDir}/apps
-      cp -r ${config.package}/apps/* ${config.dataDir}/apps/
-      chmod -R ug+rw ${config.dataDir}
-      chmod -R o-rwx ${config.dataDir}
-      chown -R wwwrun:wwwrun ${config.dataDir}
-
-      ${pkgs.sudo}/bin/sudo -u postgres ${setupDb}
-    fi
-
-    if [ -e ${config.package}/config/ca-bundle.crt ]; then
-      cp -f ${config.package}/config/ca-bundle.crt ${config.dataDir}/config/
-    fi
-
-    ${php}/bin/php ${config.package}/occ upgrade >> ${config.dataDir}/upgrade.log || true
-
-    chown wwwrun:wwwrun ${config.dataDir}/owncloud.log || true
-
-    QUERY="INSERT INTO groups (gid) values('admin');
-           INSERT INTO users (uid,password)
-             values('${config.adminUser}','${builtins.hashString "sha1" config.adminPassword}');
-           INSERT INTO group_user (gid,uid)
-             values('admin','${config.adminUser}');"
-    ${pkgs.sudo}/bin/sudo -u postgres ${postgresql}/bin/psql -h "/tmp" -U postgres -d ${config.dbName} -Atw -c "$QUERY" || true
-  '';
-}
diff --git a/nixos/modules/services/web-servers/apache-httpd/wordpress.nix b/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
index c810b914e258a..c68bfd25f6a86 100644
--- a/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
+++ b/nixos/modules/services/web-servers/apache-httpd/wordpress.nix
@@ -85,10 +85,10 @@ let
       # remove bundled themes(s) coming with wordpress
       rm -Rf $out/wp-content/themes/*
 
-      # symlink additional theme(s)
-      ${concatMapStrings (theme: "ln -s ${theme} $out/wp-content/themes/${theme.name}\n") config.themes}
-      # symlink additional plugin(s)
-      ${concatMapStrings (plugin: "ln -s ${plugin} $out/wp-content/plugins/${plugin.name}\n") (config.plugins) }
+      # 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
diff --git a/nixos/modules/services/web-servers/lighttpd/collectd.nix b/nixos/modules/services/web-servers/lighttpd/collectd.nix
index 35b5edced68b6..e70c980d52437 100644
--- a/nixos/modules/services/web-servers/lighttpd/collectd.nix
+++ b/nixos/modules/services/web-servers/lighttpd/collectd.nix
@@ -48,7 +48,7 @@ in
           "/collectd" => "${cfg.collectionCgi}"
         )
         setenv.add-environment = (
-          "PERL5LIB" => "${with pkgs; lib.makePerlPath [ perlPackages.CGI perlPackages.HTMLParser perlPackages.URI rrdtool ]}",
+          "PERL5LIB" => "${with pkgs.perlPackages; makePerlPath [ CGI HTMLParser URI pkgs.rrdtool ]}",
           "COLLECTION_CONF" => "${collectionConf}"
         )
       }
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix
index b231ee5a3f018..6c733f093ba8c 100644
--- a/nixos/modules/services/web-servers/nginx/default.nix
+++ b/nixos/modules/services/web-servers/nginx/default.nix
@@ -46,7 +46,7 @@ let
 
   configFile = pkgs.writeText "nginx.conf" ''
     user ${cfg.user} ${cfg.group};
-    error_log stderr;
+    error_log ${cfg.logError};
     daemon off;
 
     ${cfg.config}
@@ -245,8 +245,8 @@ let
         }
       ''
   ) virtualHosts);
-  mkLocations = locations: concatStringsSep "\n" (mapAttrsToList (location: config: ''
-    location ${location} {
+  mkLocations = locations: concatStringsSep "\n" (map (config: ''
+    location ${config.location} {
       ${optionalString (config.proxyPass != null && !cfg.proxyResolveWhileRunning)
         "proxy_pass ${config.proxyPass};"
       }
@@ -266,7 +266,18 @@ let
       ${config.extraConfig}
       ${optionalString (config.proxyPass != null && cfg.recommendedProxySettings) "include ${recommendedProxyConfig};"}
     }
-  '') locations);
+  '') (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}
@@ -330,6 +341,35 @@ in
         ";
       };
 
+      logError = mkOption {
+        default = "stderr";
+        description = "
+          Configures logging.
+          The first parameter defines a file that will store the log. The
+          special value stderr selects the standard error file. Logging to
+          syslog can be configured by specifying the “syslog:” prefix.
+          The second parameter determines the level of logging, and can be
+          one of the following: debug, info, notice, warn, error, crit,
+          alert, or emerg. Log levels above are listed in the order of
+          increasing severity. Setting a certain log level will cause all
+          messages of the specified and more severe log levels to be logged.
+          If this parameter is omitted then error is used.
+        ";
+      };
+
+      preStart =  mkOption {
+        type = types.lines;
+        default = ''
+          test -d ${cfg.stateDir}/logs || mkdir -m 750 -p ${cfg.stateDir}/logs  
+          test `stat -c %a ${cfg.stateDir}` = "750" || chmod 750 ${cfg.stateDir}
+          test `stat -c %a ${cfg.stateDir}/logs` = "750" || chmod 750 ${cfg.stateDir}/logs
+          chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+        '';
+        description = "
+          Shell commands executed before the service's nginx is started.
+        ";
+      };
+
       config = mkOption {
         default = "";
         description = "
@@ -597,9 +637,7 @@ in
       stopIfChanged = false;
       preStart =
         ''
-        mkdir -p ${cfg.stateDir}/logs
-        chmod 700 ${cfg.stateDir}
-        chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir}
+        ${cfg.preStart}
         ${cfg.package}/bin/nginx -c ${configFile} -p ${cfg.stateDir} -t
         '';
       serviceConfig = {
diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix
index 4c772734a749a..9b44433d3845c 100644
--- a/nixos/modules/services/web-servers/nginx/location-options.nix
+++ b/nixos/modules/services/web-servers/nginx/location-options.nix
@@ -71,6 +71,16 @@ with lib;
         These lines go to the end of the location verbatim.
       '';
     };
+
+    priority = mkOption {
+      type = types.int;
+      default = 1000;
+      description = ''
+        Order of this location block in relation to the others in the vhost.
+        The semantics are the same as with `lib.mkOrder`. Smaller values have
+        a greater priority.
+      '';
+    };
   };
 }
 
diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix
index 8e2b8819e71bd..6a50d8ed5cd46 100644
--- a/nixos/modules/services/web-servers/nginx/vhost-options.nix
+++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix
@@ -69,7 +69,7 @@ with lib;
     acmeRoot = mkOption {
       type = types.str;
       default = "/var/lib/acme/acme-challenge";
-      description = "Directory to store certificates and keys managed by the ACME service.";
+      description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here";
     };
 
     acmeFallbackHost = mkOption {
diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix
index e1f4ff5db7f2c..97c730061bd19 100644
--- a/nixos/modules/services/web-servers/phpfpm/default.nix
+++ b/nixos/modules/services/web-servers/phpfpm/default.nix
@@ -8,25 +8,37 @@ let
 
   stateDir = "/run/phpfpm";
 
-  poolConfigs = cfg.poolConfigs // mapAttrs mkPool cfg.pools;
+  poolConfigs =
+    (mapAttrs mapPoolConfig cfg.poolConfigs) //
+    (mapAttrs mapPool cfg.pools);
+
+  mapPoolConfig = n: p: {
+    phpPackage = cfg.phpPackage;
+    phpOptions = cfg.phpOptions;
+    config = p;
+  };
 
-  mkPool = n: p: ''
-    listen = ${p.listen}
-    ${p.extraConfig}
-  '';
+  mapPool = n: p: {
+    phpPackage = p.phpPackage;
+    phpOptions = p.phpOptions;
+    config = ''
+      listen = ${p.listen}
+      ${p.extraConfig}
+    '';
+  };
 
-  fpmCfgFile = pool: poolConfig: pkgs.writeText "phpfpm-${pool}.conf" ''
+  fpmCfgFile = pool: conf: pkgs.writeText "phpfpm-${pool}.conf" ''
     [global]
     error_log = syslog
     daemonize = no
     ${cfg.extraConfig}
 
     [${pool}]
-    ${poolConfig}
+    ${conf}
   '';
 
-  phpIni = pkgs.runCommand "php.ini" {
-    inherit (cfg) phpPackage phpOptions;
+  phpIni = pool: pkgs.runCommand "php.ini" {
+    inherit (pool) phpPackage phpOptions;
     nixDefaults = ''
       sendmail_path = "/run/wrappers/bin/sendmail -t -i"
     '';
@@ -97,13 +109,14 @@ in {
 
       pools = mkOption {
         type = types.attrsOf (types.submodule (import ./pool-options.nix {
-          inherit lib;
+          inherit lib config;
         }));
         default = {};
         example = literalExample ''
          {
            mypool = {
              listen = "/path/to/unix/socket";
+             phpPackage = pkgs.php;
              extraConfig = '''
                user = nobody
                pm = dynamic
@@ -144,7 +157,8 @@ in {
           mkdir -p ${stateDir}
         '';
         serviceConfig = let
-          cfgFile = fpmCfgFile pool poolConfig;
+          cfgFile = fpmCfgFile pool poolConfig.config;
+          iniFile = phpIni poolConfig;
         in {
           Slice = "phpfpm.slice";
           PrivateDevices = true;
@@ -153,7 +167,7 @@ in {
           # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work
           RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
           Type = "notify";
-          ExecStart = "${cfg.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${phpIni}";
+          ExecStart = "${poolConfig.phpPackage}/bin/php-fpm -y ${cfgFile} -c ${iniFile}";
           ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
         };
       }
diff --git a/nixos/modules/services/web-servers/phpfpm/pool-options.nix b/nixos/modules/services/web-servers/phpfpm/pool-options.nix
index cc688c2c48a25..d9ad7eff71f2c 100644
--- a/nixos/modules/services/web-servers/phpfpm/pool-options.nix
+++ b/nixos/modules/services/web-servers/phpfpm/pool-options.nix
@@ -1,4 +1,8 @@
-{ lib }:
+{ lib, config }:
+
+let
+  fpmCfg = config.services.phpfpm;
+in
 
 with lib; {
 
@@ -12,6 +16,24 @@ with lib; {
       '';
     };
 
+    phpPackage = mkOption {
+      type = types.package;
+      default = fpmCfg.phpPackage;
+      defaultText = "config.services.phpfpm.phpPackage";
+      description = ''
+        The PHP package to use for running this PHP-FPM pool.
+      '';
+    };
+
+    phpOptions = mkOption {
+      type = types.lines;
+      default = fpmCfg.phpOptions;
+      defaultText = "config.services.phpfpm.phpOptions";
+      description = ''
+        "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool."
+      '';
+    };
+
     extraConfig = mkOption {
       type = types.lines;
       example = ''
diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix
index d92ba72a83362..68261c50324d0 100644
--- a/nixos/modules/services/web-servers/tomcat.nix
+++ b/nixos/modules/services/web-servers/tomcat.nix
@@ -31,10 +31,26 @@ in
         '';
       };
 
+      purifyOnStart = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          On startup, the `baseDir` directory is populated with various files,
+          subdirectories and symlinks. If this option is enabled, these items
+          (except for the `logs` and `work` subdirectories) are first removed.
+          This prevents interference from remainders of an old configuration
+          (libraries, webapps, etc.), so it's recommended to enable this option.
+        '';
+      };
+
       baseDir = mkOption {
         type = lib.types.path;
         default = "/var/tomcat";
-        description = "Location where Tomcat stores configuration files, webapplications and logfiles";
+        description = ''
+          Location where Tomcat stores configuration files, web applications
+          and logfiles. Note that it is partially cleared on each service startup
+          if `purifyOnStart` is enabled.
+        '';
       };
 
       logDirs = mkOption {
@@ -121,6 +137,11 @@ in
               type = types.str;
               description = "name of the virtualhost";
             };
+            aliases = mkOption {
+              type = types.listOf types.str;
+              description = "aliases of the virtualhost";
+              default = [];
+            };
             webapps = mkOption {
               type = types.listOf types.path;
               description = ''
@@ -192,6 +213,15 @@ in
       after = [ "network.target" ];
 
       preStart = ''
+        ${lib.optionalString cfg.purifyOnStart ''
+          # Delete most directories/symlinks we create from the existing base directory,
+          # to get rid of remainders of an old configuration.
+          # The list of directories to delete is taken from the "mkdir" command below,
+          # excluding "logs" (because logs are valuable) and "work" (because normally
+          # session files are there), and additionally including "bin".
+          rm -rf ${cfg.baseDir}/{conf,virtualhosts,temp,lib,shared/lib,webapps,bin}
+        ''}
+
         # Create the base directory
         mkdir -p \
           ${cfg.baseDir}/{conf,virtualhosts,logs,temp,lib,shared/lib,webapps,work}
@@ -220,10 +250,28 @@ in
 
         ${if cfg.serverXml != "" then ''
           cp -f ${pkgs.writeTextDir "server.xml" cfg.serverXml}/* ${cfg.baseDir}/conf/
-          '' else ''
-          # Create a modified server.xml which also includes all virtual hosts
-          sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\  ${toString (map (virtualHost: ''<Host name=\"${virtualHost.name}\" appBase=\"virtualhosts/${virtualHost.name}/webapps\" unpackWARs=\"true\" autoDeploy=\"true\" xmlValidation=\"false\" xmlNamespaceAware=\"false\" >${if cfg.logPerVirtualHost then ''<Valve className=\"org.apache.catalina.valves.AccessLogValve\" directory=\"logs/${virtualHost.name}\"  prefix=\"${virtualHost.name}_access_log.\" pattern=\"combined\" resolveHosts=\"false\"/>'' else ""}</Host>'') cfg.virtualHosts)}" \
-                ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
+        '' else
+          let
+            hostElementForVirtualHost = virtualHost: ''
+              <Host name="${virtualHost.name}" appBase="virtualhosts/${virtualHost.name}/webapps"
+                    unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
+            '' + concatStrings (innerElementsForVirtualHost virtualHost) + ''
+              </Host>
+            '';
+            innerElementsForVirtualHost = virtualHost:
+              (map (alias: ''
+                <Alias>${alias}</Alias>
+              '') virtualHost.aliases)
+              ++ (optional cfg.logPerVirtualHost ''
+                <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs/${virtualHost.name}"
+                       prefix="${virtualHost.name}_access_log." pattern="combined" resolveHosts="false"/>
+              '');
+            hostElementsString = concatMapStringsSep "\n" hostElementForVirtualHost cfg.virtualHosts;
+            hostElementsSedString = replaceStrings ["\n"] ["\\\n"] hostElementsString;
+          in ''
+            # Create a modified server.xml which also includes all virtual hosts
+            sed -e "/<Engine name=\"Catalina\" defaultHost=\"localhost\">/a\\"${escapeShellArg hostElementsSedString} \
+                  ${tomcat}/conf/server.xml > ${cfg.baseDir}/conf/server.xml
           ''
         }
         ${optionalString (cfg.logDirs != []) ''
diff --git a/nixos/modules/services/x11/compton.nix b/nixos/modules/services/x11/compton.nix
index 8641c05de52e8..cafd8d88ec4d3 100644
--- a/nixos/modules/services/x11/compton.nix
+++ b/nixos/modules/services/x11/compton.nix
@@ -238,6 +238,12 @@ in {
       description = "Compton composite manager";
       wantedBy = [ "graphical-session.target" ];
       partOf = [ "graphical-session.target" ];
+
+      # Temporarily fixes corrupt colours with Mesa 18
+      environment = mkIf (cfg.backend == "glx") {
+        allow_rgb10_configs = "false";
+      };
+
       serviceConfig = {
         ExecStart = "${cfg.package}/bin/compton --config ${configFile}";
         RestartSec = 3;
diff --git a/nixos/modules/services/x11/desktop-managers/gnome3.nix b/nixos/modules/services/x11/desktop-managers/gnome3.nix
index eb86f7b53bb63..7544ba4638acf 100644
--- a/nixos/modules/services/x11/desktop-managers/gnome3.nix
+++ b/nixos/modules/services/x11/desktop-managers/gnome3.nix
@@ -5,14 +5,6 @@ with lib;
 let
   cfg = config.services.xserver.desktopManager.gnome3;
 
-  # Remove packages of ys from xs, based on their names
-  removePackagesByName = xs: ys:
-    let
-      pkgName = drv: (builtins.parseDrvName drv.name).name;
-      ysNames = map pkgName ys;
-    in
-      filter (x: !(builtins.elem (pkgName x) ysNames)) xs;
-
   # Prioritize nautilus by default when opening directories
   mimeAppsList = pkgs.writeTextFile {
     name = "gnome-mimeapps";
@@ -44,6 +36,8 @@ let
      ${pkgs.glib.dev}/bin/glib-compile-schemas $out/share/gsettings-schemas/nixos-gsettings-overrides/glib-2.0/schemas/
     '';
 
+  flashbackEnabled = cfg.flashback.enableMetacity || length cfg.flashback.customSessions > 0;
+
 in {
 
   options = {
@@ -79,6 +73,36 @@ in {
       };
 
       debug = mkEnableOption "gnome-session debug messages";
+
+      flashback = {
+        enableMetacity = mkEnableOption "Enable the standard GNOME Flashback session with Metacity.";
+
+        customSessions = mkOption {
+          type = types.listOf (types.submodule {
+            options = {
+              wmName = mkOption {
+                type = types.str;
+                description = "The filename-compatible name of the window manager to use.";
+                example = "xmonad";
+              };
+
+              wmLabel = mkOption {
+                type = types.str;
+                description = "The pretty name of the window manager to use.";
+                example = "XMonad";
+              };
+
+              wmCommand = mkOption {
+                type = types.str;
+                description = "The executable of the window manager to use.";
+                example = "\${pkgs.haskellPackages.xmonad}/bin/xmonad";
+              };
+            };
+          });
+          default = [];
+          description = "Other GNOME Flashback sessions to enable.";
+        };
+      };
     };
 
     environment.gnome3.excludePackages = mkOption {
@@ -103,10 +127,12 @@ in {
     services.dleyna-server.enable = mkDefault true;
     services.gnome3.at-spi2-core.enable = true;
     services.gnome3.evolution-data-server.enable = true;
+    services.gnome3.file-roller.enable = mkDefault true;
     services.gnome3.gnome-disks.enable = mkDefault true;
     services.gnome3.gnome-documents.enable = mkDefault true;
     services.gnome3.gnome-keyring.enable = true;
     services.gnome3.gnome-online-accounts.enable = mkDefault true;
+    services.gnome3.gnome-remote-desktop.enable = mkDefault true;
     services.gnome3.gnome-terminal-server.enable = mkDefault true;
     services.gnome3.gnome-user-share.enable = mkDefault true;
     services.gnome3.gvfs.enable = true;
@@ -119,7 +145,9 @@ in {
     services.telepathy.enable = mkDefault true;
     networking.networkmanager.enable = mkDefault true;
     services.upower.enable = config.powerManagement.enable;
-    services.dbus.packages = mkIf config.services.printing.enable [ pkgs.system-config-printer ];
+    services.dbus.packages =
+      optional config.services.printing.enable pkgs.system-config-printer ++
+      optional flashbackEnabled pkgs.gnome3.gnome-screensaver;
     services.colord.enable = mkDefault true;
     services.packagekit.enable = mkDefault true;
     hardware.bluetooth.enable = mkDefault true;
@@ -133,22 +161,27 @@ in {
 
     fonts.fonts = [ pkgs.dejavu_fonts pkgs.cantarell-fonts ];
 
-    services.xserver.displayManager.gdm.enable = mkDefault true;
-    services.xserver.displayManager.extraSessionFilePackages = [ pkgs.gnome3.gnome-session ];
-
-    services.xserver.displayManager.sessionCommands = ''
-      if test "$XDG_CURRENT_DESKTOP" = "GNOME"; then
-          ${concatMapStrings (p: ''
-            if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
-              export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
-            fi
-
-            if [ -d "${p}/lib/girepository-1.0" ]; then
-              export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
-              export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
-            fi
-          '') cfg.sessionPath}
-      fi
+    services.xserver.displayManager.extraSessionFilePackages = [ pkgs.gnome3.gnome-session ]
+      ++ map
+        (wm: pkgs.gnome3.gnome-flashback.mkSessionForWm {
+          inherit (wm) wmName wmLabel wmCommand;
+        }) (optional cfg.flashback.enableMetacity {
+              wmName = "metacity";
+              wmLabel = "Metacity";
+              wmCommand = "${pkgs.gnome3.metacity}/bin/metacity";
+            } ++ cfg.flashback.customSessions);
+
+    environment.extraInit = ''
+      ${concatMapStrings (p: ''
+        if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
+          export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
+        fi
+
+        if [ -d "${p}/lib/girepository-1.0" ]; then
+          export GI_TYPELIB_PATH=$GI_TYPELIB_PATH''${GI_TYPELIB_PATH:+:}${p}/lib/girepository-1.0
+          export LD_LIBRARY_PATH=$LD_LIBRARY_PATH''${LD_LIBRARY_PATH:+:}${p}/lib
+        fi
+      '') cfg.sessionPath}
     '';
 
     environment.variables.GNOME_SESSION_DEBUG = optionalString cfg.debug "1";
@@ -169,7 +202,7 @@ in {
                                                 "${pkgs.gnome3.glib-networking.out}/lib/gio/modules"
                                                 "${pkgs.gnome3.gvfs}/lib/gio/modules" ];
     environment.systemPackages = pkgs.gnome3.corePackages ++ cfg.sessionPath
-      ++ (removePackagesByName pkgs.gnome3.optionalPackages config.environment.gnome3.excludePackages) ++ [
+      ++ (pkgs.gnome3.removePackagesByName pkgs.gnome3.optionalPackages config.environment.gnome3.excludePackages) ++ [
       pkgs.xdg-user-dirs # Update user dirs as described in http://freedesktop.org/wiki/Software/xdg-user-dirs/
     ];
 
@@ -181,8 +214,14 @@ in {
                               networkmanager-iodine networkmanager-l2tp; };
 
     # Needed for themes and backgrounds
-    environment.pathsToLink = [ "/share" ];
+    environment.pathsToLink = [
+      "/share"
+      "/share/nautilus-python/extensions"
+    ];
 
+    security.pam.services.gnome-screensaver = mkIf flashbackEnabled {
+      enableGnomeKeyring = true;
+    };
   };
 
 
diff --git a/nixos/modules/services/x11/desktop-managers/lxqt.nix b/nixos/modules/services/x11/desktop-managers/lxqt.nix
index 896f70c86ebb6..686bbd0dcf98b 100644
--- a/nixos/modules/services/x11/desktop-managers/lxqt.nix
+++ b/nixos/modules/services/x11/desktop-managers/lxqt.nix
@@ -3,15 +3,6 @@
 with lib;
 
 let
-
-  # Remove packages of ys from xs, based on their names
-  removePackagesByName = xs: ys:
-    let
-      pkgName = drv: (builtins.parseDrvName drv.name).name;
-      ysNames = map pkgName ys;
-    in
-      filter (x: !(builtins.elem (pkgName x) ysNames)) xs;
-
   xcfg = config.services.xserver;
   cfg = xcfg.desktopManager.lxqt;
 
@@ -60,7 +51,7 @@ in
     environment.systemPackages =
       pkgs.lxqt.preRequisitePackages ++
       pkgs.lxqt.corePackages ++
-      (removePackagesByName
+      (pkgs.gnome3.removePackagesByName
         pkgs.lxqt.optionalPackages
         config.environment.lxqt.excludePackages);
 
diff --git a/nixos/modules/services/x11/desktop-managers/mate.nix b/nixos/modules/services/x11/desktop-managers/mate.nix
index db83aaf3c19f3..4d2fafd149617 100644
--- a/nixos/modules/services/x11/desktop-managers/mate.nix
+++ b/nixos/modules/services/x11/desktop-managers/mate.nix
@@ -4,14 +4,6 @@ with lib;
 
 let
 
-  # Remove packages of ys from xs, based on their names
-  removePackagesByName = xs: ys:
-    let
-      pkgName = drv: (builtins.parseDrvName drv.name).name;
-      ysNames = map pkgName ys;
-    in
-      filter (x: !(builtins.elem (pkgName x) ysNames)) xs;
-
   addToXDGDirs = p: ''
     if [ -d "${p}/share/gsettings-schemas/${p.name}" ]; then
       export XDG_DATA_DIRS=$XDG_DATA_DIRS''${XDG_DATA_DIRS:+:}${p}/share/gsettings-schemas/${p.name}
@@ -96,7 +88,7 @@ in
 
     environment.systemPackages =
       pkgs.mate.basePackages ++
-      (removePackagesByName
+      (pkgs.gnome3.removePackagesByName
         pkgs.mate.extraPackages
         config.environment.mate.excludePackages);
 
diff --git a/nixos/modules/services/x11/desktop-managers/plasma5.nix b/nixos/modules/services/x11/desktop-managers/plasma5.nix
index d1cb962f6ff87..704cc78c1528f 100644
--- a/nixos/modules/services/x11/desktop-managers/plasma5.nix
+++ b/nixos/modules/services/x11/desktop-managers/plasma5.nix
@@ -64,7 +64,7 @@ in
       };
 
       security.wrappers = {
-        kcheckpass.source = "${lib.getBin plasma5.plasma-workspace}/lib/libexec/kcheckpass";
+        kcheckpass.source = "${lib.getBin plasma5.kscreenlocker}/lib/libexec/kcheckpass";
         "start_kdeinit".source = "${lib.getBin pkgs.kinit}/lib/libexec/kf5/start_kdeinit";
         kwin_wayland = {
           source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland";
@@ -81,6 +81,7 @@ in
           kconfig
           kconfigwidgets
           kcoreaddons
+          kdoctools
           kdbusaddons
           kdeclarative
           kded
@@ -184,10 +185,8 @@ in
         target = "X11/xkb";
       };
 
-      environment.variables = {
-        # Enable GTK applications to load SVG icons
-        GDK_PIXBUF_MODULE_FILE = "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache";
-      };
+      # Enable GTK applications to load SVG icons
+      services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
 
       fonts.fonts = with pkgs; [ noto-fonts hack-font ];
       fonts.fontconfig.defaultFonts = {
@@ -224,11 +223,8 @@ in
       security.pam.services.sddm.enableKwallet = true;
       security.pam.services.slim.enableKwallet = true;
 
-      # Update the start menu for each user that has `isNormalUser` set.
-      system.activationScripts.plasmaSetup = stringAfter [ "users" "groups" ]
-        (concatStringsSep "\n"
-          (mapAttrsToList (name: value: "${pkgs.su}/bin/su ${name} -c ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5")
-            (filterAttrs (n: v: v.isNormalUser) config.users.users)));
+      # Update the start menu for each user that is currently logged in
+      system.userActivationScripts.plasmaSetup = "${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5";
     })
   ];
 
diff --git a/nixos/modules/services/x11/desktop-managers/xfce.nix b/nixos/modules/services/x11/desktop-managers/xfce.nix
index 75b9a76e1924c..dabf09418da72 100644
--- a/nixos/modules/services/x11/desktop-managers/xfce.nix
+++ b/nixos/modules/services/x11/desktop-managers/xfce.nix
@@ -101,10 +101,11 @@ in
     ];
 
     environment.variables = {
-      GDK_PIXBUF_MODULE_FILE = "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache";
       GIO_EXTRA_MODULES = [ "${pkgs.xfce.gvfs}/lib/gio/modules" ];
     };
 
+    services.xserver.gdk-pixbuf.modulePackages = [ pkgs.librsvg ];
+
     services.xserver.desktopManager.session = [{
       name = "xfce";
       bgSupport = true;
diff --git a/nixos/modules/services/x11/display-managers/default.nix b/nixos/modules/services/x11/display-managers/default.nix
index f561c5f8b7a92..0e87e6adbab8e 100644
--- a/nixos/modules/services/x11/display-managers/default.nix
+++ b/nixos/modules/services/x11/display-managers/default.nix
@@ -78,8 +78,7 @@ let
       # This is required by user units using the session bus.
       ${config.systemd.package}/bin/systemctl --user import-environment DISPLAY XAUTHORITY DBUS_SESSION_BUS_ADDRESS
 
-      # Load X defaults.
-      # FIXME: Check XDG_SESSION_TYPE against x11
+      # Load X defaults. This should probably be safe on wayland too.
       ${xorg.xrdb}/bin/xrdb -merge ${xresourcesXft}
       if test -e ~/.Xresources; then
           ${xorg.xrdb}/bin/xrdb -merge ~/.Xresources
@@ -192,7 +191,17 @@ let
       '') names}
 
       ${concatMapStrings (pkg: ''
-        ${xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions
+        if test -d ${pkg}/share/xsessions; then
+          ${xorg.lndir}/bin/lndir ${pkg}/share/xsessions $out/share/xsessions
+        fi
+      '') cfg.displayManager.extraSessionFilePackages}
+
+      
+      ${concatMapStrings (pkg: ''
+        if test -d ${pkg}/share/wayland-sessions; then
+          mkdir -p "$out/share/wayland-sessions"
+          ${xorg.lndir}/bin/lndir ${pkg}/share/wayland-sessions $out/share/wayland-sessions
+        fi
       '') cfg.displayManager.extraSessionFilePackages}
     '';
 
@@ -222,6 +231,17 @@ in
         description = "List of arguments for the X server.";
       };
 
+      setupCommands = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Shell commands executed just after the X server has started.
+
+          This option is only effective for display managers for which this feature
+          is supported; currently these are LightDM, GDM and SDDM.
+        '';
+      };
+
       sessionCommands = mkOption {
         type = types.lines;
         default = "";
@@ -229,7 +249,10 @@ in
           ''
             xmessage "Hello World!" &
           '';
-        description = "Shell commands executed just before the window or desktop manager is started.";
+        description = ''
+          Shell commands executed just before the window or desktop manager is
+          started. These commands are not currently sourced for Wayland sessions.
+        '';
       };
 
       hiddenUsers = mkOption {
@@ -266,7 +289,7 @@ in
           session.  Each session script can set the
           <varname>waitPID</varname> shell variable to make this script
           wait until the end of the user session.  Each script is used
-          to define either a windows manager or a desktop manager.  These
+          to define either a window manager or a desktop manager.  These
           can be differentiated by setting the attribute
           <varname>manage</varname> either to <literal>"window"</literal>
           or <literal>"desktop"</literal>.
diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix
index a775dd0f0e04a..226fee7491c1a 100644
--- a/nixos/modules/services/x11/display-managers/gdm.nix
+++ b/nixos/modules/services/x11/display-managers/gdm.nix
@@ -7,6 +7,13 @@ let
   cfg = config.services.xserver.displayManager;
   gdm = pkgs.gnome3.gdm;
 
+  xSessionWrapper = if (cfg.setupCommands == "") then null else
+    pkgs.writeScript "gdm-x-session-wrapper" ''
+      #!${pkgs.bash}/bin/bash
+      ${cfg.setupCommands}
+      exec "$@"
+    '';
+
 in
 
 {
@@ -109,9 +116,14 @@ in
         environment = {
           GDM_X_SERVER_EXTRA_ARGS = toString
             (filter (arg: arg != "-terminate") cfg.xserverArgs);
-          GDM_SESSIONS_DIR = "${cfg.session.desktops}/share/xsessions";
+          XDG_DATA_DIRS = "${cfg.session.desktops}/share/";
           # Find the mouse
           XCURSOR_PATH = "~/.icons:${pkgs.gnome3.adwaita-icon-theme}/share/icons";
+        } // optionalAttrs (xSessionWrapper != null) {
+          # Make GDM use this wrapper before running the session, which runs the
+          # configured setupCommands. This relies on a patched GDM which supports
+          # this environment variable.
+          GDM_X_SESSION_WRAPPER = "${xSessionWrapper}";
         };
         execCmd = "exec ${gdm}/bin/gdm";
       };
@@ -142,7 +154,10 @@ in
 
     systemd.user.services.dbus.wantedBy = [ "default.target" ];
 
-    programs.dconf.profiles.gdm = "${gdm}/share/dconf/profile/gdm";
+    programs.dconf.profiles.gdm = pkgs.writeText "dconf-gdm-profile" ''
+      system-db:local
+      ${gdm}/share/dconf/profile/gdm
+    '';
 
     # 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/display-managers/lightdm-greeters/enso-os.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
new file mode 100644
index 0000000000000..7c794b1ba1755
--- /dev/null
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/enso-os.nix
@@ -0,0 +1,159 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+let
+  dmcfg = config.services.xserver.displayManager;
+  ldmcfg = dmcfg.lightdm;
+  cfg = ldmcfg.greeters.enso;
+
+  theme = cfg.theme.package;
+  icons = cfg.iconTheme.package;
+  cursors = cfg.cursorTheme.package;
+
+  # We need a few things in the environment for the greeter to run with
+  # fonts/icons.
+  wrappedEnsoGreeter = pkgs.runCommand "lightdm-enso-os-greeter"
+    { buildInputs = [ pkgs.makeWrapper ]; }
+    ''
+      # This wrapper ensures that we actually get themes
+      makeWrapper ${pkgs.lightdm-enso-os-greeter}/bin/pantheon-greeter \
+        $out/greeter \
+        --prefix PATH : "${pkgs.glibc.bin}/bin" \
+        --set GDK_PIXBUF_MODULE_FILE "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" \
+        --set GTK_PATH "${theme}:${pkgs.gtk3.out}" \
+        --set GTK_EXE_PREFIX "${theme}" \
+        --set GTK_DATA_PREFIX "${theme}" \
+        --set XDG_DATA_DIRS "${theme}/share:${icons}/share:${cursors}/share" \
+        --set XDG_CONFIG_HOME "${theme}/share"
+
+      cat - > $out/lightdm-enso-os-greeter.desktop << EOF
+      [Desktop Entry]
+      Name=LightDM Greeter
+      Comment=This runs the LightDM Greeter
+      Exec=$out/greeter
+      Type=Application
+      EOF
+    '';
+
+  ensoGreeterConf = pkgs.writeText "lightdm-enso-os-greeter.conf" ''
+    [greeter]
+    default-wallpaper=${ldmcfg.background}
+    gtk-theme=${cfg.theme.name}
+    icon-theme=${cfg.iconTheme.name}
+    cursor-theme=${cfg.cursorTheme.name}
+    blur=${toString cfg.blur}
+    brightness=${toString cfg.brightness}
+    ${cfg.extraConfig}
+  '';
+in {
+  options = {
+    services.xserver.displayManager.lightdm.greeters.enso = {
+      enable = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether to enable enso-os-greeter as the lightdm greeter
+        '';
+      };
+
+      theme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.gnome3.gnome-themes-extra;
+          defaultText = "pkgs.gnome3.gnome-themes-extra";
+          description = ''
+            The package path that contains the theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = ''
+            Name of the theme to use for the lightdm-enso-os-greeter
+          '';
+        };
+      };
+
+      iconTheme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.papirus-icon-theme;
+          defaultText = "pkgs.papirus-icon-theme";
+          description = ''
+            The package path that contains the icon theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "ePapirus";
+          description = ''
+            Name of the icon theme to use for the lightdm-enso-os-greeter
+          '';
+        };
+      };
+
+      cursorTheme = {
+        package = mkOption {
+          type = types.package;
+          default = pkgs.capitaine-cursors;
+          defaultText = "pkgs.capitaine-cursors";
+          description = ''
+            The package path that contains the cursor theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "capitane-cursors";
+          description = ''
+            Name of the cursor theme to use for the lightdm-enso-os-greeter
+          '';
+        };
+      };
+
+      blur = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Whether or not to enable blur
+        '';
+      };
+
+      brightness = mkOption {
+        type = types.int;
+        default = 7;
+        description = ''
+          Brightness
+        '';
+      };
+
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = ''
+          Extra configuration that should be put in the greeter.conf
+          configuration file
+        '';
+      };
+    };
+  };
+
+  config = mkIf (ldmcfg.enable && cfg.enable) {
+    environment.etc."lightdm/greeter.conf".source = ensoGreeterConf;
+
+    services.xserver.displayManager.lightdm = {
+      greeter = mkDefault {
+        package = wrappedEnsoGreeter;
+        name = "lightdm-enso-os-greeter";
+      };
+
+      greeters = {
+        gtk = {
+          enable = mkDefault false;
+        };
+      };
+    };
+  };
+}
diff --git a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
index 013956c054664..efec943c00757 100644
--- a/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm-greeters/gtk.nix
@@ -6,12 +6,14 @@ let
 
   dmcfg = config.services.xserver.displayManager;
   ldmcfg = dmcfg.lightdm;
+  xcfg = config.services.xserver;
   cfg = ldmcfg.greeters.gtk;
 
   inherit (pkgs) writeText;
 
   theme = cfg.theme.package;
   icons = cfg.iconTheme.package;
+  cursors = cfg.cursorTheme.package;
 
   # The default greeter provided with this expression is the GTK greeter.
   # Again, we need a few things in the environment for the greeter to run with
@@ -22,13 +24,14 @@ let
       # This wrapper ensures that we actually get themes
       makeWrapper ${pkgs.lightdm_gtk_greeter}/sbin/lightdm-gtk-greeter \
         $out/greeter \
-        --prefix PATH : "${pkgs.glibc.bin}/bin" \
+        --prefix PATH : "${lib.getBin pkgs.stdenv.cc.libc}/bin" \
         --set GDK_PIXBUF_MODULE_FILE "${pkgs.librsvg.out}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache" \
         --set GTK_PATH "${theme}:${pkgs.gtk3.out}" \
         --set GTK_EXE_PREFIX "${theme}" \
         --set GTK_DATA_PREFIX "${theme}" \
         --set XDG_DATA_DIRS "${theme}/share:${icons}/share" \
-        --set XDG_CONFIG_HOME "${theme}/share"
+        --set XDG_CONFIG_HOME "${theme}/share" \
+        --set XCURSOR_PATH "${cursors}/share/icons"
 
       cat - > $out/lightdm-gtk-greeter.desktop << EOF
       [Desktop Entry]
@@ -44,9 +47,12 @@ let
     [greeter]
     theme-name = ${cfg.theme.name}
     icon-theme-name = ${cfg.iconTheme.name}
+    cursor-theme-name = ${cfg.cursorTheme.name}
+    cursor-theme-size = ${toString cfg.cursorTheme.size}
     background = ${ldmcfg.background}
     ${optionalString (cfg.clock-format != null) "clock-format = ${cfg.clock-format}"}
     ${optionalString (cfg.indicators != null) "indicators = ${concatStringsSep ";" cfg.indicators}"}
+    ${optionalString (xcfg.dpi != null) "xft-dpi=${toString xcfg.dpi}"}
     ${cfg.extraConfig}
     '';
 
@@ -106,6 +112,33 @@ in
 
       };
 
+      cursorTheme = {
+
+        package = mkOption {
+          default = pkgs.gnome3.defaultIconTheme;
+          defaultText = "pkgs.gnome3.defaultIconTheme";
+          description = ''
+            The package path that contains the cursor theme given in the name option.
+          '';
+        };
+
+        name = mkOption {
+          type = types.str;
+          default = "Adwaita";
+          description = ''
+            Name of the cursor theme to use for the lightdm-gtk-greeter.
+          '';
+        };
+
+        size = mkOption {
+          type = types.int;
+          default = 16;
+          description = ''
+            Size of the cursor theme to use for the lightdm-gtk-greeter.
+          '';
+        };
+      };
+
       clock-format = mkOption {
         type = types.nullOr types.str;
         default = null;
diff --git a/nixos/modules/services/x11/display-managers/lightdm.nix b/nixos/modules/services/x11/display-managers/lightdm.nix
index cd9c3d81a0fb8..567c3ac345492 100644
--- a/nixos/modules/services/x11/display-managers/lightdm.nix
+++ b/nixos/modules/services/x11/display-managers/lightdm.nix
@@ -46,6 +46,7 @@ let
         greeters-directory = ${cfg.greeter.package}
       ''}
       sessions-directory = ${dmcfg.session.desktops}/share/xsessions
+      ${cfg.extraConfig}
 
       [Seat:*]
       xserver-command = ${xserverWrapper}
@@ -61,6 +62,12 @@ let
       ${optionalString hasDefaultUserSession ''
         user-session=${defaultSessionName}
       ''}
+      ${optionalString (dmcfg.setupCommands != "") ''
+        display-setup-script=${pkgs.writeScript "lightdm-display-setup" ''
+          #!${pkgs.bash}/bin/bash
+          ${dmcfg.setupCommands}
+        ''}
+      ''}
       ${cfg.extraSeatDefaults}
     '';
 
@@ -73,6 +80,7 @@ in
   imports = [
     ./lightdm-greeters/gtk.nix
     ./lightdm-greeters/mini.nix
+    ./lightdm-greeters/enso-os.nix
   ];
 
   options = {
@@ -113,6 +121,15 @@ in
         };
       };
 
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        example = ''
+          user-authority-in-system-dir = true
+        '';
+        description = "Extra lines to append to LightDM section.";
+      };
+
       background = mkOption {
         type = types.str;
         default = "${pkgs.nixos-artwork.wallpapers.simple-dark-gray-bottom}/share/artwork/gnome/nix-wallpaper-simple-dark-gray_bottom.png";
@@ -179,7 +196,7 @@ in
       { assertion = cfg.autoLogin.enable -> dmDefault != "none" || wmDefault != "none";
         message = ''
           LightDM auto-login requires that services.xserver.desktopManager.default and
-          services.xserver.windowMananger.default are set to valid values. The current
+          services.xserver.windowManager.default are set to valid values. The current
           default session: ${defaultSessionName} is not valid.
         '';
       }
@@ -191,15 +208,11 @@ in
       }
     ];
 
-    services.xserver.displayManager.job = {
-      logToFile = true;
-
-      # lightdm relaunches itself via just `lightdm`, so needs to be on the PATH
-      execCmd = ''
-        export PATH=${lightdm}/sbin:$PATH
-        exec ${lightdm}/sbin/lightdm --log-dir=/var/log --run-dir=/run
-      '';
-    };
+    # lightdm relaunches itself via just `lightdm`, so needs to be on the PATH
+    services.xserver.displayManager.job.execCmd = ''
+      export PATH=${lightdm}/sbin:$PATH
+      exec ${lightdm}/sbin/lightdm
+    '';
 
     environment.etc."lightdm/lightdm.conf".source = lightdmConf;
     environment.etc."lightdm/users.conf".source = usersConf;
@@ -246,12 +259,19 @@ in
     '';
 
     users.users.lightdm = {
-      createHome = true;
-      home = "/var/lib/lightdm-data";
+      home = "/var/lib/lightdm";
       group = "lightdm";
       uid = config.ids.uids.lightdm;
     };
 
+    systemd.tmpfiles.rules = [
+      "d /run/lightdm 0711 lightdm lightdm 0"
+      "d /var/cache/lightdm 0711 root lightdm -"
+      "d /var/lib/lightdm 1770 lightdm lightdm -"
+      "d /var/lib/lightdm-data 1775 lightdm lightdm -"
+      "d /var/log/lightdm 0711 root lightdm -"
+    ];
+
     users.groups.lightdm.gid = config.ids.gids.lightdm;
     services.xserver.tty     = null; # We might start multiple X servers so let the tty increment themselves..
     services.xserver.display = null; # We specify our own display (and logfile) in xserver-wrapper up there
diff --git a/nixos/modules/services/x11/display-managers/sddm.nix b/nixos/modules/services/x11/display-managers/sddm.nix
index 1635c0f9acc5f..05830e325d514 100644
--- a/nixos/modules/services/x11/display-managers/sddm.nix
+++ b/nixos/modules/services/x11/display-managers/sddm.nix
@@ -20,6 +20,7 @@ let
   Xsetup = pkgs.writeScript "Xsetup" ''
     #!/bin/sh
     ${cfg.setupScript}
+    ${dmcfg.setupCommands}
   '';
 
   Xstop = pkgs.writeScript "Xstop" ''
@@ -58,6 +59,7 @@ let
 
     [Wayland]
     EnableHidpi=${if cfg.enableHidpi then "true" else "false"}
+    SessionDir=${dmcfg.session.desktops}/share/wayland-sessions
 
     ${optionalString cfg.autoLogin.enable ''
     [Autologin]
@@ -137,7 +139,8 @@ in
           xrandr --auto
         '';
         description = ''
-          A script to execute when starting the display server.
+          A script to execute when starting the display server. DEPRECATED, please
+          use <option>services.xserver.displayManager.setupCommands</option>.
         '';
       };
 
@@ -200,15 +203,13 @@ in
       { assertion = cfg.autoLogin.enable -> elem defaultSessionName dmcfg.session.names;
         message = ''
           SDDM auto-login requires that services.xserver.desktopManager.default and
-          services.xserver.windowMananger.default are set to valid values. The current
+          services.xserver.windowManager.default are set to valid values. The current
           default session: ${defaultSessionName} is not valid.
         '';
       }
     ];
 
     services.xserver.displayManager.job = {
-      logToFile = true;
-
       environment = {
         # Load themes from system environment
         QT_PLUGIN_PATH = "/run/current-system/sw/" + pkgs.qt5.qtbase.qtPluginPrefix;
@@ -263,7 +264,9 @@ in
     };
 
     environment.etc."sddm.conf".source = cfgFile;
-    environment.pathsToLink = [ "/share/sddm/themes" ];
+    environment.pathsToLink = [ 
+      "/share/sddm" 
+    ];
 
     users.groups.sddm.gid = config.ids.gids.sddm;
 
diff --git a/nixos/modules/services/x11/display-managers/startx.nix b/nixos/modules/services/x11/display-managers/startx.nix
new file mode 100644
index 0000000000000..15609540a6e7d
--- /dev/null
+++ b/nixos/modules/services/x11/display-managers/startx.nix
@@ -0,0 +1,44 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+
+  cfg = config.services.xserver.displayManager.startx;
+
+in
+
+{
+
+  ###### interface
+
+  options = {
+    services.xserver.displayManager.startx = {
+      enable = mkOption {
+        default = false;
+        description = ''
+          Whether to enable the dummy "startx" pseudo-display manager,
+          which allows users to start X manually via the "startx" command
+          from a vt shell. The X server runs under the user's id, not as root.
+          The user must provide a ~/.xinintrc file containing session startup
+          commands, see startx(1). This is not autmatically generated
+          from the desktopManager and windowManager settings.
+        '';
+      };
+    };
+  };
+
+
+  ###### implementation
+
+  config = mkIf cfg.enable {
+    services.xserver = {
+      exportConfiguration = true;
+      displayManager.job.execCmd = "";
+      displayManager.lightdm.enable = lib.mkForce false;
+    };
+    systemd.services.display-manager.enable = false;
+    environment.systemPackages =  with pkgs; [ xorg.xinit ];
+  };
+
+}
diff --git a/nixos/modules/services/x11/display-managers/xpra.nix b/nixos/modules/services/x11/display-managers/xpra.nix
index b46ede550c16e..a4b57cfdab644 100644
--- a/nixos/modules/services/x11/display-managers/xpra.nix
+++ b/nixos/modules/services/x11/display-managers/xpra.nix
@@ -219,30 +219,26 @@ in
       VideoRam 192000
     '';
 
-    services.xserver.displayManager.job = {
-      logToFile = true;
-
-      execCmd = ''
-        ${optionalString (cfg.pulseaudio)
-          "export PULSE_COOKIE=/var/run/pulse/.config/pulse/cookie"}
-        exec ${pkgs.xpra}/bin/xpra start \
-          --daemon=off \
-          --log-dir=/var/log \
-          --log-file=xpra.log \
-          --opengl=on \
-          --clipboard=on \
-          --notifications=on \
-          --speaker=yes \
-          --mdns=no \
-          --pulseaudio=no \
-          ${optionalString (cfg.pulseaudio) "--sound-source=pulse"} \
-          --socket-dirs=/var/run/xpra \
-          --xvfb="xpra_Xdummy ${concatStringsSep " " dmcfg.xserverArgs}" \
-          ${optionalString (cfg.bindTcp != null) "--bind-tcp=${cfg.bindTcp}"} \
-          --auth=${cfg.auth} \
-          ${concatStringsSep " " cfg.extraOptions}
-      '';
-    };
+    services.xserver.displayManager.job.execCmd = ''
+      ${optionalString (cfg.pulseaudio)
+        "export PULSE_COOKIE=/var/run/pulse/.config/pulse/cookie"}
+      exec ${pkgs.xpra}/bin/xpra start \
+        --daemon=off \
+        --log-dir=/var/log \
+        --log-file=xpra.log \
+        --opengl=on \
+        --clipboard=on \
+        --notifications=on \
+        --speaker=yes \
+        --mdns=no \
+        --pulseaudio=no \
+        ${optionalString (cfg.pulseaudio) "--sound-source=pulse"} \
+        --socket-dirs=/var/run/xpra \
+        --xvfb="xpra_Xdummy ${concatStringsSep " " dmcfg.xserverArgs}" \
+        ${optionalString (cfg.bindTcp != null) "--bind-tcp=${cfg.bindTcp}"} \
+        --auth=${cfg.auth} \
+        ${concatStringsSep " " cfg.extraOptions}
+    '';
 
     services.xserver.terminateOnReset = false;
 
diff --git a/nixos/modules/services/x11/gdk-pixbuf.nix b/nixos/modules/services/x11/gdk-pixbuf.nix
new file mode 100644
index 0000000000000..58faa8e2f9dfc
--- /dev/null
+++ b/nixos/modules/services/x11/gdk-pixbuf.nix
@@ -0,0 +1,45 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+  cfg = config.services.xserver.gdk-pixbuf;
+
+  # Get packages to generate the cache for. We always include gdk_pixbuf.
+  effectivePackages = unique ([pkgs.gdk_pixbuf] ++ cfg.modulePackages);
+
+  # Generate the cache file by running gdk-pixbuf-query-loaders for each
+  # package and concatenating the results.
+  loadersCache = pkgs.runCommand "gdk-pixbuf-loaders.cache" {} ''
+    (
+      for package in ${concatStringsSep " " effectivePackages}; do
+        module_dir="$package/${pkgs.gdk_pixbuf.moduleDir}"
+        if [[ ! -d $module_dir ]]; then
+          echo "Warning (services.xserver.gdk-pixbuf): missing module directory $module_dir" 1>&2
+          continue
+        fi
+        GDK_PIXBUF_MODULEDIR="$module_dir" \
+          ${pkgs.gdk_pixbuf.dev}/bin/gdk-pixbuf-query-loaders
+      done
+    ) > "$out"
+  '';
+in
+
+{
+  options = {
+    services.xserver.gdk-pixbuf.modulePackages = mkOption {
+      type = types.listOf types.package;
+      default = [ ];
+      description = "Packages providing GDK-Pixbuf modules, for cache generation.";
+    };
+  };
+
+  # If there is any package configured in modulePackages, we generate the
+  # loaders.cache based on that and set the environment variable
+  # GDK_PIXBUF_MODULE_FILE to point to it.
+  config = mkIf (cfg.modulePackages != []) {
+    environment.variables = {
+      GDK_PIXBUF_MODULE_FILE = "${loadersCache}";
+    };
+  };
+}
diff --git a/nixos/modules/services/x11/hardware/libinput.nix b/nixos/modules/services/x11/hardware/libinput.nix
index 072004d5dd91c..58fe702d4969c 100644
--- a/nixos/modules/services/x11/hardware/libinput.nix
+++ b/nixos/modules/services/x11/hardware/libinput.nix
@@ -205,7 +205,7 @@ in {
       })
     ];
 
-    services.udev.packages = [ pkgs.libinput ];
+    services.udev.packages = [ pkgs.libinput.out ];
 
     services.xserver.config =
       ''
diff --git a/nixos/modules/services/x11/urxvtd.nix b/nixos/modules/services/x11/urxvtd.nix
index f2ce089ce19aa..d916fa5bb393c 100644
--- a/nixos/modules/services/x11/urxvtd.nix
+++ b/nixos/modules/services/x11/urxvtd.nix
@@ -7,41 +7,41 @@ with lib;
 let
   cfg = config.services.urxvtd;
 in {
+  options.services.urxvtd = {
+    enable = mkOption {
+      type = types.bool;
+      default = false;
+      description = ''
+        Enable urxvtd, the urxvt terminal daemon. To use urxvtd, run
+        "urxvtc".
+      '';
+    };
 
-  options.services.urxvtd.enable = mkOption {
-    type = types.bool;
-    default = false;
-    description = ''
-      Enable urxvtd, the urxvt terminal daemon. To use urxvtd, run
-      "urxvtc".
-    '';
+    package = mkOption {
+      default = pkgs.rxvt_unicode-with-plugins;
+      defaultText = "pkgs.rxvt_unicode-with-plugins";
+      description = ''
+        Package to install. Usually pkgs.rxvt_unicode-with-plugins or pkgs.rxvt_unicode
+      '';
+      type = types.package;
+    };
   };
 
   config = mkIf cfg.enable {
-    systemd.user = {
-      sockets.urxvtd = {
-        description = "socket for urxvtd, the urxvt terminal daemon";
-        wantedBy = [ "graphical-session.target" ];
-        partOf = [ "graphical-session.target" ];
-        socketConfig = {
-          ListenStream = "%t/urxvtd-socket";
-        };
+    systemd.user.services.urxvtd = {
+      description = "urxvt terminal daemon";
+      wantedBy = [ "graphical-session.target" ];
+      partOf = [ "graphical-session.target" ];
+      path = [ pkgs.xsel ];
+      serviceConfig = {
+        ExecStart = "${cfg.package}/bin/urxvtd -o";
+        Environment = "RXVT_SOCKET=%t/urxvtd-socket";
+        Restart = "on-failure";
+        RestartSec = "5s";
       };
-
-      services.urxvtd = {
-        description = "urxvt terminal daemon";
-        path = [ pkgs.xsel ];
-        serviceConfig = {
-          ExecStart = "${pkgs.rxvt_unicode-with-plugins}/bin/urxvtd -o";
-          Environment = "RXVT_SOCKET=%t/urxvtd-socket";
-          Restart = "on-failure";
-          RestartSec = "5s";
-        };
-      };
-
     };
 
-    environment.systemPackages = [ pkgs.rxvt_unicode-with-plugins ];
+    environment.systemPackages = [ cfg.package ];
     environment.variables.RXVT_SOCKET = "/run/user/$(id -u)/urxvtd-socket";
   };
 
diff --git a/nixos/modules/services/x11/xautolock.nix b/nixos/modules/services/x11/xautolock.nix
index a614559970e93..cbe000058dc64 100644
--- a/nixos/modules/services/x11/xautolock.nix
+++ b/nixos/modules/services/x11/xautolock.nix
@@ -21,7 +21,7 @@ in
           type = types.int;
 
           description = ''
-            Idle time to wait until xautolock locks the computer.
+            Idle time (in minutes) to wait until xautolock locks the computer.
           '';
         };
 
diff --git a/nixos/modules/services/x11/xserver.nix b/nixos/modules/services/x11/xserver.nix
index 75bfeaac1fa34..34ae8c11a3f0c 100644
--- a/nixos/modules/services/x11/xserver.nix
+++ b/nixos/modules/services/x11/xserver.nix
@@ -13,7 +13,8 @@ let
 
   # Map video driver names to driver packages. FIXME: move into card-specific modules.
   knownVideoDrivers = {
-    virtualbox = { modules = [ kernelPackages.virtualboxGuestAdditions ]; driverName = "vboxvideo"; };
+    # Alias so people can keep using "virtualbox" instead of "vboxvideo".
+    virtualbox = { modules = [ xorg.xf86videovboxvideo ]; driverName = "vboxvideo"; };
 
     # modesetting does not have a xf86videomodesetting package as it is included in xorgserver
     modesetting = {};
@@ -374,6 +375,12 @@ in
         description = "Contents of the first Monitor section of the X server configuration file.";
       };
 
+      extraConfig = mkOption {
+        type = types.lines;
+        default = "";
+        description = "Additional contents (sections) included in the X server configuration file";
+      };
+
       xrandrHeads = mkOption {
         default = [];
         example = [
@@ -558,8 +565,6 @@ in
           knownVideoDrivers;
       in optional (driver != null) ({ inherit name; modules = []; driverName = name; } // driver));
 
-    nixpkgs.config = optionalAttrs (elem "vboxvideo" cfg.videoDrivers) { xorg.abiCompat = "1.18"; };
-
     assertions = [
       { assertion = config.security.polkit.enable;
         message = "X11 requires Polkit to be enabled (‘security.polkit.enable = true’).";
@@ -625,6 +630,8 @@ in
       ]
       ++ optional (elem "virtualbox" cfg.videoDrivers) xorg.xrefresh;
 
+    environment.pathsToLink = [ "/share/X11" ];
+
     xdg = { 
       autostart.enable = true;
       menus.enable = true;
@@ -754,6 +761,7 @@ in
             Driver "${driver.driverName or driver.name}"
             ${if cfg.useGlamor then ''Option "AccelMethod" "glamor"'' else ""}
             ${cfg.deviceSection}
+            ${driver.deviceSection or ""}
             ${xrandrDeviceSection}
           EndSection
 
@@ -765,6 +773,7 @@ in
             ''}
 
             ${cfg.screenSection}
+            ${driver.screenSection or ""}
 
             ${optionalString (cfg.defaultDepth != 0) ''
               DefaultDepth ${toString cfg.defaultDepth}
@@ -794,6 +803,8 @@ in
         '')}
 
         ${xrandrMonitorSections}
+
+        ${cfg.extraConfig}
       '';
 
     fonts.enableDefaultFonts = mkDefault true;
diff --git a/nixos/modules/system/activation/activation-script.nix b/nixos/modules/system/activation/activation-script.nix
index 93a1b13a81dda..74c150a848d13 100644
--- a/nixos/modules/system/activation/activation-script.nix
+++ b/nixos/modules/system/activation/activation-script.nix
@@ -21,7 +21,8 @@ let
     [ coreutils
       gnugrep
       findutils
-      glibc # needed for getent
+      getent
+      stdenv.cc.libc # nscd in update-users-groups.pl
       shadow
       nettools # needed for hostname
       utillinux # needed for mount and mountpoint
@@ -100,6 +101,52 @@ in
             exit $_status
           '';
       };
+    };
+
+    system.userActivationScripts = mkOption {
+      default = {};
+
+      example = literalExample ''
+        { plasmaSetup = {
+            text = '''
+              ${pkgs.libsForQt5.kservice}/bin/kbuildsycoca5"
+            ''';
+            deps = [];
+          };
+        }
+      '';
+
+      description = ''
+        A set of shell script fragments that are executed by a systemd user
+        service when a NixOS system configuration is activated. Examples are
+        rebuilding the .desktop file cache for showing applications in the menu.
+        Since these are executed every time you run
+        <command>nixos-rebuild</command>, it's important that they are
+        idempotent and fast.
+      '';
+
+      type = types.attrsOf types.unspecified;
+
+      apply = set: {
+        script = ''
+          unset PATH
+          for i in ${toString path}; do
+            PATH=$PATH:$i/bin:$i/sbin
+          done
+
+          _status=0
+          trap "_status=1 _localstatus=\$?" ERR
+
+          ${
+            let
+              set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set;
+              withHeadlines = addAttributeName set';
+            in textClosureMap id (withHeadlines) (attrNames withHeadlines)
+          }
+
+          exit $_status
+        '';
+      };
 
     };
 
@@ -128,14 +175,6 @@ in
       ''
         # Various log/runtime directories.
 
-        mkdir -m 0755 -p /run/nix/current-load # for distributed builds
-        mkdir -m 0700 -p /run/nix/remote-stores
-
-        mkdir -m 0755 -p /var/log
-
-        touch /var/log/wtmp /var/log/lastlog # must exist
-        chmod 644 /var/log/wtmp /var/log/lastlog
-
         mkdir -m 1777 -p /var/tmp
 
         # Empty, immutable home directory of many system accounts.
@@ -177,6 +216,14 @@ in
         source ${config.system.build.earlyMountScript}
       '';
 
+    systemd.user = {
+      services.nixos-activation = {
+        description = "Run user specific NixOS activation";
+        script = config.system.userActivationScripts.script;
+        unitConfig.ConditionUser = "!@system";
+        serviceConfig.Type = "oneshot";
+      };
+    };
   };
 
 }
diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl
index c3e469e4b8a12..397b308b73118 100644
--- a/nixos/modules/system/activation/switch-to-configuration.pl
+++ b/nixos/modules/system/activation/switch-to-configuration.pl
@@ -419,7 +419,8 @@ while (my $f = <$listActiveUsers>) {
     my ($uid, $name) = ($+{uid}, $+{user});
     print STDERR "reloading user units for $name...\n";
 
-    system("su", "-s", "@shell@", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user daemon-reload");
+    system("@su@", "-s", "@shell@", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user daemon-reload");
+    system("@su@", "-s", "@shell@", "-l", $name, "-c", "XDG_RUNTIME_DIR=/run/user/$uid @systemd@/bin/systemctl --user start nixos-activation.service");
 }
 
 close $listActiveUsers;
diff --git a/nixos/modules/system/activation/top-level.nix b/nixos/modules/system/activation/top-level.nix
index 9797ef641e4fc..a560af5ce96da 100644
--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -93,49 +93,53 @@ let
       ${config.system.extraSystemBuilderCmds}
     '';
 
-  # Handle assertions
-
-  failed = map (x: x.message) (filter (x: !x.assertion) config.assertions);
-
-  showWarnings = res: fold (w: x: builtins.trace "warning: ${w}" x) res config.warnings;
-
   # Putting it all together.  This builds a store path containing
   # symlinks to the various parts of the built configuration (the
   # kernel, systemd units, init scripts, etc.) as well as a script
   # `switch-to-configuration' that activates the configuration and
   # makes it bootable.
-  baseSystem = showWarnings (
-    if [] == failed then pkgs.stdenvNoCC.mkDerivation {
-      name = let hn = config.networking.hostName;
-                 nn = if (hn != "") then hn else "unnamed";
-          in "nixos-system-${nn}-${config.system.nixos.label}";
-      preferLocalBuild = true;
-      allowSubstitutes = false;
-      buildCommand = systemBuilder;
-
-      inherit (pkgs) utillinux coreutils;
-      systemd = config.systemd.package;
-      inherit (pkgs.stdenv) shell;
-
-      inherit children;
-      kernelParams = config.boot.kernelParams;
-      installBootLoader =
-        config.system.build.installBootLoader
-        or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
-      activationScript = config.system.activationScripts.script;
-      nixosLabel = config.system.nixos.label;
-
-      configurationName = config.boot.loader.grub.configurationName;
-
-      # Needed by switch-to-configuration.
-
-      perl = "${pkgs.perl}/bin/perl " + (concatMapStringsSep " " (lib: "-I${lib}/${pkgs.perl.libPrefix}") (with pkgs.perlPackages; [ FileSlurp NetDBus XMLParser XMLTwig ]));
-  } else throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failed)}");
+  baseSystem = pkgs.stdenvNoCC.mkDerivation {
+    name = let hn = config.networking.hostName;
+               nn = if (hn != "") then hn else "unnamed";
+        in "nixos-system-${nn}-${config.system.nixos.label}";
+    preferLocalBuild = true;
+    allowSubstitutes = false;
+    buildCommand = systemBuilder;
+
+    inherit (pkgs) utillinux coreutils;
+    systemd = config.systemd.package;
+    shell = "${pkgs.bash}/bin/sh";
+    su = "${pkgs.shadow.su}/bin/su";
+
+    inherit children;
+    kernelParams = config.boot.kernelParams;
+    installBootLoader =
+      config.system.build.installBootLoader
+      or "echo 'Warning: do not know how to make this configuration bootable; please enable a boot loader.' 1>&2; true";
+    activationScript = config.system.activationScripts.script;
+    nixosLabel = config.system.nixos.label;
+
+    configurationName = config.boot.loader.grub.configurationName;
+
+    # Needed by switch-to-configuration.
+
+    perl = "${pkgs.perl}/bin/perl " + (concatMapStringsSep " " (lib: "-I${lib}/${pkgs.perl.libPrefix}") (with pkgs.perlPackages; [ FileSlurp NetDBus XMLParser XMLTwig ]));
+  };
+
+  # Handle assertions and warnings
+
+  failedAssertions = map (x: x.message) (filter (x: !x.assertion) config.assertions);
+
+  showWarnings = res: fold (w: x: builtins.trace "warning: ${w}" x) res config.warnings;
+
+  baseSystemAssertWarn = if failedAssertions != []
+    then throw "\nFailed assertions:\n${concatStringsSep "\n" (map (x: "- ${x}") failedAssertions)}"
+    else showWarnings baseSystem;
 
   # Replace runtime dependencies
   system = fold ({ oldDependency, newDependency }: drv:
       pkgs.replaceDependency { inherit oldDependency newDependency drv; }
-    ) baseSystem config.system.replaceRuntimeDependencies;
+    ) baseSystemAssertWarn config.system.replaceRuntimeDependencies;
 
 in
 
diff --git a/nixos/modules/system/boot/initrd-network.nix b/nixos/modules/system/boot/initrd-network.nix
index 384ae909b7011..cb8fc957a990b 100644
--- a/nixos/modules/system/boot/initrd-network.nix
+++ b/nixos/modules/system/boot/initrd-network.nix
@@ -6,11 +6,22 @@ let
 
   cfg = config.boot.initrd.network;
 
+  dhcpinterfaces = lib.attrNames (lib.filterAttrs (iface: v: v.useDHCP == true) (config.networking.interfaces or {}));
+
   udhcpcScript = pkgs.writeScript "udhcp-script"
     ''
       #! /bin/sh
       if [ "$1" = bound ]; then
         ip address add "$ip/$mask" dev "$interface"
+        if [ -n "$mtu" ]; then
+          ip link set mtu "$mtu" dev "$interface"
+        fi
+        if [ -n "$staticroutes" ]; then
+          echo "$staticroutes" \
+            | sed -r "s@(\S+) (\S+)@ ip route add \"\1\" via \"\2\" dev \"$interface\" ; @g" \
+            | sed -r "s@ via \"0\.0\.0\.0\"@@g" \
+            | /bin/sh
+        fi
         if [ -n "$router" ]; then
           ip route add "$router" dev "$interface" # just in case if "$router" is not within "$ip/$mask" (e.g. Hetzner Cloud)
           ip route add default via "$router" dev "$interface"
@@ -45,7 +56,8 @@ in
         is acquired using DHCP.
 
         You should add the module(s) required for your network card to
-        boot.initrd.availableKernelModules. lspci -v -s &lt;ethernet controller&gt;
+        boot.initrd.availableKernelModules.
+        <literal>lspci -v | grep -iA8 'network\|ethernet'</literal>
         will tell you which.
       '';
     };
@@ -93,18 +105,24 @@ in
       ''
 
       # Otherwise, use DHCP.
-      + optionalString config.networking.useDHCP ''
+      + optionalString (config.networking.useDHCP || dhcpinterfaces != []) ''
         if [ -z "$hasNetwork" ]; then
 
           # Bring up all interfaces.
-          for iface in $(cd /sys/class/net && ls); do
+          for iface in $(ls /sys/class/net/); do
             echo "bringing up network interface $iface..."
             ip link set "$iface" up
           done
 
-          # Acquire a DHCP lease.
-          echo "acquiring IP address via DHCP..."
-          udhcpc --quit --now --script ${udhcpcScript} ${udhcpcArgs} && hasNetwork=1
+          # Acquire DHCP leases.
+          for iface in ${ if config.networking.useDHCP then
+                            "$(ls /sys/class/net/ | grep -v ^lo$)"
+                          else
+                            lib.concatMapStringsSep " " lib.escapeShellArg dhcpinterfaces
+                        }; do
+            echo "acquiring IP address via DHCP on $iface..."
+            udhcpc --quit --now -i $iface -O staticroutes --script ${udhcpcScript} ${udhcpcArgs} && hasNetwork=1
+          done
         fi
       ''
 
diff --git a/nixos/modules/system/boot/initrd-ssh.nix b/nixos/modules/system/boot/initrd-ssh.nix
index 53e993603e273..2d3e3b05c9807 100644
--- a/nixos/modules/system/boot/initrd-ssh.nix
+++ b/nixos/modules/system/boot/initrd-ssh.nix
@@ -82,6 +82,7 @@ in
       default = config.users.users.root.openssh.authorizedKeys.keys;
       description = ''
         Authorized keys for the root user on initrd.
+        Note that Dropbear doesn't support OpenSSH's Ed25519 key type.
       '';
     };
 
diff --git a/nixos/modules/system/boot/kernel_config.nix b/nixos/modules/system/boot/kernel_config.nix
new file mode 100644
index 0000000000000..fbbd0982b2c64
--- /dev/null
+++ b/nixos/modules/system/boot/kernel_config.nix
@@ -0,0 +1,137 @@
+{ lib, config, ... }:
+
+with lib;
+let
+  findWinner = candidates: winner:
+    any (x: x == winner) candidates;
+
+  # winners is an ordered list where first item wins over 2nd etc
+  mergeAnswer = winners: locs: defs:
+    let
+      values = map (x: x.value) defs;
+      freeformAnswer = intersectLists values winners;
+      inter = intersectLists values winners;
+      winner = head winners;
+    in
+    if defs == [] then abort "This case should never happen."
+    else if winner == [] then abort "Give a valid list of winner"
+    else if inter == [] then mergeOneOption locs defs
+    else if findWinner values winner then
+      winner
+    else
+      mergeAnswer (tail winners) locs defs;
+
+  mergeFalseByDefault = locs: defs:
+    if defs == [] then abort "This case should never happen."
+    else if any (x: x == false) defs then false
+    else true;
+
+  kernelItem = types.submodule {
+    options = {
+      tristate = mkOption {
+        type = types.enum [ "y" "m" "n" null ] // {
+          merge = mergeAnswer [ "y" "m" "n" ];
+        };
+        default = null;
+        internal = true;
+        visible = true;
+        description = ''
+          Use this field for tristate kernel options expecting a "y" or "m" or "n".
+        '';
+      };
+
+      freeform = mkOption {
+        type = types.nullOr types.str // {
+          merge = mergeEqualOption;
+        };
+        default = null;
+        example = ''MMC_BLOCK_MINORS.freeform = "32";'';
+        description = ''
+          Freeform description of a kernel configuration item value.
+        '';
+      };
+
+      optional = mkOption {
+        type = types.bool // { merge = mergeFalseByDefault; };
+        default = false;
+        description = ''
+          Wether option should generate a failure when unused.
+        '';
+      };
+    };
+  };
+
+  mkValue = with lib; val:
+  let
+    isNumber = c: elem c ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"];
+
+  in
+    if (val == "") then "\"\""
+    else if val == "y" || val == "m" || val == "n" then val
+    else if all isNumber (stringToCharacters val) then val
+    else if substring 0 2 val == "0x" then val
+    else val; # FIXME: fix quoting one day
+
+
+  # generate nix intermediate kernel config file of the form
+  #
+  #       VIRTIO_MMIO m
+  #       VIRTIO_BLK y
+  #       VIRTIO_CONSOLE n
+  #       NET_9P_VIRTIO? y
+  #
+  # Borrowed from copumpkin https://github.com/NixOS/nixpkgs/pull/12158
+  # returns a string, expr should be an attribute set
+  # Use mkValuePreprocess to preprocess option values, aka mark 'modules' as 'yes' or vice-versa
+  # use the identity if you don't want to override the configured values
+  generateNixKConf = exprs:
+  let
+    mkConfigLine = key: item:
+      let
+        val = if item.freeform != null then item.freeform else item.tristate;
+      in
+        if val == null
+          then ""
+          else if (item.optional)
+            then "${key}? ${mkValue val}\n"
+            else "${key} ${mkValue val}\n";
+
+    mkConf = cfg: concatStrings (mapAttrsToList mkConfigLine cfg);
+  in mkConf exprs;
+
+in
+{
+
+  options = {
+
+    intermediateNixConfig = mkOption {
+      readOnly = true;
+      type = types.lines;
+      example = ''
+        USB? y
+        DEBUG n
+      '';
+      description = ''
+        The result of converting the structured kernel configuration in settings
+        to an intermediate string that can be parsed by generate-config.pl to
+        answer the kernel `make defconfig`.
+      '';
+    };
+
+    settings = mkOption {
+      type = types.attrsOf kernelItem;
+      example = literalExample '' with lib.kernel; {
+        "9P_NET" = yes;
+        USB = optional yes;
+        MMC_BLOCK_MINORS = freeform "32";
+      }'';
+      description = ''
+        Structured kernel configuration.
+      '';
+    };
+  };
+
+  config = {
+    intermediateNixConfig = generateNixKConf config.settings;
+  };
+}
diff --git a/nixos/modules/system/boot/kexec.nix b/nixos/modules/system/boot/kexec.nix
index 3e5d7b40f2c58..61f9c6d0e7eb9 100644
--- a/nixos/modules/system/boot/kexec.nix
+++ b/nixos/modules/system/boot/kexec.nix
@@ -1,7 +1,7 @@
 { pkgs, lib, ... }:
 
 {
-  config = lib.mkIf (pkgs.kexectools.meta.available) {
+  config = lib.mkIf (lib.any (lib.meta.platformMatch pkgs.stdenv.hostPlatform) pkgs.kexectools.meta.platforms) {
     environment.systemPackages = [ pkgs.kexectools ];
 
     systemd.services."prepare-kexec" =
diff --git a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
index 2d27611946e2c..ff90a9b461799 100644
--- a/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
+++ b/nixos/modules/system/boot/loader/generations-dir/generations-dir.nix
@@ -7,8 +7,8 @@ let
   generationsDirBuilder = pkgs.substituteAll {
     src = ./generations-dir-builder.sh;
     isExecutable = true;
-    inherit (pkgs) bash;
-    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+    inherit (pkgs.buildPackages) bash;
+    path = with pkgs.buildPackages; [coreutils gnused gnugrep];
     inherit (config.boot.loader.generationsDir) copyKernels;
   };
 
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
index af39c7bb68418..5f5dbe1092d00 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/default.nix
@@ -8,7 +8,7 @@ let
 
   timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
 
-  builder = import ./extlinux-conf-builder.nix { inherit pkgs; };
+  builder = import ./extlinux-conf-builder.nix { pkgs = pkgs.buildPackages; };
 in
 {
   options = {
diff --git a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
index 576a07c1d272e..9ac6b6b12242c 100644
--- a/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
+++ b/nixos/modules/system/boot/loader/generic-extlinux-compatible/extlinux-conf-builder.nix
@@ -3,6 +3,6 @@
 pkgs.substituteAll {
   src = ./extlinux-conf-builder.sh;
   isExecutable = true;
-  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
-  inherit (pkgs) bash;
+  path = [pkgs.buildPackages.coreutils pkgs.buildPackages.gnused pkgs.buildPackages.gnugrep];
+  inherit (pkgs.buildPackages) bash;
 }
diff --git a/nixos/modules/system/boot/loader/grub/grub.nix b/nixos/modules/system/boot/loader/grub/grub.nix
index 3a33b3f65d36f..a1537ad3cedc3 100644
--- a/nixos/modules/system/boot/loader/grub/grub.nix
+++ b/nixos/modules/system/boot/loader/grub/grub.nix
@@ -586,7 +586,7 @@ in
         in pkgs.writeScript "install-grub.sh" (''
         #!${pkgs.runtimeShell}
         set -e
-        export PERL5LIB=${makePerlPath (with pkgs.perlPackages; [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare ])}
+        export PERL5LIB=${with pkgs.perlPackages; makePerlPath [ FileSlurp XMLLibXML XMLSAX XMLSAXBase ListCompare ]}
         ${optionalString cfg.enableCryptodisk "export GRUB_ENABLE_CRYPTODISK=y"}
       '' + flip concatMapStrings cfg.mirroredBoots (args: ''
         ${pkgs.perl}/bin/perl ${install-grub-pl} ${grubConfig args} $@
diff --git a/nixos/modules/system/boot/loader/init-script/init-script.nix b/nixos/modules/system/boot/loader/init-script/init-script.nix
index 374d9524ff1ee..385a26036784d 100644
--- a/nixos/modules/system/boot/loader/init-script/init-script.nix
+++ b/nixos/modules/system/boot/loader/init-script/init-script.nix
@@ -7,8 +7,8 @@ let
   initScriptBuilder = pkgs.substituteAll {
     src = ./init-script-builder.sh;
     isExecutable = true;
-    inherit (pkgs) bash;
-    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+    inherit (pkgs.buildPackages) bash;
+    path = with pkgs.buildPackages; [coreutils gnused gnugrep];
   };
 
 in
diff --git a/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.sh b/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.sh
deleted file mode 100644
index 36bf150662745..0000000000000
--- a/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#! @bash@/bin/sh -e
-
-copyForced() {
-    local src="$1"
-    local dst="$2"
-    cp $src $dst.tmp
-    mv $dst.tmp $dst
-}
-
-# Call the extlinux builder
-"@extlinuxConfBuilder@" "$@"
-
-# Add the firmware files
-fwdir=@firmware@/share/raspberrypi/boot/
-copyForced $fwdir/bootcode.bin  /boot/bootcode.bin
-copyForced $fwdir/fixup.dat     /boot/fixup.dat
-copyForced $fwdir/fixup_cd.dat  /boot/fixup_cd.dat
-copyForced $fwdir/fixup_db.dat  /boot/fixup_db.dat
-copyForced $fwdir/fixup_x.dat   /boot/fixup_x.dat
-copyForced $fwdir/start.elf     /boot/start.elf
-copyForced $fwdir/start_cd.elf  /boot/start_cd.elf
-copyForced $fwdir/start_db.elf  /boot/start_db.elf
-copyForced $fwdir/start_x.elf   /boot/start_x.elf
-
-# Add the uboot file
-copyForced @uboot@/u-boot.bin /boot/u-boot-rpi.bin
-
-# Add the config.txt
-copyForced @configTxt@ /boot/config.txt
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix
new file mode 100644
index 0000000000000..7eb52e3d021ff
--- /dev/null
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.nix
@@ -0,0 +1,10 @@
+{ pkgs, configTxt }:
+
+pkgs.substituteAll {
+  src = ./raspberrypi-builder.sh;
+  isExecutable = true;
+  inherit (pkgs) bash;
+  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  firmware = pkgs.raspberrypifw;
+  inherit configTxt;
+}
diff --git a/nixos/modules/system/boot/loader/raspberrypi/builder.sh b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
index 8adc8a6a7e114..0fb07de10c044 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/builder.sh
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi-builder.sh
@@ -5,15 +5,25 @@ shopt -s nullglob
 export PATH=/empty
 for i in @path@; do PATH=$PATH:$i/bin; done
 
-default=$1
-if test -z "$1"; then
-    echo "Syntax: builder.sh <DEFAULT-CONFIG>"
+usage() {
+    echo "usage: $0 -c <path-to-default-configuration> [-d <boot-dir>]" >&2
     exit 1
-fi
+}
+
+default=                # Default configuration
+target=/boot            # Target directory
+
+while getopts "c:d:" opt; do
+    case "$opt" in
+        c) default="$OPTARG" ;;
+        d) target="$OPTARG" ;;
+        \?) usage ;;
+    esac
+done
 
 echo "updating the boot generations directory..."
 
-mkdir -p /boot/old
+mkdir -p $target/old
 
 # Convert a path to a file in the Nix store such as
 # /nix/store/<hash>-<name>/file to <hash>-<name>-<file>.
@@ -22,12 +32,12 @@ cleanName() {
     echo "$path" | sed 's|^/nix/store/||' | sed 's|/|-|g'
 }
 
-# Copy a file from the Nix store to /boot/kernels.
+# Copy a file from the Nix store to $target/kernels.
 declare -A filesCopied
 
 copyToKernelsDir() {
     local src="$1"
-    local dst="/boot/old/$(cleanName $src)"
+    local dst="$target/old/$(cleanName $src)"
     # Don't copy the file if $dst already exists.  This means that we
     # have to create $dst atomically to prevent partially copied
     # kernels or initrd if this script is ever interrupted.
@@ -47,10 +57,10 @@ copyForced() {
     mv $dst.tmp $dst
 }
 
-outdir=/boot/old
+outdir=$target/old
 mkdir -p $outdir || true
 
-# Copy its kernel and initrd to /boot/kernels.
+# Copy its kernel and initrd to $target/old.
 addEntry() {
     local path="$1"
     local generation="$2"
@@ -74,25 +84,21 @@ addEntry() {
     echo $initrd > $outdir/$generation-initrd
     echo $kernel > $outdir/$generation-kernel
 
-    if test $(readlink -f "$path") = "$default"; then
-      if [ @version@ -eq 1 ]; then
-        copyForced $kernel /boot/kernel.img
-      else
-        copyForced $kernel /boot/kernel7.img
-      fi
-      copyForced $initrd /boot/initrd
-      for dtb in $dtb_path/bcm*.dtb; do
-        dst="/boot/$(basename $dtb)"
+    if test "$generation" = "default"; then
+      copyForced $kernel $target/kernel.img
+      copyForced $initrd $target/initrd
+      for dtb in $dtb_path/{broadcom,}/bcm*.dtb; do
+        dst="$target/$(basename $dtb)"
         copyForced $dtb "$dst"
         filesCopied[$dst]=1
       done
-      cp "$(readlink -f "$path/init")" /boot/nixos-init
-      echo "`cat $path/kernel-params` init=$path/init" >/boot/cmdline.txt
-
-      echo "$2" > /boot/defaultgeneration
+      cp "$(readlink -f "$path/init")" $target/nixos-init
+      echo "`cat $path/kernel-params` init=$path/init" >$target/cmdline.txt
     fi
 }
 
+addEntry $default default
+
 # Add all generations of the system profile to the menu, in reverse
 # (most recent to least recent) order.
 for generation in $(
@@ -105,21 +111,21 @@ done
 
 # Add the firmware files
 fwdir=@firmware@/share/raspberrypi/boot/
-copyForced $fwdir/bootcode.bin  /boot/bootcode.bin
-copyForced $fwdir/fixup.dat     /boot/fixup.dat
-copyForced $fwdir/fixup_cd.dat  /boot/fixup_cd.dat
-copyForced $fwdir/fixup_db.dat  /boot/fixup_db.dat
-copyForced $fwdir/fixup_x.dat   /boot/fixup_x.dat
-copyForced $fwdir/start.elf     /boot/start.elf
-copyForced $fwdir/start_cd.elf  /boot/start_cd.elf
-copyForced $fwdir/start_db.elf  /boot/start_db.elf
-copyForced $fwdir/start_x.elf   /boot/start_x.elf
+copyForced $fwdir/bootcode.bin  $target/bootcode.bin
+copyForced $fwdir/fixup.dat     $target/fixup.dat
+copyForced $fwdir/fixup_cd.dat  $target/fixup_cd.dat
+copyForced $fwdir/fixup_db.dat  $target/fixup_db.dat
+copyForced $fwdir/fixup_x.dat   $target/fixup_x.dat
+copyForced $fwdir/start.elf     $target/start.elf
+copyForced $fwdir/start_cd.elf  $target/start_cd.elf
+copyForced $fwdir/start_db.elf  $target/start_db.elf
+copyForced $fwdir/start_x.elf   $target/start_x.elf
 
 # Add the config.txt
-copyForced @configTxt@ /boot/config.txt
+copyForced @configTxt@ $target/config.txt
 
-# Remove obsolete files from /boot and /boot/old.
-for fn in /boot/old/*linux* /boot/old/*initrd-initrd* /boot/bcm*.dtb; do
+# Remove obsolete files from $target and $target/old.
+for fn in $target/old/*linux* $target/old/*initrd-initrd* $target/bcm*.dtb; do
     if ! test "${filesCopied[$fn]}" = 1; then
         rm -vf -- "$fn"
     fi
diff --git a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
index 9bec24c53f5bf..7db60daa60b8f 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/raspberrypi.nix
@@ -5,30 +5,21 @@ with lib;
 let
   cfg = config.boot.loader.raspberryPi;
 
-  builderGeneric = pkgs.substituteAll {
-    src = ./builder.sh;
-    isExecutable = true;
-    inherit (pkgs) bash;
-    path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
-    firmware = pkgs.raspberrypifw;
-    version = cfg.version;
-    inherit configTxt;
-  };
-
   inherit (pkgs.stdenv.hostPlatform) platform;
 
-  builderUboot = import ./builder_uboot.nix { inherit config; inherit pkgs; inherit configTxt; };
+  builderUboot = import ./uboot-builder.nix { inherit pkgs configTxt; inherit (cfg) version; };
+  builderGeneric = import ./raspberrypi-builder.nix { inherit pkgs configTxt; };
 
   builder = 
     if cfg.uboot.enable then
       "${builderUboot} -g ${toString cfg.uboot.configurationLimit} -t ${timeoutStr} -c"
     else
-      builderGeneric;
+      "${builderGeneric} -c";
 
   blCfg = config.boot.loader;
   timeoutStr = if blCfg.timeout == null then "-1" else toString blCfg.timeout;
 
-  isAarch64 = pkgs.stdenv.isAarch64;
+  isAarch64 = pkgs.stdenv.hostPlatform.isAarch64;
   optional = pkgs.stdenv.lib.optionalString;
 
   configTxt =
@@ -42,10 +33,13 @@ let
       avoid_warnings=1
     '' + optional isAarch64 ''
       # Boot in 64-bit mode.
-      arm_control=0x200
-    '' + optional cfg.uboot.enable ''
+      arm_64bit=1
+    '' + (if cfg.uboot.enable then ''
       kernel=u-boot-rpi.bin
-    '' + optional (cfg.firmwareConfig != null) cfg.firmwareConfig);
+    '' else ''
+      kernel=kernel.img
+      initramfs initrd followkernel
+    '') + optional (cfg.firmwareConfig != null) cfg.firmwareConfig);
 
 in
 
@@ -65,7 +59,7 @@ in
 
       version = mkOption {
         default = 2;
-        type = types.enum [ 1 2 3 ];
+        type = types.enum [ 0 1 2 3 ];
         description = ''
         '';
       };
@@ -103,7 +97,7 @@ in
 
   config = mkIf cfg.enable {
     assertions = singleton {
-      assertion = !pkgs.stdenv.isAarch64 || cfg.version == 3;
+      assertion = !pkgs.stdenv.hostPlatform.isAarch64 || cfg.version == 3;
       message = "Only Raspberry Pi 3 supports aarch64.";
     };
 
diff --git a/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.nix b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
index 47f25a9c2b1be..94599a0081c62 100644
--- a/nixos/modules/system/boot/loader/raspberrypi/builder_uboot.nix
+++ b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.nix
@@ -1,13 +1,14 @@
-{ config, pkgs, configTxt }:
+{ pkgs, version, configTxt }:
 
 let
-  cfg = config.boot.loader.raspberryPi;
-  isAarch64 = pkgs.stdenv.isAarch64;
+  isAarch64 = pkgs.stdenv.hostPlatform.isAarch64;
 
   uboot =
-    if cfg.version == 1 then
+    if version == 0 then
+      pkgs.ubootRaspberryPiZero
+    else if version == 1 then
       pkgs.ubootRaspberryPi
-    else if cfg.version == 2 then
+    else if version == 2 then
       pkgs.ubootRaspberryPi2
     else
       if isAarch64 then
@@ -17,18 +18,17 @@ let
 
   extlinuxConfBuilder =
     import ../generic-extlinux-compatible/extlinux-conf-builder.nix {
-      inherit pkgs;
+      pkgs = pkgs.buildPackages;
     };
 in
 pkgs.substituteAll {
-  src = ./builder_uboot.sh;
+  src = ./uboot-builder.sh;
   isExecutable = true;
-  inherit (pkgs) bash;
-  path = [pkgs.coreutils pkgs.gnused pkgs.gnugrep];
+  inherit (pkgs.buildPackages) bash;
+  path = with pkgs.buildPackages; [coreutils gnused gnugrep];
   firmware = pkgs.raspberrypifw;
   inherit uboot;
   inherit configTxt;
   inherit extlinuxConfBuilder;
-  version = cfg.version;
+  inherit version;
 }
-
diff --git a/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh
new file mode 100644
index 0000000000000..ea591427179f5
--- /dev/null
+++ b/nixos/modules/system/boot/loader/raspberrypi/uboot-builder.sh
@@ -0,0 +1,38 @@
+#! @bash@/bin/sh -e
+
+target=/boot # Target directory
+
+while getopts "t:c:d:g:" opt; do
+    case "$opt" in
+        d) target="$OPTARG" ;;
+        *) ;;
+    esac
+done
+
+copyForced() {
+    local src="$1"
+    local dst="$2"
+    cp $src $dst.tmp
+    mv $dst.tmp $dst
+}
+
+# Call the extlinux builder
+"@extlinuxConfBuilder@" "$@"
+
+# Add the firmware files
+fwdir=@firmware@/share/raspberrypi/boot/
+copyForced $fwdir/bootcode.bin  $target/bootcode.bin
+copyForced $fwdir/fixup.dat     $target/fixup.dat
+copyForced $fwdir/fixup_cd.dat  $target/fixup_cd.dat
+copyForced $fwdir/fixup_db.dat  $target/fixup_db.dat
+copyForced $fwdir/fixup_x.dat   $target/fixup_x.dat
+copyForced $fwdir/start.elf     $target/start.elf
+copyForced $fwdir/start_cd.elf  $target/start_cd.elf
+copyForced $fwdir/start_db.elf  $target/start_db.elf
+copyForced $fwdir/start_x.elf   $target/start_x.elf
+
+# Add the uboot file
+copyForced @uboot@/u-boot.bin $target/u-boot-rpi.bin
+
+# Add the config.txt
+copyForced @configTxt@ $target/config.txt
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 feed863efd662..9ad2a2779e183 100644
--- a/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
+++ b/nixos/modules/system/boot/loader/systemd-boot/systemd-boot.nix
@@ -12,7 +12,7 @@ let
 
     isExecutable = true;
 
-    inherit (pkgs) python3;
+    inherit (pkgs.buildPackages) python3;
 
     systemd = config.systemd.package;
 
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index ed8b9f01e2750..aa4a5f8abccea 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -7,8 +7,19 @@ let
 
   commonFunctions = ''
     die() {
-      echo "$@" >&2
-      exit 1
+        echo "$@" >&2
+        exit 1
+    }
+
+    dev_exist() {
+        local target="$1"
+        if [ -e $target ]; then
+            return 0
+        else
+            local uuid=$(echo -n $target | sed -e 's,UUID=\(.*\),\1,g')
+            blkid --uuid $uuid >/dev/null
+            return $?
+        fi
     }
 
     wait_target() {
@@ -17,13 +28,13 @@ let
         local secs="''${3:-10}"
         local desc="''${4:-$name $target to appear}"
 
-        if [ ! -e $target ]; then
+        if ! dev_exist $target; then
             echo -n "Waiting $secs seconds for $desc..."
             local success=false;
             for try in $(seq $secs); do
                 echo -n "."
                 sleep 1
-                if [ -e $target ]; then
+                if dev_exist $target; then
                     success=true
                     break
                 fi
@@ -40,30 +51,30 @@ let
     }
 
     wait_yubikey() {
-      local secs="''${1:-10}"
-
-      ykinfo -v 1>/dev/null 2>&1
-      if [ $? != 0 ]; then
-          echo -n "Waiting $secs seconds for Yubikey to appear..."
-          local success=false
-          for try in $(seq $secs); do
-              echo -n .
-              sleep 1
-              ykinfo -v 1>/dev/null 2>&1
-              if [ $? == 0 ]; then
-                  success=true
-                  break
-              fi
-          done
-          if [ $success == true ]; then
-              echo " - success";
-              return 0
-          else
-              echo " - failure";
-              return 1
-          fi
-      fi
-      return 0
+        local secs="''${1:-10}"
+
+        ykinfo -v 1>/dev/null 2>&1
+        if [ $? != 0 ]; then
+            echo -n "Waiting $secs seconds for Yubikey to appear..."
+            local success=false
+            for try in $(seq $secs); do
+                echo -n .
+                sleep 1
+                ykinfo -v 1>/dev/null 2>&1
+                if [ $? == 0 ]; then
+                    success=true
+                    break
+                fi
+            done
+            if [ $success == true ]; then
+                echo " - success";
+                return 0
+            else
+                echo " - failure";
+                return 1
+            fi
+        fi
+        return 0
     }
   '';
 
@@ -133,7 +144,7 @@ let
                     fi
                 fi
             done
-            echo -n "Verifiying passphrase for ${device}..."
+            echo -n "Verifying passphrase for ${device}..."
             echo -n "$passphrase" | ${csopen} --key-file=-
             if [ $? == 0 ]; then
                 echo " - success"
diff --git a/nixos/modules/system/boot/stage-1-init.sh b/nixos/modules/system/boot/stage-1-init.sh
index 3bc33a20a09f7..6dafc6cddde78 100644
--- a/nixos/modules/system/boot/stage-1-init.sh
+++ b/nixos/modules/system/boot/stage-1-init.sh
@@ -246,10 +246,7 @@ checkFS() {
     if [ "$fsType" = iso9660 -o "$fsType" = udf ]; then return 0; fi
 
     # Don't check resilient COWs as they validate the fs structures at mount time
-    if [ "$fsType" = btrfs -o "$fsType" = zfs ]; then return 0; fi
-
-    # Skip fsck for bcachefs - not implemented yet.
-    if [ "$fsType" = bcachefs ]; then return 0; fi
+    if [ "$fsType" = btrfs -o "$fsType" = zfs -o "$fsType" = bcachefs ]; then return 0; fi
 
     # Skip fsck for nilfs2 - not needed by design and no fsck tool for this filesystem.
     if [ "$fsType" = nilfs2 ]; then return 0; fi
@@ -343,6 +340,10 @@ mountFS() {
                 echo "resizing $device..."
                 e2fsck -fp "$device"
                 resize2fs "$device"
+            elif [ "$fsType" = f2fs ]; then
+                echo "resizing $device..."
+                fsck.f2fs -fp "$device"
+                resize.f2fs "$device" 
             fi
             ;;
     esac
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index f58b68cb3353d..5e27b24ac4475 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -127,8 +127,8 @@ let
       copy_bin_and_libs ${pkgs.kmod}/bin/kmod
       ln -sf kmod $out/bin/modprobe
 
-      # Copy resize2fs if needed.
-      ${optionalString (any (fs: fs.autoResize) fileSystems) ''
+      # Copy resize2fs if any ext* filesystems are to be resized
+      ${optionalString (any (fs: fs.autoResize && (lib.hasPrefix "ext" fs.fsType)) fileSystems) ''
         # We need mke2fs in the initrd.
         copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/resize2fs
       ''}
@@ -147,7 +147,7 @@ let
       ${config.boot.initrd.extraUtilsCommands}
 
       # Copy ld manually since it isn't detected correctly
-      cp -pv ${pkgs.glibc.out}/lib/ld*.so.? $out/lib
+      cp -pv ${pkgs.stdenv.cc.libc.out}/lib/ld*.so.? $out/lib
 
       # Copy all of the needed libraries
       find $out/bin $out/lib -type f | while read BIN; do
@@ -251,9 +251,9 @@ let
     postInstall = ''
       echo checking syntax
       # check both with bash
-      ${pkgs.bash}/bin/sh -n $target
+      ${pkgs.buildPackages.bash}/bin/sh -n $target
       # and with ash shell, just in case
-      ${extraUtils}/bin/ash -n $target
+      ${pkgs.buildPackages.busybox}/bin/ash -n $target
     '';
 
     inherit udevRules extraUtils modulesClosure;
@@ -525,16 +525,18 @@ in
       };
 
     fileSystems = mkOption {
-      options.neededForBoot = mkOption {
-        default = false;
-        type = types.bool;
-        description = ''
-          If set, this file system will be mounted in the initial
-          ramdisk.  By default, this applies to the root file system
-          and to the file system containing
-          <filename>/nix/store</filename>.
-        '';
-      };
+      type = with lib.types; loaOf (submodule {
+        options.neededForBoot = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            If set, this file system will be mounted in the initial
+            ramdisk.  By default, this applies to the root file system
+            and to the file system containing
+            <filename>/nix/store</filename>.
+          '';
+        };
+      });
     };
 
   };
diff --git a/nixos/modules/system/boot/stage-2-init.sh b/nixos/modules/system/boot/stage-2-init.sh
index 49764b75a5579..03daafa1ce4fb 100644
--- a/nixos/modules/system/boot/stage-2-init.sh
+++ b/nixos/modules/system/boot/stage-2-init.sh
@@ -152,6 +152,14 @@ ln -sfn /run/booted-system /nix/var/nix/gcroots/booted-system
 @shell@ @postBootCommands@
 
 
+# Ensure systemd doesn't try to populate /etc, by forcing its first-boot
+# heuristic off. It doesn't matter what's in /etc/machine-id for this purpose,
+# and systemd will immediately fill in the file when it starts, so just
+# creating it is enough. This `: >>` pattern avoids forking and avoids changing
+# the mtime if the file already exists.
+: >> /etc/machine-id
+
+
 # Reset the logging file descriptors.
 exec 1>&$logOutFd 2>&$logErrFd
 exec {logOutFd}>&- {logErrFd}>&-
diff --git a/nixos/modules/system/boot/systemd-lib.nix b/nixos/modules/system/boot/systemd-lib.nix
index 9c8d4a026b4af..68a40377ee133 100644
--- a/nixos/modules/system/boot/systemd-lib.nix
+++ b/nixos/modules/system/boot/systemd-lib.nix
@@ -63,7 +63,7 @@ in rec {
 
   assertValueOneOf = name: values: group: attr:
     optional (attr ? ${name} && !elem attr.${name} values)
-      "Systemd ${group} field `${name}' cannot have value `${attr.${name}}'.";
+      "Systemd ${group} field `${name}' cannot have value `${toString attr.${name}}'.";
 
   assertHasField = name: group: attr:
     optional (!(attr ? ${name}))
diff --git a/nixos/modules/system/boot/systemd-nspawn.nix b/nixos/modules/system/boot/systemd-nspawn.nix
index 83fef83543600..34a34091a7dc6 100644
--- a/nixos/modules/system/boot/systemd-nspawn.nix
+++ b/nixos/modules/system/boot/systemd-nspawn.nix
@@ -10,8 +10,13 @@ let
   checkExec = checkUnitConfig "Exec" [
     (assertOnlyFields [
       "Boot" "ProcessTwo" "Parameters" "Environment" "User" "WorkingDirectory"
-      "Capability" "DropCapability" "KillSignal" "Personality" "MachineId"
-      "PrivateUsers" "NotifyReady"
+      "PivotRoot" "Capability" "DropCapability" "NoNewPrivileges" "KillSignal"
+      "Personality" "MachineId" "PrivateUsers" "NotifyReady" "SystemCallFilter"
+      "LimitCPU" "LimitFSIZE" "LimitDATA" "LimitSTACK" "LimitCORE" "LimitRSS"
+      "LimitNOFILE" "LimitAS" "LimitNPROC" "LimitMEMLOCK" "LimitLOCKS"
+      "LimitSIGPENDING" "LimitMSGQUEUE" "LimitNICE" "LimitRTPRIO" "LimitRTTIME"
+      "OOMScoreAdjust" "CPUAffinity" "Hostname" "ResolvConf" "Timezone"
+      "LinkJournal"
     ])
     (assertValueOneOf "Boot" boolValues)
     (assertValueOneOf "ProcessTwo" boolValues)
@@ -20,8 +25,8 @@ let
 
   checkFiles = checkUnitConfig "Files" [
     (assertOnlyFields [
-      "ReadOnly" "Volatile" "Bind" "BindReadOnly" "TemporaryFileSystems"
-      "PrivateUsersChown"
+      "ReadOnly" "Volatile" "Bind" "BindReadOnly" "TemporaryFileSystem"
+      "Overlay" "OverlayReadOnly" "PrivateUsersChown"
     ])
     (assertValueOneOf "ReadOnly" boolValues)
     (assertValueOneOf "Volatile" (boolValues ++ [ "state" ]))
@@ -112,9 +117,7 @@ in {
 
       environment.etc."systemd/nspawn".source = generateUnits "nspawn" units [] [];
 
-      systemd.services."systemd-nspawn@" = {
-        wantedBy = [ "machine.target" ];
-      };
+      systemd.targets."multi-user".wants = [ "machines.target" ];
   };
 
 }
diff --git a/nixos/modules/system/boot/systemd-unit-options.nix b/nixos/modules/system/boot/systemd-unit-options.nix
index 2cff25a8c854f..63f974b704f38 100644
--- a/nixos/modules/system/boot/systemd-unit-options.nix
+++ b/nixos/modules/system/boot/systemd-unit-options.nix
@@ -210,6 +210,15 @@ in rec {
       '';
     };
 
+    startLimitIntervalSec = mkOption {
+       type = types.int;
+       description = ''
+         Configure unit start rate limiting. Units which are started
+         more than burst times within an interval time interval are
+         not permitted to start any more.
+       '';
+    };
+
   };
 
 
@@ -394,7 +403,7 @@ in rec {
         Each attribute in this set specifies an option in the
         <literal>[Timer]</literal> section of the unit.  See
         <citerefentry><refentrytitle>systemd.timer</refentrytitle>
-        <manvolnum>7</manvolnum></citerefentry> and
+        <manvolnum>5</manvolnum></citerefentry> and
         <citerefentry><refentrytitle>systemd.time</refentrytitle>
         <manvolnum>7</manvolnum></citerefentry> for details.
       '';
diff --git a/nixos/modules/system/boot/systemd.nix b/nixos/modules/system/boot/systemd.nix
index 12e029ae57f87..9fdef0251d70f 100644
--- a/nixos/modules/system/boot/systemd.nix
+++ b/nixos/modules/system/boot/systemd.nix
@@ -193,7 +193,7 @@ let
     let mkScriptName =  s: "unit-script-" + (replaceChars [ "\\" "@" ] [ "-" "_" ] (shellEscape s) );
     in  pkgs.writeTextFile { name = mkScriptName name; executable = true; inherit text; };
 
-  unitConfig = { config, ... }: {
+  unitConfig = { config, options, ... }: {
     config = {
       unitConfig =
         optionalAttrs (config.requires != [])
@@ -219,7 +219,9 @@ let
         // optionalAttrs (config.documentation != []) {
           Documentation = toString config.documentation; }
         // optionalAttrs (config.onFailure != []) {
-          OnFailure = toString config.onFailure;
+          OnFailure = toString config.onFailure; }
+        // optionalAttrs (options.startLimitIntervalSec.isDefined) {
+          StartLimitIntervalSec = toString config.startLimitIntervalSec;
         };
     };
   };
@@ -319,7 +321,9 @@ let
             in concatMapStrings (n:
               let s = optionalString (env."${n}" != null)
                 "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n";
-              in if stringLength s >= 2048 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env)}
+              # systemd max line length is now 1MiB
+              # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
+              in if stringLength s >= 1048576 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env)}
           ${if def.reloadIfChanged then ''
             X-ReloadIfChanged=true
           '' else if !def.restartIfChanged then ''
@@ -387,7 +391,7 @@ let
 
   logindHandlerType = types.enum [
     "ignore" "poweroff" "reboot" "halt" "kexec" "suspend"
-    "hibernate" "hybrid-sleep" "lock"
+    "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock"
   ];
 
 in
@@ -587,13 +591,41 @@ in
       '';
     };
 
+    services.journald.forwardToSyslog = mkOption {
+      default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
+      defaultText = "config.services.rsyslogd.enable || config.services.syslog-ng.enable";
+      type = types.bool;
+      description = ''
+        Whether to forward log messages to syslog.
+      '';
+    };
+
     services.logind.extraConfig = mkOption {
       default = "";
       type = types.lines;
       example = "IdleAction=lock";
       description = ''
-        Extra config options for systemd-logind. See man logind.conf for
-        available options.
+        Extra config options for systemd-logind. See
+        <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html">
+        logind.conf(5)</link> for available options.
+      '';
+    };
+
+    services.logind.killUserProcesses = mkOption {
+      default = false;
+      type = types.bool;
+      description = ''
+        Specifies whether the processes of a user should be killed
+        when the user logs out.  If true, the scope unit corresponding
+        to the session and all processes inside that scope will be
+        terminated.  If false, the scope is "abandoned" (see
+        <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.scope.html#">
+        systemd.scope(5)</link>), and processes are not killed.
+        </para>
+
+        <para>
+        See <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=">logind.conf(5)</link>
+        for more details.
       '';
     };
 
@@ -618,6 +650,18 @@ in
       '';
     };
 
+    services.logind.lidSwitchExternalPower = mkOption {
+      default = config.services.logind.lidSwitch;
+      example = "ignore";
+      type = logindHandlerType;
+
+      description = ''
+        Specifies what to do when the laptop lid is closed and the system is
+        on external power. By default use the same action as specified in
+        services.logind.lidSwitch.
+      '';
+    };
+
     systemd.user.extraConfig = mkOption {
       default = "";
       type = types.lines;
@@ -747,20 +791,25 @@ in
 
       "systemd/journald.conf".text = ''
         [Journal]
+        Storage=persistent
         RateLimitInterval=${config.services.journald.rateLimitInterval}
         RateLimitBurst=${toString config.services.journald.rateLimitBurst}
         ${optionalString (config.services.journald.console != "") ''
           ForwardToConsole=yes
           TTYPath=${config.services.journald.console}
         ''}
+        ${optionalString (config.services.journald.forwardToSyslog) ''
+          ForwardToSyslog=yes
+        ''}
         ${config.services.journald.extraConfig}
       '';
 
       "systemd/logind.conf".text = ''
         [Login]
-        KillUserProcesses=no
+        KillUserProcesses=${if config.services.logind.killUserProcesses then "yes" else "no"}
         HandleLidSwitch=${config.services.logind.lidSwitch}
         HandleLidSwitchDocked=${config.services.logind.lidSwitchDocked}
+        HandleLidSwitchExternalPower=${config.services.logind.lidSwitchExternalPower}
         ${config.services.logind.extraConfig}
       '';
 
@@ -783,19 +832,6 @@ in
 
     services.dbus.enable = true;
 
-    system.activationScripts.systemd = stringAfter [ "groups" ]
-      ''
-        mkdir -m 0755 -p /var/lib/udev
-
-        if ! [ -e /etc/machine-id ]; then
-          ${systemd}/bin/systemd-machine-id-setup
-        fi
-
-        # Keep a persistent journal. Note that systemd-tmpfiles will
-        # set proper ownership/permissions.
-        mkdir -m 0700 -p /var/log/journal
-      '';
-
     users.users.systemd-network.uid = config.ids.uids.systemd-network;
     users.groups.systemd-network.gid = config.ids.gids.systemd-network;
     users.users.systemd-resolve.uid = config.ids.uids.systemd-resolve;
@@ -879,6 +915,7 @@ in
     systemd.services.systemd-remount-fs.restartIfChanged = false;
     systemd.services.systemd-update-utmp.restartIfChanged = false;
     systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions.
+    systemd.services.systemd-udev-settle.restartIfChanged = false; # Causes long delays in nixos-rebuild
     # Restarting systemd-logind breaks X11
     # - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101
     # - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112
@@ -886,6 +923,9 @@ in
     #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ];
     systemd.services.systemd-logind.restartIfChanged = false;
     systemd.services.systemd-logind.stopIfChanged = false;
+    # The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions.
+    systemd.services."user-runtime-dir@".stopIfChanged = false;
+    systemd.services."user-runtime-dir@".restartIfChanged = false;
     systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
     systemd.services.systemd-journald.stopIfChanged = false;
     systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true;
diff --git a/nixos/modules/system/etc/etc.nix b/nixos/modules/system/etc/etc.nix
index 7d43ba07ca57f..57ade28809625 100644
--- a/nixos/modules/system/etc/etc.nix
+++ b/nixos/modules/system/etc/etc.nix
@@ -154,7 +154,7 @@ in
       ''
         # Set up the statically computed bits of /etc.
         echo "setting up /etc..."
-        ${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl ${./setup-etc.pl} ${etc}/etc
+        ${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix} ${./setup-etc.pl} ${etc}/etc
       '';
 
   };
diff --git a/nixos/modules/installer/tools/auto-upgrade.nix b/nixos/modules/tasks/auto-upgrade.nix
index 7b756b70e2fcb..d225778a3878e 100644
--- a/nixos/modules/installer/tools/auto-upgrade.nix
+++ b/nixos/modules/tasks/auto-upgrade.nix
@@ -78,7 +78,7 @@ let cfg = config.system.autoUpgrade; in
           HOME = "/root";
         } // config.networking.proxy.envVars;
 
-      path = [ pkgs.gnutar pkgs.xz.bin config.nix.package.out ];
+      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}
diff --git a/nixos/modules/tasks/cpu-freq.nix b/nixos/modules/tasks/cpu-freq.nix
index 5f8b5df52acf0..513382936e477 100644
--- a/nixos/modules/tasks/cpu-freq.nix
+++ b/nixos/modules/tasks/cpu-freq.nix
@@ -10,43 +10,81 @@ in
 {
   ###### interface
 
-  options = {
+  options.powerManagement = {
 
-    powerManagement.cpuFreqGovernor = mkOption {
+    # TODO: This should be aliased to powerManagement.cpufreq.governor.
+    # https://github.com/NixOS/nixpkgs/pull/53041#commitcomment-31825338
+    cpuFreqGovernor = mkOption {
       type = types.nullOr types.str;
       default = null;
       example = "ondemand";
       description = ''
         Configure the governor used to regulate the frequence of the
         available CPUs. By default, the kernel configures the
-        performance governor.
+        performance governor, although this may be overwritten in your
+        hardware-configuration.nix file.
+
+        Often used values: "ondemand", "powersave", "performance"
       '';
     };
 
+    cpufreq = {
+
+      max = mkOption {
+        type = types.nullOr types.ints.unsigned;
+        default = null;
+        example = 2200000;
+        description = ''
+          The maximum frequency the CPU will use.  Defaults to the maximum possible.
+        '';
+      };
+
+      min = mkOption {
+        type = types.nullOr types.ints.unsigned;
+        default = null;
+        example = 800000;
+        description = ''
+          The minimum frequency the CPU will use.
+        '';
+      };
+    };
+
   };
 
 
   ###### implementation
 
-  config = mkIf (!config.boot.isContainer && config.powerManagement.cpuFreqGovernor != null) {
+  config =
+    let
+      governorEnable = cfg.cpuFreqGovernor != null;
+      maxEnable = cfg.cpufreq.max != null;
+      minEnable = cfg.cpufreq.min != null;
+      enable =
+        !config.boot.isContainer &&
+        (governorEnable || maxEnable || minEnable);
+    in
+    mkIf enable {
 
-    boot.kernelModules = [ "cpufreq_${cfg.cpuFreqGovernor}" ];
+      boot.kernelModules = optional governorEnable "cpufreq_${cfg.cpuFreqGovernor}";
 
-    environment.systemPackages = [ cpupower ];
+      environment.systemPackages = [ cpupower ];
 
-    systemd.services.cpufreq = {
-      description = "CPU Frequency Governor Setup";
-      after = [ "systemd-modules-load.service" ];
-      wantedBy = [ "multi-user.target" ];
-      path = [ cpupower pkgs.kmod ];
-      unitConfig.ConditionVirtualization = false;
-      serviceConfig = {
-        Type = "oneshot";
-        RemainAfterExit = "yes";
-        ExecStart = "${cpupower}/bin/cpupower frequency-set -g ${cfg.cpuFreqGovernor}";
-        SuccessExitStatus = "0 237";
+      systemd.services.cpufreq = {
+        description = "CPU Frequency Setup";
+        after = [ "systemd-modules-load.service" ];
+        wantedBy = [ "multi-user.target" ];
+        path = [ cpupower pkgs.kmod ];
+        unitConfig.ConditionVirtualization = false;
+        serviceConfig = {
+          Type = "oneshot";
+          RemainAfterExit = "yes";
+          ExecStart = "${cpupower}/bin/cpupower frequency-set " +
+            optionalString governorEnable "--governor ${cfg.cpuFreqGovernor} " +
+            optionalString maxEnable "--max ${toString cfg.cpufreq.max} " +
+            optionalString minEnable "--min ${toString cfg.cpufreq.min} ";
+          SuccessExitStatus = "0 237";
+        };
       };
-    };
 
   };
 }
diff --git a/nixos/modules/tasks/encrypted-devices.nix b/nixos/modules/tasks/encrypted-devices.nix
index 11ed5d7e4d0c7..2c9231f552363 100644
--- a/nixos/modules/tasks/encrypted-devices.nix
+++ b/nixos/modules/tasks/encrypted-devices.nix
@@ -12,7 +12,7 @@ let
 
   encryptedFSOptions = {
 
-    encrypted = {
+    options.encrypted = {
       enable = mkOption {
         default = false;
         type = types.bool;
@@ -47,10 +47,10 @@ in
 
   options = {
     fileSystems = mkOption {
-      options = [encryptedFSOptions];
+      type = with lib.types; loaOf (submodule encryptedFSOptions);
     };
     swapDevices = mkOption {
-      options = [encryptedFSOptions];
+      type = with lib.types; listOf (submodule encryptedFSOptions);
     };
   };
 
diff --git a/nixos/modules/tasks/filesystems.nix b/nixos/modules/tasks/filesystems.nix
index b3690fad1a6a2..07f8214cea2cf 100644
--- a/nixos/modules/tasks/filesystems.nix
+++ b/nixos/modules/tasks/filesystems.nix
@@ -230,6 +230,8 @@ in
       let
         fsToSkipCheck = [ "none" "bindfs" "btrfs" "zfs" "tmpfs" "nfs" "vboxsf" "glusterfs" ];
         skipCheck = fs: fs.noCheck || fs.device == "none" || builtins.elem fs.fsType fsToSkipCheck;
+        # https://wiki.archlinux.org/index.php/fstab#Filepath_spaces
+        escape = string: builtins.replaceStrings [ " " "\t" ] [ "\\040" "\\011" ] string;
       in ''
         # This is a generated file.  Do not edit!
         #
@@ -238,10 +240,10 @@ in
 
         # Filesystems.
         ${concatMapStrings (fs:
-            (if fs.device != null then fs.device
-             else if fs.label != null then "/dev/disk/by-label/${fs.label}"
+            (if fs.device != null then escape fs.device
+             else if fs.label != null then "/dev/disk/by-label/${escape fs.label}"
              else throw "No device specified for mount point ‘${fs.mountPoint}’.")
-            + " " + fs.mountPoint
+            + " " + escape fs.mountPoint
             + " " + fs.fsType
             + " " + builtins.concatStringsSep "," fs.options
             + " 0"
diff --git a/nixos/modules/tasks/filesystems/bcachefs.nix b/nixos/modules/tasks/filesystems/bcachefs.nix
index 227707173a3df..5fda24adb9782 100644
--- a/nixos/modules/tasks/filesystems/bcachefs.nix
+++ b/nixos/modules/tasks/filesystems/bcachefs.nix
@@ -1,26 +1,65 @@
-{ config, lib, pkgs, ... }:
+{ config, lib, pkgs, utils, ... }:
 
 with lib;
 
 let
 
-  inInitrd = any (fs: fs == "bcachefs") config.boot.initrd.supportedFilesystems;
+  bootFs = filterAttrs (n: fs: (fs.fsType == "bcachefs") && (utils.fsNeededForBoot fs)) config.fileSystems;
+
+  commonFunctions = ''
+    prompt() {
+        local name="$1"
+        printf "enter passphrase for $name: "
+    }
+    tryUnlock() {
+        local name="$1"
+        local path="$2"
+        if bcachefs unlock -c $path > /dev/null 2> /dev/null; then    # test for encryption
+            prompt $name
+            until bcachefs unlock $path 2> /dev/null; do              # repeat until sucessfully unlocked
+                printf "unlocking failed!\n"
+                prompt $name
+            done
+            printf "unlocking successful.\n"
+        fi
+    }
+  '';
+
+  openCommand = name: fs:
+    let
+      # we need only unlock one device manually, and cannot pass multiple at once
+      # remove this adaptation when bcachefs implements mounting by filesystem uuid
+      # also, implement automatic waiting for the constituent devices when that happens
+      # bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
+      firstDevice = head (splitString ":" fs.device);
+    in
+      ''
+        tryUnlock ${name} ${firstDevice}
+      '';
 
 in
 
 {
-  config = mkIf (any (fs: fs == "bcachefs") config.boot.supportedFilesystems) {
+  config = mkIf (elem "bcachefs" config.boot.supportedFilesystems) (mkMerge [
+    {
+      system.fsPackages = [ pkgs.bcachefs-tools ];
 
-    system.fsPackages = [ pkgs.bcachefs-tools ];
+      # use kernel package with bcachefs support until it's in mainline
+      boot.kernelPackages = pkgs.linuxPackages_testing_bcachefs;
+    }
 
-    # use kernel package with bcachefs support until it's in mainline
-    boot.kernelPackages = pkgs.linuxPackages_testing_bcachefs;
-    boot.initrd.availableKernelModules = mkIf inInitrd [ "bcachefs" ];
+    (mkIf ((elem "bcachefs" config.boot.initrd.supportedFilesystems) || (bootFs != {})) {
+      # the cryptographic modules are required only for decryption attempts
+      boot.initrd.availableKernelModules = [ "bcachefs" "chacha20" "poly1305" ];
 
-    boot.initrd.extraUtilsCommands = mkIf inInitrd
-      ''
-        copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/fsck.bcachefs
+      boot.initrd.extraUtilsCommands = ''
+        copy_bin_and_libs ${pkgs.bcachefs-tools}/bin/bcachefs
+      '';
+      boot.initrd.extraUtilsCommandsTest = ''
+        $out/bin/bcachefs version
       '';
 
-  };
+      boot.initrd.postDeviceCommands = commonFunctions + concatStrings (mapAttrsToList openCommand bootFs);
+    })
+  ]);
 }
diff --git a/nixos/modules/tasks/filesystems/f2fs.nix b/nixos/modules/tasks/filesystems/f2fs.nix
index d103ff1a57b5a..a305235979a2f 100644
--- a/nixos/modules/tasks/filesystems/f2fs.nix
+++ b/nixos/modules/tasks/filesystems/f2fs.nix
@@ -4,6 +4,7 @@ with lib;
 
 let
   inInitrd = any (fs: fs == "f2fs") config.boot.initrd.supportedFilesystems;
+  fileSystems = filter (x: x.fsType == "f2fs") config.system.build.fileSystems;
 in
 {
   config = mkIf (any (fs: fs == "f2fs") config.boot.supportedFilesystems) {
@@ -14,6 +15,11 @@ in
 
     boot.initrd.extraUtilsCommands = mkIf inInitrd ''
       copy_bin_and_libs ${pkgs.f2fs-tools}/sbin/fsck.f2fs
+      ${optionalString (any (fs: fs.autoResize) fileSystems) ''
+        # We need f2fs-tools' tools to resize filesystems
+        copy_bin_and_libs ${pkgs.f2fs-tools}/sbin/resize.f2fs
+      ''}
+
     '';
   };
 }
diff --git a/nixos/modules/tasks/filesystems/zfs.nix b/nixos/modules/tasks/filesystems/zfs.nix
index 2b3b09d725c74..37a19fb9fc8c7 100644
--- a/nixos/modules/tasks/filesystems/zfs.nix
+++ b/nixos/modules/tasks/filesystems/zfs.nix
@@ -74,7 +74,7 @@ let
   importLib = {zpoolCmd, awkCmd, cfgZfs}: ''
     poolReady() {
       pool="$1"
-      state="$("${zpoolCmd}" import | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
+      state="$("${zpoolCmd}" import 2>/dev/null | "${awkCmd}" "/pool: $pool/ { found = 1 }; /state:/ { if (found == 1) { print \$2; exit } }; END { if (found == 0) { print \"MISSING\" } }")"
       if [[ "$state" = "ONLINE" ]]; then
         return 0
       else
@@ -535,6 +535,7 @@ in
 
       systemd.timers.zfs-scrub = {
         wantedBy = [ "timers.target" ];
+        after = [ "multi-user.target" ]; # Apparently scrubbing before boot is complete hangs the system? #53583
         timerConfig = {
           OnCalendar = cfgScrub.interval;
           Persistent = "yes";
diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix
index af61c95da0af6..93dfefdce9022 100644
--- a/nixos/modules/tasks/network-interfaces-scripted.nix
+++ b/nixos/modules/tasks/network-interfaces-scripted.nix
@@ -85,7 +85,8 @@ let
             after = [ "network-pre.target" "systemd-udevd.service" "systemd-sysctl.service" ];
             before = [ "network.target" "shutdown.target" ];
             wants = [ "network.target" ];
-            partOf = map (i: "network-addresses-${i.name}.service") interfaces;
+            # exclude bridges from the partOf relationship to fix container networking bug #47210
+            partOf = map (i: "network-addresses-${i.name}.service") (filter (i: !(hasAttr i.name cfg.bridges)) interfaces);
             conflicts = [ "shutdown.target" ];
             wantedBy = [ "multi-user.target" ] ++ optional hasDefaultGatewaySet "network-online.target";
 
diff --git a/nixos/modules/testing/service-runner.nix b/nixos/modules/testing/service-runner.nix
index 25490d671152a..17d5e33769086 100644
--- a/nixos/modules/testing/service-runner.nix
+++ b/nixos/modules/testing/service-runner.nix
@@ -6,7 +6,7 @@ let
 
   makeScript = name: service: pkgs.writeScript "${name}-runner"
     ''
-      #! ${pkgs.perl}/bin/perl -w -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl
+      #! ${pkgs.perl}/bin/perl -w -I${pkgs.perlPackages.FileSlurp}/${pkgs.perl.libPrefix}
 
       use File::Slurp;
 
@@ -92,23 +92,24 @@ let
       exit($mainRes & 127 ? 255 : $mainRes << 8);
     '';
 
+  opts = { config, name, ... }: {
+    options.runner = mkOption {
+    internal = true;
+    description = ''
+        A script that runs the service outside of systemd,
+        useful for testing or for using NixOS services outside
+        of NixOS.
+    '';
+    };
+    config.runner = makeScript name config;
+  };
+
 in
 
 {
   options = {
     systemd.services = mkOption {
-      options =
-        { config, name, ... }:
-        { options.runner = mkOption {
-            internal = true;
-            description = ''
-              A script that runs the service outside of systemd,
-              useful for testing or for using NixOS services outside
-              of NixOS.
-            '';
-          };
-          config.runner = makeScript name config;
-        };
+      type = with types; attrsOf (submodule opts);
     };
   };
 }
diff --git a/nixos/modules/virtualisation/amazon-image.nix b/nixos/modules/virtualisation/amazon-image.nix
index e9e935e90202e..6f4f99caa6f13 100644
--- a/nixos/modules/virtualisation/amazon-image.nix
+++ b/nixos/modules/virtualisation/amazon-image.nix
@@ -8,7 +8,13 @@
 
 with lib;
 
-let cfg = config.ec2; in
+let
+  cfg = config.ec2;
+  metadataFetcher = import ./ec2-metadata-fetcher.nix {
+    targetRoot = "$targetRoot/";
+    wgetExtraOptions = "-q";
+  };
+in
 
 {
   imports = [ ../profiles/headless.nix ./ec2-data.nix ./amazon-init.nix ];
@@ -53,7 +59,7 @@ let cfg = config.ec2; in
     # Mount all formatted ephemeral disks and activate all swap devices.
     # We cannot do this with the ‘fileSystems’ and ‘swapDevices’ options
     # because the set of devices is dependent on the instance type
-    # (e.g. "m1.large" has one ephemeral filesystem and one swap device,
+    # (e.g. "m1.small" has one ephemeral filesystem and one swap device,
     # while "m1.large" has two ephemeral filesystems and no swap
     # devices).  Also, put /tmp and /var on /disk0, since it has a lot
     # more space than the root device.  Similarly, "move" /nix to /disk0
@@ -61,26 +67,7 @@ let cfg = config.ec2; in
     # Nix operations.
     boot.initrd.postMountCommands =
       ''
-        metaDir=$targetRoot/etc/ec2-metadata
-        mkdir -m 0755 -p "$metaDir"
-
-        echo "getting EC2 instance metadata..."
-
-        if ! [ -e "$metaDir/ami-manifest-path" ]; then
-          wget -q -O "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path
-        fi
-
-        if ! [ -e "$metaDir/user-data" ]; then
-          wget -q -O "$metaDir/user-data" http://169.254.169.254/1.0/user-data && chmod 600 "$metaDir/user-data"
-        fi
-
-        if ! [ -e "$metaDir/hostname" ]; then
-          wget -q -O "$metaDir/hostname" http://169.254.169.254/1.0/meta-data/hostname
-        fi
-
-        if ! [ -e "$metaDir/public-keys-0-openssh-key" ]; then
-          wget -q -O "$metaDir/public-keys-0-openssh-key" http://169.254.169.254/1.0/meta-data/public-keys/0/openssh-key
-        fi
+        ${metadataFetcher}
 
         diskNr=0
         diskForUnionfs=
@@ -145,8 +132,12 @@ let cfg = config.ec2; in
     environment.systemPackages = [ pkgs.cryptsetup ];
 
     boot.initrd.supportedFilesystems = [ "unionfs-fuse" ];
-    
+
     # EC2 has its own NTP server provided by the hypervisor
     networking.timeServers = [ "169.254.169.123" ];
+
+    # udisks has become too bloated to have in a headless system
+    # (e.g. it depends on GTK+).
+    services.udisks2.enable = false;
   };
 }
diff --git a/nixos/modules/virtualisation/container-config.nix b/nixos/modules/virtualisation/container-config.nix
index 5e368acd6d8b5..604fb8a759326 100644
--- a/nixos/modules/virtualisation/container-config.nix
+++ b/nixos/modules/virtualisation/container-config.nix
@@ -7,7 +7,6 @@ with lib;
   config = mkIf config.boot.isContainer {
 
     # Disable some features that are not useful in a container.
-    sound.enable = mkDefault false;
     services.udisks2.enable = mkDefault false;
     powerManagement.enable = mkDefault false;
 
@@ -22,6 +21,9 @@ with lib;
     # Not supported in systemd-nspawn containers.
     security.audit.enable = false;
 
+    # Use the host's nix-daemon.
+    environment.variables.NIX_REMOTE = "daemon";
+
   };
 
 }
diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix
index b91165ce3b827..7c9909ae27875 100644
--- a/nixos/modules/virtualisation/containers.nix
+++ b/nixos/modules/virtualisation/containers.nix
@@ -36,7 +36,7 @@ let
         #! ${pkgs.runtimeShell} -e
 
         # Initialise the container side of the veth pair.
-        if [ "$PRIVATE_NETWORK" = 1 ]; then
+        if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ] || [ -n "$HOST_BRIDGE" ]; then
 
           ip link set host0 name eth0
           ip link set dev eth0 up
@@ -85,19 +85,25 @@ let
       cp --remove-destination /etc/resolv.conf "$root/etc/resolv.conf"
 
       if [ "$PRIVATE_NETWORK" = 1 ]; then
+        extraFlags+=" --private-network"
+      fi
+
+      if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ]; then
         extraFlags+=" --network-veth"
-        if [ -n "$HOST_BRIDGE" ]; then
-          extraFlags+=" --network-bridge=$HOST_BRIDGE"
-        fi
-        if [ -n "$HOST_PORT" ]; then
-          OIFS=$IFS
-          IFS=","
-          for i in $HOST_PORT
-          do
-              extraFlags+=" --port=$i"
-          done
-          IFS=$OIFS
-        fi
+      fi
+
+      if [ -n "$HOST_PORT" ]; then
+        OIFS=$IFS
+        IFS=","
+        for i in $HOST_PORT
+        do
+            extraFlags+=" --port=$i"
+        done
+        IFS=$OIFS
+      fi
+
+      if [ -n "$HOST_BRIDGE" ]; then
+        extraFlags+=" --network-bridge=$HOST_BRIDGE"
       fi
 
       extraFlags+=" ${concatStringsSep " " (mapAttrsToList nspawnExtraVethArgs cfg.extraVeths)}"
@@ -130,6 +136,7 @@ let
         --bind-ro=/nix/var/nix/daemon-socket \
         --bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles" \
         --bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots" \
+        --link-journal=try-guest \
         --setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \
         --setenv HOST_BRIDGE="$HOST_BRIDGE" \
         --setenv HOST_ADDRESS="$HOST_ADDRESS" \
@@ -152,7 +159,7 @@ let
       # Clean up existing machined registration and interfaces.
       machinectl terminate "$INSTANCE" 2> /dev/null || true
 
-      if [ "$PRIVATE_NETWORK" = 1 ]; then
+      if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ]; then
         ip link del dev "ve-$INSTANCE" 2> /dev/null || true
         ip link del dev "vb-$INSTANCE" 2> /dev/null || true
       fi
@@ -183,6 +190,8 @@ let
           ''
         else
           ''
+            echo "Bring ${name} up"
+            ip link set dev ${name} up
             # Set IPs and routes for ${name}
             ${optionalString (cfg.hostAddress != null) ''
               ip addr add ${cfg.hostAddress} dev ${name}
@@ -199,7 +208,7 @@ let
           '';
     in
       ''
-        if [ "$PRIVATE_NETWORK" = 1 ]; then
+        if [ -n "$HOST_ADDRESS" ] || [ -n "$LOCAL_ADDRESS" ]; then
           if [ -z "$HOST_BRIDGE" ]; then
             ifaceHost=ve-$INSTANCE
             ip link set dev $ifaceHost up
@@ -242,6 +251,9 @@ let
 
     Restart = "on-failure";
 
+    Slice = "machine.slice";
+    Delegate = true;
+
     # Hack: we don't want to kill systemd-nspawn, since we call
     # "machinectl poweroff" in preStop to shut down the
     # container cleanly. But systemd requires sending a signal
@@ -348,7 +360,7 @@ let
         List of forwarded ports from host to container. Each forwarded port
         is specified by protocol, hostPort and containerPort. By default,
         protocol is tcp and hostPort and containerPort are assumed to be
-        the same if containerPort is not explicitly given. 
+        the same if containerPort is not explicitly given.
       '';
     };
 
@@ -453,6 +465,16 @@ in
                       { 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 ];
@@ -605,7 +627,7 @@ in
               { config =
                   { config, pkgs, ... }:
                   { services.postgresql.enable = true;
-                    services.postgresql.package = pkgs.postgresql96;
+                    services.postgresql.package = pkgs.postgresql_9_6;
 
                     system.stateVersion = "17.03";
                   };
@@ -656,6 +678,8 @@ in
       serviceConfig = serviceDirectives dummyConfig;
     };
   in {
+    systemd.targets."multi-user".wants = [ "machines.target" ];
+
     systemd.services = listToAttrs (filter (x: x.value != null) (
       # The generic container template used by imperative containers
       [{ name = "container@"; value = unit; }]
@@ -679,7 +703,7 @@ in
           } // (
           if config.autoStart then
             {
-              wantedBy = [ "multi-user.target" ];
+              wantedBy = [ "machines.target" ];
               wants = [ "network.target" ];
               after = [ "network.target" ];
               restartTriggers = [ config.path ];
@@ -693,7 +717,7 @@ in
     # container so that container@.target can get the container
     # configuration.
     environment.etc =
-      let mkPortStr = p: p.protocol + ":" + (toString p.hostPort) + ":" + (if p.containerPort == null then toString p.hostPort else toString p.containerPort); 
+      let mkPortStr = p: p.protocol + ":" + (toString p.hostPort) + ":" + (if p.containerPort == null then toString p.hostPort else toString p.containerPort);
       in mapAttrs' (name: cfg: nameValuePair "containers/${name}.conf"
       { text =
           ''
diff --git a/nixos/modules/virtualisation/docker-image.nix b/nixos/modules/virtualisation/docker-image.nix
index 2f304094d55bb..baac3a35a78e4 100644
--- a/nixos/modules/virtualisation/docker-image.nix
+++ b/nixos/modules/virtualisation/docker-image.nix
@@ -17,3 +17,41 @@
   # Socket activated ssh presents problem in Docker.
   services.openssh.startWhenNeeded = false;
 }
+
+# Example usage:
+#
+## default.nix
+# let
+#   nixos = import <nixpkgs/nixos> {
+#     configuration = ./configuration.nix;
+#     system = "x86_64-linux";
+#   };
+# in
+# nixos.config.system.build.tarball
+#
+## configuration.nix
+# { pkgs, config, lib, ... }:
+# {
+#   imports = [
+#     <nixpkgs/nixos/modules/virtualisation/docker-image.nix>
+#     <nixpkgs/nixos/modules/installer/cd-dvd/channel.nix>
+#   ];
+#
+#   documentation.doc.enable = false;
+#
+#   environment.systemPackages = with pkgs; [
+#     bashInteractive
+#     cacert
+#     nix
+#   ];
+# }
+#
+## Run
+# Build the tarball:
+# $ nix-build default.nix
+# Load into docker:
+# $ docker import result/tarball/nixos-system-*.tar.xz nixos-docker
+# Boots into systemd
+# $ docker run --privileged -it nixos-docker /init
+# Log into the container
+# $ docker exec -it <container-name> /run/current-system/sw/bin/bash
diff --git a/nixos/modules/virtualisation/docker-preloader.nix b/nixos/modules/virtualisation/docker-preloader.nix
new file mode 100644
index 0000000000000..6ab83058dee1d
--- /dev/null
+++ b/nixos/modules/virtualisation/docker-preloader.nix
@@ -0,0 +1,134 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+with builtins;
+
+let
+  cfg = config.virtualisation;
+
+  sanitizeImageName = image: replaceStrings ["/"] ["-"] image.imageName;
+  hash = drv: head (split "-" (baseNameOf drv.outPath));
+  # The label of an ext4 FS is limited to 16 bytes
+  labelFromImage = image: substring 0 16 (hash image);
+
+  # The Docker image is loaded and some files from /var/lib/docker/
+  # are written into a qcow image.
+  preload = image: pkgs.vmTools.runInLinuxVM (
+    pkgs.runCommand "docker-preload-image-${sanitizeImageName image}" {
+      buildInputs = with pkgs; [ docker e2fsprogs utillinux curl kmod ];
+      preVM = pkgs.vmTools.createEmptyImage {
+        size = cfg.dockerPreloader.qcowSize;
+        fullName = "docker-deamon-image.qcow2";
+      };
+    }
+    ''
+      mkfs.ext4 /dev/vda
+      e2label /dev/vda ${labelFromImage image}
+      mkdir -p /var/lib/docker
+      mount -t ext4 /dev/vda /var/lib/docker
+
+      modprobe overlay
+
+      # from https://github.com/tianon/cgroupfs-mount/blob/master/cgroupfs-mount
+      mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
+      cd /sys/fs/cgroup
+      for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do
+        mkdir -p $sys
+        if ! mountpoint -q $sys; then
+          if ! mount -n -t cgroup -o $sys cgroup $sys; then
+            rmdir $sys || true
+          fi
+        fi
+      done
+
+      dockerd -H tcp://127.0.0.1:5555 -H unix:///var/run/docker.sock &
+
+      until $(curl --output /dev/null --silent --connect-timeout 2 http://127.0.0.1:5555); do
+        printf '.'
+        sleep 1
+      done
+
+      docker load -i ${image}
+
+      kill %1
+      find /var/lib/docker/ -maxdepth 1 -mindepth 1 -not -name "image" -not -name "overlay2" | xargs rm -rf
+    '');
+
+  preloadedImages = map preload cfg.dockerPreloader.images;
+
+in
+
+{
+  options.virtualisation.dockerPreloader = {
+    images = mkOption {
+      default = [ ];
+      type = types.listOf types.package;
+      description =
+      ''
+        A list of Docker images to preload (in the /var/lib/docker directory).
+      '';
+    };
+    qcowSize = mkOption {
+      default = 1024;
+      type = types.int;
+      description =
+      ''
+        The size (MB) of qcow files.
+      '';
+    };
+  };
+
+  config = mkIf (cfg.dockerPreloader.images != []) {
+    assertions = [{
+      # If docker.storageDriver is null, Docker choose the storage
+      # driver. So, in this case, we cannot be sure overlay2 is used.
+      assertion = cfg.docker.storageDriver == "overlay2"
+        || cfg.docker.storageDriver == "overlay"
+        || cfg.docker.storageDriver == null;
+      message = "The Docker image Preloader only works with overlay2 storage driver!";
+    }];
+
+    virtualisation.qemu.options =
+      map (path: "-drive if=virtio,file=${path}/disk-image.qcow2,readonly,media=cdrom,format=qcow2")
+      preloadedImages;
+
+
+    # All attached QCOW files are mounted and their contents are linked
+    # to /var/lib/docker/ in order to make image available.
+    systemd.services.docker-preloader = {
+      description = "Preloaded Docker images";
+      wantedBy = ["docker.service"];
+      after = ["network.target"];
+      path = with pkgs; [ mount rsync jq ];
+      script = ''
+        mkdir -p /var/lib/docker/overlay2/l /var/lib/docker/image/overlay2
+        echo '{}' > /tmp/repositories.json
+
+        for i in ${concatStringsSep " " (map labelFromImage cfg.dockerPreloader.images)}; do
+          mkdir -p /mnt/docker-images/$i
+
+          # The ext4 label is limited to 16 bytes
+          mount /dev/disk/by-label/$(echo $i | cut -c1-16) -o ro,noload /mnt/docker-images/$i
+
+          find /mnt/docker-images/$i/overlay2/ -maxdepth 1 -mindepth 1 -not -name l\
+             -exec ln -s '{}' /var/lib/docker/overlay2/ \;
+          cp -P /mnt/docker-images/$i/overlay2/l/* /var/lib/docker/overlay2/l/
+
+          rsync -a /mnt/docker-images/$i/image/ /var/lib/docker/image/
+
+          # Accumulate image definitions
+          cp /tmp/repositories.json /tmp/repositories.json.tmp
+          jq -s '.[0] * .[1]' \
+            /tmp/repositories.json.tmp \
+            /mnt/docker-images/$i/image/overlay2/repositories.json \
+            > /tmp/repositories.json
+        done
+
+        mv /tmp/repositories.json /var/lib/docker/image/overlay2/repositories.json
+      '';
+      serviceConfig = {
+        Type = "oneshot";
+      };
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/ec2-amis.nix b/nixos/modules/virtualisation/ec2-amis.nix
index 76facac39fc6e..aaea06bb9a63f 100644
--- a/nixos/modules/virtualisation/ec2-amis.nix
+++ b/nixos/modules/virtualisation/ec2-amis.nix
@@ -257,5 +257,22 @@ let self = {
   "18.03".sa-east-1.hvm-ebs = "ami-163e1f7a";
   "18.03".ap-south-1.hvm-ebs = "ami-6a390b05";
 
-  latest = self."18.03";
+  # 18.09.910.c15e342304a
+  "18.09".eu-west-1.hvm-ebs = "ami-0f412186fb8a0ec97";
+  "18.09".eu-west-2.hvm-ebs = "ami-0dada3805ce43c55e";
+  "18.09".eu-west-3.hvm-ebs = "ami-074df85565f2e02e2";
+  "18.09".eu-central-1.hvm-ebs = "ami-07c9b884e679df4f8";
+  "18.09".us-east-1.hvm-ebs = "ami-009c9c3f1af480ff3";
+  "18.09".us-east-2.hvm-ebs = "ami-08199961085ea8bc6";
+  "18.09".us-west-1.hvm-ebs = "ami-07aa7f56d612ddd38";
+  "18.09".us-west-2.hvm-ebs = "ami-01c84b7c368ac24d1";
+  "18.09".ca-central-1.hvm-ebs = "ami-04f66113f76198f6c";
+  "18.09".ap-southeast-1.hvm-ebs = "ami-0892c7e24ebf2194f";
+  "18.09".ap-southeast-2.hvm-ebs = "ami-010730f36424b0a2c";
+  "18.09".ap-northeast-1.hvm-ebs = "ami-0cdba8e998f076547";
+  "18.09".ap-northeast-2.hvm-ebs = "ami-0400a698e6a9f4a15";
+  "18.09".sa-east-1.hvm-ebs = "ami-0e4a8a47fd6db6112";
+  "18.09".ap-south-1.hvm-ebs = "ami-0880a678d3f555313";
+
+  latest = self."18.09";
 }; in self
diff --git a/nixos/modules/virtualisation/ec2-metadata-fetcher.nix b/nixos/modules/virtualisation/ec2-metadata-fetcher.nix
new file mode 100644
index 0000000000000..b531787c31a29
--- /dev/null
+++ b/nixos/modules/virtualisation/ec2-metadata-fetcher.nix
@@ -0,0 +1,23 @@
+{ targetRoot, wgetExtraOptions }:
+''
+  metaDir=${targetRoot}etc/ec2-metadata
+  mkdir -m 0755 -p "$metaDir"
+
+  echo "getting EC2 instance metadata..."
+
+  if ! [ -e "$metaDir/ami-manifest-path" ]; then
+    wget ${wgetExtraOptions} -O "$metaDir/ami-manifest-path" http://169.254.169.254/1.0/meta-data/ami-manifest-path
+  fi
+
+  if ! [ -e "$metaDir/user-data" ]; then
+    wget ${wgetExtraOptions} -O "$metaDir/user-data" http://169.254.169.254/1.0/user-data && chmod 600 "$metaDir/user-data"
+  fi
+
+  if ! [ -e "$metaDir/hostname" ]; then
+    wget ${wgetExtraOptions} -O "$metaDir/hostname" http://169.254.169.254/1.0/meta-data/hostname
+  fi
+
+  if ! [ -e "$metaDir/public-keys-0-openssh-key" ]; then
+    wget ${wgetExtraOptions} -O "$metaDir/public-keys-0-openssh-key" http://169.254.169.254/1.0/meta-data/public-keys/0/openssh-key
+  fi
+''
diff --git a/nixos/modules/virtualisation/gce-images.nix b/nixos/modules/virtualisation/gce-images.nix
index 575bbaadbcdb3..5354d91deb935 100644
--- a/nixos/modules/virtualisation/gce-images.nix
+++ b/nixos/modules/virtualisation/gce-images.nix
@@ -4,6 +4,6 @@ let self = {
   "16.03" = "gs://nixos-cloud-images/nixos-image-16.03.847.8688c17-x86_64-linux.raw.tar.gz";
   "17.03" = "gs://nixos-cloud-images/nixos-image-17.03.1082.4aab5c5798-x86_64-linux.raw.tar.gz";
   "18.03" = "gs://nixos-cloud-images/nixos-image-18.03.132536.fdb5ba4cdf9-x86_64-linux.raw.tar.gz";
-
-  latest = self."18.03";
+  "18.09" = "gs://nixos-cloud-images/nixos-image-18.09.1228.a4c4cbb613c-x86_64-linux.raw.tar.gz";
+  latest = self."18.09";
 }; in self
diff --git a/nixos/modules/virtualisation/google-compute-config.nix b/nixos/modules/virtualisation/google-compute-config.nix
index 8f20100bc1b18..8c7331fe4d2b1 100644
--- a/nixos/modules/virtualisation/google-compute-config.nix
+++ b/nixos/modules/virtualisation/google-compute-config.nix
@@ -1,5 +1,235 @@
-{ ... }:
-
+{ config, lib, pkgs, ... }:
+with lib;
+let
+  gce = pkgs.google-compute-engine;
+  cfg = config.virtualisation.googleComputeImage;
+in
 {
-  imports = [ <nixpkgs/nixos/modules/virtualisation/google-compute-image.nix> ];
+  imports = [
+    ../profiles/headless.nix
+    ../profiles/qemu-guest.nix
+  ];
+
+
+  fileSystems."/" = {
+    device = "/dev/disk/by-label/nixos";
+    autoResize = true;
+  };
+
+  boot.growPartition = true;
+  boot.kernelParams = [ "console=ttyS0" "panic=1" "boot.panic_on_fail" ];
+  boot.initrd.kernelModules = [ "virtio_scsi" ];
+  boot.kernelModules = [ "virtio_pci" "virtio_net" ];
+
+  # Generate a GRUB menu.  Amazon's pv-grub uses this to boot our kernel/initrd.
+  boot.loader.grub.device = "/dev/sda";
+  boot.loader.timeout = 0;
+
+  # Don't put old configurations in the GRUB menu.  The user has no
+  # way to select them anyway.
+  boot.loader.grub.configurationLimit = 0;
+
+  # Allow root logins only using the SSH key that the user specified
+  # at instance creation time.
+  services.openssh.enable = true;
+  services.openssh.permitRootLogin = "prohibit-password";
+  services.openssh.passwordAuthentication = mkDefault false;
+
+  # Use GCE udev rules for dynamic disk volumes
+  services.udev.packages = [ gce ];
+
+  # Force getting the hostname from Google Compute.
+  networking.hostName = mkDefault "";
+
+  # Always include cryptsetup so that NixOps can use it.
+  environment.systemPackages = [ pkgs.cryptsetup ];
+
+  # Make sure GCE image does not replace host key that NixOps sets
+  environment.etc."default/instance_configs.cfg".text = lib.mkDefault ''
+    [InstanceSetup]
+    set_host_keys = false
+  '';
+
+  # Rely on GCP's firewall instead
+  networking.firewall.enable = mkDefault false;
+
+  # Configure default metadata hostnames
+  networking.extraHosts = ''
+    169.254.169.254 metadata.google.internal metadata
+  '';
+
+  networking.timeServers = [ "metadata.google.internal" ];
+
+  networking.usePredictableInterfaceNames = false;
+
+  # GC has 1460 MTU
+  networking.interfaces.eth0.mtu = 1460;
+
+  security.googleOsLogin.enable = true;
+
+  systemd.services.google-clock-skew-daemon = {
+    description = "Google Compute Engine Clock Skew Daemon";
+    after = [
+      "network.target"
+      "google-instance-setup.service"
+      "google-network-setup.service"
+    ];
+    requires = ["network.target"];
+    wantedBy = ["multi-user.target"];
+    serviceConfig = {
+      Type = "simple";
+      ExecStart = "${gce}/bin/google_clock_skew_daemon --debug";
+    };
+  };
+
+  systemd.services.google-instance-setup = {
+    description = "Google Compute Engine Instance Setup";
+    after = ["local-fs.target" "network-online.target" "network.target" "rsyslog.service"];
+    before = ["sshd.service"];
+    wants = ["local-fs.target" "network-online.target" "network.target"];
+    wantedBy = [ "sshd.service" "multi-user.target" ];
+    path = with pkgs; [ ethtool openssh ];
+    serviceConfig = {
+      ExecStart = "${gce}/bin/google_instance_setup --debug";
+      Type = "oneshot";
+    };
+  };
+
+  systemd.services.google-network-daemon = {
+    description = "Google Compute Engine Network Daemon";
+    after = ["local-fs.target" "network-online.target" "network.target" "rsyslog.service" "google-instance-setup.service"];
+    wants = ["local-fs.target" "network-online.target" "network.target"];
+    requires = ["network.target"];
+    partOf = ["network.target"];
+    wantedBy = [ "multi-user.target" ];
+    path = with pkgs; [ iproute ];
+    serviceConfig = {
+      ExecStart = "${gce}/bin/google_network_daemon --debug";
+    };
+  };
+
+  systemd.services.google-shutdown-scripts = {
+    description = "Google Compute Engine Shutdown Scripts";
+    after = [
+      "local-fs.target"
+      "network-online.target"
+      "network.target"
+      "rsyslog.service"
+      "systemd-resolved.service"
+      "google-instance-setup.service"
+      "google-network-daemon.service"
+    ];
+    wants = [ "local-fs.target" "network-online.target" "network.target"];
+    wantedBy = [ "multi-user.target" ];
+    serviceConfig = {
+      ExecStart = "${pkgs.coreutils}/bin/true";
+      ExecStop = "${gce}/bin/google_metadata_script_runner --debug --script-type shutdown";
+      Type = "oneshot";
+      RemainAfterExit = true;
+      TimeoutStopSec = "infinity";
+    };
+  };
+
+  systemd.services.google-startup-scripts = {
+    description = "Google Compute Engine Startup Scripts";
+    after = [
+      "local-fs.target"
+      "network-online.target"
+      "network.target"
+      "rsyslog.service"
+      "google-instance-setup.service"
+      "google-network-daemon.service"
+    ];
+    wants = ["local-fs.target" "network-online.target" "network.target"];
+    wantedBy = [ "multi-user.target" ];
+    serviceConfig = {
+      ExecStart = "${gce}/bin/google_metadata_script_runner --debug --script-type startup";
+      KillMode = "process";
+      Type = "oneshot";
+    };
+  };
+
+
+  # Settings taken from https://github.com/GoogleCloudPlatform/compute-image-packages/blob/master/google_config/sysctl/11-gce-network-security.conf
+  boot.kernel.sysctl = {
+    # Turn on SYN-flood protections.  Starting with 2.6.26, there is no loss
+    # of TCP functionality/features under normal conditions.  When flood
+    # protections kick in under high unanswered-SYN load, the system
+    # should remain more stable, with a trade off of some loss of TCP
+    # functionality/features (e.g. TCP Window scaling).
+    "net.ipv4.tcp_syncookies" = mkDefault "1";
+
+    # ignores source-routed packets
+    "net.ipv4.conf.all.accept_source_route" = mkDefault "0";
+
+    # ignores source-routed packets
+    "net.ipv4.conf.default.accept_source_route" = mkDefault "0";
+
+    # ignores ICMP redirects
+    "net.ipv4.conf.all.accept_redirects" = mkDefault "0";
+
+    # ignores ICMP redirects
+    "net.ipv4.conf.default.accept_redirects" = mkDefault "0";
+
+    # ignores ICMP redirects from non-GW hosts
+    "net.ipv4.conf.all.secure_redirects" = mkDefault "1";
+
+    # ignores ICMP redirects from non-GW hosts
+    "net.ipv4.conf.default.secure_redirects" = mkDefault "1";
+
+    # don't allow traffic between networks or act as a router
+    "net.ipv4.ip_forward" = mkDefault "0";
+
+    # don't allow traffic between networks or act as a router
+    "net.ipv4.conf.all.send_redirects" = mkDefault "0";
+
+    # don't allow traffic between networks or act as a router
+    "net.ipv4.conf.default.send_redirects" = mkDefault "0";
+
+    # reverse path filtering - IP spoofing protection
+    "net.ipv4.conf.all.rp_filter" = mkDefault "1";
+
+    # reverse path filtering - IP spoofing protection
+    "net.ipv4.conf.default.rp_filter" = mkDefault "1";
+
+    # ignores ICMP broadcasts to avoid participating in Smurf attacks
+    "net.ipv4.icmp_echo_ignore_broadcasts" = mkDefault "1";
+
+    # ignores bad ICMP errors
+    "net.ipv4.icmp_ignore_bogus_error_responses" = mkDefault "1";
+
+    # logs spoofed, source-routed, and redirect packets
+    "net.ipv4.conf.all.log_martians" = mkDefault "1";
+
+    # log spoofed, source-routed, and redirect packets
+    "net.ipv4.conf.default.log_martians" = mkDefault "1";
+
+    # implements RFC 1337 fix
+    "net.ipv4.tcp_rfc1337" = mkDefault "1";
+
+    # randomizes addresses of mmap base, heap, stack and VDSO page
+    "kernel.randomize_va_space" = mkDefault "2";
+
+    # Reboot the machine soon after a kernel panic.
+    "kernel.panic" = mkDefault "10";
+
+    ## Not part of the original config
+
+    # provides protection from ToCToU races
+    "fs.protected_hardlinks" = mkDefault "1";
+
+    # provides protection from ToCToU races
+    "fs.protected_symlinks" = mkDefault "1";
+
+    # makes locating kernel addresses more difficult
+    "kernel.kptr_restrict" = mkDefault "1";
+
+    # set ptrace protections
+    "kernel.yama.ptrace_scope" = mkOverride 500 "1";
+
+    # set perf only available to root
+    "kernel.perf_event_paranoid" = mkDefault "2";
+
+  };
+
 }
diff --git a/nixos/modules/virtualisation/google-compute-image.nix b/nixos/modules/virtualisation/google-compute-image.nix
index 4c7cffcf45572..0d2d25d30752a 100644
--- a/nixos/modules/virtualisation/google-compute-image.nix
+++ b/nixos/modules/virtualisation/google-compute-image.nix
@@ -2,348 +2,59 @@
 
 with lib;
 let
-  diskSize = 1536; # MB
-  gce = pkgs.google-compute-engine;
+  cfg = config.virtualisation.googleComputeImage;
+  defaultConfigFile = pkgs.writeText "configuration.nix" ''
+    { ... }:
+    {
+      imports = [
+        <nixpkgs/nixos/modules/virtualisation/google-compute-image.nix>
+      ];
+    }
+  '';
 in
 {
-  imports = [ ../profiles/headless.nix ../profiles/qemu-guest.nix ];
-
-  system.build.googleComputeImage = import ../../lib/make-disk-image.nix {
-    name = "google-compute-image";
-    postVM = ''
-      PATH=$PATH:${pkgs.stdenv.lib.makeBinPath [ pkgs.gnutar pkgs.gzip ]}
-      pushd $out
-      mv $diskImage disk.raw
-      tar -Szcf nixos-image-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.raw.tar.gz disk.raw
-      rm $out/disk.raw
-      popd
-    '';
-    configFile = <nixpkgs/nixos/modules/virtualisation/google-compute-config.nix>;
-    format = "raw";
-    inherit diskSize;
-    inherit config lib pkgs;
-  };
-
-  fileSystems."/" = {
-    device = "/dev/disk/by-label/nixos";
-    autoResize = true;
-  };
-
-  boot.growPartition = true;
-  boot.kernelParams = [ "console=ttyS0" "panic=1" "boot.panic_on_fail" ];
-  boot.initrd.kernelModules = [ "virtio_scsi" ];
-  boot.kernelModules = [ "virtio_pci" "virtio_net" ];
-
-  # Generate a GRUB menu.  Amazon's pv-grub uses this to boot our kernel/initrd.
-  boot.loader.grub.device = "/dev/sda";
-  boot.loader.timeout = 0;
-
-  # Don't put old configurations in the GRUB menu.  The user has no
-  # way to select them anyway.
-  boot.loader.grub.configurationLimit = 0;
-
-  # Allow root logins only using the SSH key that the user specified
-  # at instance creation time.
-  services.openssh.enable = true;
-  services.openssh.permitRootLogin = "prohibit-password";
-  services.openssh.passwordAuthentication = mkDefault false;
-
-  # Use GCE udev rules for dynamic disk volumes
-  services.udev.packages = [ gce ];
-
-  # Force getting the hostname from Google Compute.
-  networking.hostName = mkDefault "";
-
-  # Always include cryptsetup so that NixOps can use it.
-  environment.systemPackages = [ pkgs.cryptsetup ];
-
-  # Make sure GCE image does not replace host key that NixOps sets
-  environment.etc."default/instance_configs.cfg".text = lib.mkDefault ''
-    [InstanceSetup]
-    set_host_keys = false
-  '';
-
-  # Rely on GCP's firewall instead
-  networking.firewall.enable = mkDefault false;
-
-  # Configure default metadata hostnames
-  networking.extraHosts = ''
-    169.254.169.254 metadata.google.internal metadata
-  '';
-
-  networking.timeServers = [ "metadata.google.internal" ];
 
-  networking.usePredictableInterfaceNames = false;
-
-  # GC has 1460 MTU
-  networking.interfaces.eth0.mtu = 1460;
-
-  # allow the google-accounts-daemon to manage users
-  users.mutableUsers = true;
-  # and allow users to sudo without password
-  security.sudo.enable = true;
-  security.sudo.extraConfig = ''
-  %google-sudoers ALL=(ALL:ALL) NOPASSWD:ALL
-  '';
-
-  # NOTE: google-accounts tries to write to /etc/sudoers.d but the folder doesn't exist
-  # FIXME: not such file or directory on dynamic SSH provisioning
-  systemd.services.google-accounts-daemon = {
-    description = "Google Compute Engine Accounts Daemon";
-    # This daemon creates dynamic users
-    enable = config.users.mutableUsers;
-    after = [
-      "network.target"
-      "google-instance-setup.service"
-      "google-network-setup.service"
-    ];
-    wantedBy = [ "multi-user.target" ];
-    requires = ["network.target"];
-    path = with pkgs; [ shadow ];
-    serviceConfig = {
-      Type = "simple";
-      ExecStart = "${gce}/bin/google_accounts_daemon --debug";
-    };
-  };
-
-  systemd.services.google-clock-skew-daemon = {
-    description = "Google Compute Engine Clock Skew Daemon";
-    after = [
-      "network.target"
-      "google-instance-setup.service"
-      "google-network-setup.service"
-    ];
-    requires = [ "network.target" ];
-    wantedBy = [ "multi-user.target" ];
-    serviceConfig = {
-      Type = "simple";
-      ExecStart = "${gce}/bin/google_clock_skew_daemon --debug";
-    };
-  };
-
-  systemd.services.google-instance-setup = {
-    description = "Google Compute Engine Instance Setup";
-    after = ["fs.target" "network-online.target" "network.target" "rsyslog.service"];
-    before = ["sshd.service"];
-    wants = ["local-fs.target" "network-online.target" "network.target"];
-    wantedBy = [ "sshd.service" "multi-user.target" ];
-    path = with pkgs; [ ethtool openssh ];
-    serviceConfig = {
-      ExecStart = "${gce}/bin/google_instance_setup --debug";
-      Type = "oneshot";
+  imports = [ ./google-compute-config.nix ];
+
+  options = {
+    virtualisation.googleComputeImage.diskSize = mkOption {
+      type = with types; int;
+      default = 1536;
+      description = ''
+        Size of disk image. Unit is MB.
+      '';
+    };
+
+    virtualisation.googleComputeImage.configFile = mkOption {
+      type = with types; nullOr str;
+      default = null;
+      description = ''
+        A path to a configuration file which will be placed at `/etc/nixos/configuration.nix`
+        and be used when switching to a new configuration.
+        If set to `null`, a default configuration is used, where the only import is
+        `<nixpkgs/nixos/modules/virtualisation/google-compute-image.nix>`.
+      '';
+    };
+  };
+
+  #### implementation
+  config = {
+
+    system.build.googleComputeImage = import ../../lib/make-disk-image.nix {
+      name = "google-compute-image";
+      postVM = ''
+        PATH=$PATH:${with pkgs; stdenv.lib.makeBinPath [ gnutar gzip ]}
+        pushd $out
+        mv $diskImage disk.raw
+        tar -Szcf nixos-image-${config.system.nixos.label}-${pkgs.stdenv.hostPlatform.system}.raw.tar.gz disk.raw
+        rm $out/disk.raw
+        popd
+      '';
+      format = "raw";
+      configFile = if isNull cfg.configFile then defaultConfigFile else cfg.configFile;
+      inherit (cfg) diskSize;
+      inherit config lib pkgs;
     };
-  };
-
-  systemd.services.google-ip-forwarding-daemon = {
-    description = "Google Compute Engine IP Forwarding Daemon";
-    after = ["network.target" "google-instance-setup.service" "google-network-setup.service"];
-    requires = ["network.target"];
-    wantedBy = [ "multi-user.target" ];
-    path = with pkgs; [ iproute ];
-    serviceConfig = {
-      Type = "simple";
-      ExecStart = "${gce}/bin/google_ip_forwarding_daemon --debug";
-    };
-  };
-
-  systemd.services.google-shutdown-scripts = {
-    description = "Google Compute Engine Shutdown Scripts";
-    after = [
-      "local-fs.target"
-      "network-online.target"
-      "network.target"
-      "rsyslog.service"
-      "google-instance-setup.service"
-      "google-network-setup.service"
-    ];
-    wants = [ "local-fs.target" "network-online.target" "network.target"];
-    wantedBy = [ "multi-user.target" ];
-    serviceConfig = {
-      ExecStart = "${pkgs.coreutils}/bin/true";
-      ExecStop = "${gce}/bin/google_metadata_script_runner --debug --script-type shutdown";
-      Type = "oneshot";
-      RemainAfterExit = true;
-      TimeoutStopSec = 0;
-    };
-  };
-
-  systemd.services.google-network-setup = {
-    description = "Google Compute Engine Network Setup";
-    after = [
-      "local-fs.target"
-      "network-online.target"
-      "network.target"
-      "rsyslog.service"
-    ];
-    wants = [ "local-fs.target" "network-online.target" "network.target"];
-    wantedBy = [ "multi-user.target" ];
-    serviceConfig = {
-      ExecStart = "${gce}/bin/google_network_setup --debug";
-      KillMode = "process";
-      Type = "oneshot";
-    };
-  };
-
-  systemd.services.google-startup-scripts = {
-    description = "Google Compute Engine Startup Scripts";
-    after = [
-      "local-fs.target"
-      "network-online.target"
-      "network.target"
-      "rsyslog.service"
-      "google-instance-setup.service"
-      "google-network-setup.service"
-    ];
-    wants = [ "local-fs.target" "network-online.target" "network.target"];
-    wantedBy = [ "multi-user.target" ];
-    serviceConfig = {
-      ExecStart = "${gce}/bin/google_metadata_script_runner --debug --script-type startup";
-      KillMode = "process";
-      Type = "oneshot";
-    };
-  };
-
-  # TODO: remove this
-  systemd.services.fetch-ssh-keys =
-    { description = "Fetch host keys and authorized_keys for root user";
-
-      wantedBy = [ "sshd.service" ];
-      before = [ "sshd.service" ];
-      after = [ "network-online.target" ];
-      wants = [ "network-online.target" ];
-
-      script = let wget = "${pkgs.wget}/bin/wget --retry-connrefused -t 15 --waitretry=10 --header='Metadata-Flavor: Google'";
-                   mktemp = "mktemp --tmpdir=/run"; in
-        ''
-          # When dealing with cryptographic keys, we want to keep things private.
-          umask 077
-          # Don't download the SSH key if it has already been downloaded
-          echo "Obtaining SSH keys..."
-          mkdir -m 0700 -p /root/.ssh
-          AUTH_KEYS=$(${mktemp})
-          ${wget} -O $AUTH_KEYS http://metadata.google.internal/computeMetadata/v1/instance/attributes/sshKeys
-          if [ -s $AUTH_KEYS ]; then
-
-            # Read in key one by one, split in case Google decided
-            # to append metadata (it does sometimes) and add to
-            # authorized_keys if not already present.
-            touch /root/.ssh/authorized_keys
-            NEW_KEYS=$(${mktemp})
-            # Yes this is a nix escape of two single quotes.
-            while IFS=''' read -r line || [[ -n "$line" ]]; do
-              keyLine=$(echo -n "$line" | cut -d ':' -f2)
-              IFS=' ' read -r -a array <<< "$keyLine"
-              if [ ''${#array[@]} -ge 3 ]; then
-                echo ''${array[@]:0:3} >> $NEW_KEYS
-                echo "Added ''${array[@]:2} to authorized_keys"
-              fi
-            done < $AUTH_KEYS
-            mv $NEW_KEYS /root/.ssh/authorized_keys
-            chmod 600 /root/.ssh/authorized_keys
-            rm -f $KEY_PUB
-          else
-            echo "Downloading http://metadata.google.internal/computeMetadata/v1/project/attributes/sshKeys failed."
-            false
-          fi
-          rm -f $AUTH_KEYS
-          SSH_HOST_KEYS_DIR=$(${mktemp} -d)
-          ${wget} -O $SSH_HOST_KEYS_DIR/ssh_host_ed25519_key http://metadata.google.internal/computeMetadata/v1/instance/attributes/ssh_host_ed25519_key
-          ${wget} -O $SSH_HOST_KEYS_DIR/ssh_host_ed25519_key.pub http://metadata.google.internal/computeMetadata/v1/instance/attributes/ssh_host_ed25519_key_pub
-          if [ -s $SSH_HOST_KEYS_DIR/ssh_host_ed25519_key -a -s $SSH_HOST_KEYS_DIR/ssh_host_ed25519_key.pub ]; then
-              mv -f $SSH_HOST_KEYS_DIR/ssh_host_ed25519_key* /etc/ssh/
-              chmod 600 /etc/ssh/ssh_host_ed25519_key
-              chmod 644 /etc/ssh/ssh_host_ed25519_key.pub
-          else
-              echo "Setup of ssh host keys from http://metadata.google.internal/computeMetadata/v1/instance/attributes/ failed."
-              false
-          fi
-          rm -rf $SSH_HOST_KEYS_DIR
-        '';
-      serviceConfig.Type = "oneshot";
-      serviceConfig.RemainAfterExit = true;
-      serviceConfig.StandardError = "journal+console";
-      serviceConfig.StandardOutput = "journal+console";
-    };
-
-  # Settings taken from https://github.com/GoogleCloudPlatform/compute-image-packages/blob/master/google_config/sysctl/11-gce-network-security.conf
-  boot.kernel.sysctl = {
-    # Turn on SYN-flood protections.  Starting with 2.6.26, there is no loss
-    # of TCP functionality/features under normal conditions.  When flood
-    # protections kick in under high unanswered-SYN load, the system
-    # should remain more stable, with a trade off of some loss of TCP
-    # functionality/features (e.g. TCP Window scaling).
-    "net.ipv4.tcp_syncookies" = mkDefault "1";
-
-    # ignores source-routed packets
-    "net.ipv4.conf.all.accept_source_route" = mkDefault "0";
-
-    # ignores source-routed packets
-    "net.ipv4.conf.default.accept_source_route" = mkDefault "0";
-
-    # ignores ICMP redirects
-    "net.ipv4.conf.all.accept_redirects" = mkDefault "0";
-
-    # ignores ICMP redirects
-    "net.ipv4.conf.default.accept_redirects" = mkDefault "0";
-
-    # ignores ICMP redirects from non-GW hosts
-    "net.ipv4.conf.all.secure_redirects" = mkDefault "1";
-
-    # ignores ICMP redirects from non-GW hosts
-    "net.ipv4.conf.default.secure_redirects" = mkDefault "1";
-
-    # don't allow traffic between networks or act as a router
-    "net.ipv4.ip_forward" = mkDefault "0";
-
-    # don't allow traffic between networks or act as a router
-    "net.ipv4.conf.all.send_redirects" = mkDefault "0";
-
-    # don't allow traffic between networks or act as a router
-    "net.ipv4.conf.default.send_redirects" = mkDefault "0";
-
-    # reverse path filtering - IP spoofing protection
-    "net.ipv4.conf.all.rp_filter" = mkDefault "1";
-
-    # reverse path filtering - IP spoofing protection
-    "net.ipv4.conf.default.rp_filter" = mkDefault "1";
-
-    # ignores ICMP broadcasts to avoid participating in Smurf attacks
-    "net.ipv4.icmp_echo_ignore_broadcasts" = mkDefault "1";
-
-    # ignores bad ICMP errors
-    "net.ipv4.icmp_ignore_bogus_error_responses" = mkDefault "1";
-
-    # logs spoofed, source-routed, and redirect packets
-    "net.ipv4.conf.all.log_martians" = mkDefault "1";
-
-    # log spoofed, source-routed, and redirect packets
-    "net.ipv4.conf.default.log_martians" = mkDefault "1";
-
-    # implements RFC 1337 fix
-    "net.ipv4.tcp_rfc1337" = mkDefault "1";
-
-    # randomizes addresses of mmap base, heap, stack and VDSO page
-    "kernel.randomize_va_space" = mkDefault "2";
-
-    # Reboot the machine soon after a kernel panic.
-    "kernel.panic" = mkDefault "10";
-
-    ## Not part of the original config
-
-    # provides protection from ToCToU races
-    "fs.protected_hardlinks" = mkDefault "1";
-
-    # provides protection from ToCToU races
-    "fs.protected_symlinks" = mkDefault "1";
-
-    # makes locating kernel addresses more difficult
-    "kernel.kptr_restrict" = mkDefault "1";
-
-    # set ptrace protections
-    "kernel.yama.ptrace_scope" = mkOverride 500 "1";
-
-    # set perf only available to root
-    "kernel.perf_event_paranoid" = mkDefault "2";
 
   };
 
diff --git a/nixos/modules/virtualisation/hyperv-guest.nix b/nixos/modules/virtualisation/hyperv-guest.nix
index ecd2a8117710f..0f1f052880c5f 100644
--- a/nixos/modules/virtualisation/hyperv-guest.nix
+++ b/nixos/modules/virtualisation/hyperv-guest.nix
@@ -9,20 +9,47 @@ in {
   options = {
     virtualisation.hypervGuest = {
       enable = mkEnableOption "Hyper-V Guest Support";
+
+      videoMode = mkOption {
+        type = types.str;
+        default = "1152x864";
+        example = "1024x768";
+        description = ''
+          Resolution at which to initialize the video adapter.
+
+          Supports screen resolution up to Full HD 1920x1080 with 32 bit color
+          on Windows Server 2012, and 1600x1200 with 16 bit color on Windows
+          Server 2008 R2 or earlier.
+        '';
+      };
     };
   };
 
   config = mkIf cfg.enable {
+    boot = {
+      initrd.kernelModules = [
+        "hv_balloon" "hv_netvsc" "hv_storvsc" "hv_utils" "hv_vmbus"
+      ];
+
+      kernelParams = [
+        "video=hyperv_fb:${cfg.videoMode}"
+      ];
+    };
+
     environment.systemPackages = [ config.boot.kernelPackages.hyperv-daemons.bin ];
 
     security.rngd.enable = false;
 
-    # enable hotadding memory
+    # enable hotadding cpu/memory
     services.udev.packages = lib.singleton (pkgs.writeTextFile {
-      name = "hyperv-memory-hotadd-udev-rules";
-      destination = "/etc/udev/rules.d/99-hyperv-memory-hotadd.rules";
+      name = "hyperv-cpu-and-memory-hotadd-udev-rules";
+      destination = "/etc/udev/rules.d/99-hyperv-cpu-and-memory-hotadd.rules";
       text = ''
-        ACTION="add", SUBSYSTEM=="memory", ATTR{state}="online"
+        # Memory hotadd
+        SUBSYSTEM=="memory", ACTION=="add", DEVPATH=="/devices/system/memory/memory[0-9]*", TEST=="state", ATTR{state}="online"
+
+        # CPU hotadd
+        SUBSYSTEM=="cpu", ACTION=="add", DEVPATH=="/devices/system/cpu/cpu[0-9]*", TEST=="online", ATTR{online}="1"
       '';
     });
 
diff --git a/nixos/modules/virtualisation/kvmgt.nix b/nixos/modules/virtualisation/kvmgt.nix
index fc0bedb68bd08..132815a0ad632 100644
--- a/nixos/modules/virtualisation/kvmgt.nix
+++ b/nixos/modules/virtualisation/kvmgt.nix
@@ -50,11 +50,17 @@ in {
       nameValuePair "kvmgt-${name}" {
         description = "KVMGT VGPU ${name}";
         serviceConfig = {
-          Type = "oneshot";
+          Type = "forking";
           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 3e38662f5b0fd..f4d7af1664af8 100644
--- a/nixos/modules/virtualisation/libvirtd.nix
+++ b/nixos/modules/virtualisation/libvirtd.nix
@@ -196,6 +196,8 @@ in {
       wantedBy = [ "multi-user.target" ];
       path = with pkgs; [ coreutils libvirt gawk ];
       restartIfChanged = false;
+
+      environment.ON_SHUTDOWN = "${cfg.onShutdown}";
     };
 
     systemd.sockets.virtlogd = {
diff --git a/nixos/modules/virtualisation/nova-config.nix b/nixos/modules/virtualisation/nova-config.nix
deleted file mode 100644
index cecf2a3f144c1..0000000000000
--- a/nixos/modules/virtualisation/nova-config.nix
+++ /dev/null
@@ -1,60 +0,0 @@
-{ lib, ... }:
-
-with lib;
-
-{
-  imports = [
-    ../profiles/qemu-guest.nix
-    ../profiles/headless.nix
-  ];
-
-  config = {
-    fileSystems."/" = {
-      device = "/dev/disk/by-label/nixos";
-      autoResize = true;
-    };
-
-    boot.growPartition = true;
-    boot.kernelParams = [ "console=ttyS0" ];
-    boot.loader.grub.device = "/dev/vda";
-    boot.loader.timeout = 0;
-
-    # Allow root logins
-    services.openssh = {
-      enable = true;
-      permitRootLogin = "prohibit-password";
-      passwordAuthentication = mkDefault false;
-    };
-
-    services.cloud-init.enable = true;
-
-    # Put /tmp and /var on /ephemeral0, which has a lot more space.
-    # Unfortunately we can't do this with the `fileSystems' option
-    # because it has no support for creating the source of a bind
-    # mount.  Also, "move" /nix to /ephemeral0 by layering a unionfs-fuse
-    # mount on top of it so we have a lot more space for Nix operations.
-
-    /*
-    boot.initrd.postMountCommands =
-      ''
-        mkdir -m 1777 -p $targetRoot/ephemeral0/tmp
-        mkdir -m 1777 -p $targetRoot/tmp
-        mount --bind $targetRoot/ephemeral0/tmp $targetRoot/tmp
-
-        mkdir -m 755 -p $targetRoot/ephemeral0/var
-        mkdir -m 755 -p $targetRoot/var
-        mount --bind $targetRoot/ephemeral0/var $targetRoot/var
-
-        mkdir -p /unionfs-chroot/ro-nix
-        mount --rbind $targetRoot/nix /unionfs-chroot/ro-nix
-
-        mkdir -p /unionfs-chroot/rw-nix
-        mkdir -m 755 -p $targetRoot/ephemeral0/nix
-        mount --rbind $targetRoot/ephemeral0/nix /unionfs-chroot/rw-nix
-        unionfs -o allow_other,cow,nonempty,chroot=/unionfs-chroot,max_files=32768 /rw-nix=RW:/ro-nix=RO $targetRoot/nix
-      '';
-
-      boot.initrd.supportedFilesystems = [ "unionfs-fuse" ];
-    */
-  };
-}
diff --git a/nixos/modules/virtualisation/openstack-config.nix b/nixos/modules/virtualisation/openstack-config.nix
new file mode 100644
index 0000000000000..d5e862da0ea48
--- /dev/null
+++ b/nixos/modules/virtualisation/openstack-config.nix
@@ -0,0 +1,57 @@
+{ pkgs, lib, ... }:
+
+with lib;
+
+let
+  metadataFetcher = import ./ec2-metadata-fetcher.nix {
+    targetRoot = "/";
+    wgetExtraOptions = "--retry-connrefused";
+  };
+in
+{
+  imports = [
+    ../profiles/qemu-guest.nix
+    ../profiles/headless.nix
+    # The Openstack Metadata service exposes data on an EC2 API also.
+    ./ec2-data.nix
+    ./amazon-init.nix
+  ];
+
+  config = {
+    fileSystems."/" = {
+      device = "/dev/disk/by-label/nixos";
+      autoResize = true;
+    };
+
+    boot.growPartition = true;
+    boot.kernelParams = [ "console=ttyS0" ];
+    boot.loader.grub.device = "/dev/vda";
+    boot.loader.timeout = 0;
+
+    # Allow root logins
+    services.openssh = {
+      enable = true;
+      permitRootLogin = "prohibit-password";
+      passwordAuthentication = mkDefault false;
+    };
+
+    # Force getting the hostname from Openstack metadata.
+    networking.hostName = mkDefault "";
+
+    systemd.services.openstack-init = {
+      path = [ pkgs.wget ];
+      description = "Fetch Metadata on startup";
+      wantedBy = [ "multi-user.target" ];
+      before = [ "apply-ec2-data.service" "amazon-init.service"];
+      wants = [ "network-online.target" ];
+      after = [ "network-online.target" ];
+      script = metadataFetcher;
+      restartIfChanged = false;
+      unitConfig.X-StopOnRemoval = false;
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = true;
+      };
+    };
+  };
+}
diff --git a/nixos/modules/virtualisation/parallels-guest.nix b/nixos/modules/virtualisation/parallels-guest.nix
index 36ca7f356d442..4e0f2cae299ec 100644
--- a/nixos/modules/virtualisation/parallels-guest.nix
+++ b/nixos/modules/virtualisation/parallels-guest.nix
@@ -1,4 +1,4 @@
-{ config, lib, pkgs, pkgs_i686, ... }:
+{ config, lib, pkgs, ... }:
 
 with lib;
 
@@ -64,7 +64,7 @@ in
     };
 
     hardware.opengl.package = prl-tools;
-    hardware.opengl.package32 = pkgs_i686.linuxPackages.prl-tools.override { libsOnly = true; kernel = null; };
+    hardware.opengl.package32 = pkgs.pkgsi686Linux.linuxPackages.prl-tools.override { libsOnly = true; kernel = null; };
 
     services.udev.packages = [ prl-tools ];
 
diff --git a/nixos/modules/virtualisation/qemu-guest-agent.nix b/nixos/modules/virtualisation/qemu-guest-agent.nix
index e0d2b3dc509de..665224e35d8cd 100644
--- a/nixos/modules/virtualisation/qemu-guest-agent.nix
+++ b/nixos/modules/virtualisation/qemu-guest-agent.nix
@@ -25,7 +25,7 @@ in {
       systemd.services.qemu-guest-agent = {
         description = "Run the QEMU Guest Agent";
         serviceConfig = {
-          ExecStart = "${pkgs.kvm.ga}/bin/qemu-ga";
+          ExecStart = "${pkgs.qemu.ga}/bin/qemu-ga";
           Restart = "always";
           RestartSec = 0;
         };
diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix
index 4e9c87222d0a8..ed3431554be45 100644
--- a/nixos/modules/virtualisation/qemu-vm.nix
+++ b/nixos/modules/virtualisation/qemu-vm.nix
@@ -185,7 +185,10 @@ let
 in
 
 {
-  imports = [ ../profiles/qemu-guest.nix ];
+  imports = [
+    ../profiles/qemu-guest.nix
+   ./docker-preloader.nix
+  ];
 
   options = {
 
diff --git a/nixos/modules/virtualisation/virtualbox-image.nix b/nixos/modules/virtualisation/virtualbox-image.nix
index 60048911658c4..037c0d2f0d82f 100644
--- a/nixos/modules/virtualisation/virtualbox-image.nix
+++ b/nixos/modules/virtualisation/virtualbox-image.nix
@@ -12,7 +12,7 @@ in {
     virtualbox = {
       baseImageSize = mkOption {
         type = types.int;
-        default = 10 * 1024;
+        default = 50 * 1024;
         description = ''
           The size of the VirtualBox base image in MiB.
         '';
@@ -61,7 +61,7 @@ in {
           export HOME=$PWD
           export PATH=${pkgs.virtualbox}/bin:$PATH
 
-          echo "creating VirtualBox pass-through disk wrapper (no copying invovled)..."
+          echo "creating VirtualBox pass-through disk wrapper (no copying involved)..."
           VBoxManage internalcommands createrawvmdk -filename disk.vmdk -rawdisk $diskImage
 
           echo "creating VirtualBox VM..."
@@ -72,9 +72,9 @@ in {
             --memory ${toString cfg.memorySize} --acpi on --vram 32 \
             ${optionalString (pkgs.stdenv.hostPlatform.system == "i686-linux") "--pae on"} \
             --nictype1 virtio --nic1 nat \
-            --audiocontroller ac97 --audio alsa \
+            --audiocontroller ac97 --audio alsa --audioout on \
             --rtcuseutc on \
-            --usb on --mouse usbtablet
+            --usb on --usbehci on --mouse usbtablet
           VBoxManage storagectl "$vmName" --name SATA --add sata --portcount 4 --bootable on --hostiocache on
           VBoxManage storageattach "$vmName" --storagectl SATA --port 0 --device 0 --type hdd \
             --medium disk.vmdk
@@ -82,7 +82,7 @@ in {
           echo "exporting VirtualBox VM..."
           mkdir -p $out
           fn="$out/${cfg.vmFileName}"
-          VBoxManage export "$vmName" --output "$fn"
+          VBoxManage export "$vmName" --output "$fn" --options manifest
 
           rm -v $diskImage
 
diff --git a/nixos/modules/virtualisation/xe-guest-utilities.nix b/nixos/modules/virtualisation/xe-guest-utilities.nix
index d703353858c03..675cf92973711 100644
--- a/nixos/modules/virtualisation/xe-guest-utilities.nix
+++ b/nixos/modules/virtualisation/xe-guest-utilities.nix
@@ -5,7 +5,7 @@ let
 in {
   options = {
     services.xe-guest-utilities = {
-      enable = mkEnableOption "Whether to enable the Xen guest utilities daemon.";
+      enable = mkEnableOption "the Xen guest utilities daemon";
     };
   };
   config = mkIf cfg.enable {
diff --git a/nixos/release-combined.nix b/nixos/release-combined.nix
index 66b253c230f1d..ea8b92e94f01e 100644
--- a/nixos/release-combined.nix
+++ b/nixos/release-combined.nix
@@ -5,7 +5,7 @@
 { nixpkgs ? { outPath = (import ../lib).cleanSource ./..; revCount = 56789; shortRev = "gfedcba"; }
 , stableBranch ? false
 , supportedSystems ? [ "x86_64-linux" ]
-, limitedSupportedSystems ? [ "i686-linux" ]
+, limitedSupportedSystems ? [ "i686-linux" "aarch64-linux" ]
 }:
 
 let
@@ -46,16 +46,20 @@ in rec {
     };
     constituents =
       let
-        all = x: map (system: x.${system}) supportedSystems;
+        # Except for the given systems, return the system-specific constituent
+        except = systems: x: map (system: x.${system}) (pkgs.lib.subtractLists systems supportedSystems);
+        all = x: except [] x;
       in [
         nixos.channel
         (all nixos.dummy)
         (all nixos.manual)
 
-        nixos.iso_minimal.x86_64-linux or []
-        nixos.iso_minimal.i686-linux or []
         nixos.iso_graphical.x86_64-linux or []
+        nixos.iso_minimal.aarch64-linux or []
+        nixos.iso_minimal.i686-linux or []
+        nixos.iso_minimal.x86_64-linux or []
         nixos.ova.x86_64-linux or []
+        nixos.sd_image.aarch64-linux or []
 
         #(all nixos.tests.containers)
         (all nixos.tests.containers-imperative)
@@ -63,24 +67,24 @@ in rec {
         nixos.tests.chromium.x86_64-linux or []
         (all nixos.tests.firefox)
         (all nixos.tests.firewall)
-        (all nixos.tests.gnome3)
+        (except ["aarch64-linux"] nixos.tests.gnome3)
         nixos.tests.installer.zfsroot.x86_64-linux or [] # ZFS is 64bit only
-        (all nixos.tests.installer.lvm)
-        (all nixos.tests.installer.luksroot)
-        (all nixos.tests.installer.separateBoot)
-        (all nixos.tests.installer.separateBootFat)
-        (all nixos.tests.installer.simple)
-        (all nixos.tests.installer.simpleLabels)
-        (all nixos.tests.installer.simpleProvided)
-        (all nixos.tests.installer.simpleUefiSystemdBoot)
-        (all nixos.tests.installer.swraid)
-        (all nixos.tests.installer.btrfsSimple)
-        (all nixos.tests.installer.btrfsSubvols)
-        (all nixos.tests.installer.btrfsSubvolDefault)
-        (all nixos.tests.boot.biosCdrom)
-        #(all nixos.tests.boot.biosUsb) # disabled due to issue #15690
-        (all nixos.tests.boot.uefiCdrom)
-        (all nixos.tests.boot.uefiUsb)
+        (except ["aarch64-linux"] nixos.tests.installer.lvm)
+        (except ["aarch64-linux"] nixos.tests.installer.luksroot)
+        (except ["aarch64-linux"] nixos.tests.installer.separateBoot)
+        (except ["aarch64-linux"] nixos.tests.installer.separateBootFat)
+        (except ["aarch64-linux"] nixos.tests.installer.simple)
+        (except ["aarch64-linux"] nixos.tests.installer.simpleLabels)
+        (except ["aarch64-linux"] nixos.tests.installer.simpleProvided)
+        (except ["aarch64-linux"] nixos.tests.installer.simpleUefiSystemdBoot)
+        (except ["aarch64-linux"] nixos.tests.installer.swraid)
+        (except ["aarch64-linux"] nixos.tests.installer.btrfsSimple)
+        (except ["aarch64-linux"] nixos.tests.installer.btrfsSubvols)
+        (except ["aarch64-linux"] nixos.tests.installer.btrfsSubvolDefault)
+        (except ["aarch64-linux"] nixos.tests.boot.biosCdrom)
+        #(except ["aarch64-linux"] nixos.tests.boot.biosUsb) # disabled due to issue #15690
+        (except ["aarch64-linux"] nixos.tests.boot.uefiCdrom)
+        (except ["aarch64-linux"] nixos.tests.boot.uefiUsb)
         (all nixos.tests.boot-stage1)
         (all nixos.tests.hibernate)
         nixos.tests.docker.x86_64-linux or []
@@ -132,7 +136,8 @@ in rec {
 
         nixpkgs.tarball
         (all allSupportedNixpkgs.emacs)
-        (all allSupportedNixpkgs.jdk)
+        # The currently available aarch64 JDK is unfree
+        (except ["aarch64-linux"] allSupportedNixpkgs.jdk)
       ];
   });
 
diff --git a/nixos/release.nix b/nixos/release.nix
index 0fd8d694641f4..df2c52ccd0b66 100644
--- a/nixos/release.nix
+++ b/nixos/release.nix
@@ -1,10 +1,12 @@
-{ nixpkgs ? { outPath = (import ../lib).cleanSource ./..; revCount = 130979; shortRev = "gfedcba"; }
+with import ../lib;
+
+{ nixpkgs ? { outPath = cleanSource ./..; revCount = 130979; shortRev = "gfedcba"; }
 , stableBranch ? false
 , supportedSystems ? [ "x86_64-linux" "aarch64-linux" ]
+, configuration ? {}
 }:
 
 with import ../pkgs/top-level/release-lib.nix { inherit supportedSystems; };
-with import ../lib;
 
 let
 
@@ -12,28 +14,19 @@ let
   versionSuffix =
     (if stableBranch then "." else "pre") + "${toString nixpkgs.revCount}.${nixpkgs.shortRev}";
 
-  importTest = fn: args: system: import fn ({
-    inherit system;
-  } // args);
-
-  # Note: only supportedSystems are considered.
-  callTestOnMatchingSystems = systems: fn: args:
-    forMatchingSystems
-      (intersectLists supportedSystems systems)
-      (system: hydraJob (importTest fn args system));
-  callTest = callTestOnMatchingSystems supportedSystems;
-
-  callSubTests = callSubTestsOnMatchingSystems supportedSystems;
-  callSubTestsOnMatchingSystems = systems: fn: args: let
-    discover = attrs: let
-      subTests = filterAttrs (const (hasAttr "test")) attrs;
-    in mapAttrs (const (t: hydraJob t.test)) subTests;
-
-    discoverForSystem = system: mapAttrs (_: test: {
-      ${system} = test;
-    }) (discover (importTest fn args system));
-
-  in foldAttrs mergeAttrs {} (map discoverForSystem (intersectLists systems supportedSystems));
+  # Run the tests for each platform.  You can run a test by doing
+  # e.g. ‘nix-build -A tests.login.x86_64-linux’, or equivalently,
+  # ‘nix-build tests/login.nix -A result’.
+  allTestsForSystem = system:
+    import ./tests/all-tests.nix {
+      inherit system;
+      pkgs = import nixpkgs { inherit system; };
+      callTest = t: {
+        ${system} = hydraJob t.test;
+      };
+    };
+  allTests =
+    foldAttrs recursiveUpdate {} (map allTestsForSystem supportedSystems);
 
   pkgs = import nixpkgs { system = "x86_64-linux"; };
 
@@ -43,6 +36,7 @@ let
       system.nixos.revision = nixpkgs.rev or nixpkgs.shortRev;
     };
 
+  makeModules = module: rest: [ configuration versionModule module rest ];
 
   makeIso =
     { module, type, system, ... }:
@@ -51,7 +45,9 @@ let
 
     hydraJob ((import lib/eval-config.nix {
       inherit system;
-      modules = [ module versionModule { isoImage.isoBaseName = "nixos-${type}"; } ];
+      modules = makeModules module {
+        isoImage.isoBaseName = "nixos-${type}";
+      };
     }).config.system.build.isoImage);
 
 
@@ -62,7 +58,7 @@ let
 
     hydraJob ((import lib/eval-config.nix {
       inherit system;
-      modules = [ module versionModule ];
+      modules = makeModules module {};
     }).config.system.build.sdImage);
 
 
@@ -75,7 +71,7 @@ let
 
       config = (import lib/eval-config.nix {
         inherit system;
-        modules = [ module versionModule ];
+        modules = makeModules module {};
       }).config;
 
       tarball = config.system.build.tarball;
@@ -95,16 +91,19 @@ let
 
   buildFromConfig = module: sel: forAllSystems (system: hydraJob (sel (import ./lib/eval-config.nix {
     inherit system;
-    modules = [ module versionModule ] ++ singleton
+    modules = makeModules module
       ({ ... }:
       { fileSystems."/".device  = mkDefault "/dev/sda1";
         boot.loader.grub.device = mkDefault "/dev/sda";
       });
   }).config));
 
-  makeNetboot = config:
+  makeNetboot = { module, system, ... }:
     let
-      configEvaled = import lib/eval-config.nix config;
+      configEvaled = import lib/eval-config.nix {
+        inherit system;
+        modules = makeModules module {};
+      };
       build = configEvaled.config.system.build;
       kernelTarget = configEvaled.pkgs.stdenv.hostPlatform.platform.kernelTarget;
     in
@@ -128,7 +127,8 @@ in rec {
 
   channel = import lib/make-channel.nix { inherit pkgs nixpkgs version versionSuffix; };
 
-  manual = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manual);
+  manualHTML = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manualHTML);
+  manual = manualHTML; # TODO(@oxij): remove eventually
   manualEpub = (buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manualEpub));
   manpages = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.manpages);
   manualGeneratedSources = buildFromConfig ({ ... }: { }) (config: config.system.build.manual.generatedSources);
@@ -139,11 +139,8 @@ in rec {
   initialRamdisk = buildFromConfig ({ ... }: { }) (config: config.system.build.initialRamdisk);
 
   netboot = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system: makeNetboot {
+    module = ./modules/installer/netboot/netboot-minimal.nix;
     inherit system;
-    modules = [
-      ./modules/installer/netboot/netboot-minimal.nix
-      versionModule
-    ];
   });
 
   iso_minimal = forAllSystems (system: makeIso {
@@ -160,7 +157,7 @@ in rec {
 
   # A variant with a more recent (but possibly less stable) kernel
   # that might support more hardware.
-  iso_minimal_new_kernel = forMatchingSystems [ "x86_64-linux" ] (system: makeIso {
+  iso_minimal_new_kernel = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system: makeIso {
     module = ./modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix;
     type = "minimal-new-kernel";
     inherit system;
@@ -175,6 +172,14 @@ in rec {
     inherit system;
   });
 
+  sd_image_new_kernel = forMatchingSystems [ "aarch64-linux" ] (system: makeSdImage {
+    module = {
+        aarch64-linux = ./modules/installer/cd-dvd/sd-image-aarch64-new-kernel.nix;
+      }.${system};
+    type = "minimal-new-kernel";
+    inherit system;
+  });
+
   # A bootable VirtualBox virtual appliance as an OVA file (i.e. packaged OVF).
   ova = forMatchingSystems [ "x86_64-linux" ] (system:
 
@@ -239,189 +244,7 @@ in rec {
     };
   */
 
-
-  # Run the tests for each platform.  You can run a test by doing
-  # e.g. ‘nix-build -A tests.login.x86_64-linux’, or equivalently,
-  # ‘nix-build tests/login.nix -A result’.
-  tests.atd = callTest tests/atd.nix {};
-  tests.acme = callTest tests/acme.nix {};
-  tests.avahi = callTest tests/avahi.nix {};
-  tests.beegfs = callTest tests/beegfs.nix {};
-  tests.bittorrent = callTest tests/bittorrent.nix {};
-  tests.bind = callTest tests/bind.nix {};
-  #tests.blivet = callTest tests/blivet.nix {};   # broken since 2017-07024
-  tests.boot = callSubTests tests/boot.nix {};
-  tests.boot-stage1 = callTest tests/boot-stage1.nix {};
-  tests.borgbackup = callTest tests/borgbackup.nix {};
-  tests.buildbot = callTest tests/buildbot.nix {};
-  tests.cadvisor = callTestOnMatchingSystems ["x86_64-linux"] tests/cadvisor.nix {};
-  tests.ceph = callTestOnMatchingSystems ["x86_64-linux"] tests/ceph.nix {};
-  tests.certmgr = callSubTests tests/certmgr.nix {};
-  tests.cfssl = callTestOnMatchingSystems ["x86_64-linux"] tests/cfssl.nix {};
-  tests.chromium = (callSubTestsOnMatchingSystems ["x86_64-linux"] tests/chromium.nix {}).stable or {};
-  tests.cjdns = callTest tests/cjdns.nix {};
-  tests.cloud-init = callTest tests/cloud-init.nix {};
-  tests.containers-ipv4 = callTest tests/containers-ipv4.nix {};
-  tests.containers-ipv6 = callTest tests/containers-ipv6.nix {};
-  tests.containers-bridge = callTest tests/containers-bridge.nix {};
-  tests.containers-imperative = callTest tests/containers-imperative.nix {};
-  tests.containers-extra_veth = callTest tests/containers-extra_veth.nix {};
-  tests.containers-physical_interfaces = callTest tests/containers-physical_interfaces.nix {};
-  tests.containers-restart_networking = callTest tests/containers-restart_networking.nix {};
-  tests.containers-tmpfs = callTest tests/containers-tmpfs.nix {};
-  tests.containers-hosts = callTest tests/containers-hosts.nix {};
-  tests.containers-macvlans = callTest tests/containers-macvlans.nix {};
-  tests.couchdb = callTest tests/couchdb.nix {};
-  tests.deluge = callTest tests/deluge.nix {};
-  tests.dhparams = callTest tests/dhparams.nix {};
-  tests.docker = callTestOnMatchingSystems ["x86_64-linux"] tests/docker.nix {};
-  tests.docker-tools = callTestOnMatchingSystems ["x86_64-linux"] tests/docker-tools.nix {};
-  tests.docker-tools-overlay = callTestOnMatchingSystems ["x86_64-linux"] tests/docker-tools-overlay.nix {};
-  tests.docker-edge = callTestOnMatchingSystems ["x86_64-linux"] tests/docker-edge.nix {};
-  tests.docker-registry = callTest tests/docker-registry.nix {};
-  tests.dovecot = callTest tests/dovecot.nix {};
-  tests.dnscrypt-proxy = callTestOnMatchingSystems ["x86_64-linux"] tests/dnscrypt-proxy.nix {};
-  tests.ecryptfs = callTest tests/ecryptfs.nix {};
-  tests.etcd = callTestOnMatchingSystems ["x86_64-linux"] tests/etcd.nix {};
-  tests.ec2-nixops = (callSubTestsOnMatchingSystems ["x86_64-linux"] tests/ec2.nix {}).boot-ec2-nixops or {};
-  tests.ec2-config = (callSubTestsOnMatchingSystems ["x86_64-linux"] tests/ec2.nix {}).boot-ec2-config or {};
-  tests.elk = callSubTestsOnMatchingSystems ["x86_64-linux"] tests/elk.nix {};
-  tests.env = callTest tests/env.nix {};
-  tests.ferm = callTest tests/ferm.nix {};
-  tests.firefox = callTest tests/firefox.nix {};
-  tests.flatpak = callTest tests/flatpak.nix {};
-  tests.firewall = callTest tests/firewall.nix {};
-  tests.fsck = callTest tests/fsck.nix {};
-  tests.fwupd = callTest tests/fwupd.nix {};
-  tests.gdk-pixbuf = callTest tests/gdk-pixbuf.nix {};
-  #tests.gitlab = callTest tests/gitlab.nix {};
-  tests.gitolite = callTest tests/gitolite.nix {};
-  tests.gjs = callTest tests/gjs.nix {};
-  tests.gocd-agent = callTest tests/gocd-agent.nix {};
-  tests.gocd-server = callTest tests/gocd-server.nix {};
-  tests.gnome3 = callTest tests/gnome3.nix {};
-  tests.gnome3-gdm = callTest tests/gnome3-gdm.nix {};
-  tests.grafana = callTest tests/grafana.nix {};
-  tests.graphite = callTest tests/graphite.nix {};
-  tests.hadoop.hdfs = callTestOnMatchingSystems [ "x86_64-linux" ] tests/hadoop/hdfs.nix {};
-  tests.hadoop.yarn = callTestOnMatchingSystems [ "x86_64-linux" ] tests/hadoop/yarn.nix {};
-  tests.hardened = callTest tests/hardened.nix { };
-  tests.haproxy = callTest tests/haproxy.nix {};
-  tests.hibernate = callTest tests/hibernate.nix {};
-  tests.hitch = callTest tests/hitch {};
-  tests.home-assistant = callTest tests/home-assistant.nix { };
-  tests.hound = callTest tests/hound.nix {};
-  tests.hocker-fetchdocker = callTest tests/hocker-fetchdocker {};
-  tests.hydra = callTest tests/hydra {};
-  tests.i3wm = callTest tests/i3wm.nix {};
-  tests.iftop = callTest tests/iftop.nix {};
-  tests.initrd-network-ssh = callTest tests/initrd-network-ssh {};
-  tests.installer = callSubTests tests/installer.nix {};
-  tests.influxdb = callTest tests/influxdb.nix {};
-  tests.ipv6 = callTest tests/ipv6.nix {};
-  tests.jenkins = callTest tests/jenkins.nix {};
-  tests.ostree = callTest tests/ostree.nix {};
-  tests.osquery = callTest tests/osquery.nix {};
-  tests.plasma5 = callTest tests/plasma5.nix {};
-  tests.plotinus = callTest tests/plotinus.nix {};
-  tests.keymap = callSubTests tests/keymap.nix {};
-  tests.initrdNetwork = callTest tests/initrd-network.nix {};
-  tests.kafka = callSubTests tests/kafka.nix {};
-  tests.kernel-latest = callTest tests/kernel-latest.nix {};
-  tests.kernel-lts = callTest tests/kernel-lts.nix {};
-  tests.kubernetes.dns = callSubTestsOnMatchingSystems ["x86_64-linux"] tests/kubernetes/dns.nix {};
-  ## kubernetes.e2e should eventually replace kubernetes.rbac when it works
-  #tests.kubernetes.e2e = callSubTestsOnMatchingSystems ["x86_64-linux"] tests/kubernetes/e2e.nix {};
-  tests.kubernetes.rbac = callSubTestsOnMatchingSystems ["x86_64-linux"] tests/kubernetes/rbac.nix {};
-  tests.latestKernel.login = callTest tests/login.nix { latestKernel = true; };
-  tests.ldap = callTest tests/ldap.nix {};
-  #tests.lightdm = callTest tests/lightdm.nix {};
-  tests.login = callTest tests/login.nix {};
-  #tests.logstash = callTest tests/logstash.nix {};
-  tests.mathics = callTest tests/mathics.nix {};
-  tests.matrix-synapse = callTest tests/matrix-synapse.nix {};
-  tests.memcached = callTest tests/memcached.nix {};
-  tests.mesos = callTest tests/mesos.nix {};
-  tests.misc = callTest tests/misc.nix {};
-  tests.mongodb = callTest tests/mongodb.nix {};
-  tests.mpd = callTest tests/mpd.nix {};
-  tests.mumble = callTest tests/mumble.nix {};
-  tests.munin = callTest tests/munin.nix {};
-  tests.mutableUsers = callTest tests/mutable-users.nix {};
-  tests.mysql = callTest tests/mysql.nix {};
-  tests.mysqlBackup = callTest tests/mysql-backup.nix {};
-  tests.mysqlReplication = callTest tests/mysql-replication.nix {};
-  tests.nat.firewall = callTest tests/nat.nix { withFirewall = true; };
-  tests.nat.firewall-conntrack = callTest tests/nat.nix { withFirewall = true; withConntrackHelpers = true; };
-  tests.nat.standalone = callTest tests/nat.nix { withFirewall = false; };
-  tests.netdata = callTest tests/netdata.nix { };
-  tests.networking.networkd = callSubTests tests/networking.nix { networkd = true; };
-  tests.networking.scripted = callSubTests tests/networking.nix { networkd = false; };
-  # TODO: put in networking.nix after the test becomes more complete
-  tests.networkingProxy = callTest tests/networking-proxy.nix {};
-  tests.nexus = callTest tests/nexus.nix { };
-  tests.nfs3 = callTest tests/nfs.nix { version = 3; };
-  tests.nfs4 = callTest tests/nfs.nix { version = 4; };
-  tests.nginx = callTest tests/nginx.nix { };
-  tests.nghttpx = callTest tests/nghttpx.nix { };
-  tests.nix-ssh-serve = callTest tests/nix-ssh-serve.nix { };
-  tests.novacomd = callTestOnMatchingSystems ["x86_64-linux"] tests/novacomd.nix { };
-  tests.leaps = callTest tests/leaps.nix { };
-  tests.nsd = callTest tests/nsd.nix {};
-  tests.openssh = callTest tests/openssh.nix {};
-  tests.openldap = callTest tests/openldap.nix {};
-  tests.opensmtpd = callTest tests/opensmtpd.nix {};
-  tests.owncloud = callTest tests/owncloud.nix {};
-  tests.pam-oath-login = callTest tests/pam-oath-login.nix {};
-  tests.peerflix = callTest tests/peerflix.nix {};
-  tests.php-pcre = callTest tests/php-pcre.nix {};
-  tests.postgresql = callSubTests tests/postgresql.nix {};
-  tests.pgmanage = callTest tests/pgmanage.nix {};
-  tests.postgis = callTest tests/postgis.nix {};
-  tests.powerdns = callTest tests/powerdns.nix {};
-  #tests.pgjwt = callTest tests/pgjwt.nix {};
-  tests.predictable-interface-names = callSubTests tests/predictable-interface-names.nix {};
-  tests.printing = callTest tests/printing.nix {};
-  tests.prometheus = callTest tests/prometheus.nix {};
-  tests.prosody = callTest tests/prosody.nix {};
-  tests.proxy = callTest tests/proxy.nix {};
-  tests.quagga = callTest tests/quagga.nix {};
-  tests.quake3 = callTest tests/quake3.nix {};
-  tests.rabbitmq = callTest tests/rabbitmq.nix {};
-  tests.radicale = callTest tests/radicale.nix {};
-  tests.rspamd = callSubTests tests/rspamd.nix {};
-  tests.runInMachine = callTest tests/run-in-machine.nix {};
-  tests.rxe = callTest tests/rxe.nix {};
-  tests.samba = callTest tests/samba.nix {};
-  tests.sddm = callSubTests tests/sddm.nix {};
-  tests.simple = callTest tests/simple.nix {};
-  tests.slim = callTest tests/slim.nix {};
-  tests.slurm = callTest tests/slurm.nix {};
-  tests.smokeping = callTest tests/smokeping.nix {};
-  tests.snapper = callTest tests/snapper.nix {};
-  #tests.statsd = callTest tests/statsd.nix {}; # statsd is broken: #45946
-  tests.strongswan-swanctl = callTest tests/strongswan-swanctl.nix {};
-  tests.sudo = callTest tests/sudo.nix {};
-  tests.systemd = callTest tests/systemd.nix {};
-  tests.switchTest = callTest tests/switch-test.nix {};
-  tests.taskserver = callTest tests/taskserver.nix {};
-  tests.tomcat = callTest tests/tomcat.nix {};
-  tests.tor = callTest tests/tor.nix {};
-  tests.transmission = callTest tests/transmission.nix {};
-  tests.udisks2 = callTest tests/udisks2.nix {};
-  tests.vault = callTest tests/vault.nix {};
-  tests.virtualbox = callSubTestsOnMatchingSystems ["x86_64-linux"] tests/virtualbox.nix {};
-  tests.wordpress = callTest tests/wordpress.nix {};
-  tests.xautolock = callTest tests/xautolock.nix {};
-  tests.xdg-desktop-portal = callTest tests/xdg-desktop-portal.nix {};
-  tests.xfce = callTest tests/xfce.nix {};
-  tests.xmonad = callTest tests/xmonad.nix {};
-  tests.xrdp = callTest tests/xrdp.nix {};
-  tests.xss-lock = callTest tests/xss-lock.nix {};
-  tests.yabar = callTest tests/yabar.nix {};
-  tests.zookeeper = callTest tests/zookeeper.nix {};
-  tests.morty = callTest tests/morty.nix { };
-  tests.bcachefs = callTest tests/bcachefs.nix { };
+  tests = allTests;
 
   /* Build a bunch of typical closures so that Hydra can keep track of
      the evolution of closure sizes. */
@@ -458,7 +281,7 @@ in rec {
       { services.httpd.enable = true;
         services.httpd.adminAddr = "foo@example.org";
         services.postgresql.enable = true;
-        services.postgresql.package = pkgs.postgresql93;
+        services.postgresql.package = pkgs.postgresql;
         environment.systemPackages = [ pkgs.php ];
       });
   };
diff --git a/nixos/tests/acme.nix b/nixos/tests/acme.nix
index c7fd4910e0722..4669a092433e7 100644
--- a/nixos/tests/acme.nix
+++ b/nixos/tests/acme.nix
@@ -1,32 +1,5 @@
 let
-  commonConfig = { lib, nodes, ... }: {
-    networking.nameservers = [
-      nodes.letsencrypt.config.networking.primaryIPAddress
-    ];
-
-    nixpkgs.overlays = lib.singleton (self: super: {
-      cacert = super.cacert.overrideDerivation (drv: {
-        installPhase = (drv.installPhase or "") + ''
-          cat "${nodes.letsencrypt.config.test-support.letsencrypt.caCert}" \
-            >> "$out/etc/ssl/certs/ca-bundle.crt"
-        '';
-      });
-
-      # Override certifi so that it accepts fake certificate for Let's Encrypt
-      # Need to override the attribute used by simp_le, which is python3Packages
-      python3Packages = (super.python3.override {
-        packageOverrides = lib.const (pysuper: {
-          certifi = pysuper.certifi.overridePythonAttrs (attrs: {
-            postPatch = (attrs.postPatch or "") + ''
-              cat "${self.cacert}/etc/ssl/certs/ca-bundle.crt" \
-                > certifi/cacert.pem
-            '';
-          });
-        });
-      }).pkgs;
-    });
-  };
-
+  commonConfig = ./common/letsencrypt/common.nix;
 in import ./make-test.nix {
   name = "acme";
 
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
new file mode 100644
index 0000000000000..229f2c3abf7b6
--- /dev/null
+++ b/nixos/tests/all-tests.nix
@@ -0,0 +1,234 @@
+{ system, pkgs, callTest }:
+# The return value of this function will be an attrset with arbitrary depth and
+# the `anything` returned by callTest at its test leafs.
+# The tests not supported by `system` will be replaced with `{}`, so that
+# `passthru.tests` can contain links to those without breaking on architectures
+# where said tests are unsupported.
+# Example callTest that just extracts the derivation from the test:
+#   callTest = t: t.test;
+
+with pkgs.lib;
+
+let
+  discoverTests = val:
+    if !isAttrs val then val
+    else if hasAttr "test" val then callTest val
+    else mapAttrs (n: s: discoverTests s) val;
+  handleTest = path: args:
+    discoverTests (import path ({ inherit system pkgs; } // args));
+  handleTestOn = systems: path: args:
+    if elem system systems then handleTest path args
+    else {};
+in
+{
+  acme = handleTestOn ["x86_64-linux"] ./acme.nix {};
+  atd = handleTest ./atd.nix {};
+  avahi = handleTest ./avahi.nix {};
+  bcachefs = handleTestOn ["x86_64-linux"] ./bcachefs.nix {}; # linux-4.18.2018.10.12 is unsupported on aarch64
+  beegfs = handleTestOn ["x86_64-linux"] ./beegfs.nix {}; # beegfs is unsupported on aarch64
+  bind = handleTest ./bind.nix {};
+  bittorrent = handleTest ./bittorrent.nix {};
+  #blivet = handleTest ./blivet.nix {};   # broken since 2017-07024
+  boot = handleTestOn ["x86_64-linux"] ./boot.nix {}; # syslinux is unsupported on aarch64
+  boot-stage1 = handleTest ./boot-stage1.nix {};
+  borgbackup = handleTest ./borgbackup.nix {};
+  buildbot = handleTest ./buildbot.nix {};
+  cadvisor = handleTestOn ["x86_64-linux"] ./cadvisor.nix {};
+  ceph = handleTestOn ["x86_64-linux"] ./ceph.nix {};
+  certmgr = handleTest ./certmgr.nix {};
+  cfssl = handleTestOn ["x86_64-linux"] ./cfssl.nix {};
+  chromium = (handleTestOn ["x86_64-linux"] ./chromium.nix {}).stable or {};
+  cjdns = handleTest ./cjdns.nix {};
+  clickhouse = handleTest ./clickhouse.nix {};
+  cloud-init = handleTest ./cloud-init.nix {};
+  codimd = handleTest ./codimd.nix {};
+  containers-bridge = handleTest ./containers-bridge.nix {};
+  containers-extra_veth = handleTest ./containers-extra_veth.nix {};
+  containers-hosts = handleTest ./containers-hosts.nix {};
+  containers-imperative = handleTest ./containers-imperative.nix {};
+  containers-ipv4 = handleTest ./containers-ipv4.nix {};
+  containers-ipv6 = handleTest ./containers-ipv6.nix {};
+  containers-macvlans = handleTest ./containers-macvlans.nix {};
+  containers-physical_interfaces = handleTest ./containers-physical_interfaces.nix {};
+  containers-restart_networking = handleTest ./containers-restart_networking.nix {};
+  containers-tmpfs = handleTest ./containers-tmpfs.nix {};
+  #couchdb = handleTest ./couchdb.nix {}; # spidermonkey-1.8.5 is marked as broken
+  deluge = handleTest ./deluge.nix {};
+  dhparams = handleTest ./dhparams.nix {};
+  dnscrypt-proxy = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy.nix {};
+  docker = handleTestOn ["x86_64-linux"] ./docker.nix {};
+  docker-edge = handleTestOn ["x86_64-linux"] ./docker-edge.nix {};
+  docker-preloader = handleTestOn ["x86_64-linux"] ./docker-preloader.nix {};
+  docker-registry = handleTest ./docker-registry.nix {};
+  docker-tools = handleTestOn ["x86_64-linux"] ./docker-tools.nix {};
+  docker-tools-overlay = handleTestOn ["x86_64-linux"] ./docker-tools-overlay.nix {};
+  dovecot = handleTest ./dovecot.nix {};
+  # ec2-config doesn't work in a sandbox as the simulated ec2 instance needs network access
+  #ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {};
+  ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {};
+  ecryptfs = handleTest ./ecryptfs.nix {};
+  elk = handleTestOn ["x86_64-linux"] ./elk.nix {};
+  env = handleTest ./env.nix {};
+  etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {};
+  ferm = handleTest ./ferm.nix {};
+  firefox = handleTest ./firefox.nix {};
+  firewall = handleTest ./firewall.nix {};
+  flannel = handleTestOn ["x86_64-linux"] ./flannel.nix {};
+  flatpak = handleTest ./flatpak.nix {};
+  fsck = handleTest ./fsck.nix {};
+  fwupd = handleTestOn ["x86_64-linux"] ./fwupd.nix {}; # libsmbios is unsupported on aarch64
+  gdk-pixbuf = handleTest ./gdk-pixbuf.nix {};
+  gitea = handleTest ./gitea.nix {};
+  gitlab = handleTest ./gitlab.nix {};
+  gitolite = handleTest ./gitolite.nix {};
+  gjs = handleTest ./gjs.nix {};
+  google-oslogin = handleTest ./google-oslogin {};
+  gnome3 = handleTestOn ["x86_64-linux"] ./gnome3.nix {}; # libsmbios is unsupported on aarch64
+  gnome3-gdm = handleTestOn ["x86_64-linux"] ./gnome3-gdm.nix {}; # libsmbios is unsupported on aarch64
+  gocd-agent = handleTest ./gocd-agent.nix {};
+  gocd-server = handleTest ./gocd-server.nix {};
+  grafana = handleTest ./grafana.nix {};
+  graphite = handleTest ./graphite.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 {};
+  haproxy = handleTest ./haproxy.nix {};
+  hardened = handleTest ./hardened.nix {};
+  hibernate = handleTest ./hibernate.nix {};
+  hitch = handleTest ./hitch {};
+  hocker-fetchdocker = handleTest ./hocker-fetchdocker {};
+  home-assistant = handleTest ./home-assistant.nix {};
+  hound = handleTest ./hound.nix {};
+  hydra = handleTest ./hydra {};
+  i3wm = handleTest ./i3wm.nix {};
+  iftop = handleTest ./iftop.nix {};
+  incron = handleTest ./incron.nix {};
+  influxdb = handleTest ./influxdb.nix {};
+  initrd-network-ssh = handleTest ./initrd-network-ssh {};
+  initrdNetwork = handleTest ./initrd-network.nix {};
+  installer = handleTest ./installer.nix {};
+  ipv6 = handleTest ./ipv6.nix {};
+  jackett = handleTest ./jackett.nix {};
+  jenkins = handleTest ./jenkins.nix {};
+  #kafka = handleTest ./kafka.nix {}; # broken since openjdk: 8u181 -> 8u192
+  kerberos = handleTest ./kerberos/default.nix {};
+  kernel-latest = handleTest ./kernel-latest.nix {};
+  kernel-lts = handleTest ./kernel-lts.nix {};
+  keymap = handleTest ./keymap.nix {};
+  kubernetes.dns = handleTestOn ["x86_64-linux"] ./kubernetes/dns.nix {};
+  # kubernetes.e2e should eventually replace kubernetes.rbac when it works
+  #kubernetes.e2e = handleTestOn ["x86_64-linux"] ./kubernetes/e2e.nix {};
+  kubernetes.rbac = handleTestOn ["x86_64-linux"] ./kubernetes/rbac.nix {};
+  latestKernel.login = handleTest ./login.nix { latestKernel = true; };
+  ldap = handleTest ./ldap.nix {};
+  leaps = handleTest ./leaps.nix {};
+  lidarr = handleTest ./lidarr.nix {};
+  #lightdm = handleTest ./lightdm.nix {};
+  login = handleTest ./login.nix {};
+  #logstash = handleTest ./logstash.nix {};
+  mathics = handleTest ./mathics.nix {};
+  matrix-synapse = handleTest ./matrix-synapse.nix {};
+  memcached = handleTest ./memcached.nix {};
+  mesos = handleTest ./mesos.nix {};
+  minio = handleTest ./minio.nix {};
+  misc = handleTest ./misc.nix {};
+  mongodb = handleTest ./mongodb.nix {};
+  morty = handleTest ./morty.nix {};
+  mpd = handleTest ./mpd.nix {};
+  mumble = handleTest ./mumble.nix {};
+  munin = handleTest ./munin.nix {};
+  mutableUsers = handleTest ./mutable-users.nix {};
+  mysql = handleTest ./mysql.nix {};
+  mysqlBackup = handleTest ./mysql-backup.nix {};
+  mysqlReplication = handleTest ./mysql-replication.nix {};
+  nat.firewall = handleTest ./nat.nix { withFirewall = true; };
+  nat.firewall-conntrack = handleTest ./nat.nix { withFirewall = true; withConntrackHelpers = true; };
+  nat.standalone = handleTest ./nat.nix { withFirewall = false; };
+  ndppd = handleTest ./ndppd.nix {};
+  neo4j = handleTest ./neo4j.nix {};
+  netdata = handleTest ./netdata.nix {};
+  networking.networkd = handleTest ./networking.nix { networkd = true; };
+  networking.scripted = handleTest ./networking.nix { networkd = false; };
+  # TODO: put in networking.nix after the test becomes more complete
+  networkingProxy = handleTest ./networking-proxy.nix {};
+  nextcloud = handleTest ./nextcloud {};
+  nexus = handleTest ./nexus.nix {};
+  nfs3 = handleTest ./nfs.nix { version = 3; };
+  nfs4 = handleTest ./nfs.nix { version = 4; };
+  nghttpx = handleTest ./nghttpx.nix {};
+  nginx = handleTest ./nginx.nix {};
+  nginx-sso = handleTest ./nginx-sso.nix {};
+  nix-ssh-serve = handleTest ./nix-ssh-serve.nix {};
+  novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {};
+  nsd = handleTest ./nsd.nix {};
+  openldap = handleTest ./openldap.nix {};
+  opensmtpd = handleTest ./opensmtpd.nix {};
+  openssh = handleTest ./openssh.nix {};
+  # openstack-image-userdata doesn't work in a sandbox as the simulated openstack instance needs network access
+  #openstack-image-userdata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).userdata or {};
+  openstack-image-metadata = (handleTestOn ["x86_64-linux"] ./openstack-image.nix {}).metadata or {};
+  osquery = handleTest ./osquery.nix {};
+  osrm-backend = handleTest ./osrm-backend.nix {};
+  ostree = handleTest ./ostree.nix {};
+  pam-oath-login = handleTest ./pam-oath-login.nix {};
+  pam-u2f = handleTest ./pam-u2f.nix {};
+  peerflix = handleTest ./peerflix.nix {};
+  pgjwt = handleTest ./pgjwt.nix {};
+  pgmanage = handleTest ./pgmanage.nix {};
+  php-pcre = handleTest ./php-pcre.nix {};
+  plasma5 = handleTest ./plasma5.nix {};
+  plotinus = handleTest ./plotinus.nix {};
+  postgis = handleTest ./postgis.nix {};
+  postgresql = handleTest ./postgresql.nix {};
+  powerdns = handleTest ./powerdns.nix {};
+  predictable-interface-names = handleTest ./predictable-interface-names.nix {};
+  printing = handleTest ./printing.nix {};
+  prometheus = handleTest ./prometheus.nix {};
+  prometheus-exporters = handleTest ./prometheus-exporters.nix {};
+  prosody = handleTest ./prosody.nix {};
+  proxy = handleTest ./proxy.nix {};
+  quagga = handleTest ./quagga.nix {};
+  quake3 = handleTest ./quake3.nix {};
+  rabbitmq = handleTest ./rabbitmq.nix {};
+  radarr = handleTest ./radarr.nix {};
+  radicale = handleTest ./radicale.nix {};
+  redmine = handleTest ./redmine.nix {};
+  roundcube = handleTest ./roundcube.nix {};
+  rspamd = handleTest ./rspamd.nix {};
+  rss2email = handleTest ./rss2email.nix {};
+  rsyslogd = handleTest ./rsyslogd.nix {};
+  runInMachine = handleTest ./run-in-machine.nix {};
+  rxe = handleTest ./rxe.nix {};
+  samba = handleTest ./samba.nix {};
+  sddm = handleTest ./sddm.nix {};
+  simple = handleTest ./simple.nix {};
+  slim = handleTest ./slim.nix {};
+  slurm = handleTest ./slurm.nix {};
+  smokeping = handleTest ./smokeping.nix {};
+  snapper = handleTest ./snapper.nix {};
+  solr = handleTest ./solr.nix {};
+  sonarr = handleTest ./sonarr.nix {};
+  strongswan-swanctl = handleTest ./strongswan-swanctl.nix {};
+  sudo = handleTest ./sudo.nix {};
+  switchTest = handleTest ./switch-test.nix {};
+  syncthing-relay = handleTest ./syncthing-relay.nix {};
+  systemd = handleTest ./systemd.nix {};
+  taskserver = handleTest ./taskserver.nix {};
+  telegraf = handleTest ./telegraf.nix {};
+  tomcat = handleTest ./tomcat.nix {};
+  tor = handleTest ./tor.nix {};
+  transmission = handleTest ./transmission.nix {};
+  udisks2 = handleTest ./udisks2.nix {};
+  upnp = handleTest ./upnp.nix {};
+  vault = handleTest ./vault.nix {};
+  virtualbox = handleTestOn ["x86_64-linux"] ./virtualbox.nix {};
+  wordpress = handleTest ./wordpress.nix {};
+  xautolock = handleTest ./xautolock.nix {};
+  xdg-desktop-portal = handleTest ./xdg-desktop-portal.nix {};
+  xfce = handleTest ./xfce.nix {};
+  xmonad = handleTest ./xmonad.nix {};
+  xrdp = handleTest ./xrdp.nix {};
+  xss-lock = handleTest ./xss-lock.nix {};
+  yabar = handleTest ./yabar.nix {};
+  zookeeper = handleTest ./zookeeper.nix {};
+}
diff --git a/nixos/tests/atd.nix b/nixos/tests/atd.nix
index 9f367d4c1d2a6..25db72799241f 100644
--- a/nixos/tests/atd.nix
+++ b/nixos/tests/atd.nix
@@ -16,6 +16,7 @@ import ./make-test.nix ({ pkgs, ... }:
   testScript = ''
     startAll;
 
+    $machine->waitForUnit('atd.service'); # wait for atd to start
     $machine->fail("test -f ~root/at-1");
     $machine->fail("test -f ~alice/at-1");
 
diff --git a/nixos/tests/bees.nix b/nixos/tests/bees.nix
new file mode 100644
index 0000000000000..6f68c2f834f19
--- /dev/null
+++ b/nixos/tests/bees.nix
@@ -0,0 +1,55 @@
+import ./make-test.nix ({ lib, ... }:
+{
+  name = "bees";
+
+  machine = { config, pkgs, ... }: {
+    boot.initrd.postDeviceCommands = ''
+      ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux1 /dev/vdb
+      ${pkgs.btrfs-progs}/bin/mkfs.btrfs -f -L aux2 /dev/vdc
+    '';
+    virtualisation.emptyDiskImages = [ 4096 4096 ];
+    fileSystems = lib.mkVMOverride {
+      "/aux1" = { # filesystem configured to be deduplicated
+        device = "/dev/disk/by-label/aux1";
+        fsType = "btrfs";
+      };
+      "/aux2" = { # filesystem not configured to be deduplicated
+        device = "/dev/disk/by-label/aux2";
+        fsType = "btrfs";
+      };
+    };
+    services.beesd.filesystems = {
+      aux1 = {
+        spec = "LABEL=aux1";
+        hashTableSizeMB = 16;
+        verbosity = "debug";
+      };
+    };
+  };
+
+  testScript =
+  let
+    withRetry = content: maxTests: sleepTime: ''
+      max_tests=${lib.escapeShellArg maxTests}; sleep_time=${lib.escapeShellArg sleepTime}; for ((i=0; i<max_tests; i++)); do ${content} && exit 0; sleep "$sleep_time"; done; exit 1;
+    '';
+    someContentIsShared = loc: ''[[ $(btrfs fi du -s --raw ${lib.escapeShellArg loc}/dedup-me-{1,2} | awk 'BEGIN { count=0; } NR>1 && $3 == 0 { count++ } END { print count }') -eq 0 ]]'';
+  in ''
+    # shut down the instance started by systemd at boot, so we can test our test procedure
+    $machine->succeed("systemctl stop beesd\@aux1.service");
+
+    $machine->succeed("dd if=/dev/urandom of=/aux1/dedup-me-1 bs=1M count=8");
+    $machine->succeed("cp --reflink=never /aux1/dedup-me-1 /aux1/dedup-me-2");
+    $machine->succeed("cp --reflink=never /aux1/* /aux2/");
+    $machine->succeed("sync");
+    $machine->fail(q(${someContentIsShared "/aux1"}));
+    $machine->fail(q(${someContentIsShared "/aux2"}));
+    $machine->succeed("systemctl start beesd\@aux1.service");
+
+    # assert that "Set Shared" column is nonzero
+    $machine->succeed(q(${withRetry (someContentIsShared "/aux1") 20 2}));
+    $machine->fail(q(${someContentIsShared "/aux2"}));
+
+    # assert that 16MB hash table size requested was honored
+    $machine->succeed(q([[ $(stat -c %s /aux1/.beeshome/beeshash.dat) = $(( 16 * 1024 * 1024)) ]]))
+  '';
+})
diff --git a/nixos/tests/bittorrent.nix b/nixos/tests/bittorrent.nix
index 609b1ff7a83a2..27871f72b4e53 100644
--- a/nixos/tests/bittorrent.nix
+++ b/nixos/tests/bittorrent.nix
@@ -13,57 +13,95 @@ let
   # Some random file to serve.
   file = pkgs.hello.src;
 
-  miniupnpdConf = nodes: pkgs.writeText "miniupnpd.conf"
-    ''
-      ext_ifname=eth1
-      listening_ip=${(pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address}/24
-      allow 1024-65535 192.168.2.0/24 1024-65535
-    '';
-
+  internalRouterAddress = "192.168.3.1";
+  internalClient1Address = "192.168.3.2";
+  externalRouterAddress = "80.100.100.1";
+  externalClient2Address = "80.100.100.2";
+  externalTrackerAddress = "80.100.100.3";
 in
 
 {
   name = "bittorrent";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ domenkozar eelco chaoflow rob wkennington ];
+    maintainers = [ domenkozar eelco chaoflow rob bobvanderlinden ];
   };
 
   nodes =
     { tracker =
         { pkgs, ... }:
-        { environment.systemPackages = [ pkgs.transmission pkgs.opentracker ];
+        { environment.systemPackages = [ pkgs.transmission ];
+
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalTrackerAddress; prefixLength = 24; }
+          ];
 
           # We need Apache on the tracker to serve the torrents.
           services.httpd.enable = true;
           services.httpd.adminAddr = "foo@example.org";
           services.httpd.documentRoot = "/tmp";
 
-          networking.firewall.enable = false; # FIXME: figure out what ports we actually need
+          networking.firewall.enable = false;
+
+          services.opentracker.enable = true;
+
+          services.transmission.enable = true;
+          services.transmission.settings.dht-enabled = false;
+          services.transmission.settings.port-forwaring-enabled = false;
         };
 
       router =
-        { pkgs, ... }:
-        { environment.systemPackages = [ pkgs.miniupnpd ];
-          virtualisation.vlans = [ 1 2 ];
+        { pkgs, nodes, ... }:
+        { virtualisation.vlans = [ 1 2 ];
           networking.nat.enable = true;
           networking.nat.internalInterfaces = [ "eth2" ];
           networking.nat.externalInterface = "eth1";
-          networking.firewall.enable = false;
+          networking.firewall.enable = true;
+          networking.firewall.trustedInterfaces = [ "eth2" ];
+          networking.interfaces.eth0.ipv4.addresses = [];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalRouterAddress; prefixLength = 24; }
+          ];
+          networking.interfaces.eth2.ipv4.addresses = [
+            { address = internalRouterAddress; prefixLength = 24; }
+          ];
+          services.miniupnpd = {
+            enable = true;
+            externalInterface = "eth1";
+            internalIPs = [ "eth2" ];
+            appendConfig = ''
+              ext_ip=${externalRouterAddress}
+            '';
+          };
         };
 
       client1 =
         { pkgs, nodes, ... }:
-        { environment.systemPackages = [ pkgs.transmission ];
+        { environment.systemPackages = [ pkgs.transmission pkgs.miniupnpc ];
           virtualisation.vlans = [ 2 ];
-          networking.defaultGateway =
-            (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
+          networking.interfaces.eth0.ipv4.addresses = [];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = internalClient1Address; prefixLength = 24; }
+          ];
+          networking.defaultGateway = internalRouterAddress;
           networking.firewall.enable = false;
+          services.transmission.enable = true;
+          services.transmission.settings.dht-enabled = false;
+          services.transmission.settings.message-level = 3;
         };
 
       client2 =
         { pkgs, ... }:
         { environment.systemPackages = [ pkgs.transmission ];
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth0.ipv4.addresses = [];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalClient2Address; prefixLength = 24; }
+          ];
           networking.firewall.enable = false;
+          services.transmission.enable = true;
+          services.transmission.settings.dht-enabled = false;
+          services.transmission.settings.port-forwaring-enabled = false;
         };
     };
 
@@ -72,43 +110,38 @@ in
     ''
       startAll;
 
-      # Enable NAT on the router and start miniupnpd.
-      $router->waitForUnit("nat");
-      $router->succeed(
-          "iptables -w -t nat -N MINIUPNPD",
-          "iptables -w -t nat -A PREROUTING -i eth1 -j MINIUPNPD",
-          "echo 1 > /proc/sys/net/ipv4/ip_forward",
-          "miniupnpd -f ${miniupnpdConf nodes}"
-      );
+      # Wait for network and miniupnpd.
+      $router->waitForUnit("network-online.target");
+      $router->waitForUnit("miniupnpd");
 
       # Create the torrent.
       $tracker->succeed("mkdir /tmp/data");
       $tracker->succeed("cp ${file} /tmp/data/test.tar.bz2");
-      $tracker->succeed("transmission-create /tmp/data/test.tar.bz2 -p -t http://${(pkgs.lib.head nodes.tracker.config.networking.interfaces.eth1.ipv4.addresses).address}:6969/announce -o /tmp/test.torrent");
+      $tracker->succeed("transmission-create /tmp/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent");
       $tracker->succeed("chmod 644 /tmp/test.torrent");
 
       # Start the tracker.  !!! use a less crappy tracker
-      $tracker->waitForUnit("network.target");
-      $tracker->succeed("opentracker -p 6969 >&2 &");
+      $tracker->waitForUnit("network-online.target");
+      $tracker->waitForUnit("opentracker.service");
       $tracker->waitForOpenPort(6969);
 
       # Start the initial seeder.
-      my $pid = $tracker->succeed("transmission-cli /tmp/test.torrent -M -w /tmp/data >&2 & echo \$!");
+      $tracker->succeed("transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir /tmp/data");
 
       # Now we should be able to download from the client behind the NAT.
       $tracker->waitForUnit("httpd");
-      $client1->waitForUnit("network.target");
-      $client1->succeed("transmission-cli http://tracker/test.torrent -w /tmp >&2 &");
+      $client1->waitForUnit("network-online.target");
+      $client1->succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent --download-dir /tmp >&2 &");
       $client1->waitForFile("/tmp/test.tar.bz2");
       $client1->succeed("cmp /tmp/test.tar.bz2 ${file}");
 
       # Bring down the initial seeder.
-      $tracker->succeed("kill -9 $pid");
+      # $tracker->stopJob("transmission");
 
       # Now download from the second client.  This can only succeed if
       # the first client created a NAT hole in the router.
-      $client2->waitForUnit("network.target");
-      $client2->succeed("transmission-cli http://tracker/test.torrent -M -w /tmp >&2 &");
+      $client2->waitForUnit("network-online.target");
+      $client2->succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht --download-dir /tmp >&2 &");
       $client2->waitForFile("/tmp/test.tar.bz2");
       $client2->succeed("cmp /tmp/test.tar.bz2 ${file}");
     '';
diff --git a/nixos/tests/boot.nix b/nixos/tests/boot.nix
index 301d9d0f817f8..c9bb1e77c6d03 100644
--- a/nixos/tests/boot.nix
+++ b/nixos/tests/boot.nix
@@ -1,6 +1,9 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
diff --git a/nixos/tests/borgbackup.nix b/nixos/tests/borgbackup.nix
index 9b39abdfa8edf..fdb87dbea4383 100644
--- a/nixos/tests/borgbackup.nix
+++ b/nixos/tests/borgbackup.nix
@@ -130,6 +130,9 @@ in {
       # Make sure keepFile has the correct content
       $client->succeed("$borg extract '${localRepo}::${archiveName}'");
       $client->succeed('c=$(cat ${dataDir}/${keepFile}) && [[ "$c" == "${keepFileData}" ]]');
+      # Make sure the same is true when using `borg mount`
+      $client->succeed("mkdir -p /mnt/borg && $borg mount '${localRepo}::${archiveName}' /mnt/borg");
+      $client->succeed('c=$(cat /mnt/borg/${dataDir}/${keepFile}) && [[ "$c" == "${keepFileData}" ]]');
     };
 
     subtest "remote", sub {
diff --git a/nixos/tests/buildbot.nix b/nixos/tests/buildbot.nix
index cf408dc7fec92..210ad8e91df7b 100644
--- a/nixos/tests/buildbot.nix
+++ b/nixos/tests/buildbot.nix
@@ -1,111 +1,120 @@
-# Test ensures buildbot master comes up correctly and workers can connect
-
-import ./make-test.nix ({ pkgs, ... } : {
-  name = "buildbot";
-
-  nodes = {
-    bbmaster = { pkgs, ... }: {
-      services.buildbot-master = {
-        enable = true;
-        package = pkgs.buildbot-full;
-
-        # NOTE: use fake repo due to no internet in hydra ci
-        factorySteps = [
-          "steps.Git(repourl='git://gitrepo/fakerepo.git', mode='incremental')"
-          "steps.ShellCommand(command=['bash', 'fakerepo.sh'])"
-        ];
-        changeSource = [
-          "changes.GitPoller('git://gitrepo/fakerepo.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
-        ];
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+
+let
+  # Test ensures buildbot master comes up correctly and workers can connect
+  mkBuildbotTest = python: makeTest {
+    name = "buildbot";
+
+    nodes = {
+      bbmaster = { pkgs, ... }: {
+        services.buildbot-master = {
+          enable = true;
+          package = python.pkgs.buildbot-full;
+
+          # NOTE: use fake repo due to no internet in hydra ci
+          factorySteps = [
+            "steps.Git(repourl='git://gitrepo/fakerepo.git', mode='incremental')"
+            "steps.ShellCommand(command=['bash', 'fakerepo.sh'])"
+          ];
+          changeSource = [
+            "changes.GitPoller('git://gitrepo/fakerepo.git', workdir='gitpoller-workdir', branch='master', pollinterval=300)"
+          ];
+        };
+        networking.firewall.allowedTCPPorts = [ 8010 8011 9989 ];
+        environment.systemPackages = with pkgs; [ git python.pkgs.buildbot-full ];
       };
-      networking.firewall.allowedTCPPorts = [ 8010 8011 9989 ];
-      environment.systemPackages = with pkgs; [ git buildbot-full ];
-    };
 
-    bbworker = { pkgs, ... }: {
-      services.buildbot-worker = {
-        enable = true;
-        masterUrl = "bbmaster:9989";
+      bbworker = { pkgs, ... }: {
+        services.buildbot-worker = {
+          enable = true;
+          masterUrl = "bbmaster:9989";
+        };
+        environment.systemPackages = with pkgs; [ git python.pkgs.buildbot-worker ];
       };
-      environment.systemPackages = with pkgs; [ git buildbot-worker ];
-    };
 
-    gitrepo = { pkgs, ... }: {
-      services.openssh.enable = true;
-      networking.firewall.allowedTCPPorts = [ 22 9418 ];
-      environment.systemPackages = with pkgs; [ git ];
+      gitrepo = { pkgs, ... }: {
+        services.openssh.enable = true;
+        networking.firewall.allowedTCPPorts = [ 22 9418 ];
+        environment.systemPackages = with pkgs; [ git ];
+      };
     };
-  };
 
-  testScript = ''
-    #Start up and populate fake repo
-    $gitrepo->waitForUnit("multi-user.target");
-    print($gitrepo->execute(" \
-      git config --global user.name 'Nobody Fakeuser' && \
-      git config --global user.email 'nobody\@fakerepo.com' && \
-      rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo && \
-      mkdir -pv /srv/repos/fakerepo ~/.ssh && \
-      ssh-keyscan -H gitrepo > ~/.ssh/known_hosts && \
-      cat ~/.ssh/known_hosts && \
-      cd /srv/repos/fakerepo && \
-      git init && \
-      echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh && \
-      cat fakerepo.sh && \
-      touch .git/git-daemon-export-ok && \
-      git add fakerepo.sh .git/git-daemon-export-ok && \
-      git commit -m fakerepo && \
-      git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr & \
-    "));
-
-    # Test gitrepo
-    $bbmaster->waitForUnit("network-online.target");
-    #$bbmaster->execute("nc -z gitrepo 9418");
-    print($bbmaster->execute(" \
-      rm -rfv /tmp/fakerepo && \
-      git clone git://gitrepo/fakerepo /tmp/fakerepo && \
-      pwd && \
-      ls -la && \
-      ls -la /tmp/fakerepo \
-    "));
-
-    # Test start master and connect worker
-    $bbmaster->waitForUnit("buildbot-master.service");
-    $bbmaster->waitUntilSucceeds("curl -s --head http://bbmaster:8010") =~ /200 OK/;
-    $bbworker->waitForUnit("network-online.target");
-    $bbworker->execute("nc -z bbmaster 8010");
-    $bbworker->execute("nc -z bbmaster 9989");
-    $bbworker->waitForUnit("buildbot-worker.service");
-    print($bbworker->execute("ls -la /home/bbworker/worker"));
-
-
-    # Test stop buildbot master and worker
-    print($bbmaster->execute(" \
-      systemctl -l --no-pager status buildbot-master && \
-      systemctl stop buildbot-master \
-    "));
-    $bbworker->fail("nc -z bbmaster 8010");
-    $bbworker->fail("nc -z bbmaster 9989");
-    print($bbworker->execute(" \
-      systemctl -l --no-pager status buildbot-worker && \
-      systemctl stop buildbot-worker && \
-      ls -la /home/bbworker/worker \
-    "));
-
-
-    # Test buildbot daemon mode
-    # NOTE: daemon mode tests disabled due to broken PYTHONPATH child inheritence
-    #
-    #$bbmaster->execute("buildbot create-master /tmp");
-    #$bbmaster->execute("mv -fv /tmp/master.cfg.sample /tmp/master.cfg");
-    #$bbmaster->execute("sed -i 's/8010/8011/' /tmp/master.cfg");
-    #$bbmaster->execute("buildbot start /tmp");
-    #$bbworker->execute("nc -z bbmaster 8011");
-    #$bbworker->waitUntilSucceeds("curl -s --head http://bbmaster:8011") =~ /200 OK/;
-    #$bbmaster->execute("buildbot stop /tmp");
-    #$bbworker->fail("nc -z bbmaster 8011");
-
-  '';
-
-  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ nand0p ];
-
-})
+    testScript = ''
+      #Start up and populate fake repo
+      $gitrepo->waitForUnit("multi-user.target");
+      print($gitrepo->execute(" \
+        git config --global user.name 'Nobody Fakeuser' && \
+        git config --global user.email 'nobody\@fakerepo.com' && \
+        rm -rvf /srv/repos/fakerepo.git /tmp/fakerepo && \
+        mkdir -pv /srv/repos/fakerepo ~/.ssh && \
+        ssh-keyscan -H gitrepo > ~/.ssh/known_hosts && \
+        cat ~/.ssh/known_hosts && \
+        cd /srv/repos/fakerepo && \
+        git init && \
+        echo -e '#!/bin/sh\necho fakerepo' > fakerepo.sh && \
+        cat fakerepo.sh && \
+        touch .git/git-daemon-export-ok && \
+        git add fakerepo.sh .git/git-daemon-export-ok && \
+        git commit -m fakerepo && \
+        git daemon --verbose --export-all --base-path=/srv/repos --reuseaddr & \
+      "));
+
+      # Test gitrepo
+      $bbmaster->waitForUnit("network-online.target");
+      #$bbmaster->execute("nc -z gitrepo 9418");
+      print($bbmaster->execute(" \
+        rm -rfv /tmp/fakerepo && \
+        git clone git://gitrepo/fakerepo /tmp/fakerepo && \
+        pwd && \
+        ls -la && \
+        ls -la /tmp/fakerepo \
+      "));
+
+      # Test start master and connect worker
+      $bbmaster->waitForUnit("buildbot-master.service");
+      $bbmaster->waitUntilSucceeds("curl -s --head http://bbmaster:8010") =~ /200 OK/;
+      $bbworker->waitForUnit("network-online.target");
+      $bbworker->execute("nc -z bbmaster 8010");
+      $bbworker->execute("nc -z bbmaster 9989");
+      $bbworker->waitForUnit("buildbot-worker.service");
+      print($bbworker->execute("ls -la /home/bbworker/worker"));
+
+
+      # Test stop buildbot master and worker
+      print($bbmaster->execute(" \
+        systemctl -l --no-pager status buildbot-master && \
+        systemctl stop buildbot-master \
+      "));
+      $bbworker->fail("nc -z bbmaster 8010");
+      $bbworker->fail("nc -z bbmaster 9989");
+      print($bbworker->execute(" \
+        systemctl -l --no-pager status buildbot-worker && \
+        systemctl stop buildbot-worker && \
+        ls -la /home/bbworker/worker \
+      "));
+
+
+      # Test buildbot daemon mode
+      $bbmaster->execute("buildbot create-master /tmp");
+      $bbmaster->execute("mv -fv /tmp/master.cfg.sample /tmp/master.cfg");
+      $bbmaster->execute("sed -i 's/8010/8011/' /tmp/master.cfg");
+      $bbmaster->execute("buildbot start /tmp");
+      $bbworker->execute("nc -z bbmaster 8011");
+      $bbworker->waitUntilSucceeds("curl -s --head http://bbmaster:8011") =~ /200 OK/;
+      $bbmaster->execute("buildbot stop /tmp");
+      $bbworker->fail("nc -z bbmaster 8011");
+
+    '';
+
+    meta.maintainers = with pkgs.stdenv.lib.maintainers; [ nand0p ];
+
+  };
+in {
+  python2 = mkBuildbotTest pkgs.python2;
+  python3 = mkBuildbotTest pkgs.python3;
+}
diff --git a/nixos/tests/ceph.nix b/nixos/tests/ceph.nix
index dd45f0157b01a..7408029c460e3 100644
--- a/nixos/tests/ceph.nix
+++ b/nixos/tests/ceph.nix
@@ -10,9 +10,8 @@ import ./make-test.nix ({pkgs, ...}: rec {
         emptyDiskImages = [ 20480 20480 ];
         vlans = [ 1 ];
       };
-      
+
       networking = {
-        firewall.allowPing = true;
         useDHCP = false;
         interfaces.eth1.ipv4.addresses = pkgs.lib.mkOverride 0 [
           { address = "192.168.1.1"; prefixLength = 24; }
@@ -54,7 +53,7 @@ import ./make-test.nix ({pkgs, ...}: rec {
       };
     };
   };
-  
+
   testScript = { ... }: ''
     startAll;
 
@@ -83,7 +82,7 @@ import ./make-test.nix ({pkgs, ...}: rec {
 
     # Can't check ceph status until a mon is up
     $aio->succeed("ceph -s | grep 'mon: 1 daemons'");
-          
+
     # Start the ceph-mgr daemon, it has no deps and hardly any setup
     $aio->mustSucceed(
       "ceph auth get-or-create mgr.aio mon 'allow profile mgr' osd 'allow *' mds 'allow *' > /var/lib/ceph/mgr/ceph-aio/keyring",
diff --git a/nixos/tests/certmgr.nix b/nixos/tests/certmgr.nix
index 8354c46b85f7e..fe67833808ce4 100644
--- a/nixos/tests/certmgr.nix
+++ b/nixos/tests/certmgr.nix
@@ -1,6 +1,9 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 let
   mkSpec = { host, service ? null, action }: {
     inherit action;
diff --git a/nixos/tests/chromium.nix b/nixos/tests/chromium.nix
index c341e83961a8e..af5db2a3dbe1f 100644
--- a/nixos/tests/chromium.nix
+++ b/nixos/tests/chromium.nix
@@ -1,5 +1,6 @@
 { system ? builtins.currentSystem
-, pkgs ? import ../.. { inherit system; }
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
 , channelMap ? {
     stable = pkgs.chromium;
     beta   = pkgs.chromiumBeta;
@@ -7,13 +8,15 @@
   }
 }:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
 mapAttrs (channel: chromiumPkg: makeTest rec {
   name = "chromium-${channel}";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ aszlig ];
+  meta = {
+    maintainers = with maintainers; [ aszlig ];
+    # https://github.com/NixOS/hydra/issues/591#issuecomment-435125621
+    inherit (chromiumPkg.meta) timeout;
   };
 
   enableOCR = true;
@@ -166,7 +169,7 @@ mapAttrs (channel: chromiumPkg: makeTest rec {
 
       my $clipboard = $machine->succeed(ru "${pkgs.xclip}/bin/xclip -o");
       die "sandbox not working properly: $clipboard"
-      unless $clipboard =~ /namespace sandbox.*yes/mi
+      unless $clipboard =~ /layer 1 sandbox.*namespace/mi
           && $clipboard =~ /pid namespaces.*yes/mi
           && $clipboard =~ /network namespaces.*yes/mi
           && $clipboard =~ /seccomp.*sandbox.*yes/mi
@@ -184,7 +187,7 @@ mapAttrs (channel: chromiumPkg: makeTest rec {
 
       my $clipboard = $machine->succeed(ru "${pkgs.xclip}/bin/xclip -o");
       die "copying twice in a row does not work properly: $clipboard"
-      unless $clipboard =~ /namespace sandbox.*yes/mi
+      unless $clipboard =~ /layer 1 sandbox.*namespace/mi
           && $clipboard =~ /pid namespaces.*yes/mi
           && $clipboard =~ /network namespaces.*yes/mi
           && $clipboard =~ /seccomp.*sandbox.*yes/mi
diff --git a/nixos/tests/cjdns.nix b/nixos/tests/cjdns.nix
index ab5f8e0bcf3e5..e03bb9882540c 100644
--- a/nixos/tests/cjdns.nix
+++ b/nixos/tests/cjdns.nix
@@ -13,9 +13,6 @@ let
 
       # CJDNS output is incompatible with the XML log.
       systemd.services.cjdns.serviceConfig.StandardOutput = "null";
-      #networking.firewall.enable = true;
-      networking.firewall.allowPing = true;
-      #networking.firewall.rejectPackets = true;
     };
 
 in
diff --git a/nixos/tests/clickhouse.nix b/nixos/tests/clickhouse.nix
new file mode 100644
index 0000000000000..7d835069ec4d8
--- /dev/null
+++ b/nixos/tests/clickhouse.nix
@@ -0,0 +1,25 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "clickhouse";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ ma27 ];
+
+  machine = {
+    services.clickhouse.enable = true;
+  };
+
+  testScript =
+    let
+      # work around quote/substitution complexity by Nix, Perl, bash and SQL.
+      tableDDL = pkgs.writeText "ddl.sql" "CREATE TABLE `demo` (`value` FixedString(10)) engine = MergeTree PARTITION BY value ORDER BY tuple();";
+      insertQuery = pkgs.writeText "insert.sql" "INSERT INTO `demo` (`value`) VALUES ('foo');";
+      selectQuery = pkgs.writeText "select.sql" "SELECT * from `demo`";
+    in
+      ''
+        $machine->start();
+        $machine->waitForUnit("clickhouse.service");
+        $machine->waitForOpenPort(9000);
+
+        $machine->succeed("cat ${tableDDL} | clickhouse-client");
+        $machine->succeed("cat ${insertQuery} | clickhouse-client");
+        $machine->succeed("cat ${selectQuery} | clickhouse-client | grep foo");
+      '';
+})
diff --git a/nixos/tests/cloud-init.nix b/nixos/tests/cloud-init.nix
index 303e74086460f..516d29c9036be 100644
--- a/nixos/tests/cloud-init.nix
+++ b/nixos/tests/cloud-init.nix
@@ -1,6 +1,9 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
diff --git a/nixos/tests/cockroachdb.nix b/nixos/tests/cockroachdb.nix
new file mode 100644
index 0000000000000..56c624d8cf2f4
--- /dev/null
+++ b/nixos/tests/cockroachdb.nix
@@ -0,0 +1,126 @@
+# This performs a full 'end-to-end' test of a multi-node CockroachDB cluster
+# using the built-in 'cockroach workload' command, to simulate a semi-realistic
+# test load. It generally takes anywhere from 3-5 minutes to run and 1-2GB of
+# RAM (though each of 3 workers gets 1GB allocated)
+#
+# CockroachDB requires synchronized system clocks within a small error window
+# (~500ms by default) on each node in order to maintain a multi-node cluster.
+# Cluster joins that are outside this window will fail, and nodes that skew
+# outside the window after joining will promptly get kicked out.
+#
+# To accomodate this, we use QEMU/virtio infrastructure and load the 'ptp_kvm'
+# driver inside a guest. This driver allows the host machine to pass its clock
+# through to the guest as a hardware clock that appears as a Precision Time
+# Protocol (PTP) Clock device, generally /dev/ptp0. PTP devices can be measured
+# and used as hardware reference clocks (similar to an on-board GPS clock) by
+# NTP software. In our case, we use Chrony to synchronize to the reference
+# clock.
+#
+# This test is currently NOT enabled as a continuously-checked NixOS test.
+# Ideally, this test would be run by Hydra and Borg on all relevant changes,
+# except:
+#
+#   - Not every build machine is compatible with the ptp_kvm driver.
+#     Virtualized EC2 instances, for example, do not support loading the ptp_kvm
+#     driver into guests. However, bare metal builders (e.g. Packet) do seem to
+#     work just fine. In practice, this means x86_64-linux builds would fail
+#     randomly, depending on which build machine got the job. (This is probably
+#     worth some investigation; I imagine it's based on ptp_kvm's usage of paravirt
+#     support which may not be available in 'nested' environments.)
+#
+#   - ptp_kvm is not supported on aarch64, otherwise it seems likely Cockroach
+#     could be tested there, as well. This seems to be due to the usage of
+#     the TSC in ptp_kvm, which isn't supported (easily) on AArch64. (And:
+#     testing stuff, not just making sure it builds, is important to ensure
+#     aarch64 support remains viable.)
+#
+# For future developers who are reading this message, are daring and would want
+# to fix this, some options are:
+#
+#   - Just test a single node cluster instead (boring and less thorough).
+#   - Move all CI to bare metal packet builders, and we can at least do x86_64-linux.
+#   - Get virtualized clocking working in aarch64, somehow.
+#   - Add a 4th node that acts as an NTP service and uses no PTP clocks for
+#     references, at the client level. This bloats the node and memory
+#     requirements, but would probably allow both aarch64/x86_64 to work.
+#
+
+let
+
+  # Creates a node. If 'joinNode' parameter, a string containing an IP address,
+  # is non-null, then the CockroachDB server will attempt to join/connect to
+  # the cluster node specified at that address.
+  makeNode = locality: myAddr: joinNode:
+    { nodes, pkgs, lib, config, ... }:
+
+    {
+      # Bank/TPC-C benchmarks take some memory to complete
+      virtualisation.memorySize = 1024;
+
+      # Install the KVM PTP "Virtualized Clock" driver. This allows a /dev/ptp0
+      # device to appear as a reference clock, synchronized to the host clock.
+      # Because CockroachDB *requires* a time-synchronization mechanism for
+      # the system time in a cluster scenario, this is necessary to work.
+      boot.kernelModules = [ "ptp_kvm" ];
+
+      # Enable and configure Chrony, using the given virtualized clock passed
+      # through by KVM.
+      services.chrony.enable = true;
+      services.chrony.servers = lib.mkForce [ ];
+      services.chrony.extraConfig = ''
+        refclock PHC /dev/ptp0 poll 2 prefer require refid KVM
+        makestep 0.1 3
+      '';
+
+      # Enable CockroachDB. In order to ensure that Chrony has performed its
+      # first synchronization at boot-time (which may take ~10 seconds) before
+      # starting CockroachDB, we block the ExecStartPre directive using the
+      # 'waitsync' command. This ensures Cockroach doesn't have its system time
+      # leap forward out of nowhere during startup/execution.
+      #
+      # Note that the default threshold for NTP-based skew in CockroachDB is
+      # ~500ms by default, so making sure it's started *after* accurate time
+      # synchronization is extremely important.
+      services.cockroachdb.enable = true;
+      services.cockroachdb.insecure = true;
+      services.cockroachdb.openPorts = true;
+      services.cockroachdb.locality = locality;
+      services.cockroachdb.listen.address = myAddr;
+      services.cockroachdb.join = lib.mkIf (joinNode != null) joinNode;
+
+      # Hold startup until Chrony has performed its first measurement (which
+      # will probably result in a full timeskip, thanks to makestep)
+      systemd.services.cockroachdb.preStart = ''
+        ${pkgs.chrony}/bin/chronyc waitsync
+      '';
+    };
+
+in import ./make-test.nix ({ pkgs, ...} : {
+  name = "cockroachdb";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers;
+    [ thoughtpolice ];
+
+  nodes = rec {
+    node1 = makeNode "country=us,region=east,dc=1"  "192.168.1.1" null;
+    node2 = makeNode "country=us,region=west,dc=2b" "192.168.1.2" "192.168.1.1";
+    node3 = makeNode "country=eu,region=west,dc=2"  "192.168.1.3" "192.168.1.1";
+  };
+
+  # NOTE: All the nodes must start in order and you must NOT use startAll, because
+  # there's otherwise no way to guarantee that node1 will start before the others try
+  # to join it.
+  testScript = ''
+    $node1->start;
+    $node1->waitForUnit("cockroachdb");
+
+    $node2->start;
+    $node2->waitForUnit("cockroachdb");
+
+    $node3->start;
+    $node3->waitForUnit("cockroachdb");
+
+    $node1->mustSucceed("cockroach sql --host=192.168.1.1 --insecure -e 'SHOW ALL CLUSTER SETTINGS' 2>&1");
+    $node1->mustSucceed("cockroach workload init bank 'postgresql://root\@192.168.1.1:26257?sslmode=disable'");
+    $node1->mustSucceed("cockroach workload run bank --duration=1m 'postgresql://root\@192.168.1.1:26257?sslmode=disable'");
+  '';
+})
diff --git a/nixos/tests/codimd.nix b/nixos/tests/codimd.nix
new file mode 100644
index 0000000000000..562f6f24f999c
--- /dev/null
+++ b/nixos/tests/codimd.nix
@@ -0,0 +1,54 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+{
+  name = "codimd";
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+
+  nodes = {
+    codimdSqlite = { ... }: {
+      services = {
+        codimd = {
+          enable = true;
+          configuration.dbURL = "sqlite:///var/lib/codimd/codimd.db";
+        };
+      };
+    };
+
+    codimdPostgres = { ... }: {
+      systemd.services.codimd.after = [ "postgresql.service" ];
+      services = {
+        codimd = {
+          enable = true;
+          configuration.dbURL = "postgres://codimd:snakeoilpassword@localhost:5432/codimddb";
+        };
+        postgresql = {
+          enable = true;
+          initialScript = pkgs.writeText "pg-init-script.sql" ''
+            CREATE ROLE codimd LOGIN PASSWORD 'snakeoilpassword';
+            CREATE DATABASE codimddb OWNER codimd;
+          '';
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll();
+
+    subtest "CodiMD sqlite", sub {
+      $codimdSqlite->waitForUnit("codimd.service");
+      $codimdSqlite->waitForOpenPort(3000);
+      $codimdSqlite->waitUntilSucceeds("curl -sSf http://localhost:3000/new");
+    };
+
+    subtest "CodiMD postgres", sub {
+      $codimdPostgres->waitForUnit("postgresql.service");
+      $codimdPostgres->waitForUnit("codimd.service");
+      $codimdPostgres->waitForOpenPort(5432);
+      $codimdPostgres->waitForOpenPort(3000);
+      $codimdPostgres->waitUntilSucceeds("curl -sSf http://localhost:3000/new");
+    };
+  '';
+})
diff --git a/nixos/tests/common/ec2.nix b/nixos/tests/common/ec2.nix
new file mode 100644
index 0000000000000..1e69b63191a70
--- /dev/null
+++ b/nixos/tests/common/ec2.nix
@@ -0,0 +1,49 @@
+{ pkgs, makeTest }:
+
+with pkgs.lib;
+
+{
+  makeEc2Test = { name, image, userData, script, hostname ? "ec2-instance", sshPublicKey ? null }:
+    let
+      metaData = pkgs.stdenv.mkDerivation {
+        name = "metadata";
+        buildCommand = ''
+          mkdir -p $out/1.0/meta-data
+          ln -s ${pkgs.writeText "userData" userData} $out/1.0/user-data
+          echo "${hostname}" > $out/1.0/meta-data/hostname
+          echo "(unknown)" > $out/1.0/meta-data/ami-manifest-path
+        '' + optionalString (sshPublicKey != null) ''
+          mkdir -p $out/1.0/meta-data/public-keys/0
+          ln -s ${pkgs.writeText "sshPublicKey" sshPublicKey} $out/1.0/meta-data/public-keys/0/openssh-key
+        '';
+      };
+    in makeTest {
+      name = "ec2-" + name;
+      nodes = {};
+      testScript =
+        ''
+          my $imageDir = ($ENV{'TMPDIR'} // "/tmp") . "/vm-state-machine";
+          mkdir $imageDir, 0700;
+          my $diskImage = "$imageDir/machine.qcow2";
+          system("qemu-img create -f qcow2 -o backing_file=${image}/nixos.qcow2 $diskImage") == 0 or die;
+          system("qemu-img resize $diskImage 10G") == 0 or die;
+
+          # Note: we use net=169.0.0.0/8 rather than
+          # net=169.254.0.0/16 to prevent dhcpcd from getting horribly
+          # confused. (It would get a DHCP lease in the 169.254.*
+          # range, which it would then configure and prompty delete
+          # again when it deletes link-local addresses.) Ideally we'd
+          # turn off the DHCP server, but qemu does not have an option
+          # to do that.
+          my $startCommand = "qemu-kvm -m 768";
+          $startCommand .= " -device virtio-net-pci,netdev=vlan0";
+          $startCommand .= " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'";
+          $startCommand .= " -drive file=$diskImage,if=virtio,werror=report";
+          $startCommand .= " \$QEMU_OPTS";
+
+          my $machine = createMachine({ startCommand => $startCommand });
+
+          ${script}
+        '';
+    };
+}
diff --git a/nixos/tests/common/letsencrypt/common.nix b/nixos/tests/common/letsencrypt/common.nix
new file mode 100644
index 0000000000000..798a749f7f9b7
--- /dev/null
+++ b/nixos/tests/common/letsencrypt/common.nix
@@ -0,0 +1,27 @@
+{ lib, nodes, ... }: {
+  networking.nameservers = [
+    nodes.letsencrypt.config.networking.primaryIPAddress
+  ];
+
+  nixpkgs.overlays = lib.singleton (self: super: {
+    cacert = super.cacert.overrideDerivation (drv: {
+      installPhase = (drv.installPhase or "") + ''
+        cat "${nodes.letsencrypt.config.test-support.letsencrypt.caCert}" \
+          >> "$out/etc/ssl/certs/ca-bundle.crt"
+      '';
+    });
+
+    # Override certifi so that it accepts fake certificate for Let's Encrypt
+    # Need to override the attribute used by simp_le, which is python3Packages
+    python3Packages = (super.python3.override {
+      packageOverrides = lib.const (pysuper: {
+        certifi = pysuper.certifi.overridePythonAttrs (attrs: {
+          postPatch = (attrs.postPatch or "") + ''
+            cat "${self.cacert}/etc/ssl/certs/ca-bundle.crt" \
+              > certifi/cacert.pem
+          '';
+        });
+      });
+    }).pkgs;
+  });
+}
diff --git a/nixos/tests/common/webroot/news-rss.xml b/nixos/tests/common/webroot/news-rss.xml
new file mode 100644
index 0000000000000..28e6fa7da1f36
--- /dev/null
+++ b/nixos/tests/common/webroot/news-rss.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss xmlns:blogChannel="http://backend.userland.com/blogChannelModule" version="2.0"><channel><title>NixOS News</title><link>https://nixos.org</link><description>News for NixOS, the purely functional Linux distribution.</description><image><title>NixOS</title><url>https://nixos.org/logo/nixos-logo-only-hires.png</url><link>https://nixos.org/</link></image><item><title>
+      NixOS 18.09 released
+    </title><link>https://nixos.org/news.html</link><description>
+      <a href="https://github.com/NixOS/nixos-artwork/blob/master/releases/18.09-jellyfish/jellyfish.png">
+        <img class="inline" src="logo/nixos-logo-18.09-jellyfish-lores.png" alt="18.09 Jellyfish logo" with="100" height="87"/>
+      </a>
+      NixOS 18.09 “Jellyfish” has been released, the tenth stable release branch.
+      See the <a href="/nixos/manual/release-notes.html#sec-release-18.09">release notes</a>
+      for details. You can get NixOS 18.09 ISOs and VirtualBox appliances
+      from the <a href="nixos/download.html">download page</a>.
+      For information on how to upgrade from older release branches
+      to 18.09, check out the
+      <a href="/nixos/manual/index.html#sec-upgrading">manual section on upgrading</a>.
+    </description><pubDate>Sat Oct 06 2018 00:00:00 GMT</pubDate></item></channel></rss>
diff --git a/nixos/tests/containers-bridge.nix b/nixos/tests/containers-bridge.nix
index bd8bd5dee9c88..0eae51433d205 100644
--- a/nixos/tests/containers-bridge.nix
+++ b/nixos/tests/containers-bridge.nix
@@ -42,10 +42,22 @@ import ./make-test.nix ({ pkgs, ...} : {
             { services.httpd.enable = true;
               services.httpd.adminAddr = "foo@example.org";
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
             };
         };
 
+      containers.web-noip =
+        {
+          autoStart = true;
+          privateNetwork = true;
+          hostBridge = "br0";
+          config =
+            { services.httpd.enable = true;
+              services.httpd.adminAddr = "foo@example.org";
+              networking.firewall.allowedTCPPorts = [ 80 ];
+            };
+        };
+
+
       virtualisation.pathsInNixDB = [ pkgs.stdenv ];
     };
 
@@ -57,6 +69,10 @@ import ./make-test.nix ({ pkgs, ...} : {
       # Start the webserver container.
       $machine->succeed("nixos-container status webserver") =~ /up/ or die;
 
+      # Check if bridges exist inside containers
+      $machine->succeed("nixos-container run webserver -- ip link show eth0");
+      $machine->succeed("nixos-container run web-noip -- ip link show eth0");
+
       "${containerIp}" =~ /([^\/]+)\/([0-9+])/;
       my $ip = $1;
       chomp $ip;
diff --git a/nixos/tests/containers-extra_veth.nix b/nixos/tests/containers-extra_veth.nix
index 8f874b3585dc4..b3d3bce87579c 100644
--- a/nixos/tests/containers-extra_veth.nix
+++ b/nixos/tests/containers-extra_veth.nix
@@ -13,6 +13,7 @@ import ./make-test.nix ({ pkgs, ...} : {
       virtualisation.memorySize = 768;
       virtualisation.vlans = [];
 
+      networking.useDHCP = false;
       networking.bridges = {
         br0 = {
           interfaces = [];
@@ -43,7 +44,6 @@ import ./make-test.nix ({ pkgs, ...} : {
           config =
             {
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
             };
         };
 
diff --git a/nixos/tests/containers-imperative.nix b/nixos/tests/containers-imperative.nix
index 913d8bed19d09..782095a09dada 100644
--- a/nixos/tests/containers-imperative.nix
+++ b/nixos/tests/containers-imperative.nix
@@ -13,6 +13,7 @@ import ./make-test.nix ({ pkgs, ...} : {
       # XXX: Sandbox setup fails while trying to hardlink files from the host's
       #      store file system into the prepared chroot directory.
       nix.useSandbox = false;
+      nix.binaryCaches = []; # don't try to access cache.nixos.org
 
       virtualisation.writableStore = true;
       virtualisation.memorySize = 1024;
@@ -27,9 +28,10 @@ import ./make-test.nix ({ pkgs, ...} : {
             };
           };
         };
-      in [
-        pkgs.stdenv pkgs.stdenvNoCC emptyContainer.config.containers.foo.path
-        pkgs.libxslt
+      in with pkgs; [
+        stdenv stdenvNoCC emptyContainer.config.containers.foo.path
+        libxslt desktop-file-utils texinfo docbook5 libxml2
+        docbook_xsl_ns xorg.lndir documentation-highlighter
       ];
     };
 
@@ -84,6 +86,9 @@ import ./make-test.nix ({ pkgs, ...} : {
       # Execute commands via the root shell.
       $machine->succeed("nixos-container run $id1 -- uname") =~ /Linux/ or die;
 
+      # Execute a nix command via the root shell. (regression test for #40355)
+      $machine->succeed("nixos-container run $id1 -- nix-instantiate -E 'derivation { name = \"empty\"; builder = \"false\"; system = \"false\"; }'");
+
       # Stop and start (regression test for #4989)
       $machine->succeed("nixos-container stop $id1");
       $machine->succeed("nixos-container start $id1");
diff --git a/nixos/tests/containers-ipv4.nix b/nixos/tests/containers-ipv4.nix
index 4affe3d9d56dc..5f83a33b1079c 100644
--- a/nixos/tests/containers-ipv4.nix
+++ b/nixos/tests/containers-ipv4.nix
@@ -20,7 +20,6 @@ import ./make-test.nix ({ pkgs, ...} : {
             { services.httpd.enable = true;
               services.httpd.adminAddr = "foo@example.org";
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
               system.stateVersion = "18.03";
             };
         };
diff --git a/nixos/tests/containers-ipv6.nix b/nixos/tests/containers-ipv6.nix
index 7db389a18e726..5866e51b731df 100644
--- a/nixos/tests/containers-ipv6.nix
+++ b/nixos/tests/containers-ipv6.nix
@@ -25,7 +25,6 @@ import ./make-test.nix ({ pkgs, ...} : {
             { services.httpd.enable = true;
               services.httpd.adminAddr = "foo@example.org";
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
             };
         };
 
diff --git a/nixos/tests/containers-portforward.nix b/nixos/tests/containers-portforward.nix
index be83f82445edc..d2dda926fc0eb 100644
--- a/nixos/tests/containers-portforward.nix
+++ b/nixos/tests/containers-portforward.nix
@@ -28,7 +28,6 @@ import ./make-test.nix ({ pkgs, ...} : {
             { services.httpd.enable = true;
               services.httpd.adminAddr = "foo@example.org";
               networking.firewall.allowedTCPPorts = [ 80 ];
-              networking.firewall.allowPing = true;
             };
         };
 
diff --git a/nixos/tests/containers-restart_networking.nix b/nixos/tests/containers-restart_networking.nix
index aeb0a6e68e216..0fb3b591e9f91 100644
--- a/nixos/tests/containers-restart_networking.nix
+++ b/nixos/tests/containers-restart_networking.nix
@@ -10,7 +10,6 @@ let
       hostBridge = "br0";
       config = {
         networking.firewall.enable = false;
-        networking.firewall.allowPing = true;
         networking.interfaces.eth0.ipv4.addresses = [
           { address = "192.168.1.122"; prefixLength = 24; }
         ];
diff --git a/nixos/tests/docker-preloader.nix b/nixos/tests/docker-preloader.nix
new file mode 100644
index 0000000000000..eeedec9a392ed
--- /dev/null
+++ b/nixos/tests/docker-preloader.nix
@@ -0,0 +1,27 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "docker-preloader";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ lewo ];
+  };
+
+  nodes = {
+    docker =
+      { pkgs, ... }:
+        {
+          virtualisation.docker.enable = true;
+          virtualisation.dockerPreloader.images = [ pkgs.dockerTools.examples.nix pkgs.dockerTools.examples.bash ];
+
+          services.openssh.enable = true;
+          services.openssh.permitRootLogin = "yes";
+          services.openssh.extraConfig = "PermitEmptyPasswords yes";
+          users.extraUsers.root.password = "";
+        };
+  };    
+  testScript = ''
+    startAll;
+    
+    $docker->waitForUnit("sockets.target");
+    $docker->succeed("docker run nix nix-store --version");
+    $docker->succeed("docker run bash bash --version");
+  '';
+})
diff --git a/nixos/tests/docker-tools.nix b/nixos/tests/docker-tools.nix
index db4eacc37287f..58f106314ab3f 100644
--- a/nixos/tests/docker-tools.nix
+++ b/nixos/tests/docker-tools.nix
@@ -20,7 +20,10 @@ import ./make-test.nix ({ pkgs, ... }: {
     ''
       $docker->waitForUnit("sockets.target");
 
+      # Ensure Docker images use a stable date by default
       $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'");
+      $docker->succeed("[ '1970-01-01T00:00:01Z' = \"\$(docker inspect ${pkgs.dockerTools.examples.bash.imageName} | ${pkgs.jq}/bin/jq -r .[].Created)\" ]");
+
       $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.bash.imageName} bash --version");
       $docker->succeed("docker rmi ${pkgs.dockerTools.examples.bash.imageName}");
 
@@ -51,5 +54,18 @@ import ./make-test.nix ({ pkgs, ... }: {
       $docker->succeed("docker run --rm runasrootextracommands cat extraCommands");
       $docker->succeed("docker run --rm runasrootextracommands cat runAsRoot");
       $docker->succeed("docker rmi '${pkgs.dockerTools.examples.runAsRootExtraCommands.imageName}'");
+
+      # Ensure Docker images can use an unstable date
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.bash}'");
+      $docker->succeed("[ '1970-01-01T00:00:01Z' != \"\$(docker inspect ${pkgs.dockerTools.examples.unstableDate.imageName} | ${pkgs.jq}/bin/jq -r .[].Created)\" ]");
+
+      # Ensure Layered Docker images work
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.layered-image}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-image.imageName}");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-image.imageName} cat extraCommands");
+
+      # Ensure building an image on top of a layered Docker images work
+      $docker->succeed("docker load --input='${pkgs.dockerTools.examples.layered-on-top}'");
+      $docker->succeed("docker run --rm ${pkgs.dockerTools.examples.layered-on-top.imageName}");
     '';
 })
diff --git a/nixos/tests/ec2.nix b/nixos/tests/ec2.nix
index 8271747ccc631..384fce67c227d 100644
--- a/nixos/tests/ec2.nix
+++ b/nixos/tests/ec2.nix
@@ -1,8 +1,13 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
+with import common/ec2.nix { inherit makeTest pkgs; };
+
 let
   image =
     (import ../lib/eval-config.nix {
@@ -36,65 +41,14 @@ let
       ];
     }).config.system.build.amazonImage;
 
-  makeEc2Test = { name, userData, script, hostname ? "ec2-instance", sshPublicKey ? null }:
-    let
-      metaData = pkgs.stdenv.mkDerivation {
-        name = "metadata";
-        buildCommand = ''
-          mkdir -p $out/1.0/meta-data
-          ln -s ${pkgs.writeText "userData" userData} $out/1.0/user-data
-          echo "${hostname}" > $out/1.0/meta-data/hostname
-          echo "(unknown)" > $out/1.0/meta-data/ami-manifest-path
-        '' + optionalString (sshPublicKey != null) ''
-          mkdir -p $out/1.0/meta-data/public-keys/0
-          ln -s ${pkgs.writeText "sshPublicKey" sshPublicKey} $out/1.0/meta-data/public-keys/0/openssh-key
-        '';
-      };
-    in makeTest {
-      name = "ec2-" + name;
-      nodes = {};
-      testScript =
-        ''
-          my $imageDir = ($ENV{'TMPDIR'} // "/tmp") . "/vm-state-machine";
-          mkdir $imageDir, 0700;
-          my $diskImage = "$imageDir/machine.qcow2";
-          system("qemu-img create -f qcow2 -o backing_file=${image}/nixos.qcow2 $diskImage") == 0 or die;
-          system("qemu-img resize $diskImage 10G") == 0 or die;
-
-          # Note: we use net=169.0.0.0/8 rather than
-          # net=169.254.0.0/16 to prevent dhcpcd from getting horribly
-          # confused. (It would get a DHCP lease in the 169.254.*
-          # range, which it would then configure and prompty delete
-          # again when it deletes link-local addresses.) Ideally we'd
-          # turn off the DHCP server, but qemu does not have an option
-          # to do that.
-          my $startCommand = "qemu-kvm -m 768";
-          $startCommand .= " -device virtio-net-pci,netdev=vlan0";
-          $startCommand .= " -netdev 'user,id=vlan0,net=169.0.0.0/8,guestfwd=tcp:169.254.169.254:80-cmd:${pkgs.micro-httpd}/bin/micro_httpd ${metaData}'";
-          $startCommand .= " -drive file=$diskImage,if=virtio,werror=report";
-          $startCommand .= " \$QEMU_OPTS";
-
-          my $machine = createMachine({ startCommand => $startCommand });
-
-          ${script}
-        '';
-    };
-
-  snakeOilPrivateKey = ''
-    -----BEGIN OPENSSH PRIVATE KEY-----
-    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
-    QyNTUxOQAAACDEPmwZv5dDPrMUaq0dDP+6eBTTe+QNrz14KBEIdhHd1QAAAJDufJ4S7nye
-    EgAAAAtzc2gtZWQyNTUxOQAAACDEPmwZv5dDPrMUaq0dDP+6eBTTe+QNrz14KBEIdhHd1Q
-    AAAECgwbDlYATM5/jypuptb0GF/+zWZcJfoVIFBG3LQeRyGsQ+bBm/l0M+sxRqrR0M/7p4
-    FNN75A2vPXgoEQh2Ed3VAAAADEVDMiB0ZXN0IGtleQE=
-    -----END OPENSSH PRIVATE KEY-----
-  '';
-
-  snakeOilPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMQ+bBm/l0M+sxRqrR0M/7p4FNN75A2vPXgoEQh2Ed3V EC2 test key";
+  sshKeys = import ./ssh-keys.nix pkgs;
+  snakeOilPrivateKey = sshKeys.snakeOilPrivateKey.text;
+  snakeOilPublicKey = sshKeys.snakeOilPublicKey;
 
 in {
   boot-ec2-nixops = makeEc2Test {
     name         = "nixops-userdata";
+    inherit image;
     sshPublicKey = snakeOilPublicKey; # That's right folks! My user's key is also the host key!
 
     userData = ''
@@ -139,6 +93,7 @@ in {
 
   boot-ec2-config = makeEc2Test {
     name         = "config-userdata";
+    inherit image;
     sshPublicKey = snakeOilPublicKey;
 
     # ### http://nixos.org/channels/nixos-unstable nixos
diff --git a/nixos/tests/elk.nix b/nixos/tests/elk.nix
index 15be72b80bbad..d787ac9730057 100644
--- a/nixos/tests/elk.nix
+++ b/nixos/tests/elk.nix
@@ -1,6 +1,12 @@
-{ system ? builtins.currentSystem, enableUnfree ? false }:
-with import ../lib/testing.nix { inherit system; };
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; },
+  enableUnfree ? false
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
+
 let
   esUrl = "http://localhost:9200";
 
diff --git a/nixos/tests/ferm.nix b/nixos/tests/ferm.nix
index 24b74df85ad1d..b8e8663e3ad2b 100644
--- a/nixos/tests/ferm.nix
+++ b/nixos/tests/ferm.nix
@@ -11,6 +11,7 @@ import ./make-test.nix ({ pkgs, ...} : {
         with pkgs.lib;
         {
           networking = {
+            dhcpcd.enable = false;
             interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::2"; prefixLength = 64; } ];
             interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.2"; prefixLength = 24; } ];
           };
@@ -20,6 +21,7 @@ import ./make-test.nix ({ pkgs, ...} : {
         with pkgs.lib;
         {
           networking = {
+            dhcpcd.enable = false;
             interfaces.eth1.ipv6.addresses = mkOverride 0 [ { address = "fd00::1"; prefixLength = 64; } ];
             interfaces.eth1.ipv4.addresses = mkOverride 0 [ { address = "192.168.1.1"; prefixLength = 24; } ];
           };
@@ -51,7 +53,7 @@ import ./make-test.nix ({ pkgs, ...} : {
     ''
       startAll;
 
-      $client->waitForUnit("network.target");
+      $client->waitForUnit("network-online.target");
       $server->waitForUnit("ferm.service");
       $server->waitForUnit("nginx.service");
       $server->waitUntilSucceeds("ss -ntl | grep -q 80");
diff --git a/nixos/tests/flannel.nix b/nixos/tests/flannel.nix
index fb66fe282090e..0b261a6847727 100644
--- a/nixos/tests/flannel.nix
+++ b/nixos/tests/flannel.nix
@@ -21,8 +21,9 @@ import ./make-test.nix ({ pkgs, ...} : rec {
       services = {
         etcd = {
           enable = true;
-          listenClientUrls = ["http://etcd:2379"];
-          listenPeerUrls = ["http://etcd:2380"];
+          listenClientUrls = ["http://0.0.0.0:2379"]; # requires ip-address for binding
+          listenPeerUrls = ["http://0.0.0.0:2380"]; # requires ip-address for binding
+          advertiseClientUrls = ["http://etcd:2379"];
           initialAdvertisePeerUrls = ["http://etcd:2379"];
           initialCluster = ["etcd=http://etcd:2379"];
         };
diff --git a/nixos/tests/fwupd.nix b/nixos/tests/fwupd.nix
index 2e64149b2db3a..88dac8ccbcdb6 100644
--- a/nixos/tests/fwupd.nix
+++ b/nixos/tests/fwupd.nix
@@ -8,6 +8,8 @@ import ./make-test.nix ({ pkgs, ... }: {
 
   machine = { pkgs, ... }: {
     services.fwupd.enable = true;
+    services.fwupd.blacklistPlugins = []; # don't blacklist test plugin
+    services.fwupd.enableTestRemote = true;
     environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
     environment.variables.XDG_DATA_DIRS = [ "${pkgs.fwupd.installedTests}/share" ];
     virtualisation.memorySize = 768;
diff --git a/nixos/tests/gdk-pixbuf.nix b/nixos/tests/gdk-pixbuf.nix
index b20f61b5ffe26..005c5111da2bd 100644
--- a/nixos/tests/gdk-pixbuf.nix
+++ b/nixos/tests/gdk-pixbuf.nix
@@ -10,10 +10,12 @@ import ./make-test.nix ({ pkgs, ... }: {
     environment.systemPackages = with pkgs; [ gnome-desktop-testing ];
     environment.variables.XDG_DATA_DIRS = [ "${pkgs.gdk_pixbuf.installedTests}/share" ];
 
-    virtualisation.memorySize = 4096; # Tests allocate a lot of memory trying to exploit a CVE
+    # Tests allocate a lot of memory trying to exploit a CVE
+    # but qemu-system-i386 has a 2047M memory limit
+    virtualisation.memorySize = if pkgs.stdenv.isi686 then 2047 else 4096;
   };
 
   testScript = ''
-    $machine->succeed("gnome-desktop-testing-runner");
+    $machine->succeed("gnome-desktop-testing-runner -t 1800"); # increase timeout to 1800s
   '';
 })
diff --git a/nixos/tests/gitea.nix b/nixos/tests/gitea.nix
new file mode 100644
index 0000000000000..28e6479e9cbed
--- /dev/null
+++ b/nixos/tests/gitea.nix
@@ -0,0 +1,79 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+{
+  mysql = makeTest {
+    name = "gitea-mysql";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    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.database.type = "mysql";
+        services.gitea.database.socket = "/run/mysqld/mysqld.sock";
+      };
+
+    testScript = ''
+      startAll;
+
+      $machine->waitForUnit('gitea.service');
+      $machine->waitForOpenPort('3000');
+      $machine->succeed("curl --fail http://localhost:3000/");
+    '';
+  };
+
+  postgres = makeTest {
+    name = "gitea-postgres";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      {
+        services.gitea.enable = true;
+        services.gitea.database.type = "postgres";
+        services.gitea.database.password = "secret";
+      };
+
+    testScript = ''
+      startAll;
+
+      $machine->waitForUnit('gitea.service');
+      $machine->waitForOpenPort('3000');
+      $machine->succeed("curl --fail http://localhost:3000/");
+    '';
+  };
+
+  sqlite = makeTest {
+    name = "gitea-sqlite";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      { services.gitea.enable = true;
+        services.gitea.disableRegistration = true;
+      };
+
+    testScript = ''
+      startAll;
+
+      $machine->waitForUnit('gitea.service');
+      $machine->waitForOpenPort('3000');
+      $machine->succeed("curl --fail http://localhost:3000/");
+      $machine->succeed("curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. Please contact your site administrator.'");
+    '';
+  };
+}
diff --git a/nixos/tests/gitlab.nix b/nixos/tests/gitlab.nix
index 3af2cbcd09885..16e0dd723ecf4 100644
--- a/nixos/tests/gitlab.nix
+++ b/nixos/tests/gitlab.nix
@@ -1,17 +1,22 @@
 # This test runs gitlab and checks if it works
 
-import ./make-test.nix ({ pkgs, ...} : {
+import ./make-test.nix ({ pkgs, lib, ...} : with lib; {
   name = "gitlab";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ domenkozar offline ];
+    maintainers = [ globin ];
   };
 
   nodes = {
     gitlab = { ... }: {
-      virtualisation.memorySize = 768;
+      virtualisation.memorySize = if pkgs.stdenv.is64bit then 4096 else 2047;
+      systemd.services.gitlab.serviceConfig.Restart = mkForce "no";
+      systemd.services.gitlab-workhorse.serviceConfig.Restart = mkForce "no";
+      systemd.services.gitaly.serviceConfig.Restart = mkForce "no";
+      systemd.services.gitlab-sidekiq.serviceConfig.Restart = mkForce "no";
 
       services.nginx = {
         enable = true;
+        recommendedProxySettings = true;
         virtualHosts = {
           "localhost" = {
             locations."/".proxyPass = "http://unix:/run/gitlab/gitlab-workhorse.socket";
@@ -19,10 +24,11 @@ import ./make-test.nix ({ pkgs, ...} : {
         };
       };
 
-      systemd.services.gitlab.serviceConfig.TimeoutStartSec = "10min";
       services.gitlab = {
         enable = true;
         databasePassword = "dbPassword";
+        initialRootPassword = "notproduction";
+        smtp.enable = true;
         secrets = {
           secret = "secret";
           otp = "otpsecret";
@@ -65,8 +71,13 @@ import ./make-test.nix ({ pkgs, ...} : {
 
   testScript = ''
     $gitlab->start();
+    $gitlab->waitForUnit("gitaly.service");
+    $gitlab->waitForUnit("gitlab-workhorse.service");
     $gitlab->waitForUnit("gitlab.service");
     $gitlab->waitForUnit("gitlab-sidekiq.service");
-    $gitlab->waitUntilSucceeds("curl http://localhost:80/users/sign_in");
+    $gitlab->waitForFile("/var/gitlab/state/tmp/sockets/gitlab.socket");
+    $gitlab->waitUntilSucceeds("curl -sSf http://gitlab/users/sign_in");
+    $gitlab->succeed("curl -isSf http://gitlab  | grep -i location | grep -q http://gitlab/users/sign_in");
+    $gitlab->succeed("${pkgs.sudo}/bin/sudo -u gitlab -H gitlab-rake gitlab:check 1>&2")
   '';
 })
diff --git a/nixos/tests/gnome3-gdm.nix b/nixos/tests/gnome3-gdm.nix
index 959030d598802..c2808d87d99db 100644
--- a/nixos/tests/gnome3-gdm.nix
+++ b/nixos/tests/gnome3-gdm.nix
@@ -23,11 +23,21 @@ import ./make-test.nix ({ pkgs, ...} : {
       virtualisation.memorySize = 1024;
     };
 
-  testScript =
-    ''
-      # wait for gdm to start and bring up X
+  testScript = let
+    # Keep line widths somewhat managable
+    bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus";
+    gdbus = "${bus} gdbus";
+    # Call javascript in gnome shell, returns a tuple (success, output), where
+    # `success` is true if the dbus call was successful and output is what the
+    # javascript evaluates to.
+    eval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval";
+    # False when startup is done
+    startingUp = "${gdbus} ${eval} Main.layoutManager._startingUp";
+    # Hopefully gnome-terminal's wm class
+    wmClass = "${gdbus} ${eval} global.display.focus_window.wm_class";
+  in ''
+      # wait for gdm to start
       $machine->waitForUnit("display-manager.service");
-      $machine->waitForX;
 
       # wait for alice to be logged in
       $machine->waitForUnit("default.target","alice");
@@ -35,10 +45,16 @@ import ./make-test.nix ({ pkgs, ...} : {
       # Check that logging in has given the user ownership of devices.
       $machine->succeed("getfacl /dev/snd/timer | grep -q alice");
 
-      # open a terminal and check it's there
-      $machine->succeed("su - alice -c 'DISPLAY=:0.0 XAUTHORITY=/run/user/\$UID/gdm/Xauthority gnome-terminal'");
-      $machine->succeed("xauth merge /run/user/1000/gdm/Xauthority");
-      $machine->waitForWindow(qr/Terminal/);
+      # Wait for the wayland server
+      $machine->waitForFile("/run/user/1000/wayland-0");
+
+      # Wait for gnome shell, correct output should be "(true, 'false')"
+      $machine->waitUntilSucceeds("su - alice -c '${startingUp} | grep -q true,..false'");
+
+      # open a terminal
+      $machine->succeed("su - alice -c '${bus} gnome-terminal'");
+      # and check it's there
+      $machine->waitUntilSucceeds("su - alice -c '${wmClass} | grep -q gnome-terminal-server'");
 
       # wait to get a nice screenshot
       $machine->sleep(20);
diff --git a/nixos/tests/gnome3.nix b/nixos/tests/gnome3.nix
index 3f0368592b8d1..95694ea4828d8 100644
--- a/nixos/tests/gnome3.nix
+++ b/nixos/tests/gnome3.nix
@@ -16,7 +16,7 @@ import ./make-test.nix ({ pkgs, ...} : {
       services.xserver.displayManager.lightdm.autoLogin.enable = true;
       services.xserver.displayManager.lightdm.autoLogin.user = "alice";
       services.xserver.desktopManager.gnome3.enable = true;
-      services.xserver.desktopManager.default = "gnome";
+      services.xserver.desktopManager.default = "gnome-xorg";
 
       virtualisation.memorySize = 1024;
     };
@@ -33,7 +33,7 @@ import ./make-test.nix ({ pkgs, ...} : {
 
       $machine->succeed("su - alice -c 'DISPLAY=:0.0 gnome-terminal &'");
       $machine->succeed("xauth merge ~alice/.Xauthority");
-      $machine->waitForWindow(qr/Terminal/);
+      $machine->waitForWindow(qr/alice.*machine/);
       $machine->succeed("timeout 900 bash -c 'while read msg; do if [[ \$msg =~ \"GNOME Shell started\" ]]; then break; fi; done < <(journalctl -f)'");
       $machine->sleep(10);
       $machine->screenshot("screen");
diff --git a/nixos/tests/google-oslogin/default.nix b/nixos/tests/google-oslogin/default.nix
new file mode 100644
index 0000000000000..3b84bba3f9854
--- /dev/null
+++ b/nixos/tests/google-oslogin/default.nix
@@ -0,0 +1,52 @@
+import ../make-test.nix ({ pkgs, ... } :
+let
+  inherit (import ./../ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+in {
+  name = "google-oslogin";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ adisbladis flokli ];
+  };
+
+  nodes = {
+    # the server provides both the the mocked google metadata server and the ssh server
+    server = (import ./server.nix pkgs);
+
+    client = { ... }: {};
+  };
+  testScript =  ''
+    startAll;
+
+    $server->waitForUnit("mock-google-metadata.service");
+    $server->waitForOpenPort(80);
+
+    # mockserver should return a non-expired ssh key for both mockuser and mockadmin
+    $server->succeed('${pkgs.google-compute-engine-oslogin}/bin/google_authorized_keys mockuser | grep -q "${snakeOilPublicKey}"');
+    $server->succeed('${pkgs.google-compute-engine-oslogin}/bin/google_authorized_keys mockadmin | grep -q "${snakeOilPublicKey}"');
+
+    # install snakeoil ssh key on the client
+    $client->succeed("mkdir -p ~/.ssh");
+    $client->succeed("cat ${snakeOilPrivateKey} > ~/.ssh/id_snakeoil");
+    $client->succeed("chmod 600 ~/.ssh/id_snakeoil");
+
+    $client->waitForUnit("network.target");
+    $server->waitForUnit("sshd.service");
+
+    # we should not be able to connect as non-existing user
+    $client->fail("ssh -o User=ghost -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil 'true'");
+
+    # we should be able to connect as mockuser
+    $client->succeed("ssh -o User=mockuser -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil 'true'");
+    # but we shouldn't be able to sudo
+    $client->fail("ssh -o User=mockuser -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil '/run/wrappers/bin/sudo /run/current-system/sw/bin/id' | grep -q 'root'");
+
+    # we should also be able to log in as mockadmin
+    $client->succeed("ssh -o User=mockadmin -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil 'true'");
+    # pam_oslogin_admin.so should now have generated a sudoers file
+    $server->succeed("find /run/google-sudoers.d | grep -q '/run/google-sudoers.d/mockadmin'");
+
+    # and we should be able to sudo
+    $client->succeed("ssh -o User=mockadmin -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no server -i ~/.ssh/id_snakeoil '/run/wrappers/bin/sudo /run/current-system/sw/bin/id' | grep -q 'root'");
+  '';
+  })
+
diff --git a/nixos/tests/google-oslogin/server.nix b/nixos/tests/google-oslogin/server.nix
new file mode 100644
index 0000000000000..fdb7141da3178
--- /dev/null
+++ b/nixos/tests/google-oslogin/server.nix
@@ -0,0 +1,29 @@
+{ pkgs, ... }:
+let
+  inherit (import ./../ssh-keys.nix pkgs)
+    snakeOilPrivateKey snakeOilPublicKey;
+in {
+  networking.firewall.allowedTCPPorts = [ 80 ];
+
+  systemd.services.mock-google-metadata = {
+    description = "Mock Google metadata service";
+    serviceConfig.Type = "simple";
+    serviceConfig.ExecStart = "${pkgs.python3}/bin/python ${./server.py}";
+    environment = {
+      SNAKEOIL_PUBLIC_KEY = snakeOilPublicKey;
+    };
+    wantedBy = [ "multi-user.target" ];
+    after = [ "network.target" ];
+  };
+
+  services.openssh.enable = true;
+  services.openssh.challengeResponseAuthentication = false;
+  services.openssh.passwordAuthentication = false;
+
+  security.googleOsLogin.enable = true;
+
+  # Mock google service
+  networking.extraHosts = ''
+    127.0.0.1 metadata.google.internal
+  '';
+}
diff --git a/nixos/tests/google-oslogin/server.py b/nixos/tests/google-oslogin/server.py
new file mode 100644
index 0000000000000..bfc527cb97d37
--- /dev/null
+++ b/nixos/tests/google-oslogin/server.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+import json
+import sys
+import time
+import os
+import hashlib
+import base64
+
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from typing import Dict
+
+SNAKEOIL_PUBLIC_KEY = os.environ['SNAKEOIL_PUBLIC_KEY']
+
+
+def w(msg):
+    sys.stderr.write(f"{msg}\n")
+    sys.stderr.flush()
+
+
+def gen_fingerprint(pubkey):
+    decoded_key = base64.b64decode(pubkey.encode("ascii").split()[1])
+    return hashlib.sha256(decoded_key).hexdigest()
+
+def gen_email(username):
+    """username seems to be a 21 characters long number string, so mimic that in a reproducible way"""
+    return str(int(hashlib.sha256(username.encode()).hexdigest(), 16))[0:21]
+
+def gen_mockuser(username: str, uid: str, gid: str, home_directory: str, snakeoil_pubkey: str) -> Dict:
+    snakeoil_pubkey_fingerprint = gen_fingerprint(snakeoil_pubkey)
+    # seems to be a 21 characters long numberstring, so mimic that in a reproducible way
+    email = gen_email(username)
+    return {
+        "loginProfiles": [
+            {
+                "name": email,
+                "posixAccounts": [
+                    {
+                        "primary": True,
+                        "username": username,
+                        "uid": uid,
+                        "gid": gid,
+                        "homeDirectory": home_directory,
+                        "operatingSystemType": "LINUX"
+                    }
+                ],
+                "sshPublicKeys": {
+                    snakeoil_pubkey_fingerprint: {
+                        "key": snakeoil_pubkey,
+                        "expirationTimeUsec": str((time.time() + 600) * 1000000),  # 10 minutes in the future
+                        "fingerprint": snakeoil_pubkey_fingerprint
+                    }
+                }
+            }
+        ]
+    }
+
+
+class ReqHandler(BaseHTTPRequestHandler):
+    def _send_json_ok(self, data):
+        self.send_response(200)
+        self.send_header('Content-type', 'application/json')
+        self.end_headers()
+        out = json.dumps(data).encode()
+        w(out)
+        self.wfile.write(out)
+
+    def do_GET(self):
+        p = str(self.path)
+        # mockuser and mockadmin are allowed to login, both use the same snakeoil public key
+        if p == '/computeMetadata/v1/oslogin/users?username=mockuser' \
+            or p == '/computeMetadata/v1/oslogin/users?uid=1009719690':
+            self._send_json_ok(gen_mockuser(username='mockuser', uid='1009719690', gid='1009719690',
+                                            home_directory='/home/mockuser', snakeoil_pubkey=SNAKEOIL_PUBLIC_KEY))
+        elif p == '/computeMetadata/v1/oslogin/users?username=mockadmin' \
+            or p == '/computeMetadata/v1/oslogin/users?uid=1009719691':
+            self._send_json_ok(gen_mockuser(username='mockadmin', uid='1009719691', gid='1009719691',
+                                            home_directory='/home/mockadmin', snakeoil_pubkey=SNAKEOIL_PUBLIC_KEY))
+
+        # mockuser is allowed to login
+        elif p == f"/computeMetadata/v1/oslogin/authorize?email={gen_email('mockuser')}&policy=login":
+            self._send_json_ok({'success': True})
+
+        # mockadmin may also become root
+        elif p == f"/computeMetadata/v1/oslogin/authorize?email={gen_email('mockadmin')}&policy=login" or p == f"/computeMetadata/v1/oslogin/authorize?email={gen_email('mockadmin')}&policy=adminLogin":
+            self._send_json_ok({'success': True})
+        else:
+            sys.stderr.write(f"Unhandled path: {p}\n")
+            sys.stderr.flush()
+            self.send_response(501)
+            self.end_headers()
+            self.wfile.write(b'')
+
+
+if __name__ == '__main__':
+    s = HTTPServer(('0.0.0.0', 80), ReqHandler)
+    s.serve_forever()
diff --git a/nixos/tests/handbrake.nix b/nixos/tests/handbrake.nix
new file mode 100644
index 0000000000000..ae87e1f69a7d3
--- /dev/null
+++ b/nixos/tests/handbrake.nix
@@ -0,0 +1,25 @@
+import ./make-test.nix ({ pkgs, ... }:
+let
+  # Download Big Buck Bunny example, licensed under CC Attribution 3.0.
+  testMkv = pkgs.fetchurl {
+    url = "https://github.com/Matroska-Org/matroska-test-files/blob/cf0792be144ac470c4b8052cfe19bb691993e3a2/test_files/test1.mkv?raw=true";
+    sha256 = "1hfxbbgxwfkzv85pvpvx55a72qsd0hxjbm9hkl5r3590zw4s75h9";
+  };
+in {
+  name = "handbrake";
+
+  meta = {
+    maintainers = with pkgs.stdenv.lib.maintainers; [ danieldk ];
+  };
+
+  machine = { pkgs, ... }: {
+    environment.systemPackages = with pkgs; [ handbrake ];
+  };
+
+  testScript = ''
+    # Test MP4 and MKV transcoding. Since this is a short clip, transcoding typically
+    # only takes a few seconds.
+    $machine->succeed("HandBrakeCLI -i ${testMkv} -o test.mp4 -e x264 -q 20 -B 160");
+    $machine->succeed("HandBrakeCLI -i ${testMkv} -o test.mkv -e x264 -q 20 -B 160");
+  '';
+})
diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix
index 2700b8e5935a9..07bd10963bab6 100644
--- a/nixos/tests/hardened.nix
+++ b/nixos/tests/hardened.nix
@@ -5,11 +5,12 @@ import ./make-test.nix ({ pkgs, ...} : {
   };
 
   machine =
-    { lib, pkgs, ... }:
+    { lib, pkgs, config, ... }:
     with lib;
     { users.users.alice = { isNormalUser = true; extraGroups = [ "proc" ]; };
       users.users.sybil = { isNormalUser = true; group = "wheel"; };
       imports = [ ../modules/profiles/hardened.nix ];
+      nix.useSandbox = false;
       virtualisation.emptyDiskImages = [ 4096 ];
       boot.initrd.postDeviceCommands = ''
         ${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb
@@ -21,12 +22,19 @@ import ./make-test.nix ({ pkgs, ...} : {
           options = [ "noauto" ];
         };
       };
+      boot.extraModulePackages = [ config.boot.kernelPackages.wireguard ];
+      boot.kernelModules = [ "wireguard" ];
     };
 
   testScript =
     ''
       $machine->waitForUnit("multi-user.target");
 
+      # Test loading out-of-tree modules
+      subtest "extra-module-packages", sub {
+          $machine->succeed("grep -Fq wireguard /proc/modules");
+      };
+
       # Test hidepid
       subtest "hidepid", sub {
           $machine->succeed("grep -Fq hidepid=2 /proc/mounts");
@@ -63,5 +71,17 @@ import ./make-test.nix ({ pkgs, ...} : {
         $machine->succeed("mount /dev/disk/by-label/EFISYS /efi");
         $machine->succeed("mountpoint -q /efi"); # now mounted
       };
+
+      # Test Nix dæmon usage
+      subtest "nix-daemon", sub {
+        $machine->fail("su -l nobody -s /bin/sh -c 'nix ping-store'");
+        $machine->succeed("su -l alice -c 'nix ping-store'") =~ "OK";
+      };
+
+      # Test kernel image protection
+      subtest "kernelimage", sub {
+        $machine->fail("systemctl hibernate");
+        $machine->fail("systemctl kexec");
+      };
     '';
 })
diff --git a/nixos/tests/hibernate.nix b/nixos/tests/hibernate.nix
index 1f98bb739f210..274aa7becc825 100644
--- a/nixos/tests/hibernate.nix
+++ b/nixos/tests/hibernate.nix
@@ -35,8 +35,8 @@ import ./make-test.nix (pkgs: {
       $machine->waitForOpenPort(4444);
       $machine->succeed("systemctl hibernate &");
       $machine->waitForShutdown;
+      $probe->waitForUnit("multi-user.target");
       $machine->start;
-      $probe->waitForUnit("network.target");
       $probe->waitUntilSucceeds("echo test | nc machine 4444 -N");
     '';
 
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 2d74b59bca462..a93360b252f6c 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -4,6 +4,7 @@ let
   configDir = "/var/lib/foobar";
   apiPassword = "some_secret";
   mqttPassword = "another_secret";
+  hassCli = "hass-cli --server http://hass:8123 --password '${apiPassword}'";
 
 in {
   name = "home-assistant";
@@ -16,7 +17,7 @@ in {
       { pkgs, ... }:
       {
         environment.systemPackages = with pkgs; [
-          mosquitto
+          mosquitto home-assistant-cli
         ];
         services.home-assistant = {
           inherit configDir;
@@ -31,6 +32,9 @@ in {
               latitude = "0.0";
               longitude = "0.0";
               elevation = 0;
+              auth_providers = [
+                { type = "legacy_api_password"; }
+              ];
             };
             frontend = { };
             http.api_password = apiPassword;
@@ -46,6 +50,18 @@ in {
               }
             ];
           };
+          lovelaceConfig = {
+            title = "My Awesome Home";
+            views = [ {
+              title = "Example";
+              cards = [ {
+                type = "markdown";
+                title = "Lovelace";
+                content = "Welcome to your **Lovelace UI**.";
+              } ];
+            } ];
+          };
+          lovelaceConfigWritable = true;
         };
       };
   };
@@ -55,8 +71,10 @@ in {
     $hass->waitForUnit("home-assistant.service");
 
     # The config is specified using a Nix attribute set,
-    # but then converted from JSON to YAML
-    $hass->succeed("test -f ${configDir}/configuration.yaml");
+    # converted from JSON to YAML, and linked to the config dir
+    $hass->succeed("test -L ${configDir}/configuration.yaml");
+    # The lovelace config is copied because lovelaceConfigWritable = true
+    $hass->succeed("test -f ${configDir}/ui-lovelace.yaml");
 
     # Check that Home Assistant's web interface and API can be reached
     $hass->waitForOpenPort(8123);
@@ -68,13 +86,17 @@ in {
     $hass->waitUntilSucceeds("mosquitto_pub -V mqttv311 -t home-assistant/test -u homeassistant -P '${mqttPassword}' -m let_there_be_light");
     $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"on\"'");
 
+    # Toggle a binary sensor using hass-cli
+    $hass->succeed("${hassCli} --output json entity get binary_sensor.mqtt_binary_sensor | grep -qF '\"state\": \"on\"'");
+    $hass->succeed("${hassCli} entity edit binary_sensor.mqtt_binary_sensor --json='{\"state\": \"off\"}'");
+    $hass->succeed("curl http://localhost:8123/api/states/binary_sensor.mqtt_binary_sensor -H 'x-ha-access: ${apiPassword}' | grep -qF '\"state\": \"off\"'");
+
     # Print log to ease debugging
     my $log = $hass->succeed("cat ${configDir}/home-assistant.log");
     print "\n### home-assistant.log ###\n";
     print "$log\n";
 
     # Check that no errors were logged
-    # The timer can get out of sync due to Hydra's load, so this error is ignored
-    $hass->fail("cat ${configDir}/home-assistant.log | grep -vF 'Timer got out of sync' | grep -qF ERROR");
+    $hass->fail("cat ${configDir}/home-assistant.log | grep -qF ERROR");
   '';
 })
diff --git a/nixos/tests/hound.nix b/nixos/tests/hound.nix
index f21c0ad58a850..cb8e25332c076 100644
--- a/nixos/tests/hound.nix
+++ b/nixos/tests/hound.nix
@@ -52,7 +52,7 @@ import ./make-test.nix ({ pkgs, ... } : {
        $machine->waitForUnit("network.target");
        $machine->waitForUnit("hound.service");
        $machine->waitForOpenPort(6080);
-       $machine->succeed('curl http://127.0.0.1:6080/api/v1/search\?stats\=fosho\&repos\=\*\&rng=%3A20\&q\=hi\&files\=\&i=nope | grep "Filename" | grep "hello"');
+       $machine->waitUntilSucceeds('curl http://127.0.0.1:6080/api/v1/search\?stats\=fosho\&repos\=\*\&rng=%3A20\&q\=hi\&files\=\&i=nope | grep "Filename" | grep "hello"');
 
     '';
 })
diff --git a/nixos/tests/hydra/create-trivial-project.sh b/nixos/tests/hydra/create-trivial-project.sh
index 3cca5665acc56..39122c9b473a1 100755
--- a/nixos/tests/hydra/create-trivial-project.sh
+++ b/nixos/tests/hydra/create-trivial-project.sh
@@ -31,7 +31,8 @@ mycurl -X POST -d '@data.json' $URL/login -c hydra-cookie.txt
 cat >data.json <<EOF
 {
   "displayname":"Trivial",
-  "enabled":"1"
+  "enabled":"1",
+  "visible":"1"
 }
 EOF
 mycurl --silent -X PUT $URL/project/$PROJECT_NAME -d @data.json -b hydra-cookie.txt
diff --git a/nixos/tests/hydra/default.nix b/nixos/tests/hydra/default.nix
index db4e97e0039bd..882bced86d399 100644
--- a/nixos/tests/hydra/default.nix
+++ b/nixos/tests/hydra/default.nix
@@ -1,77 +1,91 @@
-import ../make-test.nix ({ pkgs, ...} :
+{ system ? builtins.currentSystem
+, config ? { }
+, pkgs ? import ../../.. { inherit system config; }
+}:
 
 let
-   trivialJob = pkgs.writeTextDir "trivial.nix" ''
-     { trivial = builtins.derivation {
-         name = "trivial";
-         system = "x86_64-linux";
-         builder = "/bin/sh";
-         args = ["-c" "echo success > $out; exit 0"];
-       };
-     }
-   '';
-
-    createTrivialProject = pkgs.stdenv.mkDerivation {
-      name = "create-trivial-project";
-      unpackPhase = ":";
-      buildInputs = [ pkgs.makeWrapper ];
-      installPhase = "install -m755 -D ${./create-trivial-project.sh} $out/bin/create-trivial-project.sh";
-      postFixup = ''
-        wrapProgram "$out/bin/create-trivial-project.sh" --prefix PATH ":" ${pkgs.stdenv.lib.makeBinPath [ pkgs.curl ]} --set EXPR_PATH ${trivialJob}
-      '';
-    };
-
-in {
-  name = "hydra-init-localdb";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ pstn lewo ma27 ];
-  };
 
-  machine =
-    { pkgs, ... }:
-
-    {
-      virtualisation.memorySize = 1024;
-      time.timeZone = "UTC";
-
-      environment.systemPackages = [ createTrivialProject pkgs.jq ];
-      services.hydra = {
-        enable = true;
-
-        #Hydra needs those settings to start up, so we add something not harmfull.
-        hydraURL = "example.com";
-        notificationSender = "example@example.com";
-      };
-      nix = {
-        buildMachines = [{
-          hostName = "localhost";
-          systems = [ "x86_64-linux" ];
-        }];
-
-        binaryCaches = [];
-      };
-    };
-
-  testScript =
-    ''
-      # let the system boot up
-      $machine->waitForUnit("multi-user.target");
-      # test whether the database is running
-      $machine->succeed("systemctl status postgresql.service");
-      # test whether the actual hydra daemons are running
-      $machine->succeed("systemctl status hydra-queue-runner.service");
-      $machine->succeed("systemctl status hydra-init.service");
-      $machine->succeed("systemctl status hydra-evaluator.service");
-      $machine->succeed("systemctl status hydra-send-stats.service");
-
-      $machine->succeed("hydra-create-user admin --role admin --password admin");
-
-      # create a project with a trivial job
-      $machine->waitForOpenPort(3000);
-
-      # make sure the build as been successfully built
-      $machine->succeed("create-trivial-project.sh");
-
-      $machine->waitUntilSucceeds('curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" |  jq .buildstatus | xargs test 0 -eq');
+  trivialJob = pkgs.writeTextDir "trivial.nix" ''
+   { trivial = builtins.derivation {
+       name = "trivial";
+       system = "x86_64-linux";
+       builder = "/bin/sh";
+       args = ["-c" "echo success > $out; exit 0"];
+     };
+   }
+  '';
+
+  createTrivialProject = pkgs.stdenv.mkDerivation {
+    name = "create-trivial-project";
+    unpackPhase = ":";
+    buildInputs = [ pkgs.makeWrapper ];
+    installPhase = "install -m755 -D ${./create-trivial-project.sh} $out/bin/create-trivial-project.sh";
+    postFixup = ''
+      wrapProgram "$out/bin/create-trivial-project.sh" --prefix PATH ":" ${pkgs.stdenv.lib.makeBinPath [ pkgs.curl ]} --set EXPR_PATH ${trivialJob}
     '';
-})
+  };
+
+  callTest = f: f { inherit system pkgs; };
+
+  hydraPkgs = {
+    inherit (pkgs) nixStable nixUnstable;
+  };
+
+  tests = pkgs.lib.flip pkgs.lib.mapAttrs hydraPkgs (name: nix:
+    callTest (import ../make-test.nix ({ pkgs, lib, ... }:
+      {
+        name = "hydra-with-${name}";
+        meta = with pkgs.stdenv.lib.maintainers; {
+          maintainers = [ pstn lewo ma27 ];
+        };
+
+        machine = { pkgs, ... }:
+          {
+            virtualisation.memorySize = 1024;
+            time.timeZone = "UTC";
+
+            environment.systemPackages = [ createTrivialProject pkgs.jq ];
+            services.hydra = {
+              enable = true;
+
+              #Hydra needs those settings to start up, so we add something not harmfull.
+              hydraURL = "example.com";
+              notificationSender = "example@example.com";
+
+              package = pkgs.hydra.override { inherit nix; };
+            };
+            nix = {
+              buildMachines = [{
+                hostName = "localhost";
+                systems = [ "x86_64-linux" ];
+              }];
+
+              binaryCaches = [];
+            };
+          };
+
+        testScript = ''
+          # let the system boot up
+          $machine->waitForUnit("multi-user.target");
+          # test whether the database is running
+          $machine->succeed("systemctl status postgresql.service");
+          # test whether the actual hydra daemons are running
+          $machine->succeed("systemctl status hydra-queue-runner.service");
+          $machine->succeed("systemctl status hydra-init.service");
+          $machine->succeed("systemctl status hydra-evaluator.service");
+          $machine->succeed("systemctl status hydra-send-stats.service");
+
+          $machine->succeed("hydra-create-user admin --role admin --password admin");
+
+          # create a project with a trivial job
+          $machine->waitForOpenPort(3000);
+
+          # make sure the build as been successfully built
+          $machine->succeed("create-trivial-project.sh");
+
+          $machine->waitUntilSucceeds('curl -L -s http://localhost:3000/build/1 -H "Accept: application/json" |  jq .buildstatus | xargs test 0 -eq');
+        '';
+      })));
+
+in
+  tests
diff --git a/nixos/tests/i3wm.nix b/nixos/tests/i3wm.nix
index e51aee30fdb00..d309f19a0b4a1 100644
--- a/nixos/tests/i3wm.nix
+++ b/nixos/tests/i3wm.nix
@@ -28,7 +28,7 @@ import ./make-test.nix ({ pkgs, ...} : {
     $machine->waitForFile("/home/alice/.config/i3/config");
     $machine->sleep(2);
     $machine->sendKeys("alt-ret");
-    $machine->waitForWindow(qr/machine.*alice/);
+    $machine->waitForWindow(qr/alice.*machine/);
     $machine->sleep(2);
     $machine->screenshot("terminal");
   '';
diff --git a/nixos/tests/incron.nix b/nixos/tests/incron.nix
new file mode 100644
index 0000000000000..e39bbb5f096be
--- /dev/null
+++ b/nixos/tests/incron.nix
@@ -0,0 +1,52 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+
+{
+  name = "incron";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  machine =
+    { ... }:
+    { services.incron.enable = true;
+      services.incron.extraPackages = [ pkgs.coreutils ];
+      services.incron.systab = ''
+        /test IN_CREATE,IN_MODIFY,IN_CLOSE_WRITE,IN_MOVED_FROM,IN_MOVED_TO echo "$@/$# $%" >> /root/incron.log
+      '';
+
+      # ensure the directory to be monitored exists before incron is started
+      system.activationScripts.incronTest = ''
+        mkdir /test
+      '';
+    };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit("multi-user.target");
+    $machine->waitForUnit("incron.service");
+
+    $machine->succeed("test -d /test");
+    # create some activity for incron to monitor
+    $machine->succeed("touch /test/file");
+    $machine->succeed("echo foo >> /test/file");
+    $machine->succeed("mv /test/file /root");
+    $machine->succeed("mv /root/file /test");
+
+    $machine->sleep(1);
+
+    # touch /test/file
+    $machine->succeed("grep '/test/file IN_CREATE' /root/incron.log");
+
+    # echo foo >> /test/file
+    $machine->succeed("grep '/test/file IN_MODIFY' /root/incron.log");
+    $machine->succeed("grep '/test/file IN_CLOSE_WRITE' /root/incron.log");
+
+    # mv /test/file /root
+    $machine->succeed("grep '/test/file IN_MOVED_FROM' /root/incron.log");
+
+    # mv /root/file /test
+    $machine->succeed("grep '/test/file IN_MOVED_TO' /root/incron.log");
+
+    # ensure something unexpected is not present
+    $machine->fail("grep 'IN_OPEN' /root/incron.log");
+  '';
+})
diff --git a/nixos/tests/installer.nix b/nixos/tests/installer.nix
index 3d31c8dc4457c..2553a0d116ae5 100644
--- a/nixos/tests/installer.nix
+++ b/nixos/tests/installer.nix
@@ -1,6 +1,9 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
@@ -197,7 +200,7 @@ let
       name = "installer-" + name;
       meta = with pkgs.stdenv.lib.maintainers; {
         # put global maintainers here, individuals go into makeInstallerTest fkt call
-        maintainers = [ wkennington ] ++ (meta.maintainers or []);
+        maintainers = (meta.maintainers or []);
       };
       nodes = {
 
@@ -282,9 +285,9 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary linux-swap 1M 1024M",
-              "parted --script /dev/vda -- mkpart primary ext2 1024M -1s",
+              "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",
@@ -299,11 +302,11 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel gpt",
-              "parted --script /dev/vda -- mkpart ESP fat32 1M 50MiB", # /boot
-              "parted --script /dev/vda -- set 1 boot on",
-              "parted --script /dev/vda -- mkpart primary linux-swap 50MiB 1024MiB",
-              "parted --script /dev/vda -- mkpart primary ext2 1024MiB -1MiB", # /
+              "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+              . " mkpart ESP fat32 1M 50MiB" # /boot
+              . " set 1 boot on"
+              . " mkpart primary linux-swap 50MiB 1024MiB"
+              . " mkpart primary ext2 1024MiB -1MiB", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -321,11 +324,11 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel gpt",
-              "parted --script /dev/vda -- mkpart ESP fat32 1M 50MiB", # /boot
-              "parted --script /dev/vda -- set 1 boot on",
-              "parted --script /dev/vda -- mkpart primary linux-swap 50MiB 1024MiB",
-              "parted --script /dev/vda -- mkpart primary ext2 1024MiB -1MiB", # /
+              "flock /dev/vda parted --script /dev/vda -- mklabel gpt"
+              . " mkpart ESP fat32 1M 50MiB" # /boot
+              . " set 1 boot on"
+              . " mkpart primary linux-swap 50MiB 1024MiB"
+              . " mkpart primary ext2 1024MiB -1MiB", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -345,10 +348,10 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-              "parted --script /dev/vda -- mkpart primary linux-swap 50MB 1024M",
-              "parted --script /dev/vda -- mkpart primary ext2 1024M -1s", # /
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary ext2 1M 50MB" # /boot
+              . " mkpart primary linux-swap 50MB 1024M"
+              . " mkpart primary ext2 1024M -1s", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -366,10 +369,10 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-              "parted --script /dev/vda -- mkpart primary linux-swap 50MB 1024M",
-              "parted --script /dev/vda -- mkpart primary ext2 1024M -1s", # /
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary ext2 1M 50MB" # /boot
+              . " mkpart primary linux-swap 50MB 1024M"
+              . " mkpart primary ext2 1024M -1s", # /
               "udevadm settle",
               "mkswap /dev/vda2 -L swap",
               "swapon -L swap",
@@ -402,9 +405,9 @@ in {
       createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary linux-swap 1M 1024M",
-              "parted --script /dev/vda -- mkpart primary 1024M -1s",
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary linux-swap 1M 1024M"
+              . " mkpart primary 1024M -1s",
               "udevadm settle",
 
               "mkswap /dev/vda1 -L swap",
@@ -425,11 +428,11 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda mklabel msdos",
-              "parted --script /dev/vda -- mkpart primary 1M 2048M", # PV1
-              "parted --script /dev/vda -- set 1 lvm on",
-              "parted --script /dev/vda -- mkpart primary 2048M -1s", # PV2
-              "parted --script /dev/vda -- set 2 lvm on",
+              "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+              . " mkpart primary 1M 2048M" # PV1
+              . " set 1 lvm on"
+              . " mkpart primary 2048M -1s" # PV2
+              . " set 2 lvm on",
               "udevadm settle",
               "pvcreate /dev/vda1 /dev/vda2",
               "vgcreate MyVolGroup /dev/vda1 /dev/vda2",
@@ -447,10 +450,10 @@ in {
   luksroot = makeInstallerTest "luksroot"
     { createPartitions = ''
         $machine->succeed(
-          "parted --script /dev/vda mklabel msdos",
-          "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-          "parted --script /dev/vda -- mkpart primary linux-swap 50M 1024M",
-          "parted --script /dev/vda -- mkpart primary 1024M -1s", # LUKS
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          . " mkpart primary ext2 1M 50MB" # /boot
+          . " mkpart primary linux-swap 50M 1024M"
+          . " mkpart primary 1024M -1s", # LUKS
           "udevadm settle",
           "mkswap /dev/vda2 -L swap",
           "swapon -L swap",
@@ -478,14 +481,14 @@ in {
   # Test whether opening encrypted filesystem with keyfile
   # Checks for regression of missing cryptsetup, when no luks device without
   # keyfile is configured
-  filesystemEncryptedWithKeyfile = makeInstallerTest "filesystemEncryptedWithKeyfile"
+  encryptedFSWithKeyfile = makeInstallerTest "encryptedFSWithKeyfile"
     { createPartitions = ''
        $machine->succeed(
-          "parted --script /dev/vda mklabel msdos",
-          "parted --script /dev/vda -- mkpart primary ext2 1M 50MB", # /boot
-          "parted --script /dev/vda -- mkpart primary linux-swap 50M 1024M",
-          "parted --script /dev/vda -- mkpart primary 1024M 1280M", # LUKS with keyfile
-          "parted --script /dev/vda -- mkpart primary 1280M -1s",
+          "flock /dev/vda parted --script /dev/vda -- mklabel msdos"
+          . " mkpart primary ext2 1M 50MB" # /boot
+          . " mkpart primary linux-swap 50M 1024M"
+          . " mkpart primary 1024M 1280M" # LUKS with keyfile
+          . " mkpart primary 1280M -1s",
           "udevadm settle",
           "mkswap /dev/vda2 -L swap",
           "swapon -L swap",
@@ -520,7 +523,7 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/vda --"
+              "flock /dev/vda parted --script /dev/vda --"
               . " mklabel msdos"
               . " mkpart primary ext2 1M 100MB" # /boot
               . " mkpart extended 100M -1s"
@@ -531,8 +534,10 @@ in {
               "udevadm settle",
               "ls -l /dev/vda* >&2",
               "cat /proc/partitions >&2",
+              "udevadm control --stop-exec-queue",
               "mdadm --create --force /dev/md0 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda5 /dev/vda6",
               "mdadm --create --force /dev/md1 --metadata 1.2 --level=raid1 --raid-devices=2 /dev/vda7 /dev/vda8",
+              "udevadm control --start-exec-queue",
               "udevadm settle",
               "mkswap -f /dev/md1 -L swap",
               "swapon -L swap",
@@ -555,14 +560,15 @@ in {
     { createPartitions =
         ''
           $machine->succeed(
-              "parted --script /dev/sda mklabel msdos",
-              "parted --script /dev/sda -- mkpart primary linux-swap 1M 1024M",
-              "parted --script /dev/sda -- mkpart primary ext2 1024M -1s",
+              "flock /dev/sda parted --script /dev/sda -- mklabel msdos"
+              . " mkpart primary linux-swap 1M 1024M"
+              . " mkpart primary ext2 1024M -1s",
               "udevadm settle",
               "mkswap /dev/sda1 -L swap",
               "swapon -L swap",
               "mkfs.ext3 -L nixos /dev/sda2",
               "mount LABEL=nixos /mnt",
+              "mkdir -p /mnt/tmp",
           );
         '';
       grubVersion = 1;
diff --git a/nixos/tests/jackett.nix b/nixos/tests/jackett.nix
new file mode 100644
index 0000000000000..399a0c2723273
--- /dev/null
+++ b/nixos/tests/jackett.nix
@@ -0,0 +1,18 @@
+import ./make-test.nix ({ lib, ... }:
+
+with lib;
+
+rec {
+  name = "jackett";
+  meta.maintainers = with maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.jackett.enable = true; };
+
+  testScript = ''
+    $machine->waitForUnit('jackett.service');
+    $machine->waitForOpenPort('9117');
+    $machine->succeed("curl --fail http://localhost:9117/");
+  '';
+})
diff --git a/nixos/tests/kafka.nix b/nixos/tests/kafka.nix
index c9fd74620efb1..72f91f6428a5a 100644
--- a/nixos/tests/kafka.nix
+++ b/nixos/tests/kafka.nix
@@ -1,5 +1,9 @@
-{ system ? builtins.currentSystem }:
-with import ../lib/testing.nix { inherit system; };
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
@@ -36,7 +40,7 @@ let
 
         networking.firewall.allowedTCPPorts = [ 9092 ];
         # i686 tests: qemu-system-i386 can simulate max 2047MB RAM (not 2048)
-        virtualisation.memorySize = 2047; 
+        virtualisation.memorySize = 2047;
       };
     };
 
@@ -66,4 +70,6 @@ in with pkgs; {
   kafka_0_11 = makeKafkaTest "kafka_0_11" apacheKafka_0_11;
   kafka_1_0  = makeKafkaTest "kafka_1_0"  apacheKafka_1_0;
   kafka_1_1  = makeKafkaTest "kafka_1_1"  apacheKafka_1_1;
+  kafka_2_0  = makeKafkaTest "kafka_2_0"  apacheKafka_2_0;
+  kafka_2_1  = makeKafkaTest "kafka_2_1"  apacheKafka_2_1;
 }
diff --git a/nixos/tests/kerberos/default.nix b/nixos/tests/kerberos/default.nix
new file mode 100644
index 0000000000000..f2f1a438918c0
--- /dev/null
+++ b/nixos/tests/kerberos/default.nix
@@ -0,0 +1,7 @@
+{ system ? builtins.currentSystem
+, pkgs ? import ../../.. { inherit system; }
+}:
+{
+  mit = import ./mit.nix { inherit system pkgs; };
+  heimdal = import ./heimdal.nix { inherit system pkgs; };
+}
diff --git a/nixos/tests/kerberos/heimdal.nix b/nixos/tests/kerberos/heimdal.nix
new file mode 100644
index 0000000000000..a0551b131e914
--- /dev/null
+++ b/nixos/tests/kerberos/heimdal.nix
@@ -0,0 +1,53 @@
+import ../make-test.nix ({pkgs, ...}: {
+  name = "kerberos_server-heimdal";
+  machine = { config, libs, pkgs, ...}:
+  { services.kerberos_server =
+    { enable = true;
+      realms = {
+        "FOO.BAR".acl = [{principal = "admin"; access = ["add" "cpw"];}];
+      };
+    };
+    krb5 = {
+      enable = true;
+      kerberos = pkgs.heimdalFull;
+      libdefaults = {
+        default_realm = "FOO.BAR";
+      };
+      realms = {
+        "FOO.BAR" = {
+          admin_server = "machine";
+          kdc = "machine";
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    $machine->start;
+
+    $machine->succeed(
+      "kadmin -l init --realm-max-ticket-life='8 day' \\
+       --realm-max-renewable-life='10 day' FOO.BAR"
+    );
+
+    $machine->succeed("systemctl restart kadmind.service kdc.service");
+    $machine->waitForUnit("kadmind.service");
+    $machine->waitForUnit("kdc.service");
+    $machine->waitForUnit("kpasswdd.service");
+
+    $machine->succeed(
+      "kadmin -l add --password=admin_pw --use-defaults admin"
+    );
+    $machine->succeed(
+      "kadmin -l ext_keytab --keytab=admin.keytab admin"
+    );
+    $machine->succeed(
+      "kadmin -p admin -K admin.keytab add --password=alice_pw --use-defaults \\
+       alice"
+    );
+    $machine->succeed(
+      "kadmin -l ext_keytab --keytab=alice.keytab alice"
+    );
+    $machine->succeed("kinit -kt alice.keytab alice");
+  '';
+})
diff --git a/nixos/tests/kerberos/mit.nix b/nixos/tests/kerberos/mit.nix
new file mode 100644
index 0000000000000..6da3a384aa995
--- /dev/null
+++ b/nixos/tests/kerberos/mit.nix
@@ -0,0 +1,45 @@
+import ../make-test.nix ({pkgs, ...}: {
+  name = "kerberos_server-mit";
+  machine = { config, libs, pkgs, ...}:
+  { services.kerberos_server =
+    { enable = true;
+      realms = {
+        "FOO.BAR".acl = [{principal = "admin"; access = ["add" "cpw"];}];
+      };
+    };
+    krb5 = {
+      enable = true;
+      kerberos = pkgs.krb5Full;
+      libdefaults = {
+        default_realm = "FOO.BAR";
+      };
+      realms = {
+        "FOO.BAR" = {
+          admin_server = "machine";
+          kdc = "machine";
+        };
+      };
+    };
+    users.extraUsers.alice = { isNormalUser = true; };
+  };
+
+  testScript = ''
+    $machine->start;
+
+    $machine->succeed(
+      "kdb5_util create -s -r FOO.BAR -P master_key"
+    );
+
+    $machine->succeed("systemctl restart kadmind.service kdc.service");
+    $machine->waitForUnit("kadmind.service");
+    $machine->waitForUnit("kdc.service");
+
+    $machine->succeed(
+      "kadmin.local add_principal -pw admin_pw admin"
+    );
+    $machine->succeed(
+      "kadmin -p admin -w admin_pw addprinc -pw alice_pw alice"
+    );
+    $machine->succeed("echo alice_pw | sudo -u alice kinit");
+  '';
+})
diff --git a/nixos/tests/keymap.nix b/nixos/tests/keymap.nix
index be880388314c1..2b4c1ab7b0529 100644
--- a/nixos/tests/keymap.nix
+++ b/nixos/tests/keymap.nix
@@ -1,6 +1,9 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 
 let
   readyFile  = "/tmp/readerReady";
@@ -96,7 +99,7 @@ in pkgs.lib.mapAttrs mkKeyboardTest {
       homerow.expect = [ "a" "r" "s" "t" "n" "e" "i" "o"         ];
     };
 
-    extraConfig.i18n.consoleKeyMap = "en-latin9";
+    extraConfig.i18n.consoleKeyMap = "colemak/colemak";
     extraConfig.services.xserver.layout = "us";
     extraConfig.services.xserver.xkbVariant = "colemak";
   };
diff --git a/nixos/tests/kubernetes/base.nix b/nixos/tests/kubernetes/base.nix
index e4bc5b326d345..9d77be1317518 100644
--- a/nixos/tests/kubernetes/base.nix
+++ b/nixos/tests/kubernetes/base.nix
@@ -1,6 +1,9 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; }
+}:
 
-with import ../../lib/testing.nix { inherit system; };
+with import ../../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
diff --git a/nixos/tests/kubernetes/dns.nix b/nixos/tests/kubernetes/dns.nix
index 30e1acd255a08..f25ea5b9ed841 100644
--- a/nixos/tests/kubernetes/dns.nix
+++ b/nixos/tests/kubernetes/dns.nix
@@ -87,7 +87,7 @@ let
       # check if pods are running
       $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running");
       $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running");
-      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'kube-dns.*3/3'");
+      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'");
 
       # check dns on host (dnsmasq)
       $machine1->succeed("host redis.default.svc.cluster.local");
@@ -111,7 +111,7 @@ let
       # check if pods are running
       $machine1->waitUntilSucceeds("kubectl get pod redis | grep Running");
       $machine1->waitUntilSucceeds("kubectl get pod probe | grep Running");
-      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'kube-dns.*3/3'");
+      $machine1->waitUntilSucceeds("kubectl get pods -n kube-system | grep 'coredns.*1/1'");
 
       # check dns on hosts (dnsmasq)
       $machine1->succeed("host redis.default.svc.cluster.local");
diff --git a/nixos/tests/kubernetes/kubernetes-common.nix b/nixos/tests/kubernetes/kubernetes-common.nix
index 125c176f1132c..87c65b883659c 100644
--- a/nixos/tests/kubernetes/kubernetes-common.nix
+++ b/nixos/tests/kubernetes/kubernetes-common.nix
@@ -3,7 +3,6 @@ with pkgs.lib;
 let
   base = {
     inherit roles;
-    featureGates = ["AllAlpha"];
     flannel.enable = true;
     addons.dashboard.enable = true;
 
diff --git a/nixos/tests/ldap.nix b/nixos/tests/ldap.nix
index 035a819241748..b3fd42e75886a 100644
--- a/nixos/tests/ldap.nix
+++ b/nixos/tests/ldap.nix
@@ -1,41 +1,23 @@
 import ./make-test.nix ({ pkgs, lib, ...} :
 
 let
+  unlines = lib.concatStringsSep "\n";
+  unlinesAttrs = f: as: unlines (lib.mapAttrsToList f as);
 
+  dbDomain = "example.com";
   dbSuffix = "dc=example,dc=com";
-  dbPath = "/var/db/openldap";
   dbAdminDn = "cn=admin,${dbSuffix}";
-  dbAdminPwd = "test";
-  serverUri = "ldap:///";
+  dbAdminPwd = "admin-password";
+  # NOTE: slappasswd -h "{SSHA}" -s '${dbAdminPwd}'
+  dbAdminPwdHash = "{SSHA}i7FopSzkFQMrHzDMB1vrtkI0rBnwouP8";
   ldapUser = "test-ldap-user";
   ldapUserId = 10000;
-  ldapUserPwd = "test";
+  ldapUserPwd = "user-password";
+  # NOTE: slappasswd -h "{SSHA}" -s '${ldapUserPwd}'
+  ldapUserPwdHash = "{SSHA}v12XICMZNGT6r2KJ26rIkN8Vvvp4QX6i";
   ldapGroup = "test-ldap-group";
   ldapGroupId = 10000;
-  setupLdif = pkgs.writeText "test-ldap.ldif" ''
-    dn: ${dbSuffix}
-    dc: ${with lib; let dc = head (splitString "," dbSuffix); dcName = head (tail (splitString "=" dc)); in dcName}
-    o: ${dbSuffix}
-    objectclass: top
-    objectclass: dcObject
-    objectclass: organization
-
-    dn: cn=${ldapUser},${dbSuffix}
-    sn: ${ldapUser}
-    objectClass: person
-    objectClass: posixAccount
-    uid: ${ldapUser}
-    uidNumber: ${toString ldapUserId}
-    gidNumber: ${toString ldapGroupId}
-    homeDirectory: /home/${ldapUser}
-    loginShell: /bin/sh
-    userPassword: ${ldapUserPwd}
-
-    dn: cn=${ldapGroup},${dbSuffix}
-    objectClass: posixGroup
-    gidNumber: ${toString ldapGroupId}
-    memberUid: ${ldapUser}
-  '';
+
   mkClient = useDaemon:
     { lib, ... }:
     {
@@ -43,13 +25,24 @@ let
       virtualisation.vlans = [ 1 ];
       security.pam.services.su.rootOK = lib.mkForce false;
       users.ldap.enable = true;
-      users.ldap.daemon.enable = useDaemon;
+      users.ldap.daemon = {
+        enable = useDaemon;
+        rootpwmoddn = "cn=admin,${dbSuffix}";
+        rootpwmodpw = "/etc/nslcd.rootpwmodpw";
+      };
+      # NOTE: password stored in clear in Nix's store, but this is a test.
+      environment.etc."nslcd.rootpwmodpw".source = pkgs.writeText "rootpwmodpw" dbAdminPwd;
       users.ldap.loginPam = true;
       users.ldap.nsswitch = true;
       users.ldap.server = "ldap://server";
-      users.ldap.base = "${dbSuffix}";
+      users.ldap.base = "ou=posix,${dbSuffix}";
+      users.ldap.bind = {
+        distinguishedName = "cn=admin,${dbSuffix}";
+        password = "/etc/ldap/bind.password";
+      };
+      # NOTE: password stored in clear in Nix's store, but this is a test.
+      environment.etc."ldap/bind.password".source = pkgs.writeText "password" dbAdminPwd;
     };
-
 in
 
 {
@@ -61,28 +54,237 @@ in
   nodes = {
 
     server =
-      { pkgs, ... }:
+      { pkgs, config, ... }:
+      let
+        inherit (config.services) openldap;
+
+        slapdConfig = pkgs.writeText "cn=config.ldif" (''
+          dn: cn=config
+          objectClass: olcGlobal
+          #olcPidFile: /run/slapd/slapd.pid
+          # List of arguments that were passed to the server
+          #olcArgsFile: /run/slapd/slapd.args
+          # Read slapd-config(5) for possible values
+          olcLogLevel: none
+          # The tool-threads parameter sets the actual amount of CPU's
+          # that is used for indexing.
+          olcToolThreads: 1
+
+          dn: olcDatabase={-1}frontend,cn=config
+          objectClass: olcDatabaseConfig
+          objectClass: olcFrontendConfig
+          # The maximum number of entries that is returned for a search operation
+          olcSizeLimit: 500
+          # Allow unlimited access to local connection from the local root user
+          olcAccess: to *
+            by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
+            by * break
+          # Allow unauthenticated read access for schema and base DN autodiscovery
+          olcAccess: to dn.exact=""
+            by * read
+          olcAccess: to dn.base="cn=Subschema"
+            by * read
+
+          dn: olcDatabase=config,cn=config
+          objectClass: olcDatabaseConfig
+          olcRootDN: cn=admin,cn=config
+          #olcRootPW:
+          # NOTE: access to cn=config, system root can be manager
+          # with SASL mechanism (-Y EXTERNAL) over unix socket (-H ldapi://)
+          olcAccess: to *
+            by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
+            by * break
+
+          dn: cn=schema,cn=config
+          objectClass: olcSchemaConfig
+
+          include: file://${pkgs.openldap}/etc/schema/core.ldif
+          include: file://${pkgs.openldap}/etc/schema/cosine.ldif
+          include: file://${pkgs.openldap}/etc/schema/nis.ldif
+          include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
+
+          dn: cn=module{0},cn=config
+          objectClass: olcModuleList
+          # Where the dynamically loaded modules are stored
+          #olcModulePath: /usr/lib/ldap
+          olcModuleLoad: back_mdb
+
+          ''
+          + unlinesAttrs (olcSuffix: {conf, ...}:
+              "include: file://" + pkgs.writeText "config.ldif" conf
+            ) slapdDatabases
+          );
+
+        slapdDatabases = {
+          "${dbSuffix}" = {
+            conf = ''
+              dn: olcBackend={1}mdb,cn=config
+              objectClass: olcBackendConfig
+
+              dn: olcDatabase={1}mdb,cn=config
+              olcSuffix: ${dbSuffix}
+              olcDbDirectory: ${openldap.dataDir}/${dbSuffix}
+              objectClass: olcDatabaseConfig
+              objectClass: olcMdbConfig
+              # NOTE: checkpoint the database periodically in case of system failure
+              # and to speed up slapd shutdown.
+              olcDbCheckpoint: 512 30
+              # Database max size is 1G
+              olcDbMaxSize: 1073741824
+              olcLastMod: TRUE
+              # NOTE: database superuser. Needed for syncrepl,
+              # and used to auth as admin through a TCP connection.
+              olcRootDN: cn=admin,${dbSuffix}
+              olcRootPW: ${dbAdminPwdHash}
+              #
+              olcDbIndex: objectClass eq
+              olcDbIndex: cn,uid eq
+              olcDbIndex: uidNumber,gidNumber eq
+              olcDbIndex: member,memberUid eq
+              #
+              olcAccess: to attrs=userPassword
+                by self write
+                by anonymous auth
+                by dn="cn=admin,${dbSuffix}" write
+                by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
+                by * none
+              olcAccess: to attrs=shadowLastChange
+                by self write
+                by dn="cn=admin,${dbSuffix}" write
+                by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
+                by * none
+              olcAccess: to dn.sub="ou=posix,${dbSuffix}"
+                by self read
+                by dn="cn=admin,${dbSuffix}" read
+                by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
+              olcAccess: to *
+                by self read
+                by * none
+            '';
+            data = ''
+              dn: ${dbSuffix}
+              objectClass: top
+              objectClass: dcObject
+              objectClass: organization
+              o: ${dbDomain}
+
+              dn: cn=admin,${dbSuffix}
+              objectClass: simpleSecurityObject
+              objectClass: organizationalRole
+              description: ${dbDomain} LDAP administrator
+              roleOccupant: ${dbSuffix}
+              userPassword: ${ldapUserPwdHash}
+
+              dn: ou=posix,${dbSuffix}
+              objectClass: top
+              objectClass: organizationalUnit
+
+              dn: ou=accounts,ou=posix,${dbSuffix}
+              objectClass: top
+              objectClass: organizationalUnit
+
+              dn: ou=groups,ou=posix,${dbSuffix}
+              objectClass: top
+              objectClass: organizationalUnit
+            ''
+            + lib.concatMapStrings posixAccount [
+              { uid=ldapUser; uidNumber=ldapUserId; gidNumber=ldapGroupId; userPassword=ldapUserPwdHash; }
+            ]
+            + lib.concatMapStrings posixGroup [
+              { gid=ldapGroup; gidNumber=ldapGroupId; members=[]; }
+            ];
+          };
+        };
+
+        # NOTE: create a user account using the posixAccount objectClass.
+        posixAccount =
+          { uid
+          , uidNumber ? null
+          , gidNumber ? null
+          , cn ? ""
+          , sn ? ""
+          , userPassword ? ""
+          , loginShell ? "/bin/sh"
+          }: ''
+
+            dn: uid=${uid},ou=accounts,ou=posix,${dbSuffix}
+            objectClass: person
+            objectClass: posixAccount
+            objectClass: shadowAccount
+            cn: ${cn}
+            gecos:
+            ${if gidNumber == null then "#" else "gidNumber: ${toString gidNumber}"}
+            homeDirectory: /home/${uid}
+            loginShell: ${loginShell}
+            sn: ${sn}
+            ${if uidNumber == null then "#" else "uidNumber: ${toString uidNumber}"}
+            ${if userPassword == "" then "#" else "userPassword: ${userPassword}"}
+          '';
+
+        # NOTE: create a group using the posixGroup objectClass.
+        posixGroup =
+          { gid
+          , gidNumber
+          , members
+          }: ''
+
+            dn: cn=${gid},ou=groups,ou=posix,${dbSuffix}
+            objectClass: top
+            objectClass: posixGroup
+            gidNumber: ${toString gidNumber}
+            ${lib.concatMapStrings (member: "memberUid: ${member}\n") members}
+          '';
+      in
       {
         virtualisation.memorySize = 256;
         virtualisation.vlans = [ 1 ];
         networking.firewall.allowedTCPPorts = [ 389 ];
         services.openldap.enable = true;
-        services.openldap.dataDir = dbPath;
+        services.openldap.dataDir = "/var/db/openldap";
+        services.openldap.configDir = "/var/db/slapd";
         services.openldap.urlList = [
-          serverUri
+          "ldap:///"
+          "ldapi:///"
         ];
-        services.openldap.extraConfig = ''
-          include ${pkgs.openldap.out}/etc/schema/core.schema
-          include ${pkgs.openldap.out}/etc/schema/cosine.schema
-          include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
-          include ${pkgs.openldap.out}/etc/schema/nis.schema
-
-          database mdb
-          suffix ${dbSuffix}
-          rootdn ${dbAdminDn}
-          rootpw ${dbAdminPwd}
-          directory ${dbPath}
-        '';
+        systemd.services.openldap = {
+          preStart = ''
+              set -e
+              # NOTE: slapd's config is always re-initialized.
+              rm -rf "${openldap.configDir}"/cn=config \
+                     "${openldap.configDir}"/cn=config.ldif
+              install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${openldap.configDir}"
+              # NOTE: olcDbDirectory must be created before adding the config.
+              '' +
+              unlinesAttrs (olcSuffix: {data, ...}: ''
+                # NOTE: database is always re-initialized.
+                rm -rf "${openldap.dataDir}/${olcSuffix}"
+                install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" \
+                 "${openldap.dataDir}/${olcSuffix}"
+                '') slapdDatabases
+              + ''
+              # NOTE: slapd is supposed to be stopped while in preStart,
+              #       hence slap* commands can safely be used.
+              umask 0077
+              ${pkgs.openldap}/bin/slapadd -n 0 \
+               -F "${openldap.configDir}" \
+               -l ${slapdConfig}
+              chown -R "${openldap.user}:${openldap.group}" "${openldap.configDir}"
+              # NOTE: slapadd(8): To populate the config database slapd-config(5),
+              #                   use -n 0 as it is always the first database.
+              #                   It must physically exist on the filesystem prior to this, however.
+            '' +
+            unlinesAttrs (olcSuffix: {data, ...}: ''
+              # NOTE: load database ${olcSuffix}
+              # (as root to avoid depending on sudo or chpst)
+              ${pkgs.openldap}/bin/slapadd \
+               -F "${openldap.configDir}" \
+               -l ${pkgs.writeText "data.ldif" data}
+              '' + ''
+              # NOTE: redundant with default openldap's preStart, but do not harm.
+              chown -R "${openldap.user}:${openldap.group}" \
+               "${openldap.dataDir}/${olcSuffix}"
+            '') slapdDatabases;
+        };
       };
 
     client1 = mkClient true; # use nss_pam_ldapd
@@ -91,15 +293,91 @@ in
   };
 
   testScript = ''
-    startAll;
+    $server->start;
     $server->waitForUnit("default.target");
+
+    subtest "slapd", sub {
+      subtest "auth as database admin with SASL and check a POSIX account", sub {
+        $server->succeed(join ' ', 'test',
+         '"$(ldapsearch -LLL -H ldapi:// -Y EXTERNAL',
+             '-b \'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}\' ',
+             '-s base uidNumber |',
+           'sed -ne \'s/^uidNumber: \\(.*\\)/\\1/p\' ',
+         ')" -eq ${toString ldapUserId}');
+      };
+      subtest "auth as database admin with password and check a POSIX account", sub {
+        $server->succeed(join ' ', 'test',
+         '"$(ldapsearch -LLL -H ldap://server',
+             '-D \'cn=admin,${dbSuffix}\' -w \'${dbAdminPwd}\' ',
+             '-b \'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}\' ',
+             '-s base uidNumber |',
+           'sed -ne \'s/^uidNumber: \\(.*\\)/\\1/p\' ',
+         ')" -eq ${toString ldapUserId}');
+      };
+    };
+
+    $client1->start;
     $client1->waitForUnit("default.target");
-    $client2->waitForUnit("default.target");
 
-    $server->succeed("ldapadd -D '${dbAdminDn}' -w ${dbAdminPwd} -H ${serverUri} -f '${setupLdif}'");
+    subtest "password", sub {
+      subtest "su with password to a POSIX account", sub {
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "${ldapUserPwd}\n"',
+          'expect "*"',
+          'send "whoami\n"',
+          'expect -ex "${ldapUser}" {exit}',
+          'exit 1' . "'");
+      };
+      subtest "change password of a POSIX account as root", sub {
+        $client1->succeed("chpasswd <<<'${ldapUser}:new-password'");
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "new-password\n"',
+          'expect "*"',
+          'send "whoami\n"',
+          'expect -ex "${ldapUser}" {exit}',
+          'exit 1' . "'");
+        $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' ');
+      };
+      subtest "change password of a POSIX account from itself", sub {
+        $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' ');
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su --login ${ldapUser} -c passwd',
+          'expect "Password: "',
+          'send "${ldapUserPwd}\n"',
+          'expect "(current) UNIX password: "',
+          'send "${ldapUserPwd}\n"',
+          'expect "New password: "',
+          'send "new-password\n"',
+          'expect "Retype new password: "',
+          'send "new-password\n"',
+          'expect "passwd: password updated successfully" {exit}',
+          'exit 1' . "'");
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "${ldapUserPwd}\n"',
+          'expect "su: Authentication failure" {exit}',
+          'exit 1' . "'");
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "new-password\n"',
+          'expect "*"',
+          'send "whoami\n"',
+          'expect -ex "${ldapUser}" {exit}',
+          'exit 1' . "'");
+        $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' ');
+      };
+    };
+
+    $client2->start;
+    $client2->waitForUnit("default.target");
 
-    # NSS tests
-    subtest "nss", sub {
+    subtest "NSS", sub {
         $client1->succeed("test \"\$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}");
         $client1->succeed("test \"\$(id -u -n '${ldapUser}')\" = '${ldapUser}'");
         $client1->succeed("test \"\$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}");
@@ -110,8 +388,7 @@ in
         $client2->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'");
     };
 
-    # PAM tests
-    subtest "pam", sub {
+    subtest "PAM", sub {
         $client1->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true");
         $client2->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true");
     };
diff --git a/nixos/tests/lidarr.nix b/nixos/tests/lidarr.nix
new file mode 100644
index 0000000000000..58bf82503f8c3
--- /dev/null
+++ b/nixos/tests/lidarr.nix
@@ -0,0 +1,18 @@
+import ./make-test.nix ({ lib, ... }:
+
+with lib;
+
+rec {
+  name = "lidarr";
+  meta.maintainers = with maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.lidarr.enable = true; };
+
+  testScript = ''
+    $machine->waitForUnit('lidarr.service');
+    $machine->waitForOpenPort('8686');
+    $machine->succeed("curl --fail http://localhost:8686/");
+  '';
+})
diff --git a/nixos/tests/make-test.nix b/nixos/tests/make-test.nix
index ee4ba310ad50e..cee5da93454a1 100644
--- a/nixos/tests/make-test.nix
+++ b/nixos/tests/make-test.nix
@@ -1,5 +1,9 @@
-f: { system ? builtins.currentSystem, ... } @ args:
+f: {
+  system ? builtins.currentSystem,
+  pkgs ? import ../.. { inherit system; config = {}; },
+  ...
+} @ args:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 
 makeTest (if pkgs.lib.isFunction f then f (args // { inherit pkgs; inherit (pkgs) lib; }) else f)
diff --git a/nixos/tests/matrix-synapse.nix b/nixos/tests/matrix-synapse.nix
index 8504a7c0d057c..882e4b75814bf 100644
--- a/nixos/tests/matrix-synapse.nix
+++ b/nixos/tests/matrix-synapse.nix
@@ -1,4 +1,32 @@
-import ./make-test.nix ({ pkgs, ... } : {
+import ./make-test.nix ({ pkgs, ... } : let
+
+
+  runWithOpenSSL = file: cmd: pkgs.runCommand file {
+    buildInputs = [ pkgs.openssl ];
+  } cmd;
+
+
+  ca_key = runWithOpenSSL "ca-key.pem" "openssl genrsa -out $out 2048";
+  ca_pem = runWithOpenSSL "ca.pem" ''
+    openssl req \
+      -x509 -new -nodes -key ${ca_key} \
+      -days 10000 -out $out -subj "/CN=snakeoil-ca"
+  '';
+  key = runWithOpenSSL "matrix_key.pem" "openssl genrsa -out $out 2048";
+  csr = runWithOpenSSL "matrix.csr" ''
+    openssl req \
+       -new -key ${key} \
+       -out $out -subj "/CN=localhost" \
+  '';
+  cert = runWithOpenSSL "matrix_cert.pem" ''
+    openssl x509 \
+      -req -in ${csr} \
+      -CA ${ca_pem} -CAkey ${ca_key} \
+      -CAcreateserial -out $out \
+      -days 365
+  '';
+
+in {
 
   name = "matrix-synapse";
   meta = with pkgs.stdenv.lib.maintainers; {
@@ -8,23 +36,31 @@ import ./make-test.nix ({ pkgs, ... } : {
   nodes = {
     # Since 0.33.0, matrix-synapse doesn't allow underscores in server names
     serverpostgres = args: {
-      services.matrix-synapse.enable = true;
-      services.matrix-synapse.database_type = "psycopg2";
+      services.matrix-synapse = {
+        enable = true;
+        database_type = "psycopg2";
+        tls_certificate_path = "${cert}";
+        tls_private_key_path = "${key}";
+      };
     };
 
     serversqlite = args: {
-      services.matrix-synapse.enable = true;
-      services.matrix-synapse.database_type = "sqlite3";
+      services.matrix-synapse = {
+        enable = true;
+        database_type = "sqlite3";
+        tls_certificate_path = "${cert}";
+        tls_private_key_path = "${key}";
+      };
     };
   };
 
   testScript = ''
     startAll;
     $serverpostgres->waitForUnit("matrix-synapse.service");
-    $serverpostgres->waitUntilSucceeds("curl -Lk https://localhost:8448/");
+    $serverpostgres->waitUntilSucceeds("curl -L --cacert ${ca_pem} https://localhost:8448/");
     $serverpostgres->requireActiveUnit("postgresql.service");
     $serversqlite->waitForUnit("matrix-synapse.service");
-    $serversqlite->waitUntilSucceeds("curl -Lk https://localhost:8448/");
+    $serversqlite->waitUntilSucceeds("curl -L --cacert ${ca_pem} https://localhost:8448/");
     $serversqlite->mustSucceed("[ -e /var/lib/matrix-synapse/homeserver.db ]");
   '';
 
diff --git a/nixos/tests/misc.nix b/nixos/tests/misc.nix
index b0bc1d083b16d..3ad55651b1129 100644
--- a/nixos/tests/misc.nix
+++ b/nixos/tests/misc.nix
@@ -14,7 +14,7 @@ import ./make-test.nix ({ pkgs, ...} : rec {
     { swapDevices = mkOverride 0
         [ { device = "/root/swapfile"; size = 128; } ];
       environment.variables.EDITOR = mkOverride 0 "emacs";
-      services.nixosManual.enable = mkOverride 0 true;
+      documentation.nixos.enable = mkOverride 0 true;
       systemd.tmpfiles.rules = [ "d /tmp 1777 root root 10d" ];
       fileSystems = mkVMOverride { "/tmp2" =
         { fsType = "tmpfs";
@@ -78,6 +78,8 @@ import ./make-test.nix ({ pkgs, ...} : rec {
 
       # Test whether we have a reboot record in wtmp.
       subtest "reboot-wtmp", sub {
+          $machine->shutdown;
+          $machine->waitForUnit('multi-user.target');
           $machine->succeed("last | grep reboot >&2");
       };
 
diff --git a/nixos/tests/mongodb.nix b/nixos/tests/mongodb.nix
index 2f380ff543e97..c9439b65292de 100644
--- a/nixos/tests/mongodb.nix
+++ b/nixos/tests/mongodb.nix
@@ -8,7 +8,7 @@ import ./make-test.nix ({ pkgs, ...} : let
 in {
   name = "mongodb";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ bluescreen303 offline wkennington cstrahan rvl ];
+    maintainers = [ bluescreen303 offline cstrahan rvl ];
   };
 
   nodes = {
diff --git a/nixos/tests/munin.nix b/nixos/tests/munin.nix
index 9f66005292ab5..95cecf17b8ccc 100644
--- a/nixos/tests/munin.nix
+++ b/nixos/tests/munin.nix
@@ -15,9 +15,7 @@ import ./make-test.nix ({ pkgs, ...} : {
            munin-node = {
              enable = true;
              # disable a failing plugin to prevent irrelevant error message, see #23049
-             extraConfig = ''
-               ignore_file ^apc_nis$
-             '';
+             disabledPlugins = [ "apc_nis" ];
            };
            munin-cron = {
             enable = true;
diff --git a/nixos/tests/mxisd.nix b/nixos/tests/mxisd.nix
new file mode 100644
index 0000000000000..3d03a5a53e38e
--- /dev/null
+++ b/nixos/tests/mxisd.nix
@@ -0,0 +1,21 @@
+import ./make-test.nix ({ pkgs, ... } : {
+
+  name = "mxisd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mguentner ];
+  };
+
+  nodes = {
+    server_mxisd = args : {
+      services.mxisd.enable = true;
+      services.mxisd.matrix.domain = "example.org";
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $server_mxisd->waitForUnit("mxisd.service");
+    $server_mxisd->waitForOpenPort(8090);
+    $server_mxisd->succeed("curl -Ssf \"http://127.0.0.1:8090/_matrix/identity/api/v1\"")
+  '';
+})
diff --git a/nixos/tests/nat.nix b/nixos/tests/nat.nix
index 9c280fe8b5b66..34229e913110b 100644
--- a/nixos/tests/nat.nix
+++ b/nixos/tests/nat.nix
@@ -11,7 +11,6 @@ import ./make-test.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false,
       lib.mkMerge [
         { virtualisation.vlans = [ 2 1 ];
           networking.firewall.enable = withFirewall;
-          networking.firewall.allowPing = true;
           networking.nat.internalIPs = [ "192.168.1.0/24" ];
           networking.nat.externalInterface = "eth1";
         }
@@ -25,7 +24,7 @@ import ./make-test.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false,
     name = "nat" + (if withFirewall then "WithFirewall" else "Standalone")
                  + (lib.optionalString withConntrackHelpers "withConntrackHelpers");
     meta = with pkgs.stdenv.lib.maintainers; {
-      maintainers = [ eelco chaoflow rob wkennington ];
+      maintainers = [ eelco chaoflow rob ];
     };
 
     nodes =
@@ -33,7 +32,6 @@ import ./make-test.nix ({ pkgs, lib, withFirewall, withConntrackHelpers ? false,
           { pkgs, nodes, ... }:
           lib.mkMerge [
             { virtualisation.vlans = [ 1 ];
-              networking.firewall.allowPing = true;
               networking.defaultGateway =
                 (pkgs.lib.head nodes.router.config.networking.interfaces.eth2.ipv4.addresses).address;
             }
diff --git a/nixos/tests/ndppd.nix b/nixos/tests/ndppd.nix
new file mode 100644
index 0000000000000..9f24eb6d9d457
--- /dev/null
+++ b/nixos/tests/ndppd.nix
@@ -0,0 +1,61 @@
+import ./make-test.nix ({ pkgs, lib, ...} : {
+  name = "ndppd";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ fpletz ];
+  };
+
+  nodes = {
+    upstream = { pkgs, ... }: {
+      environment.systemPackages = [ pkgs.tcpdump ];
+      networking.useDHCP = false;
+      networking.interfaces = {
+        eth1 = {
+          ipv6.addresses = [
+            { address = "fd23::1"; prefixLength = 112; }
+          ];
+          ipv6.routes = [
+            { address = "fd42::";
+              prefixLength = 112;
+            }
+          ];
+        };
+      };
+    };
+    server = { pkgs, ... }: {
+      boot.kernel.sysctl = {
+        "net.ipv6.conf.all.forwarding" = "1";
+        "net.ipv6.conf.default.forwarding" = "1";
+      };
+      environment.systemPackages = [ pkgs.tcpdump ];
+      networking.useDHCP = false;
+      networking.interfaces = {
+        eth1 = {
+          ipv6.addresses = [
+            { address = "fd23::2"; prefixLength = 112; }
+          ];
+        };
+      };
+      services.ndppd = {
+        enable = true;
+        interface = "eth1";
+        network = "fd42::/112";
+      };
+      containers.client = {
+        autoStart = true;
+        privateNetwork = true;
+        hostAddress = "192.168.255.1";
+        localAddress = "192.168.255.2";
+        hostAddress6 = "fd42::1";
+        localAddress6 = "fd42::2";
+        config = {};
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+    $server->waitForUnit("multi-user.target");
+    $upstream->waitForUnit("multi-user.target");
+    $upstream->waitUntilSucceeds("ping -c5 fd42::2");
+  '';
+})
diff --git a/nixos/tests/neo4j.nix b/nixos/tests/neo4j.nix
new file mode 100644
index 0000000000000..86ed8970517c9
--- /dev/null
+++ b/nixos/tests/neo4j.nix
@@ -0,0 +1,20 @@
+import ./make-test.nix {
+  name = "neo4j";
+
+  nodes = {
+    master =
+      { ... }:
+
+      {
+        services.neo4j.enable = true;
+      };
+  };
+
+  testScript = ''
+    startAll;
+
+    $master->waitForUnit("neo4j");
+    $master->sleep(20); # Hopefully this is long enough!!
+    $master->succeed("curl http://localhost:7474/");
+  '';
+}
diff --git a/nixos/tests/networking.nix b/nixos/tests/networking.nix
index 02bd4bd980790..ed9f287d55821 100644
--- a/nixos/tests/networking.nix
+++ b/nixos/tests/networking.nix
@@ -1,8 +1,10 @@
 { system ? builtins.currentSystem
+, config ? {}
+, pkgs ? import ../.. { inherit system config; }
 # bool: whether to use networkd in the tests
 , networkd }:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
@@ -17,7 +19,6 @@ let
       networking = {
         useDHCP = false;
         useNetworkd = networkd;
-        firewall.allowPing = true;
         firewall.checkReversePath = true;
         firewall.allowedUDPPorts = [ 547 ];
         interfaces = mkOverride 0 (listToAttrs (flip map vlanIfs (n:
@@ -86,7 +87,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           defaultGateway = "192.168.1.1";
           interfaces.eth1.ipv4.addresses = mkOverride 0 [
@@ -139,7 +139,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = true;
           interfaces.eth1 = {
             ipv4.addresses = mkOverride 0 [ ];
@@ -194,7 +193,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           interfaces.eth1 = {
             ipv4.addresses = mkOverride 0 [ ];
@@ -234,7 +232,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           bonds.bond = {
             interfaces = [ "eth1" "eth2" ];
@@ -271,7 +268,6 @@ let
         virtualisation.vlans = [ vlan ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           interfaces.eth1.ipv4.addresses = mkOverride 0
             [ { inherit address; prefixLength = 24; } ];
@@ -285,7 +281,6 @@ let
         virtualisation.vlans = [ 1 2 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           bridges.bridge.interfaces = [ "eth1" "eth2" ];
           interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
@@ -329,7 +324,6 @@ let
           # reverse path filtering rules for the macvlan interface seem
           # to be incorrect, causing the test to fail. Disable temporarily.
           firewall.checkReversePath = false;
-          firewall.allowPing = true;
           useDHCP = true;
           macvlans.macvlan.interface = "eth1";
           interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
@@ -415,7 +409,6 @@ let
         #virtualisation.vlans = [ 1 ];
         networking = {
           useNetworkd = networkd;
-          firewall.allowPing = true;
           useDHCP = false;
           vlans.vlan = {
             id = 1;
@@ -467,7 +460,7 @@ let
 
         # Wait for networking to come up
         $machine->start;
-        $machine->waitForUnit("network.target");
+        $machine->waitForUnit("network-online.target");
 
         # Test interfaces set up
         my $list = $machine->succeed("ip tuntap list | sort");
@@ -479,7 +472,9 @@ let
 
         # Test interfaces clean up
         $machine->succeed("systemctl stop network-addresses-tap0");
+        $machine->sleep(10);
         $machine->succeed("systemctl stop network-addresses-tun0");
+        $machine->sleep(10);
         my $residue = $machine->succeed("ip tuntap list");
         $residue eq "" or die(
           "Some virtual interface has not been properly cleaned:\n",
@@ -611,7 +606,4 @@ let
 
 in mapAttrs (const (attrs: makeTest (attrs // {
   name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ wkennington ];
-  };
 }))) testCases
diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix
new file mode 100644
index 0000000000000..c3b710f0f904c
--- /dev/null
+++ b/nixos/tests/nextcloud/basic.nix
@@ -0,0 +1,56 @@
+import ../make-test.nix ({ pkgs, ...}: let
+  adminpass = "notproduction";
+  adminuser = "root";
+in {
+  name = "nextcloud-basic";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ globin eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        nginx.enable = true;
+        hostName = "nextcloud";
+        config = {
+          # Don't inherit adminuser since "root" is supposed to be the default
+          inherit adminpass;
+        };
+      };
+    };
+  };
+
+  testScript = let
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.stdenv.shell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      echo 'hi' | ${withRcloneEnv} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    startAll();
+    $nextcloud->waitForUnit("multi-user.target");
+    $nextcloud->succeed("curl -sSf http://nextcloud/login");
+    $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+    $client->waitForUnit("multi-user.target");
+    $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+  '';
+})
diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix
new file mode 100644
index 0000000000000..e4c7a70606cf8
--- /dev/null
+++ b/nixos/tests/nextcloud/default.nix
@@ -0,0 +1,9 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../../.. { inherit system config; }
+}:
+{
+  basic = import ./basic.nix { inherit system pkgs; };
+  with-postgresql-and-redis = import ./with-postgresql-and-redis.nix { inherit system pkgs; };
+  with-mysql-and-memcached = import ./with-mysql-and-memcached.nix { inherit system pkgs; };
+}
diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
new file mode 100644
index 0000000000000..c0d347238b47e
--- /dev/null
+++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix
@@ -0,0 +1,97 @@
+import ../make-test.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "root";
+in {
+  name = "nextcloud-with-mysql-and-memcached";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        nginx.enable = true;
+        https = true;
+        caching = {
+          apcu = true;
+          redis = false;
+          memcached = true;
+        };
+        config = {
+          dbtype = "mysql";
+          dbname = "nextcloud";
+          dbuser = "nextcloud";
+          dbhost = "127.0.0.1";
+          dbport = 3306;
+          dbpass = "hunter2";
+          # Don't inherit adminuser since "root" is supposed to be the default
+          inherit adminpass;
+        };
+      };
+
+      services.mysql = {
+        enable = true;
+        bind = "127.0.0.1";
+        package = pkgs.mariadb;
+        initialScript = pkgs.writeText "mysql-init" ''
+          CREATE USER 'nextcloud'@'localhost' IDENTIFIED BY 'hunter2';
+          CREATE DATABASE IF NOT EXISTS nextcloud;
+          GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
+            CREATE TEMPORARY TABLES ON nextcloud.* TO 'nextcloud'@'localhost'
+            IDENTIFIED BY 'hunter2';
+          FLUSH privileges;
+        '';
+      };
+
+      systemd.services."nextcloud-setup"= {
+        requires = ["mysql.service"];
+        after = ["mysql.service"];
+      };
+
+      services.memcached.enable = true;
+    };
+  };
+
+  testScript = let
+    configureMemcached = pkgs.writeScript "configure-memcached" ''
+      #!${pkgs.stdenv.shell}
+      nextcloud-occ config:system:set memcached_servers 0 0 --value 127.0.0.1 --type string
+      nextcloud-occ config:system:set memcached_servers 0 1 --value 11211 --type integer
+      nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\APCu' --type string
+      nextcloud-occ config:system:set memcache.distributed --value '\OC\Memcache\Memcached' --type string
+    '';
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.stdenv.shell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    startAll();
+    $nextcloud->waitForUnit("multi-user.target");
+    $nextcloud->succeed("${configureMemcached}");
+    $nextcloud->succeed("curl -sSf http://nextcloud/login");
+    $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+    $client->waitForUnit("multi-user.target");
+    $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+
+  '';
+})
diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
new file mode 100644
index 0000000000000..0351d4db69ac9
--- /dev/null
+++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix
@@ -0,0 +1,130 @@
+import ../make-test.nix ({ pkgs, ...}: let
+  adminpass = "hunter2";
+  adminuser = "custom-admin-username";
+in {
+  name = "nextcloud-with-postgresql-and-redis";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ eqyiel ];
+  };
+
+  nodes = {
+    # The only thing the client needs to do is download a file.
+    client = { ... }: {};
+
+    nextcloud = { config, pkgs, ... }: {
+      networking.firewall.allowedTCPPorts = [ 80 ];
+
+      services.nextcloud = {
+        enable = true;
+        hostName = "nextcloud";
+        nginx.enable = true;
+        caching = {
+          apcu = false;
+          redis = true;
+          memcached = false;
+        };
+        config = {
+          dbtype = "pgsql";
+          dbname = "nextcloud";
+          dbuser = "nextcloud";
+          dbhost = "localhost";
+          dbpassFile = toString (pkgs.writeText "db-pass-file" ''
+            hunter2
+          '');
+          inherit adminuser;
+          adminpassFile = toString (pkgs.writeText "admin-pass-file" ''
+            ${adminpass}
+          '');
+        };
+      };
+
+      services.redis = {
+        unixSocket = "/var/run/redis/redis.sock";
+        enable = true;
+        extraConfig = ''
+          unixsocketperm 770
+        '';
+      };
+
+      systemd.services.redis = {
+        preStart = ''
+          mkdir -p /var/run/redis
+          chown ${config.services.redis.user}:${config.services.nginx.group} /var/run/redis
+        '';
+        serviceConfig.PermissionsStartOnly = true;
+      };
+
+      systemd.services."nextcloud-setup"= {
+        requires = ["postgresql.service"];
+        after = [
+          "postgresql.service"
+          "chown-redis-socket.service"
+        ];
+      };
+
+      # At the time of writing, redis creates its socket with the "nobody"
+      # group.  I figure this is slightly less bad than making the socket world
+      # readable.
+      systemd.services."chown-redis-socket" = {
+        enable = true;
+        script = ''
+          until ${pkgs.redis}/bin/redis-cli ping; do
+            echo "waiting for redis..."
+            sleep 1
+          done
+          chown ${config.services.redis.user}:${config.services.nginx.group} /var/run/redis/redis.sock
+        '';
+        after = [ "redis.service" ];
+        requires = [ "redis.service" ];
+        wantedBy = [ "redis.service" ];
+        serviceConfig = {
+          Type = "oneshot";
+        };
+      };
+
+      services.postgresql = {
+        enable = true;
+        initialScript = pkgs.writeText "psql-init" ''
+          create role nextcloud with login password 'hunter2';
+          create database nextcloud with owner nextcloud;
+        '';
+      };
+    };
+  };
+
+  testScript = let
+    configureRedis = pkgs.writeScript "configure-redis" ''
+      #!${pkgs.stdenv.shell}
+      nextcloud-occ config:system:set redis 'host' --value '/var/run/redis/redis.sock' --type string
+      nextcloud-occ config:system:set redis 'port' --value 0 --type integer
+      nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\Redis' --type string
+      nextcloud-occ config:system:set memcache.locking --value '\OC\Memcache\Redis' --type string
+    '';
+    withRcloneEnv = pkgs.writeScript "with-rclone-env" ''
+      #!${pkgs.stdenv.shell}
+      export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav
+      export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/webdav/"
+      export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud"
+      export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}"
+      export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})"
+      "''${@}"
+    '';
+    copySharedFile = pkgs.writeScript "copy-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file
+    '';
+
+    diffSharedFile = pkgs.writeScript "diff-shared-file" ''
+      #!${pkgs.stdenv.shell}
+      diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file)
+    '';
+  in ''
+    startAll();
+    $nextcloud->waitForUnit("multi-user.target");
+    $nextcloud->succeed("${configureRedis}");
+    $nextcloud->succeed("curl -sSf http://nextcloud/login");
+    $nextcloud->succeed("${withRcloneEnv} ${copySharedFile}");
+    $client->waitForUnit("multi-user.target");
+    $client->succeed("${withRcloneEnv} ${diffSharedFile}");
+  '';
+})
diff --git a/nixos/tests/nexus.nix b/nixos/tests/nexus.nix
index bf49d2247bd86..783c9f5c019ff 100644
--- a/nixos/tests/nexus.nix
+++ b/nixos/tests/nexus.nix
@@ -14,7 +14,7 @@ import ./make-test.nix ({ pkgs, ...} : {
     server =
       { ... }:
       { virtualisation.memorySize = 2047; # qemu-system-i386 has a 2047M limit
-        virtualisation.diskSize = 2048;
+        virtualisation.diskSize = 8192;
 
         services.nexus.enable = true;
       };
diff --git a/nixos/tests/nfs.nix b/nixos/tests/nfs.nix
index 0ef44f1a48905..ce1717018933a 100644
--- a/nixos/tests/nfs.nix
+++ b/nixos/tests/nfs.nix
@@ -20,7 +20,7 @@ in
 {
   name = "nfs";
   meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ eelco chaoflow wkennington ];
+    maintainers = [ eelco chaoflow  ];
   };
 
   nodes =
diff --git a/nixos/tests/nginx-sso.nix b/nixos/tests/nginx-sso.nix
new file mode 100644
index 0000000000000..e19992cb6bf75
--- /dev/null
+++ b/nixos/tests/nginx-sso.nix
@@ -0,0 +1,44 @@
+import ./make-test.nix ({ pkgs, ... }: {
+  name = "nginx-sso";
+  meta = {
+    maintainers = with pkgs.stdenv.lib.maintainers; [ delroth ];
+  };
+
+  machine = {
+    services.nginx.sso = {
+      enable = true;
+      configuration = {
+        listen = { addr = "127.0.0.1"; port = 8080; };
+
+        providers.token.tokens = {
+          myuser = "MyToken";
+        };
+
+        acl = {
+          rule_sets = [
+            {
+              rules = [ { field = "x-application"; equals = "MyApp"; } ];
+              allow = [ "myuser" ];
+            }
+          ];
+        };
+      };
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit("nginx-sso.service");
+    $machine->waitForOpenPort(8080);
+
+    # No valid user -> 401.
+    $machine->fail("curl -sSf http://localhost:8080/auth");
+
+    # Valid user but no matching ACL -> 403.
+    $machine->fail("curl -sSf -H 'Authorization: Token MyToken' http://localhost:8080/auth");
+
+    # Valid user and matching ACL -> 200.
+    $machine->succeed("curl -sSf -H 'Authorization: Token MyToken' -H 'X-Application: MyApp' http://localhost:8080/auth");
+  '';
+})
diff --git a/nixos/tests/nix-ssh-serve.nix b/nixos/tests/nix-ssh-serve.nix
index aa366d8612d72..494d55121eb12 100644
--- a/nixos/tests/nix-ssh-serve.nix
+++ b/nixos/tests/nix-ssh-serve.nix
@@ -14,8 +14,8 @@ in
              keys = [ snakeOilPublicKey ];
              protocol = "ssh-ng";
            };
-         server.nix.package = pkgs.nixUnstable;
-         client.nix.package = pkgs.nixUnstable;
+         server.nix.package = pkgs.nix;
+         client.nix.package = pkgs.nix;
        };
      testScript = ''
        startAll;
diff --git a/nixos/tests/opensmtpd.nix b/nixos/tests/opensmtpd.nix
index 4c0cbca210106..883ad76049412 100644
--- a/nixos/tests/opensmtpd.nix
+++ b/nixos/tests/opensmtpd.nix
@@ -17,11 +17,12 @@ import ./make-test.nix {
         extraServerArgs = [ "-v" ];
         serverConfiguration = ''
           listen on 0.0.0.0
+          action do_relay relay
           # DO NOT DO THIS IN PRODUCTION!
           # Setting up authentication requires a certificate which is painful in
           # a test environment, but THIS WOULD BE DANGEROUS OUTSIDE OF A
           # WELL-CONTROLLED ENVIRONMENT!
-          accept from any for any relay
+          match from any for any action do_relay
         '';
       };
     };
@@ -41,8 +42,9 @@ import ./make-test.nix {
         extraServerArgs = [ "-v" ];
         serverConfiguration = ''
           listen on 0.0.0.0
-          accept from any for local deliver to mda \
+          action dovecot_deliver mda \
             "${pkgs.dovecot}/libexec/dovecot/deliver -d %{user.username}"
+          match from any for local action dovecot_deliver
         '';
       };
       services.dovecot2 = {
@@ -118,4 +120,6 @@ import ./make-test.nix {
     $smtp2->waitUntilFails('smtpctl show queue | egrep .');
     $client->succeed('check-mail-landed >&2');
   '';
+
+  meta.timeout = 30;
 }
diff --git a/nixos/tests/openstack-image.nix b/nixos/tests/openstack-image.nix
new file mode 100644
index 0000000000000..d0225016ab762
--- /dev/null
+++ b/nixos/tests/openstack-image.nix
@@ -0,0 +1,88 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+with import common/ec2.nix { inherit makeTest pkgs; };
+
+let
+  image =
+    (import ../lib/eval-config.nix {
+      inherit system;
+      modules = [
+        ../maintainers/scripts/openstack/openstack-image.nix
+        ../modules/testing/test-instrumentation.nix
+        ../modules/profiles/qemu-guest.nix
+      ];
+    }).config.system.build.openstackImage;
+
+  sshKeys = import ./ssh-keys.nix pkgs;
+  snakeOilPrivateKey = sshKeys.snakeOilPrivateKey.text;
+  snakeOilPublicKey = sshKeys.snakeOilPublicKey;
+
+in {
+  metadata = makeEc2Test {
+    name = "openstack-ec2-metadata";
+    inherit image;
+    sshPublicKey = snakeOilPublicKey;
+    userData = ''
+      SSH_HOST_ED25519_KEY_PUB:${snakeOilPublicKey}
+      SSH_HOST_ED25519_KEY:${replaceStrings ["\n"] ["|"] snakeOilPrivateKey}
+    '';
+    script = ''
+      $machine->start;
+      $machine->waitForFile("/etc/ec2-metadata/user-data");
+      $machine->waitForUnit("sshd.service");
+
+      $machine->succeed("grep unknown /etc/ec2-metadata/ami-manifest-path");
+
+      # We have no keys configured on the client side yet, so this should fail
+      $machine->fail("ssh -o BatchMode=yes localhost exit");
+
+      # Let's install our client private key
+      $machine->succeed("mkdir -p ~/.ssh");
+
+      $machine->succeed("echo '${snakeOilPrivateKey}' > ~/.ssh/id_ed25519");
+      $machine->succeed("chmod 600 ~/.ssh/id_ed25519");
+
+      # We haven't configured the host key yet, so this should still fail
+      $machine->fail("ssh -o BatchMode=yes localhost exit");
+
+      # Add the host key; ssh should finally succeed
+      $machine->succeed("echo localhost,127.0.0.1 ${snakeOilPublicKey} > ~/.ssh/known_hosts");
+      $machine->succeed("ssh -o BatchMode=yes localhost exit");
+
+      # Just to make sure resizing is idempotent.
+      $machine->shutdown;
+      $machine->start;
+      $machine->waitForFile("/etc/ec2-metadata/user-data");
+    '';
+  };
+
+  userdata = makeEc2Test {
+    name = "openstack-ec2-metadata";
+    inherit image;
+    sshPublicKey = snakeOilPublicKey;
+    userData = ''
+      { pkgs, ... }:
+      {
+        imports = [
+          <nixpkgs/nixos/modules/virtualisation/openstack-config.nix>
+          <nixpkgs/nixos/modules/testing/test-instrumentation.nix>
+          <nixpkgs/nixos/modules/profiles/qemu-guest.nix>
+        ];
+        environment.etc.testFile = {
+          text = "whoa";
+        };
+      }
+    '';
+    script = ''
+      $machine->start;
+      $machine->waitForFile("/etc/testFile");
+      $machine->succeed("cat /etc/testFile | grep -q 'whoa'");
+    '';
+  };
+}
diff --git a/nixos/tests/osrm-backend.nix b/nixos/tests/osrm-backend.nix
new file mode 100644
index 0000000000000..6e2d098d4adbc
--- /dev/null
+++ b/nixos/tests/osrm-backend.nix
@@ -0,0 +1,53 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+let
+  port = 5000;
+in {
+  name = "osrm-backend";
+  meta.maintainers = [ lib.maintainers.erictapen ];
+
+  machine = { config, pkgs, ... }:{
+
+    services.osrm = {
+      enable = true;
+      inherit port;
+      dataFile = let
+        filename = "monaco";
+        osrm-data = pkgs.stdenv.mkDerivation {
+          name = "osrm-data";
+
+          buildInputs = [ pkgs.osrm-backend ];
+
+          # This is a pbf file of monaco, downloaded at 2019-01-04 from
+          # http://download.geofabrik.de/europe/monaco-latest.osm.pbf
+          # as apparently no provider of OSM files guarantees immutability,
+          # this is hosted as a gist on GitHub.
+          src = pkgs.fetchgit {
+            url = "https://gist.github.com/erictapen/01e39f73a6c856eac53ba809a94cdb83";
+            rev = "9b1ff0f24deb40e5cf7df51f843dbe860637b8ce";
+            sha256 = "1scqhmrfnpwsy5i2a9jpggqnvfgj4hv9p4qyvc79321pzkbv59nx";
+          };
+
+          buildCommand = ''
+            cp $src/${filename}.osm.pbf .
+            ${pkgs.osrm-backend}/bin/osrm-extract -p ${pkgs.osrm-backend}/share/osrm/profiles/car.lua ${filename}.osm.pbf
+            ${pkgs.osrm-backend}/bin/osrm-partition ${filename}.osrm
+            ${pkgs.osrm-backend}/bin/osrm-customize ${filename}.osrm
+            mkdir -p $out
+            cp ${filename}* $out/
+          '';
+        };
+      in "${osrm-data}/${filename}.osrm";
+    };
+
+    environment.systemPackages = [ pkgs.jq ];
+  };
+
+  testScript = let
+    query = "http://localhost:${toString port}/route/v1/driving/7.41720,43.73304;7.42463,43.73886?steps=true";
+  in ''
+    $machine->waitForUnit("osrm.service");
+    $machine->waitForOpenPort(${toString port});
+    $machine->succeed("curl --silent '${query}' | jq .waypoints[0].name | grep -F 'Boulevard Rainier III'");
+    $machine->succeed("curl --silent '${query}' | jq .waypoints[1].name | grep -F 'Avenue de la Costa'");
+  '';
+})
diff --git a/nixos/tests/owncloud.nix b/nixos/tests/owncloud.nix
deleted file mode 100644
index c968569f2008c..0000000000000
--- a/nixos/tests/owncloud.nix
+++ /dev/null
@@ -1,39 +0,0 @@
-import ./make-test.nix ({ ... }:
-
-{
-  name = "owncloud";
-  nodes =
-    { web =
-        { ... }:
-        {
-          services.postgresql.enable = true;
-          services.httpd = {
-            enable = true;
-            logPerVirtualHost = true;
-            adminAddr = "example@example.com";
-            virtualHosts = [
-              {
-                hostName = "owncloud";
-                extraSubservices =
-                  [
-                    {
-                      serviceType   = "owncloud";
-                      adminPassword = "secret";
-                      dbPassword    = "secret";
-                    }
-                  ];
-              }
-            ];
-          };
-        };
-    };
-
-  testScript = ''
-    startAll;
-
-    $web->waitForUnit("postgresql");
-    $web->waitForUnit("httpd");
-
-    $web->succeed("curl -L 127.0.0.1:80");
-  '';
-})
diff --git a/nixos/tests/pam-u2f.nix b/nixos/tests/pam-u2f.nix
new file mode 100644
index 0000000000000..1052a2f3b916e
--- /dev/null
+++ b/nixos/tests/pam-u2f.nix
@@ -0,0 +1,23 @@
+import ./make-test.nix ({ ... }:
+
+{
+  name = "pam-u2f";
+
+  machine =
+    { ... }:
+    {
+      security.pam.u2f = {
+        control = "required";
+        cue = true;
+        debug = true;
+        enable = true;
+        interactive = true;
+      };
+    };
+
+  testScript =
+    ''
+      $machine->waitForUnit('multi-user.target');
+      $machine->succeed('egrep "auth required .*/lib/security/pam_u2f.so.*debug.*interactive.*cue" /etc/pam.d/ -R');
+    '';
+})
diff --git a/nixos/tests/plasma5.nix b/nixos/tests/plasma5.nix
index eb705536827e7..788c8719c8d2f 100644
--- a/nixos/tests/plasma5.nix
+++ b/nixos/tests/plasma5.nix
@@ -26,31 +26,20 @@ import ./make-test.nix ({ pkgs, ...} :
     services.xserver.displayManager.sddm.theme = "breeze-ocr-theme";
     services.xserver.desktopManager.plasma5.enable = true;
     services.xserver.desktopManager.default = "plasma5";
+    services.xserver.displayManager.sddm.autoLogin = {
+      enable = true;
+      user = "alice";
+    };
     virtualisation.memorySize = 1024;
     environment.systemPackages = [ sddm_theme ];
-
-    # fontconfig-penultimate-0.3.3 -> 0.3.4 broke OCR apparently, but no idea why.
-    nixpkgs.config.packageOverrides = superPkgs: {
-      fontconfig-penultimate = superPkgs.fontconfig-penultimate.override {
-        version = "0.3.3";
-        sha256 = "1z76jbkb0nhf4w7fy647yyayqr4q02fgk6w58k0yi700p0m3h4c9";
-      };
-    };
   };
 
-  enableOCR = true;
-
   testScript = { nodes, ... }: let
     user = nodes.machine.config.users.users.alice;
     xdo = "${pkgs.xdotool}/bin/xdotool";
   in ''
     startAll;
-    # Wait for display manager to start
-    $machine->waitForText(qr/${user.description}/);
-    $machine->screenshot("sddm");
-
-    # Log in
-    $machine->sendChars("${user.password}\n");
+    # wait for log in
     $machine->waitForFile("/home/alice/.Xauthority");
     $machine->succeed("xauth merge ~alice/.Xauthority");
 
diff --git a/nixos/tests/postgis.nix b/nixos/tests/postgis.nix
index f8b63c5b6a27b..294eb50b5fe50 100644
--- a/nixos/tests/postgis.nix
+++ b/nixos/tests/postgis.nix
@@ -9,10 +9,12 @@ import ./make-test.nix ({ pkgs, ...} : {
       { pkgs, ... }:
 
       {
-        services.postgresql = let mypg = pkgs.postgresql100; in {
+        services.postgresql = let mypg = pkgs.postgresql_11; in {
             enable = true;
             package = mypg;
-            extraPlugins = [ (pkgs.postgis.override { postgresql = mypg; }) ];
+            extraPlugins = with mypg.pkgs; [
+              postgis
+            ];
         };
       };
   };
@@ -22,5 +24,6 @@ import ./make-test.nix ({ pkgs, ...} : {
     $master->waitForUnit("postgresql");
     $master->sleep(10); # Hopefully this is long enough!!
     $master->succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis;'");
+    $master->succeed("sudo -u postgres psql -c 'CREATE EXTENSION postgis_topology;'");
   '';
 })
diff --git a/nixos/tests/postgresql.nix b/nixos/tests/postgresql.nix
index f1f09277f342a..ae5d6d095ea26 100644
--- a/nixos/tests/postgresql.nix
+++ b/nixos/tests/postgresql.nix
@@ -1,8 +1,13 @@
-{ system ? builtins.currentSystem }:
-with import ../lib/testing.nix { inherit system; };
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
+
 let
-  postgresql-versions = pkgs.callPackages ../../pkgs/servers/sql/postgresql { };
+  postgresql-versions = import ../../pkgs/servers/sql/postgresql pkgs;
   test-sql = pkgs.writeText "postgresql-test" ''
     CREATE EXTENSION pgcrypto; -- just to check if lib loading works
     CREATE TABLE sth (
@@ -16,7 +21,7 @@ let
     CREATE TABLE xmltest ( doc xml );
     INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled
   '';
-  make-postgresql-test = postgresql-name: postgresql-package: makeTest {
+  make-postgresql-test = postgresql-name: postgresql-package: backup-all: makeTest {
     name = postgresql-name;
     meta = with pkgs.stdenv.lib.maintainers; {
       maintainers = [ zagy ];
@@ -24,14 +29,17 @@ let
 
     machine = {...}:
       {
-        services.postgresql.package=postgresql-package;
         services.postgresql.enable = true;
+        services.postgresql.package = postgresql-package;
 
         services.postgresqlBackup.enable = true;
-        services.postgresqlBackup.databases = [ "postgres" ];
+        services.postgresqlBackup.databases = optional (!backup-all) "postgres";
       };
 
-    testScript = ''
+    testScript = let
+      backupName = if backup-all then "all" else "postgres";
+      backupService = if backup-all then "postgresqlBackup" else "postgresqlBackup-postgres";
+    in ''
       sub check_count {
         my ($select, $nlines) = @_;
         return 'test $(sudo -u postgres psql postgres -tAc "' . $select . '"|wc -l) -eq ' . $nlines;
@@ -51,11 +59,15 @@ let
       $machine->succeed(check_count("SELECT xpath(\'/test/text()\', doc) FROM xmltest;", 1));
 
       # Check backup service
-      $machine->succeed("systemctl start postgresqlBackup-postgres.service");
-      $machine->succeed("zcat /var/backup/postgresql/postgres.sql.gz | grep '<test>ok</test>'");
+      $machine->succeed("systemctl start ${backupService}.service");
+      $machine->succeed("zcat /var/backup/postgresql/${backupName}.sql.gz | grep '<test>ok</test>'");
+      $machine->succeed("stat -c '%a' /var/backup/postgresql/${backupName}.sql.gz | grep 600");
       $machine->shutdown;
     '';
 
   };
 in
-  mapAttrs' (p-name: p-package: {name=p-name; value=make-postgresql-test p-name p-package;}) postgresql-versions
+  (mapAttrs' (name: package: { inherit name; value=make-postgresql-test name package false;}) postgresql-versions) // {
+    postgresql_11-backup-all = make-postgresql-test "postgresql_11-backup-all" postgresql-versions.postgresql_11 true;
+  }
+
diff --git a/nixos/tests/predictable-interface-names.nix b/nixos/tests/predictable-interface-names.nix
index 0d73436c1c3f5..8306abb8c42f9 100644
--- a/nixos/tests/predictable-interface-names.nix
+++ b/nixos/tests/predictable-interface-names.nix
@@ -1,7 +1,10 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
 let
-  inherit (import ../lib/testing.nix { inherit system; }) makeTest pkgs;
+  inherit (import ../lib/testing.nix { inherit system pkgs; }) makeTest;
 in pkgs.lib.listToAttrs (pkgs.lib.crossLists (predictable: withNetworkd: {
   name = pkgs.lib.optionalString (!predictable) "un" + "predictable"
        + pkgs.lib.optionalString withNetworkd "Networkd";
diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix
index d85abf3c105cc..7026637ead115 100644
--- a/nixos/tests/printing.nix
+++ b/nixos/tests/printing.nix
@@ -39,6 +39,8 @@ import ./make-test.nix ({pkgs, ... }: {
       $client->waitForUnit("cups.service");
       $client->sleep(10); # wait until cups is fully initialized
       $client->succeed("lpstat -r") =~ /scheduler is running/ or die;
+      # check local encrypted connections work without error
+      $client->succeed("lpstat -E -r") =~ /scheduler is running/ or die;
       # Test that UNIX socket is used for connections.
       $client->succeed("lpstat -H") =~ "/var/run/cups/cups.sock" or die;
       # Test that HTTP server is available too.
diff --git a/nixos/tests/prometheus-exporters.nix b/nixos/tests/prometheus-exporters.nix
new file mode 100644
index 0000000000000..140687a8182f4
--- /dev/null
+++ b/nixos/tests/prometheus-exporters.nix
@@ -0,0 +1,335 @@
+import ./make-test.nix ({ lib, pkgs, ... }:
+let
+  escape' = str: lib.replaceChars [''"'' "$" "\n"] [''\\\"'' "\\$" ""] str;
+
+/*
+ * The attrset `exporterTests` contains one attribute
+ * for each exporter test. Each of these attributes
+ * is expected to be an attrset containing:
+ *
+ *  `exporterConfig`:
+ *    this attribute set contains config for the exporter itself
+ *
+ *  `exporterTest`
+ *    this attribute set contains test instructions
+ *
+ *  `metricProvider` (optional)
+ *    this attribute contains additional machine config
+ *
+ *  Example:
+ *    exporterTests.<exporterName> = {
+ *      exporterConfig = {
+ *        enable = true;
+ *      };
+ *      metricProvider = {
+ *        services.<metricProvider>.enable = true;
+ *      };
+ *      exporterTest = ''
+ *        waitForUnit("prometheus-<exporterName>-exporter.service");
+ *        waitForOpenPort("1234");
+ *        succeed("curl -sSf 'localhost:1234/metrics'");
+ *      '';
+ *    };
+ *
+ *  # this would generate the following test config:
+ *
+ *    nodes.<exporterName> = {
+ *      services.prometheus.<exporterName> = {
+ *        enable = true;
+ *      };
+ *      services.<metricProvider>.enable = true;
+ *    };
+ *
+ *    testScript = ''
+ *      $<exporterName>->start();
+ *      $<exporterName>->waitForUnit("prometheus-<exporterName>-exporter.service");
+ *      $<exporterName>->waitForOpenPort("1234");
+ *      $<exporterName>->succeed("curl -sSf 'localhost:1234/metrics'");
+ *      $<exporterName>->shutdown();
+ *    '';
+ */
+
+  exporterTests = {
+
+    blackbox = {
+      exporterConfig = {
+        enable = true;
+        configFile = pkgs.writeText "config.yml" (builtins.toJSON {
+          modules.icmp_v6 = {
+            prober = "icmp";
+            icmp.preferred_ip_protocol = "ip6";
+          };
+        });
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-blackbox-exporter.service");
+        waitForOpenPort(9115);
+        succeed("curl -sSf 'http://localhost:9115/probe?target=localhost&module=icmp_v6' | grep -q 'probe_success 1'");
+      '';
+    };
+
+    collectd = {
+      exporterConfig = {
+        enable = true;
+        extraFlags = [ "--web.collectd-push-path /collectd" ];
+      };
+      exporterTest =let postData = escape' ''
+        [{
+          "values":[23],
+          "dstypes":["gauge"],
+          "type":"gauge",
+          "interval":1000,
+          "host":"testhost",
+          "plugin":"testplugin",
+          "time":$(date +%s)
+        }]
+        ''; in ''
+        waitForUnit("prometheus-collectd-exporter.service");
+        waitForOpenPort(9103);
+        succeed("curl -sSfH 'Content-Type: application/json' -X POST --data \"${postData}\" localhost:9103/collectd");
+        succeed("curl -sSf localhost:9103/metrics | grep -q 'collectd_testplugin_gauge{instance=\"testhost\"} 23'");
+      '';
+    };
+
+    dnsmasq = {
+      exporterConfig = {
+        enable = true;
+        leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
+      };
+      metricProvider = {
+        services.dnsmasq.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-dnsmasq-exporter.service");
+        waitForOpenPort(9153);
+        succeed("curl -sSf http://localhost:9153/metrics | grep -q 'dnsmasq_leases 0'");
+      '';
+    };
+
+    bind = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.bind.enable = true;
+        services.bind.extraConfig = ''
+          statistics-channels {
+            inet 127.0.0.1 port 8053 allow { localhost; };
+          };
+        '';
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-bind-exporter.service");
+        waitForOpenPort(9119);
+        succeed("curl -sSf http://localhost:9119/metrics" | grep -q 'bind_query_recursions_total 0');
+      '';
+    };
+
+    dovecot = {
+      exporterConfig = {
+        enable = true;
+        scopes = [ "global" ];
+        socketPath = "/var/run/dovecot2/old-stats";
+        user = "root"; # <- don't use user root in production
+      };
+      metricProvider = {
+        services.dovecot2.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-dovecot-exporter.service");
+        waitForOpenPort(9166);
+        succeed("curl -sSf http://localhost:9166/metrics | grep -q 'dovecot_up{scope=\"global\"} 1'");
+      '';
+    };
+
+    fritzbox = { # TODO add proper test case
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-fritzbox-exporter.service");
+        waitForOpenPort(9133);
+        succeed("curl -sSf http://localhost:9133/metrics | grep -q 'fritzbox_exporter_collect_errors 0'");
+      '';
+    };
+
+    json = {
+      exporterConfig = {
+        enable = true;
+        url = "http://localhost";
+        configFile = pkgs.writeText "json-exporter-conf.json" (builtins.toJSON [{
+          name = "json_test_metric";
+          path = "$.test";
+        }]);
+      };
+      metricProvider = {
+        systemd.services.prometheus-json-exporter.after = [ "nginx.service" ];
+        services.nginx = {
+          enable = true;
+          virtualHosts.localhost.locations."/".extraConfig = ''
+            return 200 "{\"test\":1}";
+          '';
+        };
+      };
+      exporterTest = ''
+        waitForUnit("nginx.service");
+        waitForOpenPort(80);
+        waitForUnit("prometheus-json-exporter.service");
+        waitForOpenPort(7979);
+        succeed("curl -sSf localhost:7979/metrics | grep -q 'json_test_metric 1'");
+      '';
+    };
+
+    nginx = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.nginx = {
+          enable = true;
+          statusPage = true;
+          virtualHosts."/".extraConfig = "return 204;";
+        };
+      };
+      exporterTest = ''
+        waitForUnit("nginx.service")
+        waitForUnit("prometheus-nginx-exporter.service")
+        waitForOpenPort(9113)
+        succeed("curl -sSf http://localhost:9113/metrics | grep -q 'nginx_up 1'")
+      '';
+    };
+
+    node = {
+      exporterConfig = {
+        enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-node-exporter.service");
+        waitForOpenPort(9100);
+        succeed("curl -sSf http://localhost:9100/metrics | grep -q 'node_exporter_build_info{.\\+} 1'");
+      '';
+    };
+
+    postfix = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        services.postfix.enable = true;
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-postfix-exporter.service");
+        waitForOpenPort(9154);
+        succeed("curl -sSf http://localhost:9154/metrics | grep -q 'postfix_smtpd_connects_total 0'");
+      '';
+    };
+
+    snmp = {
+      exporterConfig = {
+        enable = true;
+        configuration.default = {
+          version = 2;
+          auth.community = "public";
+        };
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-snmp-exporter.service");
+        waitForOpenPort(9116);
+        succeed("curl -sSf localhost:9116/metrics | grep -q 'snmp_request_errors_total 0'");
+      '';
+    };
+
+    surfboard = {
+      exporterConfig = {
+        enable = true;
+        modemAddress = "localhost";
+      };
+      metricProvider = {
+        systemd.services.prometheus-surfboard-exporter.after = [ "nginx.service" ];
+        services.nginx = {
+          enable = true;
+          virtualHosts.localhost.locations."/cgi-bin/status".extraConfig = ''
+            return 204;
+          '';
+        };
+      };
+      exporterTest = ''
+        waitForUnit("nginx.service");
+        waitForOpenPort(80);
+        waitForUnit("prometheus-surfboard-exporter.service");
+        waitForOpenPort(9239);
+        succeed("curl -sSf localhost:9239/metrics | grep -q 'surfboard_up 1'");
+      '';
+    };
+
+    tor = {
+      exporterConfig = {
+        enable = true;
+      };
+      metricProvider = {
+        # Note: this does not connect the test environment to the Tor network.
+        # Client, relay, bridge or exit connectivity are disabled by default.
+        services.tor.enable = true;
+        services.tor.controlPort = 9051;
+      };
+      exporterTest = ''
+        waitForUnit("tor.service");
+        waitForOpenPort(9051);
+        waitForUnit("prometheus-tor-exporter.service");
+        waitForOpenPort(9130);
+        succeed("curl -sSf localhost:9130/metrics | grep -q 'tor_version{.\\+} 1'");
+      '';
+    };
+
+    varnish = {
+      exporterConfig = {
+        enable = true;
+        instance = "/var/spool/varnish/varnish";
+        group = "varnish";
+      };
+      metricProvider = {
+        systemd.services.prometheus-varnish-exporter.after = [
+          "varnish.service"
+        ];
+        services.varnish = {
+          enable = true;
+          config = ''
+            vcl 4.0;
+            backend default {
+              .host = "127.0.0.1";
+              .port = "80";
+            }
+          '';
+        };
+      };
+      exporterTest = ''
+        waitForUnit("prometheus-varnish-exporter.service");
+        waitForOpenPort(9131);
+        succeed("curl -sSf http://localhost:9131/metrics | grep -q 'varnish_up 1'");
+      '';
+    };
+  };
+
+  nodes = lib.mapAttrs (exporter: testConfig: lib.mkMerge [{
+    services.prometheus.exporters.${exporter} = testConfig.exporterConfig;
+  } testConfig.metricProvider or {}]) exporterTests;
+
+  testScript = lib.concatStrings (lib.mapAttrsToList (exporter: testConfig: (''
+    subtest "${exporter}", sub {
+      ${"$"+exporter}->start();
+      ${lib.concatStringsSep "  " (map (line: ''
+        ${"$"+exporter}->${line};
+      '') (lib.splitString "\n" (lib.removeSuffix "\n" testConfig.exporterTest)))}
+      ${"$"+exporter}->shutdown();
+    };
+  '')) exporterTests);
+in
+{
+  name = "prometheus-exporters";
+
+  inherit nodes testScript;
+
+  meta = with lib.maintainers; {
+    maintainers = [ willibutz ];
+  };
+})
diff --git a/nixos/tests/prometheus.nix b/nixos/tests/prometheus.nix
index 87a6510f40fd8..f1b20a33d71e3 100644
--- a/nixos/tests/prometheus.nix
+++ b/nixos/tests/prometheus.nix
@@ -13,6 +13,25 @@ import ./make-test.nix {
           }];
         }];
         rules = [ ''testrule = count(up{job="prometheus"})'' ];
+
+        # a very simple version of the alertmanager configuration just to see if
+        # configuration checks & service startup are working
+        alertmanager = {
+          enable = true;
+          listenAddress = "[::1]";
+          port = 9093;
+          configuration = {
+            route.receiver = "webhook";
+            receivers = [
+              {
+                name = "webhook";
+                webhook_configs = [
+                  { url = "http://localhost"; }
+                ];
+              }
+            ];
+          };
+        };
       };
     };
   };
@@ -22,5 +41,8 @@ import ./make-test.nix {
     $one->waitForUnit("prometheus.service");
     $one->waitForOpenPort(9090);
     $one->succeed("curl -s http://127.0.0.1:9090/metrics");
+    $one->waitForUnit("alertmanager.service");
+    $one->waitForOpenPort("9093");
+    $one->succeed("curl -f -s http://localhost:9093/");
   '';
 }
diff --git a/nixos/tests/prosody.nix b/nixos/tests/prosody.nix
index 5d33aaf8d65de..61ae5bb38ed9a 100644
--- a/nixos/tests/prosody.nix
+++ b/nixos/tests/prosody.nix
@@ -6,6 +6,9 @@ import ./make-test.nix {
       enable = true;
       # TODO: use a self-signed certificate
       c2sRequireEncryption = false;
+      extraConfig = ''
+        storage = "sql"
+      '';
     };
     environment.systemPackages = let
       sendMessage = pkgs.writeScriptBin "send-message" ''
diff --git a/nixos/tests/quagga.nix b/nixos/tests/quagga.nix
index 0ff14a21584a4..6aee7ea57f038 100644
--- a/nixos/tests/quagga.nix
+++ b/nixos/tests/quagga.nix
@@ -66,7 +66,6 @@ import ./make-test.nix ({ pkgs, ... }:
             virtualisation.vlans = [ 3 ];
             networking.defaultGateway = ifAddr nodes.router2 "eth1";
             networking.firewall.allowedTCPPorts = [ 80 ];
-            networking.firewall.allowPing = true;
             services.httpd.enable = true;
             services.httpd.adminAddr = "foo@example.com";
           };
diff --git a/nixos/tests/radarr.nix b/nixos/tests/radarr.nix
new file mode 100644
index 0000000000000..6b9a909e44b5b
--- /dev/null
+++ b/nixos/tests/radarr.nix
@@ -0,0 +1,18 @@
+import ./make-test.nix ({ lib, ... }:
+
+with lib;
+
+rec {
+  name = "radarr";
+  meta.maintainers = with maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.radarr.enable = true; };
+
+  testScript = ''
+    $machine->waitForUnit('radarr.service');
+    $machine->waitForOpenPort('7878');
+    $machine->succeed("curl --fail http://localhost:7878/");
+  '';
+})
diff --git a/nixos/tests/redmine.nix b/nixos/tests/redmine.nix
new file mode 100644
index 0000000000000..ea72a0121d11f
--- /dev/null
+++ b/nixos/tests/redmine.nix
@@ -0,0 +1,58 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+let
+  redmineTest = 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.package = package;
+        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;
+            sha256 = "1xb8lyarc7mpi86yflnlgyllh9hfwb9z304f19dx409gqpia99sc";
+          };
+        };
+        services.redmine.themes = {
+          dkuk-redmine_alex_skin = pkgs.fetchurl {
+            url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip;
+            sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl";
+          };
+        };
+      };
+
+    testScript = ''
+      startAll;
+
+      $machine->waitForUnit('redmine.service');
+      $machine->waitForOpenPort('3000');
+      $machine->succeed("curl --fail http://localhost:3000/");
+    '';
+  };
+in
+{
+  redmine_3 = redmineTest pkgs.redmine // {
+    name = "redmine_3";
+    meta.maintainers = [ maintainers.aanderse ];
+  };
+
+  redmine_4 = redmineTest pkgs.redmine_4 // {
+    name = "redmine_4";
+    meta.maintainers = [ maintainers.aanderse ];
+  };
+}
diff --git a/nixos/tests/roundcube.nix b/nixos/tests/roundcube.nix
new file mode 100644
index 0000000000000..ed0ebd7dd19dc
--- /dev/null
+++ b/nixos/tests/roundcube.nix
@@ -0,0 +1,30 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "roundcube";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ globin ];
+  };
+
+  nodes = {
+    roundcube = { config, pkgs, ... }: {
+      services.roundcube = {
+        enable = true;
+        hostName = "roundcube";
+        database.password = "notproduction";
+        package = pkgs.roundcube.withPlugins (plugins: [ plugins.persistent_login ]);
+        plugins = [ "persistent_login" ];
+      };
+      services.nginx.virtualHosts.roundcube = {
+        forceSSL = false;
+        enableACME = false;
+      };
+    };
+  };
+
+  testScript = ''
+    $roundcube->start;
+    $roundcube->waitForUnit("postgresql.service");
+    $roundcube->waitForUnit("phpfpm-roundcube.service");
+    $roundcube->waitForUnit("nginx.service");
+    $roundcube->succeed("curl -sSfL http://roundcube/ | grep 'Keep me logged in'");
+  '';
+})
diff --git a/nixos/tests/rspamd.nix b/nixos/tests/rspamd.nix
index 6b2e2dd3a5317..396cd5b67d81b 100644
--- a/nixos/tests/rspamd.nix
+++ b/nixos/tests/rspamd.nix
@@ -1,6 +1,11 @@
-{ system ? builtins.currentSystem }:
-with import ../lib/testing.nix { inherit system; };
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
+
 let
   initMachine = ''
     startAll
@@ -13,13 +18,10 @@ let
     $machine->succeed("[[ \"\$(stat -c %G ${socket})\" == \"${group}\" ]]");
     $machine->succeed("[[ \"\$(stat -c %a ${socket})\" == \"${mode}\" ]]");
   '';
-  simple = name: socketActivation: enableIPv6: makeTest {
+  simple = name: enableIPv6: makeTest {
     name = "rspamd-${name}";
     machine = {
-      services.rspamd = {
-        enable = true;
-        socketActivation = socketActivation;
-      };
+      services.rspamd.enable = true;
       networking.enableIPv6 = enableIPv6;
     };
     testScript = ''
@@ -30,15 +32,10 @@ let
       $machine->succeed("id \"rspamd\" >/dev/null");
       ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "660" }
       sleep 10;
-      $machine->log($machine->succeed("cat /etc/rspamd.conf"));
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf"));
       $machine->log($machine->succeed("systemctl cat rspamd.service"));
-      ${if socketActivation then ''
-        $machine->log($machine->succeed("systemctl cat rspamd-controller-1.socket"));
-        $machine->log($machine->succeed("systemctl cat rspamd-normal-1.socket"));
-      '' else ''
-        $machine->fail("systemctl cat rspamd-controller-1.socket");
-        $machine->fail("systemctl cat rspamd-normal-1.socket");
-      ''}
       $machine->log($machine->succeed("curl http://localhost:11334/auth"));
       $machine->log($machine->succeed("curl http://127.0.0.1:11334/auth"));
       ${optionalString enableIPv6 ''
@@ -48,10 +45,8 @@ let
   };
 in
 {
-  simple = simple "simple" false true;
-  ipv4only = simple "ipv4only" false false;
-  simple-socketActivated = simple "simple-socketActivated" true true;
-  ipv4only-socketActivated = simple "ipv4only-socketActivated" true false;
+  simple = simple "simple" true;
+  ipv4only = simple "ipv4only" false;
   deprecated = makeTest {
     name = "rspamd-deprecated";
     machine = {
@@ -67,8 +62,9 @@ in
       $machine->waitForFile("/run/rspamd.sock");
       ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
       ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
-      $machine->log($machine->succeed("cat /etc/rspamd.conf"));
-      $machine->fail("systemctl cat rspamd-normal-1.socket");
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf"));
       $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
       $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
     '';
@@ -79,7 +75,6 @@ in
     machine = {
       services.rspamd = {
         enable = true;
-        socketActivation = false;
         workers.normal.bindSockets = [{
           socket = "/run/rspamd.sock";
           mode = "0600";
@@ -92,6 +87,15 @@ in
           owner = "root";
           group = "root";
         }];
+        workers.controller2 = {
+          type = "controller";
+          bindSockets = [ "0.0.0.0:11335" ];
+          extraConfig = ''
+            static_dir = "''${WWWDIR}";
+            secure_ip = null;
+            password = "verysecretpassword";
+          '';
+        };
       };
     };
 
@@ -100,41 +104,152 @@ in
       $machine->waitForFile("/run/rspamd.sock");
       ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
       ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
-      $machine->log($machine->succeed("cat /etc/rspamd.conf"));
-      $machine->fail("systemctl cat rspamd-normal-1.socket");
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-controller.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'CONFDIR/worker-normal.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'LOCAL_CONFDIR/override.d/worker-controller2.inc' /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("grep 'verysecretpassword' /etc/rspamd/override.d/worker-controller2.inc"));
+      $machine->waitUntilSucceeds("journalctl -u rspamd | grep -i 'starting controller process' >&2");
       $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
       $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
+      $machine->log($machine->succeed("curl http://localhost:11335/ping"));
     '';
   };
-  socketActivated = makeTest {
-    name = "rspamd-socketActivated";
+  customLuaRules = makeTest {
+    name = "rspamd-custom-lua-rules";
     machine = {
+      environment.etc."tests/no-muh.eml".text = ''
+        From: Sheep1<bah@example.com>
+        To: Sheep2<mah@example.com>
+        Subject: Evil cows
+
+        I find cows to be evil don't you?
+      '';
+      environment.etc."tests/muh.eml".text = ''
+        From: Cow<cow@example.com>
+        To: Sheep2<mah@example.com>
+        Subject: Evil cows
+
+        Cows are majestic creatures don't Muh agree?
+      '';
       services.rspamd = {
         enable = true;
-        workers.normal.bindSockets = [{
-          socket = "/run/rspamd.sock";
-          mode = "0600";
-          owner = "root";
-          group = "root";
-        }];
-        workers.controller.bindSockets = [{
-          socket = "/run/rspamd-worker.sock";
-          mode = "0666";
-          owner = "root";
-          group = "root";
-        }];
+        locals = {
+          "antivirus.conf" = mkIf false { text = ''
+              clamav {
+                action = "reject";
+                symbol = "CLAM_VIRUS";
+                type = "clamav";
+                log_clean = true;
+                servers = "/run/clamav/clamd.ctl";
+              }
+            '';};
+          "redis.conf" = {
+            enable = false;
+            text = ''
+              servers = "127.0.0.1";
+            '';
+          };
+          "groups.conf".text = ''
+            group "cows" {
+              symbol {
+                NO_MUH = {
+                  weight = 1.0;
+                  description = "Mails should not muh";
+                }
+              }
+            }
+          '';
+        };
+        localLuaRules = pkgs.writeText "rspamd.local.lua" ''
+          local rspamd_logger = require "rspamd_logger"
+          rspamd_config.NO_MUH = {
+            callback = function (task)
+              local parts = task:get_text_parts()
+              if parts then
+                for _,part in ipairs(parts) do
+                  local content = tostring(part:get_content())
+                  rspamd_logger.infox(rspamd_config, 'Found content %s', content)
+                  local found = string.find(content, "Muh");
+                  rspamd_logger.infox(rspamd_config, 'Found muh %s', tostring(found))
+                  if found then
+                    return true
+                  end
+                end
+              end
+              return false
+            end,
+            score = 5.0,
+	          description = 'Allow no cows',
+            group = "cows",
+          }
+          rspamd_logger.infox(rspamd_config, 'Work dammit!!!')
+        '';
       };
     };
+    testScript = ''
+      ${initMachine}
+      $machine->waitForOpenPort(11334);
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.conf"));
+      $machine->log($machine->succeed("cat /etc/rspamd/rspamd.local.lua"));
+      $machine->log($machine->succeed("cat /etc/rspamd/local.d/groups.conf"));
+      # Verify that redis.conf was not written
+      $machine->fail("cat /etc/rspamd/local.d/redis.conf >&2");
+      # Verify that antivirus.conf was not written
+      $machine->fail("cat /etc/rspamd/local.d/antivirus.conf >&2");
+      ${checkSocket "/run/rspamd/rspamd.sock" "rspamd" "rspamd" "660" }
+      $machine->log($machine->succeed("curl --unix-socket /run/rspamd/rspamd.sock http://localhost/ping"));
+      $machine->log($machine->succeed("rspamc -h 127.0.0.1:11334 stat"));
+      $machine->log($machine->succeed("cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334"));
+      $machine->log($machine->succeed("cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols"));
+      $machine->waitUntilSucceeds("journalctl -u rspamd | grep -i muh >&2");
+      $machine->log($machine->fail("cat /etc/tests/no-muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH"));
+      $machine->log($machine->succeed("cat /etc/tests/muh.eml | rspamc -h 127.0.0.1:11334 symbols | grep NO_MUH"));
+    '';
+  };
+  postfixIntegration = makeTest {
+    name = "rspamd-postfix-integration";
+    machine = {
+      environment.systemPackages = with pkgs; [ msmtp ];
+      environment.etc."tests/gtube.eml".text = ''
+        From: Sheep1<bah@example.com>
+        To: Sheep2<tester@example.com>
+        Subject: Evil cows
+
+        I find cows to be evil don't you?
 
+        XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
+      '';
+      environment.etc."tests/example.eml".text = ''
+        From: Sheep1<bah@example.com>
+        To: Sheep2<tester@example.com>
+        Subject: Evil cows
+
+        I find cows to be evil don't you?
+      '';
+      users.users.tester.password = "test";
+      services.postfix = {
+        enable = true;
+        destination = ["example.com"];
+      };
+      services.rspamd = {
+        enable = true;
+        postfix.enable = true;
+        workers.rspamd_proxy.type = "proxy";
+      };
+    };
     testScript = ''
-      startAll
-      $machine->waitForFile("/run/rspamd.sock");
-      ${checkSocket "/run/rspamd.sock" "root" "root" "600" }
-      ${checkSocket "/run/rspamd-worker.sock" "root" "root" "666" }
-      $machine->log($machine->succeed("cat /etc/rspamd.conf"));
-      $machine->log($machine->succeed("systemctl cat rspamd-normal-1.socket"));
-      $machine->log($machine->succeed("rspamc -h /run/rspamd-worker.sock stat"));
-      $machine->log($machine->succeed("curl --unix-socket /run/rspamd-worker.sock http://localhost/ping"));
+      ${initMachine}
+      $machine->waitForOpenPort(11334);
+      $machine->waitForOpenPort(25);
+      ${checkSocket "/run/rspamd/rspamd-milter.sock" "rspamd" "postfix" "660" }
+      $machine->log($machine->succeed("rspamc -h 127.0.0.1:11334 stat"));
+      $machine->log($machine->succeed("msmtp --host=localhost -t --read-envelope-from < /etc/tests/example.eml"));
+      $machine->log($machine->fail("msmtp --host=localhost -t --read-envelope-from < /etc/tests/gtube.eml"));
+
+      $machine->waitUntilFails('[ "$(postqueue -p)" != "Mail queue is empty" ]');
+      $machine->fail("journalctl -u postfix | grep -i error >&2");
+      $machine->fail("journalctl -u postfix | grep -i warning >&2");
     '';
   };
 }
diff --git a/nixos/tests/rss2email.nix b/nixos/tests/rss2email.nix
new file mode 100644
index 0000000000000..492d47da9f56e
--- /dev/null
+++ b/nixos/tests/rss2email.nix
@@ -0,0 +1,66 @@
+import ./make-test.nix {
+  name = "opensmtpd";
+
+  nodes = {
+    server = { pkgs, ... }: {
+      imports = [ common/user-account.nix ];
+      services.nginx = {
+        enable = true;
+        virtualHosts."127.0.0.1".root = ./common/webroot;
+      };
+      services.rss2email = {
+        enable = true;
+        to = "alice@localhost";
+        interval = "1";
+        config.from = "test@example.org";
+        feeds = {
+          nixos = { url = "http://127.0.0.1/news-rss.xml"; };
+        };
+      };
+      services.opensmtpd = {
+        enable = true;
+        extraServerArgs = [ "-v" ];
+        serverConfiguration = ''
+          listen on 127.0.0.1
+          action dovecot_deliver mda \
+            "${pkgs.dovecot}/libexec/dovecot/deliver -d %{user.username}"
+          match from any for local action dovecot_deliver
+        '';
+      };
+      services.dovecot2 = {
+        enable = true;
+        enableImap = true;
+        mailLocation = "maildir:~/mail";
+        protocols = [ "imap" ];
+      };
+      environment.systemPackages = let
+        checkMailLanded = pkgs.writeScriptBin "check-mail-landed" ''
+          #!${pkgs.python3.interpreter}
+          import imaplib
+
+          with imaplib.IMAP4('127.0.0.1', 143) as imap:
+            imap.login('alice', 'foobar')
+            imap.select()
+            status, refs = imap.search(None, 'ALL')
+            print("=====> Result of search for all:", status, refs)
+            assert status == 'OK'
+            assert len(refs) > 0
+            status, msg = imap.fetch(refs[0], 'BODY[TEXT]')
+            assert status == 'OK'
+        '';
+      in [ pkgs.opensmtpd checkMailLanded ];
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $server->waitForUnit("network-online.target");
+    $server->waitForUnit("opensmtpd");
+    $server->waitForUnit("dovecot2");
+    $server->waitForUnit("nginx");
+    $server->waitForUnit("rss2email");
+
+    $server->waitUntilSucceeds('check-mail-landed >&2');
+  '';
+}
diff --git a/nixos/tests/rsyslogd.nix b/nixos/tests/rsyslogd.nix
new file mode 100644
index 0000000000000..f17e61814c5e9
--- /dev/null
+++ b/nixos/tests/rsyslogd.nix
@@ -0,0 +1,42 @@
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
+with pkgs.lib;
+
+{
+  test1 = makeTest {
+    name = "rsyslogd-test1";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      { services.rsyslogd.enable = true;
+        services.journald.forwardToSyslog = false;
+      };
+
+    # ensure rsyslogd isn't receiving messages from journald if explicitly disabled
+    testScript = ''
+      $machine->waitForUnit("default.target");
+      $machine->fail("test -f /var/log/messages");
+    '';
+  };
+
+  test2 = makeTest {
+    name = "rsyslogd-test2";
+    meta.maintainers = [ maintainers.aanderse ];
+
+    machine =
+      { config, pkgs, ... }:
+      { services.rsyslogd.enable = true;
+      };
+
+    # ensure rsyslogd is receiving messages from journald
+    testScript = ''
+      $machine->waitForUnit("default.target");
+      $machine->succeed("test -f /var/log/messages");
+    '';
+  };
+}
diff --git a/nixos/tests/run-in-machine.nix b/nixos/tests/run-in-machine.nix
index bd90dc3080bd9..339a4b9a7404b 100644
--- a/nixos/tests/run-in-machine.nix
+++ b/nixos/tests/run-in-machine.nix
@@ -1,17 +1,23 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 
 let
   output = runInMachine {
     drv = pkgs.hello;
     machine = { ... }: { /* services.sshd.enable = true; */ };
   };
-in pkgs.runCommand "verify-output" { inherit output; } ''
-  if [ ! -e "$output/bin/hello" ]; then
-    echo "Derivation built using runInMachine produced incorrect output:" >&2
-    ls -laR "$output" >&2
-    exit 1
-  fi
-  "$output/bin/hello" > "$out"
-''
+
+  test = pkgs.runCommand "verify-output" { inherit output; } ''
+    if [ ! -e "$output/bin/hello" ]; then
+      echo "Derivation built using runInMachine produced incorrect output:" >&2
+      ls -laR "$output" >&2
+      exit 1
+    fi
+    "$output/bin/hello" > "$out"
+  '';
+
+in test // { inherit test; } # To emulate behaviour of makeTest
diff --git a/nixos/tests/sddm.nix b/nixos/tests/sddm.nix
index 7b9fdc0b34415..678bcbeab20ae 100644
--- a/nixos/tests/sddm.nix
+++ b/nixos/tests/sddm.nix
@@ -1,6 +1,9 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 
 let
   inherit (pkgs) lib;
diff --git a/nixos/tests/slurm.nix b/nixos/tests/slurm.nix
index 60f44c3c8459c..b4458d8d09543 100644
--- a/nixos/tests/slurm.nix
+++ b/nixos/tests/slurm.nix
@@ -1,22 +1,27 @@
-import ./make-test.nix ({ ... }:
-let mungekey = "mungeverryweakkeybuteasytointegratoinatest";
+import ./make-test.nix ({ lib, ... }:
+let
+    mungekey = "mungeverryweakkeybuteasytointegratoinatest";
+
     slurmconfig = {
       controlMachine = "control";
-      nodeName = ''
-        control
-        NodeName=node[1-3] CPUs=1 State=UNKNOWN
+      nodeName = [ "node[1-3] CPUs=1 State=UNKNOWN" ];
+      partitionName = [ "debug Nodes=node[1-3] Default=YES MaxTime=INFINITE State=UP" ];
+      extraConfig = ''
+        AccountingStorageHost=dbd
+        AccountingStorageType=accounting_storage/slurmdbd
       '';
-      partitionName = "debug Nodes=node[1-3] Default=YES MaxTime=INFINITE State=UP";
     };
 in {
   name = "slurm";
 
+  meta.maintainers = [ lib.maintainers.markuskowa ];
+
   nodes =
     let
     computeNode =
       { ...}:
       {
-        # TODO slrumd port and slurmctld port should be configurations and
+        # TODO slurmd port and slurmctld port should be configurations and
         # automatically allowed by the  firewall.
         networking.firewall.enable = false;
         services.slurm = {
@@ -43,6 +48,30 @@ in {
         } // slurmconfig;
       };
 
+    dbd =
+      { pkgs, ... } :
+      {
+        networking.firewall.enable = false;
+        services.slurm.dbdserver = {
+          enable = true;
+        };
+        services.mysql = {
+          enable = true;
+          package = pkgs.mysql;
+          ensureDatabases = [ "slurm_acct_db" ];
+          ensureUsers = [{
+            ensurePermissions = { "slurm_acct_db.*" = "ALL PRIVILEGES"; };
+            name = "slurm";
+          }];
+          extraOptions = ''
+            # recommendations from: https://slurm.schedmd.com/accounting.html#mysql-configuration
+            innodb_buffer_pool_size=1024M
+            innodb_log_file_size=64M
+            innodb_lock_wait_timeout=900
+          '';
+        };
+      };
+
     node1 = computeNode;
     node2 = computeNode;
     node3 = computeNode;
@@ -54,7 +83,7 @@ in {
   startAll;
 
   # Set up authentification across the cluster
-  foreach my $node (($submit,$control,$node1,$node2,$node3))
+  foreach my $node (($submit,$control,$dbd,$node1,$node2,$node3))
   {
     $node->waitForUnit("default.target");
 
@@ -63,10 +92,23 @@ in {
     $node->succeed("chmod 0400 /etc/munge/munge.key");
     $node->succeed("chown munge:munge /etc/munge/munge.key");
     $node->succeed("systemctl restart munged");
-  }
+
+    $node->waitForUnit("munged");
+  };
 
   # Restart the services since they have probably failed due to the munge init
   # failure
+  subtest "can_start_slurmdbd", sub {
+    $dbd->succeed("systemctl restart slurmdbd");
+    $dbd->waitForUnit("slurmdbd.service");
+    $dbd->waitForOpenPort(6819);
+  };
+
+  # there needs to be an entry for the current
+  # cluster in the database before slurmctld is restarted
+  subtest "add_account", sub {
+    $control->succeed("sacctmgr -i add cluster default");
+  };
 
   subtest "can_start_slurmctld", sub {
     $control->succeed("systemctl restart slurmctld");
@@ -81,12 +123,17 @@ in {
     }
   };
 
-  # Test that the cluster work and can distribute jobs;
+  # Test that the cluster works and can distribute jobs;
 
   subtest "run_distributed_command", sub {
     # Run `hostname` on 3 nodes of the partition (so on all the 3 nodes).
     # The output must contain the 3 different names
     $submit->succeed("srun -N 3 hostname | sort | uniq | wc -l | xargs test 3 -eq");
   };
+
+  subtest "check_slurm_dbd", sub {
+    # find the srun job from above in the database
+    $submit->succeed("sacct | grep hostname");
+  };
   '';
 })
diff --git a/nixos/tests/solr.nix b/nixos/tests/solr.nix
new file mode 100644
index 0000000000000..9ba3863411ea3
--- /dev/null
+++ b/nixos/tests/solr.nix
@@ -0,0 +1,47 @@
+import ./make-test.nix ({ pkgs, lib, ... }:
+{
+  name = "solr";
+  meta.maintainers = [ lib.maintainers.aanderse ];
+
+  machine =
+    { config, pkgs, ... }:
+    {
+      # Ensure the virtual machine has enough memory for Solr to avoid the following error:
+      #
+      #   OpenJDK 64-Bit Server VM warning:
+      #     INFO: os::commit_memory(0x00000000e8000000, 402653184, 0)
+      #     failed; error='Cannot allocate memory' (errno=12)
+      #
+      #   There is insufficient memory for the Java Runtime Environment to continue.
+      #   Native memory allocation (mmap) failed to map 402653184 bytes for committing reserved memory.
+      virtualisation.memorySize = 2000;
+
+      services.solr.enable = true;
+    };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit('solr.service');
+    $machine->waitForOpenPort('8983');
+    $machine->succeed('curl --fail http://localhost:8983/solr/');
+
+    # adapted from pkgs.solr/examples/films/README.txt
+    $machine->succeed('sudo -u solr solr create -c films');
+    $machine->succeed(q(curl http://localhost:8983/solr/films/schema -X POST -H 'Content-type:application/json' --data-binary '{
+      "add-field" : {
+        "name":"name",
+        "type":"text_general",
+        "multiValued":false,
+        "stored":true
+      },
+      "add-field" : {
+        "name":"initial_release_date",
+        "type":"pdate",
+        "stored":true
+      }
+    }')) =~ /"status":0/ or die;
+    $machine->succeed('sudo -u solr post -c films ${pkgs.solr}/example/films/films.json');
+    $machine->succeed('curl http://localhost:8983/solr/films/query?q=name:batman') =~ /"name":"Batman Begins"/ or die;
+  '';
+})
diff --git a/nixos/tests/sonarr.nix b/nixos/tests/sonarr.nix
new file mode 100644
index 0000000000000..3d5c3b19b6ea8
--- /dev/null
+++ b/nixos/tests/sonarr.nix
@@ -0,0 +1,18 @@
+import ./make-test.nix ({ lib, ... }:
+
+with lib;
+
+rec {
+  name = "sonarr";
+  meta.maintainers = with maintainers; [ etu ];
+
+  nodes.machine =
+    { pkgs, ... }:
+    { services.sonarr.enable = true; };
+
+  testScript = ''
+    $machine->waitForUnit('sonarr.service');
+    $machine->waitForOpenPort('8989');
+    $machine->succeed("curl --fail http://localhost:8989/");
+  '';
+})
diff --git a/nixos/tests/statsd.nix b/nixos/tests/statsd.nix
deleted file mode 100644
index 666961249ced1..0000000000000
--- a/nixos/tests/statsd.nix
+++ /dev/null
@@ -1,51 +0,0 @@
-import ./make-test.nix ({ pkgs, lib, ... }:
-
-with lib;
-
-{
-  name = "statsd";
-  meta = with pkgs.stdenv.lib.maintainers; {
-    maintainers = [ ma27 ];
-  };
-
-  machine = {
-    services.statsd.enable = true;
-    services.statsd.backends = [ "statsd-influxdb-backend" "console" ];
-    services.statsd.extraConfig = ''
-      influxdb: {
-        username: "root",
-        password: "root",
-        database: "statsd"
-      }
-    '';
-
-    services.influxdb.enable = true;
-
-    systemd.services.influx-init = {
-      description = "Setup Influx Test Base";
-      after = [ "influxdb.service" ];
-      before = [ "statsd.service" ];
-
-      script = ''
-        echo "CREATE DATABASE statsd" | ${pkgs.influxdb}/bin/influx
-      '';
-    };
-  };
-
-  testScript = ''
-    $machine->start();
-    $machine->waitForUnit("statsd.service");
-    $machine->waitForOpenPort(8126);
-
-    # check state of the `statsd` server
-    $machine->succeed('[ "health: up" = "$(echo health | nc 127.0.0.1 8126 -w 120 -N)" ];');
-
-    # confirm basic examples for metrics derived from docs:
-    # https://github.com/etsy/statsd/blob/v0.8.0/README.md#usage and
-    # https://github.com/etsy/statsd/blob/v0.8.0/docs/admin_interface.md
-    $machine->succeed("echo 'foo:1|c' | nc -u -w 0  127.0.0.1 8125");
-    $machine->succeed("echo counters | nc -w 120 127.0.0.1 8126 -N | grep foo");
-    $machine->succeed("echo 'delcounters foo' | nc -w 120 127.0.0.1 8126 -N");
-    $machine->fail("echo counters | nc -w 120 127.0.0.1 8126 -N | grep foo");
-  '';
-})
diff --git a/nixos/tests/switch-test.nix b/nixos/tests/switch-test.nix
index 32010838e67b4..0dba3697980f9 100644
--- a/nixos/tests/switch-test.nix
+++ b/nixos/tests/switch-test.nix
@@ -18,8 +18,17 @@ import ./make-test.nix ({ pkgs, ...} : {
   testScript = {nodes, ...}: let
     originalSystem = nodes.machine.config.system.build.toplevel;
     otherSystem = nodes.other.config.system.build.toplevel;
+
+    # Ensures failures pass through using pipefail, otherwise failing to
+    # switch-to-configuration is hidden by the success of `tee`.
+    stderrRunner = pkgs.writeScript "stderr-runner" ''
+      #! ${pkgs.stdenv.shell}
+      set -e
+      set -o pipefail
+      exec env -i "$@" | tee /dev/stderr
+    '';
   in ''
-    $machine->succeed("env -i ${originalSystem}/bin/switch-to-configuration test | tee /dev/stderr");
-    $machine->succeed("env -i ${otherSystem}/bin/switch-to-configuration test | tee /dev/stderr");
+    $machine->succeed("${stderrRunner} ${originalSystem}/bin/switch-to-configuration test");
+    $machine->succeed("${stderrRunner} ${otherSystem}/bin/switch-to-configuration test");
   '';
 })
diff --git a/nixos/tests/syncthing-relay.nix b/nixos/tests/syncthing-relay.nix
new file mode 100644
index 0000000000000..f1ceb49933374
--- /dev/null
+++ b/nixos/tests/syncthing-relay.nix
@@ -0,0 +1,22 @@
+import ./make-test.nix ({ lib, pkgs, ... }: {
+  name = "syncthing-relay";
+  meta.maintainers = with pkgs.stdenv.lib.maintainers; [ delroth ];
+
+  machine = {
+    environment.systemPackages = [ pkgs.jq ];
+    services.syncthing.relay = {
+      enable = true;
+      providedBy = "nixos-test";
+      pools = [];  # Don't connect to any pool while testing.
+      port = 12345;
+      statusPort = 12346;
+    };
+  };
+
+  testScript = ''
+    $machine->waitForUnit("syncthing-relay.service");
+    $machine->waitForOpenPort(12345);
+    $machine->waitForOpenPort(12346);
+    $machine->succeed("curl http://localhost:12346/status | jq -r '.options.\"provided-by\"'") =~ /nixos-test/ or die;
+  '';
+})
diff --git a/nixos/tests/systemd.nix b/nixos/tests/systemd.nix
index 65aa553b31484..4d470126abee5 100644
--- a/nixos/tests/systemd.nix
+++ b/nixos/tests/systemd.nix
@@ -56,6 +56,11 @@ import ./make-test.nix {
       $machine->succeed('test -z $(ls -1 /var/log/journal)');
     };
 
+    # Regression test for https://github.com/NixOS/nixpkgs/issues/50273
+    subtest "DynamicUser actually allocates a user", sub {
+        $machine->succeed('systemd-run --pty --property=Type=oneshot --property=DynamicUser=yes --property=User=iamatest whoami | grep iamatest');
+    };
+
     # Regression test for https://github.com/NixOS/nixpkgs/issues/35268
     subtest "file system with x-initrd.mount is not unmounted", sub {
       $machine->shutdown;
diff --git a/nixos/tests/telegraf.nix b/nixos/tests/telegraf.nix
new file mode 100644
index 0000000000000..6776f8d8c37f0
--- /dev/null
+++ b/nixos/tests/telegraf.nix
@@ -0,0 +1,30 @@
+import ./make-test.nix ({ pkgs, ...} : {
+  name = "telegraf";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ mic92 ];
+  };
+
+  machine = { ... }: {
+    services.telegraf.enable = true;
+    services.telegraf.extraConfig = {
+      agent.interval = "1s";
+      agent.flush_interval = "1s";
+      inputs.exec = {
+        commands = [
+          "${pkgs.runtimeShell} -c 'echo example,tag=a i=42i'"
+        ];
+        timeout = "5s";
+        data_format = "influx";
+      };
+      outputs.file.files = ["/tmp/metrics.out"];
+      outputs.file.data_format = "influx";
+    };
+  };
+
+  testScript = ''
+    startAll;
+
+    $machine->waitForUnit("telegraf.service");
+    $machine->waitUntilSucceeds("grep -q example /tmp/metrics.out");
+  '';
+})
diff --git a/nixos/tests/upnp.nix b/nixos/tests/upnp.nix
new file mode 100644
index 0000000000000..3f2dd13fb560b
--- /dev/null
+++ b/nixos/tests/upnp.nix
@@ -0,0 +1,94 @@
+# This tests whether UPnP port mappings can be created using Miniupnpd
+# and Miniupnpc.
+# It runs a Miniupnpd service on one machine, and verifies
+# a client can indeed create a port mapping using Miniupnpc. If
+# this succeeds an external client will try to connect to the port
+# mapping.
+
+import ./make-test.nix ({ pkgs, ... }:
+
+let
+  internalRouterAddress = "192.168.3.1";
+  internalClient1Address = "192.168.3.2";
+  externalRouterAddress = "80.100.100.1";
+  externalClient2Address = "80.100.100.2";
+in
+{
+  name = "upnp";
+  meta = with pkgs.stdenv.lib.maintainers; {
+    maintainers = [ bobvanderlinden ];
+  };
+
+  nodes =
+    {
+      router =
+        { pkgs, nodes, ... }:
+        { virtualisation.vlans = [ 1 2 ];
+          networking.nat.enable = true;
+          networking.nat.internalInterfaces = [ "eth2" ];
+          networking.nat.externalInterface = "eth1";
+          networking.firewall.enable = true;
+          networking.firewall.trustedInterfaces = [ "eth2" ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalRouterAddress; prefixLength = 24; }
+          ];
+          networking.interfaces.eth2.ipv4.addresses = [
+            { address = internalRouterAddress; prefixLength = 24; }
+          ];
+          services.miniupnpd = {
+            enable = true;
+            externalInterface = "eth1";
+            internalIPs = [ "eth2" ];
+            appendConfig = ''
+              ext_ip=${externalRouterAddress}
+            '';
+          };
+        };
+
+      client1 =
+        { pkgs, nodes, ... }:
+        { environment.systemPackages = [ pkgs.miniupnpc pkgs.netcat ];
+          virtualisation.vlans = [ 2 ];
+          networking.defaultGateway = internalRouterAddress;
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = internalClient1Address; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+
+          services.httpd.enable = true;
+          services.httpd.listen = [{ ip = "*"; port = 9000; }];
+          services.httpd.adminAddr = "foo@example.org";
+          services.httpd.documentRoot = "/tmp";
+        };
+
+      client2 =
+        { pkgs, ... }:
+        { environment.systemPackages = [ pkgs.miniupnpc ];
+          virtualisation.vlans = [ 1 ];
+          networking.interfaces.eth1.ipv4.addresses = [
+            { address = externalClient2Address; prefixLength = 24; }
+          ];
+          networking.firewall.enable = false;
+        };
+    };
+
+  testScript =
+    { nodes, ... }:
+    ''
+      startAll;
+
+      # Wait for network and miniupnpd.
+      $router->waitForUnit("network-online.target");
+      # $router->waitForUnit("nat");
+      $router->waitForUnit("firewall.service");
+      $router->waitForUnit("miniupnpd");
+
+      $client1->waitForUnit("network-online.target");
+
+      $client1->succeed("upnpc -a ${internalClient1Address} 9000 9000 TCP");
+
+      $client1->waitForUnit("httpd");
+      $client2->waitUntilSucceeds("curl http://${externalRouterAddress}:9000/");
+    '';
+
+})
diff --git a/nixos/tests/virtualbox.nix b/nixos/tests/virtualbox.nix
index ce84576edca17..84d5f3e1530ec 100644
--- a/nixos/tests/virtualbox.nix
+++ b/nixos/tests/virtualbox.nix
@@ -1,6 +1,11 @@
-{ system ? builtins.currentSystem, debug ? false, enableUnfree ? false }:
-
-with import ../lib/testing.nix { inherit system; };
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; },
+  debug ? false,
+  enableUnfree ? false
+}:
+
+with import ../lib/testing.nix { inherit system pkgs; };
 with pkgs.lib;
 
 let
@@ -374,7 +379,7 @@ let
     '';
 
     meta = with pkgs.stdenv.lib.maintainers; {
-      maintainers = [ aszlig wkennington cdepillabout ];
+      maintainers = [ aszlig cdepillabout ];
     };
   };
 
diff --git a/nixos/tests/xmonad.nix b/nixos/tests/xmonad.nix
index 61fa7c1a67d34..6d6db6b0ea970 100644
--- a/nixos/tests/xmonad.nix
+++ b/nixos/tests/xmonad.nix
@@ -22,7 +22,7 @@ import ./make-test.nix ({ pkgs, ...} : {
     $machine->waitUntilSucceeds("xmonad --restart");
     $machine->sleep(3);
     $machine->sendKeys("alt-shift-ret");
-    $machine->waitForWindow(qr/machine.*alice/);
+    $machine->waitForWindow(qr/alice.*machine/);
     $machine->sleep(1);
     $machine->screenshot("terminal");
   '';
diff --git a/nixos/tests/xss-lock.nix b/nixos/tests/xss-lock.nix
index 045667bdcdec0..b46bb1a8f6e9d 100644
--- a/nixos/tests/xss-lock.nix
+++ b/nixos/tests/xss-lock.nix
@@ -9,7 +9,6 @@ with lib;
   machine = {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
     programs.xss-lock.enable = true;
-    programs.xss-lock.lockerCommand = "${pkgs.xlockmore}/bin/xlock";
     services.xserver.displayManager.auto.user = "alice";
   };
 
@@ -20,6 +19,6 @@ with lib;
 
     $machine->fail("pgrep xlock");
     $machine->succeed("su -l alice -c 'xset dpms force standby'");
-    $machine->waitUntilSucceeds("pgrep xlock");
+    $machine->waitUntilSucceeds("pgrep i3lock");
   '';
 })
diff --git a/nixos/tests/yabar.nix b/nixos/tests/yabar.nix
index 06fe5bc2b2784..bbc0cf4c7dd78 100644
--- a/nixos/tests/yabar.nix
+++ b/nixos/tests/yabar.nix
@@ -8,18 +8,26 @@ with lib;
     maintainers = [ ma27 ];
   };
 
-  nodes.yabar = {
+  machine = {
     imports = [ ./common/x11.nix ./common/user-account.nix ];
 
     services.xserver.displayManager.auto.user = "bob";
 
     programs.yabar.enable = true;
+    programs.yabar.bars = {
+      top.indicators.date.exec = "YABAR_DATE";
+    };
   };
 
   testScript = ''
-    $yabar->start;
-    $yabar->waitForX;
+    $machine->start;
+    $machine->waitForX;
 
-    $yabar->waitForUnit("yabar.service", "bob");
+    # confirm proper startup
+    $machine->waitForUnit("yabar.service", "bob");
+    $machine->sleep(10);
+    $machine->waitForUnit("yabar.service", "bob");
+
+    $machine->screenshot("top_bar");
   '';
 })
diff --git a/nixos/tests/zfs.nix b/nixos/tests/zfs.nix
index 1434038e90c19..d7a08268e984b 100644
--- a/nixos/tests/zfs.nix
+++ b/nixos/tests/zfs.nix
@@ -1,6 +1,9 @@
-{ system ? builtins.currentSystem }:
+{ system ? builtins.currentSystem,
+  config ? {},
+  pkgs ? import ../.. { inherit system config; }
+}:
 
-with import ../lib/testing.nix { inherit system; };
+with import ../lib/testing.nix { inherit system pkgs; };
 
 let